手机站
网通分站
电信主站
密 码:
用户名:
当前位置 : 主页>网站运营>建站经验>列表

linux中的时间流

来源:互联网 作者:west263.com 时间:2008-04-16
西部数码-全国虚拟主机10强!40余项虚拟主机管理功能,全国领先!双线多线虚拟主机南北访问畅通无阻!免费赠送企业邮局,.CN域名,自助建站480元起,免费试用7天,满意再付款! P4主机租用799元/月.月付免压金!
中,驱动程式代码需要包含该头文档。 调度器队列 调度器队列在预定义任务队列中比较独特,他运行在进程上下文中,这意味着该队列中的任务能够更多的事情。在Linux 2.4,该队列由一个专门的内核线程 keventd 管理,通过函数 schedule_task 访问。在较老的内核版本,没有用keventd,所以该队列(tq_scheduler)是直接操作的。 tq_timer 该队列由定时器处理程式(定时器嘀哒)运行。因为该处理程式(见函数do_timer)是在中断期间运行的,因此该队列中的任何任务也是在中断期间运行的。 tq_immediate 立即队列是在系统调用返回时或调度器运行时得到处理,以便尽可能快地运行该队列。该队列在中断期间得到处理。 更有其他的预定义队列,但驱动程式研发中通常不会涉及到他们。 使用任务队列的一个设备驱动程式的执行流程可见图6-1。该图演示了设备驱动程式是如何在中断处理程式中将一个函数插入tq_immediate队列中的。 Postscript Figure ./figs/ldr2_0601.eps here Figure Caption:task_queue的使用流程 示例程式是如何工作的 延迟计算的示例程式包含在jiq(Just In Queue)模块中,本节中抽取了他的部分源码。该模块创建 /proc 文档,能够用 dd 或其他工具来读,这点上和 jit 模块很相似。 读 jiq 文档的进程被转入睡眠状态直到缓冲区满*。 ===========footnote begins============ /proc文档的缓冲区是内存中的一页:4KB,或对应于使用平台的尺寸。 ===========footnote ends============ 睡眠是由一个简单的等待队列处理的,声明为 DECLARE_WAIT_QUEUE_HEAD (jiq_wait); 缓冲区由不断运行的任务队列来填充。任务队列的每次运行都会在要填充的缓冲区中添加一个字符串,该字符串记录了当前时间(jiffies值),当前进程连同 in_interrupt的返回值。 填充缓冲区的代码都在jiq_print_tq函数中,任务队列的每遍运行都要调用他。打印函数没什么意思,不在这里列出,我们还是来看看插入队列的任务的初始化代码: struct tq_struct jiq_task; /* global: initialized to zero */ /* these lines are in jiq_init() */ jiq_task.routine = jiq_print_tq; jiq_task.data = (void *)&jiq_data; 这里没必要对 jiq_task结构的 sync成员和next成员清零,因为静态变量已由编译器初始化为零了。 调度器队列 最容易使用的任务队列是调度器(scheduler)队列,因为该队列中的任务不会在中断模式运行,因此能够做更多事,特别是他们还能睡眠。内核中有多处使用该队列完成各种任务。 在内核2.4.0-test11,实际实现调度器队列的任务队列被内核的其余部分隐藏了。使用这个队列的代码必须调用schedule_task把任务放入队列,而不能直接使用queue_task: int schedule_task(struct tq_struct *task); 其中的 task 当然就是要调度的任务。返回值直接来自queue_task:假如任务不在队列中就返回非零。 再提一次,从版本 2.4.0-test11 开始内核使用了一个特别进程 keventd,他唯一的任务就是运行 scheduler 队列中的任务。keventd 为他运行的任务提供了可预期的进程上下文,而不象以前的实现,任务是在完全随机的进程上下文中运行的。 对于 keventd 的执行有几点是值得牢记的。首先,这个队列中的任务能够睡眠,一些内核代码就使用了这一长处。但是,好的代码应该只睡眠很短的时间,因为在 keventd 睡眠的时候,调度器队列中的其他任务就不会再运行了。更有一点需要牢记,您的任务是和其他任务共享调度器队列,这些任务也能够睡眠。正常情况下,调度器队列中的任务会很快运行(也许甚至在schedule_task返回之前)。但假如其他某个任务睡眠了,轮到您的任务执行时,中间流逝的时间会显得很久。所以那些有严格的执行时限的任务应该使用其他队列。 /proc/jiqsched 文档是使用调度器队列的示例文档,该文档对应的 read 函数以如下的方式将任务放进队列中: int jiq_read_sched(char *buf, char **start, off_t offset, int len, int *eof, void *data) { jiq_data.len = 0; /* nothing printed, yet */ jiq_data.buf = buf; /* print in this place */ jiq_data.jiffies = jiffies; /* initial time */ /* jiq_print will queue_task() again in jiq_data.queue */ jiq_data.queue = SCHEDULER_QUEUE; schedule_task(&jiq_task); /* ready to run */ interruptible_sleep_on(&jiq_wait); /* sleep till completion */ *eof = 1; return jiq_data.len; } 读取 /proc/jiqsched 文档产生如下输出: time delta interrupt pid cpu command 601687 0 0 2 1 keventd 601687 0 0 2 1 keventd 601687 0 0 2 1 keventd 601687 0 0 2 1 keventd 601687 0 0 2 1 keventd 601687 0 0 2 1 keventd 601687 0 0 2 1 keventd 601687 0 0 2 1 keventd 601687 0 0 2 1 keventd 上面的输出中,time域是任务运行时的jiffies值,delta是自任务最近一次运行以来jiffies的增量,interrupt是in_interrupt函数的输出,pid是运行进程的ID,cpu是正被使用的CPU的编号(在单处理器系统中始终为0),command是当前进程正在运行的命令。 在这个例子中,我们看到,任务总是在 keventd 进程中运行,而且运行得很快,一个不断把自己重复提交给调度器队列的任务能够在一次定时器滴答中运行数百甚至数千次。即使是在一个负载很重的系统,调度器队列的延迟也是很小的。 定时器队列 定时器队列的使用方法和调度器队列不同,他(tq_timer)是能够直接操作的。更有,定时器队列是在中断模式下执行的。另外,该队列一定会在下一个时钟滴答被运行,这消除了可能因系统负载造成的延迟。 示例代码使用定时器队列实现了/proc/jiqtimer。使用这个队列要用到 queue_task 函数。 int jiq_read_timer(char *buf, char **start, off_t offset, int len, int *eof, void *data) { jiq_data.len = 0; /* nothing printed, yet */ jiq_data.buf = buf; /* print in this place */ jiq_data.jiffies = jiffies; /* initial time */ jiq_data.queue = &tq_timer; /* reregister yourself here */ queue_task(&jiq_task, &tq_timer); /* ready to run */ interruptible_sleep_on(&jiq_wait); /* sleep till completion */ *eof = 1; return jiq_data.len; } 下面是在我的系统在编译一个新内核时运行命令head /proc/jiqtimer输出的结果: time delta interrupt pid cpu command 45084845 1 1 8783 0 cc1 45084846 1 1 8783 0 cc1 45084847 1 1 8783 0 cc1 45084848 1 1 8783 0 cc1 45084849 1 1 8784 0 as 45084850 1 1 8758 1 cc1 45084851 1 1 8789 0 cpp 45084852 1 1 8758 1 cc1 45084853 1 1 8758 1 cc1 45084854 1 1 8758 1 cc1 45084855 1 1 8758 1 cc1 注意,这次在任务的每次执行之间正好都经过了一个定时器滴答,而且正在运行的可能是任意一个进程。 立即队列 最后一个可由模块代码使用的预定义队列是立即队列。这个队列通过底半处理机制运行,所以要用他还需额外的步骤。底半处理程式只有在通知内核需要他运行时才会运行,这是通过“标记”底半部完成的。对于tq_immediate,必须调用mark_bh(IMMEDIATE_BH)。注意必须在任务插入队列后才能调用mark_bh,否则可能在任务还没加入队列时内核就开始运行队列了。 立即队列是系统处理得最快的队列――他反应最快并且在中断期间运行。立即队列既能够由调度器执行,也能够在一个进程从系统调用返回时被尽快地执行。典型的输出大致如下: time delta interrupt pid cpu command 45129449 0 1 8883 0 head 45129453 4 1 0 0 swapper 45129453 0 1 601 0 X 45129453 0 1 601 0 X 45129453 0 1 601 0 X 45129453 0 1 601 0 X 45129454 1 1 0 0 swapper 45129454 0 1 601 0 X 45129454 0 1 601 0 X 45129454 0 1 601 0 X 45129454 0 1 601 0 X 45129454 0 1 601 0 X 45129454 0 1 601 0 X 45129454 0 1 601 0 X 显然该队列不能用于延迟任务的执行――他是个“立即”队列。相反,他的目的是使任务尽快地得以执行,但是要在“安全的时间”内。这对中断处理很有用,因为他提供了在实际的中断处理程式之外执行处理程式代码的一个入口点,例如接收网络包的机制就类似这样。 注意不要把任务重新注册到立即队列中(尽管/proc/jiqimmed为了演示而这么做),这种做法没什么好处,而且在某些版本/平台的搭配上运行时会锁死电脑。因为在有些实现中会不断重运行立即队列直到他空为止。这种情况出现过,例如在PC上运行2.0版本的时候。 运行自己的工作队列 声明新的任务队列并不困难。驱动程式能够随意地声明一个甚至多个新任务队列。这些队列的使用和我们前面讨论过的预定义队列差不多。 和预定义队列不同的是,内核不会自动处理定制的任务队列。定制的任务队列要由程式员自己维护,并安排运行方法。 下面的宏声明一个定制队列并扩展为变量声明。最好把他放在文档开头的地方,任何函数的外面: DECLARE_TASK_QUEUE(tq_custom); 声明完队列,就能够调用下面的函数对任务进行排队。上面的宏和下面的调用相匹配: queue_task(&custom_task, &tq_custom); 当要运行累积的任务队列时,执行下面一行,运行tq_custom队列: run_task_queue(&tq_custom); 假如现在想测试定制的任务队列,则需要在某个预定义的队列中注册一个函数来触发这个队列。尽管看起来象绕了弯路,但其实并非如此。当需要累积任务以便同时得到执行时,定制的任务队列是很有用的,尽管需要用另一个队列来决定这个“同时”。 Tasklets 就在 2.4 内核发布之前,研发者们增加了一种用于内核任务延迟的新机制。这种新机制称为tasklet,现在是实现底半任务的推荐方法。实际上,现在的底半处理程式本身就是用 tasklet 实现的。 tasklets在很多方面类似任务队列。他们都是把任务延迟到安全时间执行的一种方式,都在中断期间运行。象任务队列相同,即使被调度多次,tasklet 也只运行一次,但是tasklet 能够在 SMP 系统上和其他(不同的) tasklet 并行地运行。在SMP系统上,tasklet 还被确保在第一个调度他的 CPU 上运行,因为这样能够提供更好的高速缓存行为,从而提高性能。 每个 tasklet 都和一个函数相联系,当 tasklet 要运行的时候该函数被调用。该函数只有一个 unsigned long 类型的参数,这多少使一些内核研发者的生活变得轻松;但对那些宁愿传递一个指针的研发人员来说肯定是增加了苦恼。把long类型的参数转换为一个指针类型在任何已支持的平台上都是安全的操作,在内存管理中(第13章讨论)更是普遍使用。这个tasklet的函数的类型是void,无返回值。 tasklet 的实现部分在

文章整理:西部数码--专业提供域名注册虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!