已阅读5页,还剩18页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
驱动程序需要将任务延迟到以后处理,但又不想借助中断。Linux 为此提供了三种方法:任务队列、tasklet(从内核 2.3.43 开始)和内核定时器。任务队列和 tasklet 的使用很灵活,可以或长或短地延迟任务到以后处理,在编写中断处理程序时非常有用,我们还将在第9章“Tasklet和底半部处理”一节中继续讨论。内核定时器则用来调度任务在未来某个指定时间执行,将在本章的“内核定时器”一节中讨论。 使用任务队列或tasklet的一个典型情形是,硬件不产生中断,但仍希望提供阻塞型的读取。此时需要对设备进行轮询,同时要小心地不使 CPU 负担过多无谓的操作。将读进程以固定的时间间隔唤醒(例如,使用 current-timeout 变量)并不是个很好的方法,因为每次轮询需要两次上下文切换(一次是切换到读进程中运行轮询代码,另一次是返回执行实际工作的某个进程),而且通常来讲,恰当的轮询机制应该在进程上下文之外实现。 类似的情形还有象不时地给简单的硬件设备提供输入。例如,有一个直接连接到并口的步进马达,要求该马达能一步步地移动,但马达每次只能移动一步。在这种情况下,由控制进程通知设备驱动程序进行移动,但实际上,移动是在 write 返回后,才在周期性的时间间隔内一步一步进行的。 快速完成这类不定操作的恰当方法是注册任务在未来执行。内核提供了对“任务队列”的支持,任务可以累积,而在运行队列时被“消耗”。我们可以声明自己的任务队列,并且在任意时刻触发它,或者也可以将任务注册到预定义的任务队列中去,由内核来运行(触发)它。任务队列的本质 任务队列其实是一个任务链表,每个任务用一个函数指针和一个参数表示。任务运行时,它接受一个void * 类型的参数,返回值类型为 void,而指针参数可用来将一个数据结构传入函数,或者可以被忽略。队列本身是一个结构(即任务)链表,并由声明和操纵它们的内核模块所拥有。模块要全权负责这些数据结构的分配和释放,为此一般使用静态的数据结构。 队列元素由下面这个结构来描述,这段代码是直接从头文件 拷贝下来的: struct tq_struct struct tq_struct *next; /* linked list of active bhs */ int sync; /* must be initialized to zero */ void (*routine)(void *); /* function to call */ void *data; /* argument to function */ ; 第一个注释中的 bh 指的是底半部(bottom-half)。底半部是“中断处理程序的一半部”,我们将在第9章的“tasklet和底半部”一节中介绍中断时详细讨论。现在,我们只要知道底半部是驱动程序实现的一种机制就可以了,它用于处理异步任务,这些任务通常比较大,不适于在处理硬件中断时完成。本章并不要求你理解底半部处理,但必要时也会偶尔提及。 译注:在2.4版本的内核中,tq_struct的第一个成员变量已经有所不同,改为 struct list_head list; /* linked list of active bhs */ 这是因为通用的双向链表 list_head在内核中大量采用,在很多情况下替代了数据结构中自行维护的链表。相应的task_queue的定义也改为 typedef struct list_head task_queue; 上面的数据结构中最重要的成员是routine和data。为了将随后执行的任务排队,必须先设置好结构的这些成员,并把 next 和 sync 两个字段清零。结构中的 sync 标志位由内核使用,以避免同一任务被插入多次,因为这会破坏 next 指针。一旦任务被排队,该数据结构就被认为由内核“拥有”了,不能再被修改,直到任务开始运行。 与任务队列有关的其他数据结构还有 task_queue,目前它实现为指向 tq_struct 结构的指针,之所以将这个指针(struct tq_struct* )定义成另一个数据结构(struct task_queue)是为了扩展的需要, 在需要的时候,task_queue结构中可以增加别的内容。 在使用之前,必须将 task_queue 指针初始化为 NULL。 下面汇总了所有可以在任务队列和 tq_struct 结构上执行的操作。 DECLARE_TASK_QUEUE(name); 这个宏用给定的名称 name 声明了一个任务队列,并把它初始化为空。 int queue_task(struct tq_struct *task, task_queue *list); 正如该函数的名字,它用于将任务排进队列中。如果队列中已有该任务,返回 0,否则返回非 0。 void run_task_queue(task_queue *list); run_task_queue函数用于运行累积在队列上的任务。除非你要声明和维护自己的任务队列,否则不必调用本函数。 如前所述,一个任务队列,实际上是一个函数链表。当调用 run_task_queue 运行某个队列时,列表中的每一项都会被执行。在编写和任务队列有关的函数时,必须牢记内核是在什么时候调用run_task_queue的,而且当内核调用 run_task_queue 时,实际的上下文将限制能够进行的操作。也不应对队列中任务的运行顺序做任何假定,它们每个都是独立完成自己的任务的。 那么任务队列在什么时候运行呢?如果使用的是下面一节介绍的预定义的任务队列,则答案是“在内核轮到它那里时”。不同的队列在不同的时间运行,只要内核没有其他更紧要的任务,它们总是会运行的。 更重要的是,当对任务进行排队的进程运行时,任务队列几乎肯定是不会运行的,相反,它们是异步执行的。到现在为止,示例驱动例程中所有的事情都是在这个执行系统调用的进程上下文中完成的。但当任务队列运行时,这个进程可能正在睡眠,或正在另一个处理器上运行,甚至可能已经完全退出了。 这种异步执行类似于硬件中断发生时的情景(我们会在第9章详细讨论)。实际上,任务队列常常是作为“软件中断”的结果而运行的。在中断模式(或中断期间)下,代码的运行会受到许多限制。我们现在介绍这些限制,这些限制还会在本书后面多次出现。我们也会多次重复,中断模式下的这些规则必须遵守,否则系统会有大麻烦。许多动作需要在进程上下文中才能执行。如果处于进程上下文之外(比如在中断模式下),则必须遵守如下规则: 不允许访问用户空间。因为没有进程上下文,也就没有办法访问与任何一个特定进程相关联的用户空间。 current指针在中断模式下是无效的,不能使用。 不能执行睡眠或调度。中断模式代码不可以调用schedule或者sleep_on;也不能调用任何可能引起睡眠的函数。例如,调用kmalloc(.,GFP_KERNEL)就不符合本规则。信号量也不能用,因为可能引起睡眠。 内核代码可以通过调用函数in_interrupt( ) 来判断自己是否正运行于中断模式,该函数无需参数,如果处理器在中断期间运行就返回非0值。 当前的任务队列实现还有一个特性,队列中的一个任务可以将自己重新插回到它原先所在的队列。举个例子,定时器队列中的任务可以在运行时将自己插回到定时器队列中去,从而在下一个定时器滴答又再次被运行。这是通过调用 queue_task 把自己放回队列来实现的。由于在处理任务队列之前,是先用NULL指针替换队列的头指针,也就是将任务队列初始化了,另外,在执行队列中的任务之前,首先将任务从队列中移出来,这样在任务将本身插入任务队列的时候,它其实是将指针指向新的任务队列。结果就是,随着旧队列的执行,新的队列逐渐生成。 尽管一遍遍地重新调度同一个任务看起来似乎没什么意义,但有时这也有些用处。例如,步进马达每次移动一步直到目的地,它的驱动程序就可以通过让任务在定时器队列上不断地重新调度自己来实现。其他的例子还有 jiq 模块,该模块中的打印函数通过重新调度自己来产生输出结果是利用定时器队列产生多次迭代。预定义的任务队列 延迟任务执行的最简单方法是使用由内核维护的任务队列。这种队列有好几种,但驱动程序只能使用下面列出的其中三种。任务队列的定义在头文件 中,驱动程序代码需要包含该头文件。 调度器队列 调度器队列在预定义任务队列中比较独特,它运行在进程上下文中,这意味着该队列中的任务可以更多的事情。在Linux 2.4,该队列由一个专门的内核线程 keventd 管理,通过函数 schedule_task 访问。在较老的内核版本,没有用keventd,所以该队列(tq_scheduler)是直接操作的。 tq_timer 该队列由定时器处理程序(定时器嘀哒)运行。因为该处理程序(见函数do_timer)是在中断期间运行的,因此该队列中的所有任务也是在中断期间运行的。 tq_immediate 立即队列是在系统调用返回时或调度器运行时得到处理,以便尽可能快地运行该队列。该队列在中断期间得到处理。 还有其它的预定义队列,但驱动程序开发中通常不会涉及到它们。示例程序jiq /* jiq.c - the just-in-queue module*/#ifndef _KERNEL_# define _KERNEL_#endif#ifndef MODULE# define MODULE#endif#include #include #include #include #include /* everything. */#include #include /* error codes */#include #include /* intr_count */#include sysdep.h/* This module is a silly one: it only embeds short code fragments* that show how enqueued tasks feel theit environment*/#define LIMIT (PAGE_SIZE-128) /* dont print any more after this size */* Print information about the current environment. This is called from* within the task queues. If the limit is reched, awake the reading* process.*/DECLARE_WAIT_QUEUE_HEAD (jiq_wait);struct tq_struct jiq_task; /* global: initialized to zero */* Keep track of info we need between task queue runs.*/struct clientdata int len;char *buf;unsigned long jiffies;task_queue *queue; jiq_data;#define SCHEDULER_QUEUE (task_queue *) 1)#ifdef HAVE_TASKLETSvoid jiq_print_tasklet (unsigned long);DECLARE_TASKLET (jiq_tasklet, jiq_print_tasklet, (unsigned long) &jiq_data);#endif /* HAVE_TASKLETS */* Do the printing; return non-zero if the task should be rescheduled.*/int jiq_print(void *ptr)struct clientdata *data = (struct clientdata *)ptr;int len = data-len;char *buf = data-buf;unsigned long j = jiffies;if (len LIMIT) wake_up_interruptible(&jiq_wait);return 0;if (len = 0)len = sprintf(buf, time delta interrupt pid cpu commandn);elselen =0;/* intr_count is only exported since 1.3.5, but 1.99.4 is needed anyways */len += sprintf(buf+len,%9li %3li %i %5i %3i %sn,j, j - data-jiffies,in_interrupt (), current-pid, smp_processor_id (), current-comm);data-len += len;data-buf += len;data-jiffies = j;return 1;/* Call jiq_print from a task queue*/void jiq_print_tq(void *ptr)if (jiq_print (ptr) struct clientdata *data = (struct clientdata *)ptr;if (data-queue = SCHEDULER_QUEUE)schedule_task(&jiq_task);else if (data-queue)queue_task(&jiq_task, data-queue);if (data-queue = &tq_immediate)mark_bh(IMMEDIATE_BH); /* this one needs to be marked */* Use the scheduler queue - /proc/jiqsched*/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;#ifdef USE_PROC_REGISTERstatic int jiq_old_read_sched(char *buf, char *start, off_t offset, int len,int unused)int eof;return jiq_read_sched(buf, start, offset, len, &eof, NULL);struct proc_dir_entry jiq_proc_sched = 0, /* low_ino: the inode - dynamic */8, jiqsched, /* len of name and name */S_IFREG | S_IRUGO, /* mode */1, 0, 0, /* nlinks, owner, group */0, NULL, /* size - unused; operations - use default */&jiq_old_read_sched, /* function used to read data */* nothing more */;#endifint 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; /* re-register 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;#ifdef USE_PROC_REGISTERstatic int jiq_old_read_timer(char *buf, char *start, off_t offset, int len,int unused)int eof;return jiq_read_timer(buf, start, offset, len, &eof, NULL);struct proc_dir_entry jiq_proc_timer = 0, /* low_ino: the inode - dynamic */8, jiqtimer, /* len of name and name */S_IFREG | S_IRUGO, /* mode */1, 0, 0, /* nlinks, owner, group */0, NULL, /* size - unused; operations - use default */&jiq_old_read_timer, /* function used to read data */* nothing more */;#endifint jiq_read_immed(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_immediate; /* re-register yourself here */queue_task(&jiq_task, &tq_immediate); /* ready to run */mark_bh(IMMEDIATE_BH);interruptible_sleep_on(&jiq_wait); /* sleep till completion */*eof = 1;return jiq_data.len;#ifdef USE_PROC_REGISTERstatic int jiq_old_read_immed(char *buf, char *start, off_t offset, int len,int unused)int eof;return jiq_read_immed(buf, start, offset, len, &eof, NULL);struct proc_dir_entry jiq_proc_immed = 0, /* low_ino: the inode - dynamic */8, jiqimmed, /* len of name and name */S_IFREG | S_IRUGO, /* mode */1, 0, 0, /* nlinks, owner, group */0, NULL, /* size - unused; operations - use default */&jiq_old_read_immed, /* function used to read data */* nothing more */;#endif#ifdef HAVE_TASKLETS/* Call jiq_print from a tasklet*/void jiq_print_tasklet(unsigned long ptr)if (jiq_print (void *) ptr)tasklet_schedule (&jiq_tasklet);int jiq_read_tasklet(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 */tasklet_schedule(&jiq_tasklet);interruptible_sleep_on(&jiq_wait); /* sleep till completion */*eof = 1;return jiq_data.len;/* No PROC_REGISTER junk since tasklets postdate all that */#endif /* HAVE_TASKLETS */* This one, instead, tests out the timers.*/struct timer_list jiq_timer;void jiq_timedout(unsigned long ptr)jiq_print(void *)ptr); /* print a line */wake_up_interruptible(&jiq_wait); /* awake the process */int jiq_read_run_timer(char *buf, char *start, off_t offset,int len, int *eof, void *data)jiq_data.len = 0; /* prepare the argument for jiq_print() */jiq_data.buf = buf;jiq_data.jiffies = jiffies;jiq_data.queue = NULL; /* dont requeue */init_timer(&jiq_timer); /* init the timer structure */jiq_timer.function = jiq_timedout;jiq_timer.data = (unsigned long)&jiq_data;jiq_timer.expires = jiffies + HZ; /* one second */jiq_print(&jiq_data); /* print and go to sleep */add_timer(&jiq_timer);interruptible_sleep_on(&jiq_wait);del_timer_sync(&jiq_timer); /* in case a signal woke us up */*eof = 1;return jiq_data.len;#ifdef USE_PROC_REGISTERstatic int jiq_old_read_run_timer(char *buf, char *start, off_t offset, int len,int unused)int eof;return jiq_read_run_timer(buf, start, offset, len, &eof, NULL);struct proc_dir_entry jiq_proc_run_timer = 0, /* low_ino: the inode - dynamic */7, jitimer, /* len of name and name */S_IFREG | S_IRUGO, /* mode */1, 0, 0, /* nlinks, owner, group */0, NULL, /* size - unused; operations - use default */&jiq_old_read_run_timer, /* function used to read data */* nothing more */;#endif/* the init/clean material*/int jiq_init(void)/* these lines are in jiq_init() */jiq_task.routine = jiq_print_tq;jiq_task.data = (void *)&jiq_data;#ifdef USE_PROC_REGISTERproc_register_dynamic(&proc_root, &jiq_proc_sched);proc_register_dynamic(&proc_root, &jiq_proc_timer);proc_register_dynamic(&proc_root, &jiq_proc_immed);proc_register_dynamic(&proc_root, &jiq_proc_run_timer);#elsecreate_proc_read_entry(jiqsched, 0, NULL, jiq_read_sched, NULL);create_proc_read_entry(jiqtimer, 0, NULL, jiq_read_timer, NULL);create_proc_read_entry(jiqimmed, 0, NULL, jiq_read_immed, NULL);create_proc_read_entry(jitimer, 0, NULL, jiq_read_run_timer, NULL);#ifdef HAVE_TASKLETScreate_proc_read_entry(jiqtasklet, 0, NULL, jiq_read_tasklet, NULL);#endif#endif#ifndef JIT_DEBUGEXPORT_NO_SYMBOLS;#endifreturn 0; /* succeed */void jiq_cleanup(void)#ifdef USE_PROC_REGISTERproc_unregister(&proc_root, jiq_proc_sched.low_ino);proc_unregister(&proc_root, jiq_proc_timer.low_ino);proc_unregister(&proc_root, jiq_proc_immed.low_ino);proc_unregister(&proc_root, jiq_proc_run_timer.low_ino);#elseremove_proc_entry(jiqsched, 0);remove_proc_entry(jiqtimer, 0);remove_proc_entry(jiqimmed, 0);remove_proc_entry(jitimer, 0);#ifdef HAVE_TASKLETSremove_proc_entry(jiqtasklet, 0);#endif#endifmodule_init(jiq_init);module_exit(jiq_cleanup);延迟计算的示例程序包含在jiq(Just In Queue)模块中,该模块创建 /proc 文件,可以用 dd 或者其他工具来读,这点上与 jit 模块很相似。读 jiq 文件的进程被转入睡眠状态直到缓冲区满。 /proc文件的缓冲区是内存中的一页:4KB,或对应于使用平台的尺寸。 睡眠是由一个简单的等待队列处理的,声明为 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); 如果现在想测试定制的任务队列,则需要在某个预定义的队列中注册一个函数来触发这个队列。尽管看起来象绕了弯路,但其实并非如此。当需要累积任务以便同时得到执行时,定制的任务队列是非常有用的,尽管需要用另一个队列来决定这个“同时”。Tasklet 就在 2.4 内核发布之前,开发者们增加了一种用于内核任务延迟的新机制。这种新机制称为tasklet,现在是实现底半任务的推荐方法。实际上,现在的底半处理程序本身就是用 tasklet 实现的。 tasklet 在很多方面类似任务队列。它们都是把任务延迟到安全时间执行的一种方式,都在中断期间运行。象任务队列一样,即使被调度多次,tasklet 也只运行一次,不过tasklet 可以在 SMP 系统上和其它(不同的) tasklet 并行地运行。在SMP系统
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 手机分期协议书模板
- 手机购货协议书范本
- 打假部门合作协议书
- 打架撤回控告协议书
- 打造党建品牌协议书
- 托管机构员工协议书
- 软件著作权转让合同协议(2025年版权保护版)
- 医学图书馆主任医学文献检索与服务
- 2025年初中物理声光热综合测试卷(附答案)
- 2025年北京市公务员行测判断推理专项提升试卷(含答案)
- 打击反诈宣传课件
- 2025年中国嘧霉胺行业市场分析及投资价值评估前景预测报告
- 库房消防应急预案方案
- 2025年科研伦理与学术规范考核试卷答案及解析
- 倒闸操作安全培训课件
- GB/T 46122.8-2025烟花爆竹特定化学物质检测方法第8部分:砷含量的测定氢化物发生-原子荧光光谱法
- 梁晓声母亲课件
- 肺癌化疗护理
- 婴儿饼干烘焙培训
- 成人坏死性筋膜炎诊治专家共识解读
- 民航面试全攻略:行业常见面试问题及答案解析
评论
0/150
提交评论