[ZLG-ARM] Linux设备驱动程序学习-时间、延迟及延缓操作

[复制链接]
 楼主| reeper 发表于 2009-4-6 17:29 | 显示全部楼层 |阅读模式
时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据&nbsp;HZ&nbsp;值来设定,HZ&nbsp;是一个体系依赖的值,在&nbsp;中定义或该文件包含的某个子平台相关文件中。作为通用的规则,即便如果知道&nbsp;HZ&nbsp;的值,在编程时应当不依赖这个特定值,而始终使用HZ。对于当前版本,我们应完全信任内核开发者,他们已经选择了最适合的HZ值,最好保持&nbsp;HZ&nbsp;的默认值。<br /><br />&nbsp;&nbsp;对用户空间,内核HZ几乎完全隐藏,用户&nbsp;HZ&nbsp;始终扩展为&nbsp;100。当用户空间程序包含&nbsp;param.h,且每个报告给用户空间的计数器都做了相应转换。对用户来说确切的&nbsp;HZ&nbsp;值只能通过&nbsp;/proc/interrupts&nbsp;获得:/proc/interrupts&nbsp;的计数值除以&nbsp;/proc/uptime&nbsp;中报告的系统运行时间。<br /><br /><br /><br />对于ARM体系结构:在文件中的定义如下:<br /><br />#ifdef&nbsp;__KERNEL__<br />#&nbsp;define&nbsp;HZ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CONFIG_HZ&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Internal&nbsp;kernel&nbsp;timer&nbsp;frequency&nbsp;*/<br />#&nbsp;define&nbsp;USER_HZ&nbsp;&nbsp;&nbsp;&nbsp;100&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;用户空间使用的HZ,User&nbsp;interfaces&nbsp;are&nbsp;in&nbsp;'ticks'&nbsp;*/<br />#&nbsp;define&nbsp;CLOCKS_PER_SEC&nbsp;&nbsp;&nbsp;&nbsp;(USER_HZ)&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;like&nbsp;times()&nbsp;*/<br />#else<br />#&nbsp;define&nbsp;HZ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;100<br />#endif<br /><br /><br />也就是说:HZ&nbsp;由__KERNEL__和CONFIG_HZ决定。若未定义__KERNEL__,HZ为100;否则为CONFIG_HZ。而CONFIG_HZ是在内核的根目录的.config文件中定义,并没有在make&nbsp;menuconfig的配置选项中出现。Linux的archarmconfigss3c2410_defconfig文件中的定义为:&nbsp;<br /><br />#<br />#&nbsp;Kernel&nbsp;Features<br />#<br />#&nbsp;CONFIG_PREEMPT&nbsp;is&nbsp;not&nbsp;set<br />#&nbsp;CONFIG_NO_IDLE_HZ&nbsp;is&nbsp;not&nbsp;set<br />CONFIG_HZ=200<br />#&nbsp;CONFIG_AEABI&nbsp;is&nbsp;not&nbsp;set<br />#&nbsp;CONFIG_ARCH_DISCONTIGMEM_ENABLE&nbsp;is&nbsp;not&nbsp;set<br /><br /><br />所以正常情况下s3c24x0的HZ为200。这一数值在后面的实验中可以证实。<br /><br />每次发生一个时钟中断,内核内部计数器的值就加一。这个计数器在系统启动时初始化为&nbsp;0,&nbsp;因此它代表本次系统启动以来的时钟嘀哒数。这个计数器是一个&nbsp;64-位&nbsp;变量(&nbsp;即便在&nbsp;32-位的体系上)并且称为&nbsp;“jiffies_64”。但是驱动通常访问&nbsp;jiffies&nbsp;变量(unsigned&nbsp;long)(根据体系结构的不同:可能是&nbsp;jiffies_64&nbsp;,可能是jiffies_64&nbsp;的低32位)。使用&nbsp;jiffies&nbsp;是首选,因为它访问更快,且无需在所有的体系上实现原子地访问&nbsp;64-位的&nbsp;jiffies_64&nbsp;值。&nbsp;<br /><br />使用&nbsp;jiffies&nbsp;计数器<br /><br />这个计数器和用来读取它的工具函数包含在&nbsp;,&nbsp;通常只需包含&nbsp;,它会自动放入&nbsp;jiffies.h&nbsp;。&nbsp;jiffies&nbsp;和&nbsp;jiffies_64&nbsp;必须被当作只读变量。当需要记录当前&nbsp;jiffies&nbsp;值(被声明为&nbsp;volatile&nbsp;避免编译器优化内存读)时,可以简单地访问这个&nbsp;unsigned&nbsp;long&nbsp;变量,如:&nbsp;<br /><br />#include&nbsp;<br />unsigned&nbsp;long&nbsp;j,&nbsp;stamp_1,&nbsp;stamp_half,&nbsp;stamp_n;<br /><br />j&nbsp;=&nbsp;jiffies;&nbsp;/*&nbsp;read&nbsp;the&nbsp;current&nbsp;value&nbsp;*/<br />stamp_1&nbsp;=&nbsp;j&nbsp;+&nbsp;HZ;&nbsp;/*&nbsp;1&nbsp;second&nbsp;in&nbsp;the&nbsp;future&nbsp;*/<br />stamp_half&nbsp;=&nbsp;j&nbsp;+&nbsp;HZ/2;&nbsp;/*&nbsp;half&nbsp;a&nbsp;second&nbsp;*/<br />stamp_n&nbsp;=&nbsp;j&nbsp;+&nbsp;n&nbsp;*&nbsp;HZ&nbsp;/&nbsp;1000;&nbsp;/*&nbsp;n&nbsp;milliseconds&nbsp;*/<br /><br /><br /><br />以下是一些简单的工具宏及其定义:&nbsp;<br /><br />#define&nbsp;time_after(a,b)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;(typecheck(unsigned&nbsp;long,&nbsp;a)&nbsp;&&&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;typecheck(unsigned&nbsp;long,&nbsp;b)&nbsp;&&&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;((long)(b)&nbsp;-&nbsp;(long)(a)&nbsp;&lt&nbsp;0))<br />#define&nbsp;time_before(a,b)&nbsp;&nbsp;&nbsp;&nbsp;time_after(b,a)<br />#define&nbsp;time_after_eq(a,b)&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;(typecheck(unsigned&nbsp;long,&nbsp;a)&nbsp;&&&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;typecheck(unsigned&nbsp;long,&nbsp;b)&nbsp;&&&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;((long)(a)&nbsp;-&nbsp;(long)(b)&nbsp;&gt=&nbsp;0))<br />#define&nbsp;time_before_eq(a,b)&nbsp;&nbsp;&nbsp;&nbsp;time_after_eq(b,a)<br /><br /><br /><br />用户空间的时间表述法(struct&nbsp;timeval&nbsp;和&nbsp;struct&nbsp;timespec&nbsp;)与内核表述法的转换函数:<br /><br />#include&nbsp;/*&nbsp;#include&nbsp;--&gt&nbsp;kernel&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ime.c*/<br /><br />struct&nbsp;timespec&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;time_t&nbsp;&nbsp;&nbsp;&nbsp;tv_sec;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;seconds&nbsp;*/<br />&nbsp;&nbsp;&nbsp;&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;tv_nsec;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;nanoseconds&nbsp;*/<br />};<br />#endif<br /><br />struct&nbsp;timeval&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;time_t&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tv_sec;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;seconds&nbsp;*/<br />&nbsp;&nbsp;&nbsp;&nbsp;suseconds_t&nbsp;&nbsp;&nbsp;&nbsp;tv_usec;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;microseconds&nbsp;*/<br />};<br /><br />unsigned&nbsp;long&nbsp;timespec_to_jiffies(struct&nbsp;timespec&nbsp;*value);<br />void&nbsp;jiffies_to_timespec(unsigned&nbsp;long&nbsp;jiffies,&nbsp;struct&nbsp;timespec&nbsp;*value);<br />unsigned&nbsp;long&nbsp;timeval_to_jiffies(struct&nbsp;timeval&nbsp;*value);<br />void&nbsp;jiffies_to_timeval(unsigned&nbsp;long&nbsp;jiffies,&nbsp;struct&nbsp;timeval&nbsp;*value);<br /><br /><br />访问jiffies_64&nbsp;对于&nbsp;32-位&nbsp;处理器不是原子的,这意味着如果这个变量在你正在读取它们时被更新你可能读到错误的值。若需要访问jiffies_64,内核有一个特别的辅助函数,为你完成适当的锁定:&nbsp;<br /><br />#include&nbsp;<br />u64&nbsp;get_jiffies_64(void);<br /><br /><br /><br />处理器特定的寄存器<br /><br />若需测量非常短时间间隔或需非常高的精度,可以借助平台依赖的资源。许多现代处理器包含一个随时钟周期不断递增的计数寄存器,他是进行高精度的时间管理任务唯一可靠的方法。最有名的计数器寄存器是&nbsp;TSC&nbsp;(&nbsp;timestamp&nbsp;counter),&nbsp;在&nbsp;x86&nbsp;的&nbsp;Pentium&nbsp;处理器开始引入并在之后所有的&nbsp;CPU&nbsp;中出现(包括&nbsp;x86_64&nbsp;平台)。它是一个&nbsp;64-位&nbsp;寄存器,计数&nbsp;CPU&nbsp;的时钟周期,可从内核和用户空间读取。在包含了&nbsp;(一个&nbsp;x86-特定的头文件,&nbsp;它的名子代表'machine-specific&nbsp;registers')的代码中可使用这些宏:<br /><br /><br /><br /><br />rdtsc(low32,high32);/*原子地读取&nbsp;64-位TSC&nbsp;值到&nbsp;2&nbsp;个&nbsp;32-位&nbsp;变量*/<br />rdtscl(low32);/*读取TSC的低32位到一个&nbsp;32-位&nbsp;变量*/<br />rdtscll(var64);/*读&nbsp;64-位TSC&nbsp;值到一个&nbsp;long&nbsp;long&nbsp;变量*/<br /><br />/*下面的代码行测量了指令自身的执行时间:*/<br />unsigned&nbsp;long&nbsp;ini,&nbsp;end;<br />rdtscl(ini);&nbsp;rdtscl(end);<br />printk('time&nbsp;lapse:&nbsp;%li<br />',&nbsp;end&nbsp;-&nbsp;ini);<br /><br /><br />一些其他的平台提供相似的功能,&nbsp;并且内核头文件提供一个体系无关的功能用来代替&nbsp;rdtsc,称&nbsp;get_cycles(定义在&nbsp;(&nbsp;由&nbsp;包含)),原型如下:&nbsp;<br /><br />#include&nbsp;<br />cycles_t&nbsp;get_cycles(void);&nbsp;<br />/*这个函数在每个平台都有定义,&nbsp;但在没有时钟周期计数器的平台上返回&nbsp;0&nbsp;*/<br /><br />/*由于s3c2410系列处理器上没有时钟周期计数器所以get_cycles定义如下:*/<br />typedef&nbsp;unsigned&nbsp;long&nbsp;cycles_t;<br /><br />static&nbsp;inline&nbsp;cycles_t&nbsp;get_cycles&nbsp;(void)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0;<br />}<br /><br /><br /><br /><br /><br />--------------------------------------------------------------------------------<br /><br /><br /><br />获取当前时间<br /><br />驱动一般无需知道时钟时间(用年月日、小时、分钟、秒来表达的时间),只对用户程序才需要,如&nbsp;cron&nbsp;和&nbsp;syslogd。&nbsp;内核提供了一个将时钟时间转变为秒数值的函数:<br /><br /><br /><br />unsigned&nbsp;long<br />mktime(const&nbsp;unsigned&nbsp;int&nbsp;year0,&nbsp;const&nbsp;unsigned&nbsp;int&nbsp;mon0,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;unsigned&nbsp;int&nbsp;day,&nbsp;const&nbsp;unsigned&nbsp;int&nbsp;hour,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;unsigned&nbsp;int&nbsp;min,&nbsp;const&nbsp;unsigned&nbsp;int&nbsp;sec)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;int&nbsp;mon&nbsp;=&nbsp;mon0,&nbsp;year&nbsp;=&nbsp;year0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;1..12&nbsp;-&gt&nbsp;11,12,1..10&nbsp;*/<br />&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(0&nbsp;&gt=&nbsp;(int)&nbsp;(mon&nbsp;-=&nbsp;2))&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mon&nbsp;+=&nbsp;12;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Puts&nbsp;Feb&nbsp;last&nbsp;since&nbsp;it&nbsp;has&nbsp;leap&nbsp;day&nbsp;*/<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;year&nbsp;-=&nbsp;1;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;((((unsigned&nbsp;long)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(year/4&nbsp;-&nbsp;year/100&nbsp;+&nbsp;year/400&nbsp;+&nbsp;367*mon/12&nbsp;+&nbsp;day)&nbsp;+<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;year*365&nbsp;-&nbsp;719499<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)*24&nbsp;+&nbsp;hour&nbsp;/*&nbsp;now&nbsp;have&nbsp;hours&nbsp;*/<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)*60&nbsp;+&nbsp;min&nbsp;/*&nbsp;now&nbsp;have&nbsp;minutes&nbsp;*/<br />&nbsp;&nbsp;&nbsp;&nbsp;)*60&nbsp;+&nbsp;sec;&nbsp;/*&nbsp;finally&nbsp;seconds&nbsp;*/<br />}<br />/*这个函数将时间转换成从1970年1月1日0小时0分0秒到你输入的时间所经过的秒数,溢出时间为2106-02-07&nbsp;06:28:16。本人认为这个函数的使用应这样:若你要计算2000-02-07&nbsp;06:28:16&nbsp;到2000-02-09&nbsp;06:28:16&nbsp;所经过的秒数:unsigned&nbsp;long&nbsp;time1&nbsp;=&nbsp;mktime(2000,2,7,6,28,16)-mktime(2000,2,9,6,28,16);&nbsp;若还要转成jiffies,就再加上:unsigned&nbsp;long&nbsp;time2&nbsp;=&nbsp;time1*HZ.&nbsp;注意溢出的情况!*/<br /><br /><br />为了处理绝对时间,&nbsp;导出了&nbsp;do_gettimeofday&nbsp;函数,它填充一个指向&nbsp;struct&nbsp;timeval&nbsp;的指针变量。绝对时间也可来自&nbsp;xtime&nbsp;变量,一个&nbsp;struct&nbsp;timespec&nbsp;值,为了原子地访问它,内核提供了函数&nbsp;current_kernel_time。它们的精确度由硬件决定,原型是:<br /><br />#include&nbsp;<br />void&nbsp;do_gettimeofday(struct&nbsp;timeval&nbsp;*tv);<br />struct&nbsp;timespec&nbsp;current_kernel_time(void);<br /><br />/*得到的数据都表示当前时间距UNIX时间基准1970-01-01&nbsp;00:00:00的相对时间*/<br /><br /><br />以上两个函数在ARM平台都是通过&nbsp;xtime&nbsp;变量得到数据的。&nbsp;<br /><br /><br /><br /><br />--------------------------------------------------------------------------------<br />全局变量xtime:它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准1970-01-01&nbsp;00:00:00的相对秒数值。&nbsp;<br /><br />结构timeval是Linux内核表示时间的一种格式(Linux内核对时间的表示有多种格式,每种格式都有不同的时间精度),其时间精度是微秒。该结构是内核表示时间时最常用的一种格式,它定义在头文件include/linux/time.h中,如下所示:&nbsp;<br /><br />struct&nbsp;timeval&nbsp;{&nbsp;<br /><br />time_t&nbsp;tv_sec;&nbsp;/*&nbsp;seconds&nbsp;*/&nbsp;<br /><br />suseconds_t&nbsp;tv_usec;&nbsp;/*&nbsp;microseconds&nbsp;*/&nbsp;<br /><br />};&nbsp;<br /><br />其中,成员tv_sec表示当前时间距UNIX时间基准的秒数值,而成员tv_usec则表示一秒之内的微秒值,且1000000&gttv_usec&gt=0。&nbsp;<br /><br /><br /><br />Linux内核通过timeval结构类型的全局变量xtime来维持当前时间,该变量定义在kernel/timer.c文件中,如下所示:&nbsp;<br /><br />/*&nbsp;The&nbsp;current&nbsp;time&nbsp;*/&nbsp;<br /><br />volatile&nbsp;struct&nbsp;timeval&nbsp;xtime&nbsp;__attribute__&nbsp;((aligned&nbsp;(16)));&nbsp;<br /><br />但是,全局变量xtime所维持的当前时间通常是供用户来检索和设置的,而其他内核模块通常很少使用它(其他内核模块用得最多的是jiffies),因此对xtime的更新并不是一项紧迫的任务,所以这一工作通常被延迟到时钟中断的底半部(bottom&nbsp;half)中来进行。由于bottom&nbsp;half的执行时间带有不确定性,因此为了记住内核上一次更新xtime是什么时候,Linux内核定义了一个类似于jiffies的全局变量wall_jiffies,来保存内核上一次更新xtime时的jiffies值。时钟中断的底半部分每一次更新xtime的时侯都会将wall_jiffies更新为当时的jiffies值。全局变量wall_jiffies定义在kernel/timer.c文件中:&nbsp;<br /><br />/*&nbsp;jiffies&nbsp;at&nbsp;the&nbsp;most&nbsp;recent&nbsp;update&nbsp;of&nbsp;wall&nbsp;time&nbsp;*/&nbsp;<br /><br />unsigned&nbsp;long&nbsp;wall_jiffies;&nbsp;<br /><br /><br /><br />原文网址:http://blog.csdn.net/freedom1013/archive/2007/03/13/1528310.aspx&nbsp;<br /><br /><br /><br />--------------------------------------------------------------------------------<br /><br /><br /><br /><br />--------------------------------------------------------------------------------<br /><br /><br /><br />延迟执行<br /><br />设备驱动常常需要延后一段时间执行一个特定片段的代码,&nbsp;常常允许硬件完成某个任务.<br /><br />长延迟<br /><br />有时,驱动需要延后执行相对长时间,长于一个时钟嘀哒。<br /><br />忙等待(尽量别用)<br /><br />若想延迟执行若干个时钟嘀哒,精度要求不高。最容易的(&nbsp;尽管不推荐&nbsp;)&nbsp;实现是一个监视&nbsp;jiffy&nbsp;计数器的循环。这种忙等待实现的代码如下:&nbsp;<br /><br />while&nbsp;(time_before(jiffies,&nbsp;j1))<br />&nbsp;&nbsp;&nbsp;&nbsp;cpu_relax();<br /><br /><br /><br />对&nbsp;cpu_relex&nbsp;的调用将以体系相关的方式执行,在许多系统中它根本不做任何事,这个方法应当明确地避免。对于ARM体系来说:&nbsp;<br /><br />#define&nbsp;cpu_relax()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;barrier()<br /><br /><br /><br />也就是说在ARM上运行忙等待相当于:<br /><br />while&nbsp;(time_before(jiffies,&nbsp;j1))&nbsp;;<br /><br /><br />这种忙等待严重地降低了系统性能。如果未配置内核为抢占式,&nbsp;这个循环在延时期间完全锁住了处理器,计算机直到时间&nbsp;j1&nbsp;到时会完全死掉。如果运行一个可抢占的内核时会改善一点,但是忙等待在可抢占系统中仍然是浪费资源的。更糟的是,&nbsp;当进入循环时如果中断碰巧被禁止,&nbsp;jiffies&nbsp;将不会被更新,&nbsp;并且&nbsp;while&nbsp;条件永远保持真,运行一个抢占的内核也不会有帮助,&nbsp;唯一的解决方法是重启。<br /><br />让出处理器<br /><br />忙等待加重了系统负载,必须找出一个更好的技术:不需要CPU时释放CPU&nbsp;。&nbsp;这可通过调用schedule函数实现(在&nbsp;中声明):<br /><br />while&nbsp;(time_before(jiffies,&nbsp;j1))&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;schedule();<br />}<br /><br /><br />在计算机空闲时运行空闲任务(进程号&nbsp;0,&nbsp;由于历史原因也称为swapper)可减轻处理器工作负载、降低温度、增加寿命。<br /><br />超时<br /><br />实现延迟的最好方法应该是让内核为我们完成相应的工作。<br /><br />(1)若驱动使用一个等待队列来等待某些其他事件,并想确保它在一个特定时间段内运行,可使用:&nbsp;<br /><br />#include&nbsp;<br />long&nbsp;wait_event_timeout(wait_queue_head_t&nbsp;q,&nbsp;condition,&nbsp;long&nbsp;timeout);<br />long&nbsp;wait_event_interruptible_timeout(wait_queue_head_t&nbsp;q,&nbsp;condition,&nbsp;long&nbsp;timeout);<br />/*这些函数在给定队列上睡眠,&nbsp;但是它们在超时(以&nbsp;jiffies&nbsp;表示)到后返回。如果超时,函数返回&nbsp;0;&nbsp;如果这个进程被其他事件唤醒,则返回以&nbsp;jiffies&nbsp;表示的剩余的延迟实现;返回值从不会是负值*/<br /><br /><br /><br />(2)为了实现进程在超时到期时被唤醒而又不等待特定事件(避免声明和使用一个多余的等待队列头),内核提供了&nbsp;schedule_timeout&nbsp;函数:<br /><br /><br /><br /><br />#include&nbsp;<br />signed&nbsp;long&nbsp;schedule_timeout(signed&nbsp;long&nbsp;timeout);<br /><br />/*timeout&nbsp;是要延时的&nbsp;jiffies&nbsp;数。除非这个函数在给定的&nbsp;timeout&nbsp;流失前返回,否则返回值是&nbsp;0&nbsp;。schedule_timeout&nbsp;要求调用者首先设置当前的进程状态。为获得一个不可中断的延迟,&nbsp;可使用&nbsp;TASK_UNINTERRUPTIBLE&nbsp;代替。如果你忘记改变当前进程的状态,&nbsp;调用&nbsp;schedule_time&nbsp;如同调用&nbsp;shcedule,建立一个不用的定时器。一个典型调用如下:*/<br />set_current_state(TASK_INTERRUPTIBLE);<br />schedule_timeout&nbsp;(delay);<br /><br /><br />短延迟<br /><br />当一个设备驱动需要处理硬件的延迟(latency潜伏期),&nbsp;涉及到的延时通常最多几个毫秒,在这个情况下,&nbsp;不应依靠时钟嘀哒,而是内核函数&nbsp;ndelay,&nbsp;udelay和&nbsp;mdelay&nbsp;,他们分别延后执行指定的纳秒数,&nbsp;微秒数或者毫秒数,定义在&nbsp;,原型如下:<br /><br /><br />#include&nbsp;<br />void&nbsp;ndelay(unsigned&nbsp;long&nbsp;nsecs);<br />void&nbsp;udelay(unsigned&nbsp;long&nbsp;usecs);<br />void&nbsp;mdelay(unsigned&nbsp;long&nbsp;msecs);<br /><br /><br />重要的是记住这&nbsp;3&nbsp;个延时函数是忙等待;&nbsp;其他任务在时间流失时不能运行。每个体系都实现&nbsp;udelay,&nbsp;但是其他的函数可能未定义;&nbsp;如果它们没有定义,&nbsp;提供一个缺省的基于&nbsp;udelay&nbsp;的版本。在所有的情况中,&nbsp;获得的延时至少是要求的值,&nbsp;但可能更多。udelay&nbsp;的实现使用一个软件循环,&nbsp;它基于在启动时计算的处理器速度和使用整数变量&nbsp;loos_per_jiffy确定循环次数。<br /><br />为避免在循环计算中整数溢出,&nbsp;传递给udelay&nbsp;和&nbsp;ndelay的值有一个上限,如果你的模块无法加载和显示一个未解决的符号:__bad_udelay,&nbsp;这意味着你调用&nbsp;udleay时使用太大的参数。<br />作为一个通用的规则:若试图延时几千纳秒,&nbsp;应使用&nbsp;udelay&nbsp;而不是&nbsp;ndelay;&nbsp;类似地,&nbsp;毫秒规模的延时应当使用&nbsp;mdelay&nbsp;完成而不是一个更细粒度的函数。<br /><br />有另一个方法获得毫秒(和更长)延时而不用涉及到忙等待的方法是使用以下函数(在&nbsp;中声明):&nbsp;<br /><br />void&nbsp;msleep(unsigned&nbsp;int&nbsp;millisecs);<br />unsigned&nbsp;long&nbsp;msleep_interruptible(unsigned&nbsp;int&nbsp;millisecs);<br />void&nbsp;ssleep(unsigned&nbsp;int&nbsp;seconds)<br /><br /><br /><br />若能够容忍比请求的更长的延时,应使用&nbsp;schedule_timeout,&nbsp;msleep&nbsp;或&nbsp;ssleep。&nbsp;<br /><br /><br /><br />--------------------------------------------------------------------------------<br /><br /><br /><br />内核定时器<br /><br />当需要调度一个以后发生的动作,&nbsp;而在到达该时间点时不阻塞当前进程,&nbsp;则可使用内核定时器。内核定时器用来调度一个函数在将来一个特定的时间(基于时钟嘀哒)执行,从而可完成各类任务。<br />内核定时器是一个数据结构,&nbsp;它告诉内核在一个用户定义的时间点使用用户定义的参数执行一个用户定义的函数,函数位于&nbsp;和&nbsp;kernel/timer.c&nbsp;。被调度运行的函数几乎确定不会在注册它们的进程在运行时运行,而是异步运行。实际上,&nbsp;内核定时器通常被作为一个'软件中断'的结果而实现。当在进程上下文之外(即在中断上下文)中运行程序时,&nbsp;必须遵守下列规则:<br /><br />(1)不允许访问用户空间;<br />(2)current&nbsp;指针在原子态没有意义;<br />(3)不能进行睡眠或者调度.&nbsp;例如:调用&nbsp;kmalloc(...,&nbsp;GFP_KERNEL)&nbsp;是非法的,信号量也不能使用因为它们可能睡眠。<br /><br /><br />通过调用函数&nbsp;in_interrupt()能够告知是否它在中断上下文中运行,无需参数并如果处理器当前在中断上下文运行就返回非零。<br />通过调用函数&nbsp;in_atomic()能够告知调度是否被禁止,若调度被禁止返回非零;&nbsp;调度被禁止包含硬件和软件中断上下文以及任何持有自旋锁的时候。<br /><br />在后一种情况,&nbsp;current&nbsp;可能是有效的,但是访问用户空间是被禁止的,因为它能导致调度发生.&nbsp;当使用&nbsp;in_interrupt()时,都应考虑是否真正该使用的是&nbsp;in_atomic&nbsp;。他们都在&nbsp;中声明。<br /><br />内核定时器的另一个重要特性是任务可以注册它本身在后面时间重新运行,因为每个&nbsp;timer_list&nbsp;结构都会在运行前从激活的定时器链表中去连接,因此能够立即链入其他的链表。一个重新注册它自己的定时器一直运行在同一个&nbsp;CPU.<br /><br />即便在一个单处理器系统,定时器是一个潜在的态源,这是异步运行直接结果。因此任何被定时器函数访问的数据结构应当通过原子类型或自旋锁被保护,避免并发访问。<br /><br />定时器&nbsp;API<br /><br />内核提供给驱动许多函数来声明、注册以及删除内核定时器:&nbsp;<br /><br /><br />#include&nbsp;<br />struct&nbsp;timer_list&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;list_head&nbsp;entry;<br />&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;expires;/*期望定时器运行的绝对&nbsp;jiffies&nbsp;值,不是一个&nbsp;jiffies_64&nbsp;值,因为定时器不被期望在将来很久到时*/<br />&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;(*function)(unsigned&nbsp;long);&nbsp;/*期望调用的函数*/<br />&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;data;/*传递给函数的参数,若需要在参数中传递多个数据项,可以将它们捆绑成单个数据结构并且将它的指针强制转换为&nbsp;unsiged&nbsp;long&nbsp;的指针传入。这种做法在所有支持的体系上都是安全的并且在内存管理中相当普遍*/<br />&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;tvec_t_base_s&nbsp;*base;<br />#ifdef&nbsp;CONFIG_TIMER_STATS<br />&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;*start_site;<br />&nbsp;&nbsp;&nbsp;&nbsp;char&nbsp;start_comm[16];<br />&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;start_pid;<br />#endif<br />};<br />/*这个结构必须在使用前初始化,以保证所有的成员被正确建立(包括那些对调用者不透明的初始化):*/<br />void&nbsp;init_timer(struct&nbsp;timer_list&nbsp;*timer);<br />struct&nbsp;timer_list&nbsp;TIMER_INITIALIZER(_function,&nbsp;_expires,&nbsp;_data);<br />/*在初始化后和调用&nbsp;add_timer&nbsp;前,可以改变&nbsp;3&nbsp;个公共成员:expires、function和data*/<br />void&nbsp;add_timer(struct&nbsp;timer_list&nbsp;*&nbsp;timer);<br />int&nbsp;del_timer(struct&nbsp;timer_list&nbsp;*&nbsp;timer);/*在到时前禁止一个已注册的定时器*/<br />int&nbsp;del_timer_sync(struct&nbsp;timer_list&nbsp;*timer);&nbsp;/*如同&nbsp;del_timer&nbsp;,但还保证当它返回时,&nbsp;定时器函数不在任何&nbsp;CPU&nbsp;上运行,以避免在&nbsp;SMP&nbsp;系统上竞态,&nbsp;并且在&nbsp;单处理器内核中和&nbsp;del_timer&nbsp;相同。这个函数应当在大部分情况下优先考虑。&nbsp;如果它被从非原子上下文调用,&nbsp;这个函数可能睡眠,但是在其他情况下会忙等待。当持有锁时要小心调用&nbsp;del_timer_sync&nbsp;,如果这个定时器函数试图获得同一个锁,&nbsp;系统会死锁。如果定时器函数重新注册自己,&nbsp;调用者必须首先确保这个重新注册不会发生;&nbsp;这通常通过设置一个'&nbsp;关闭&nbsp;'标志来实现,&nbsp;这个标志被定时器函数检查*/<br />int&nbsp;mod_timer(struct&nbsp;timer_list&nbsp;*timer,&nbsp;unsigned&nbsp;long&nbsp;expires);&nbsp;/*更新一个定时器的超时时间,&nbsp;常用于超时定时器。也可在正常使用&nbsp;add_timer时在不活动的定时器上调用mod_timer*/&nbsp;<br />int&nbsp;timer_pending(const&nbsp;struct&nbsp;timer_list&nbsp;*&nbsp;timer);&nbsp;/*通过调用timer_list结构中一个不可见的成员,返回定时器是否在被调度运行*/<br /><br /><br /><br />内核定时器的实现《LDD3》介绍的比较笼统,以后看《ULK3》的时候再细细研究。<br /><br />一个内核定时器还远未完善,因为它受到&nbsp;jitter&nbsp;、硬件中断,还有其他定时器和其他异步任务的影响。虽然一个简单数字&nbsp;I/O关联的定时器对简单任务是足够的,但不合适在工业环境中的生产系统,对于这样的任务,你将最可能需要实时内核扩展(RT-Linux).<br /><br /><br /><br />Tasklets&nbsp;<br /><br />另一个有关于定时的内核设施是&nbsp;tasklet。它类似内核定时器:在中断时间运行且运行同一个&nbsp;CPU&nbsp;上,&nbsp;并接收一个&nbsp;unsigned&nbsp;long&nbsp;参数。不同的是:无法要求在一个指定的时间执行函数,只能简单地要求它在以后的一个由内核选择的时间执行。它对于中断处理特别有用:硬件中断必须尽快处理,&nbsp;但大部分的数据管理可以延后到以后安全的时间执行。&nbsp;实际上,&nbsp;一个&nbsp;tasket,&nbsp;就象一个内核定时器,&nbsp;在一个'软中断'的上下文中执行(以原子模式)。软件中断是在使能硬件中断时执行异步任务的一个内核机制。<br /><br />tasklet&nbsp;以一个数据结构形式存在,使用前必须被初始化。初始化能够通过调用一个特定函数或者通过使用某些宏定义声明结构:&nbsp;<br /><br />#include&nbsp;<br />struct&nbsp;tasklet_struct<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;tasklet_struct&nbsp;*next;<br />&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;state;<br />&nbsp;&nbsp;&nbsp;&nbsp;atomic_t&nbsp;count;<br />&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;(*func)(unsigned&nbsp;long);<br />&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;data;<br />};<br />void&nbsp;tasklet_init(struct&nbsp;tasklet_struct&nbsp;*t,<br />void&nbsp;(*func)(unsigned&nbsp;long),&nbsp;unsigned&nbsp;long&nbsp;data);<br /><br />#define&nbsp;DECLARE_TASKLET(name,&nbsp;func,&nbsp;data)&nbsp;<br />struct&nbsp;tasklet_struct&nbsp;name&nbsp;=&nbsp;{&nbsp;NULL,&nbsp;0,&nbsp;ATOMIC_INIT(0),&nbsp;func,&nbsp;data&nbsp;}<br />#define&nbsp;DECLARE_TASKLET_DISABLED(name,&nbsp;func,&nbsp;data)&nbsp;<br />struct&nbsp;tasklet_struct&nbsp;name&nbsp;=&nbsp;{&nbsp;NULL,&nbsp;0,&nbsp;ATOMIC_INIT(1),&nbsp;func,&nbsp;data&nbsp;}<br /><br />void&nbsp;tasklet_disable(struct&nbsp;tasklet_struct&nbsp;*t);&nbsp;<br />/*函数暂时禁止给定的&nbsp;tasklet被&nbsp;tasklet_schedule&nbsp;调度,直到这个&nbsp;tasklet&nbsp;被再次被enable;若这个&nbsp;tasklet&nbsp;当前在运行,&nbsp;这个函数忙等待直到这个tasklet退出*/<br />void&nbsp;tasklet_disable_nosync(struct&nbsp;tasklet_struct&nbsp;*t);&nbsp;<br />/*和tasklet_disable类似,但是tasklet可能仍然运行在另一个&nbsp;CPU&nbsp;*/<br />void&nbsp;tasklet_enable(struct&nbsp;tasklet_struct&nbsp;*t);&nbsp;<br />/*使能一个之前被disable的&nbsp;tasklet;若这个&nbsp;tasklet&nbsp;已经被调度,&nbsp;它会很快运行。&nbsp;tasklet_enable&nbsp;和tasklet_disable必须匹配调用,&nbsp;因为内核跟踪每个&nbsp;tasklet&nbsp;的'禁止次数'*/&nbsp;<br />void&nbsp;tasklet_schedule(struct&nbsp;tasklet_struct&nbsp;*t);&nbsp;<br />/*调度&nbsp;tasklet&nbsp;执行,如果tasklet在运行中被调度,&nbsp;它在完成后会再次运行;&nbsp;这保证了在其他事件被处理当中发生的事件受到应有的注意.&nbsp;这个做法也允许一个&nbsp;tasklet&nbsp;重新调度它自己*/<br />void&nbsp;tasklet_hi_schedule(struct&nbsp;tasklet_struct&nbsp;*t);&nbsp;<br />/*和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时,&nbsp;它处理高优先级&nbsp;tasklet&nbsp;在其他软中断之前,只有具有低响应周期要求的驱动才应使用这个函数,&nbsp;可避免其他软件中断处理引入的附加周期*/<br />void&nbsp;tasklet_kill(struct&nbsp;tasklet_struct&nbsp;*t);&nbsp;<br />/*确保了&nbsp;tasklet&nbsp;不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果&nbsp;tasklet&nbsp;正在运行,&nbsp;这个函数等待直到它执行完毕。若&nbsp;tasklet&nbsp;重新调度它自己,则必须阻止在调用&nbsp;tasklet_kill&nbsp;前它重新调度它自己,如同使用&nbsp;del_timer_sync*/<br /><br /><br /><br />tasklet&nbsp;的特点:<br />(1)一个&nbsp;tasklet&nbsp;能够被禁止并且之后被重新使能;&nbsp;它不会执行,直到它被使能与被禁止相同的的次数;<br />(2)如同定时器,&nbsp;一个&nbsp;tasklet&nbsp;可以注册它自己;<br />(3)一个&nbsp;tasklet&nbsp;能被调度来执行以正常的优先级或者高优先级;<br />(4)&nbsp;如果系统不在重负载下,taslet&nbsp;可能立刻运行,&nbsp;但是从不会晚于下一个时钟嘀哒;<br />(5)一个&nbsp;tasklet&nbsp;可能和其他&nbsp;tasklet&nbsp;并发,&nbsp;但是它自己是严格地串行的&nbsp;,且tasklet&nbsp;从不同时运行在不同处理器上,通常在调度它的同一个&nbsp;CPU&nbsp;上运行。<br /><br />工作队列<br /><br />工作队列类似&nbsp;taskets,允许内核代码请求在将来某个时间调用一个函数,不同在于:<br />(1)tasklet&nbsp;在软件中断上下文中运行,所以&nbsp;tasklet&nbsp;代码必须是原子的。而工作队列函数在一个特殊内核进程上下文运行,有更多的灵活性,且能够休眠。<br />(2)tasklet&nbsp;只能在最初被提交的处理器上运行,这只是工作队列默认工作方式。<br />(3)内核代码可以请求工作队列函数被延后一个给定的时间间隔。<br />(4)tasklet&nbsp;执行的很快,&nbsp;短时期,&nbsp;并且在原子态,&nbsp;而工作队列函数可能是长周期且不需要是原子的,两个机制有它适合的情形。<br /><br />工作队列有&nbsp;struct&nbsp;workqueue_struct&nbsp;类型,在&nbsp;中定义。一个工作队列必须明确的在使用前创建,宏为:<br /><br />struct&nbsp;workqueue_struct&nbsp;*create_workqueue(const&nbsp;char&nbsp;*name);<br />struct&nbsp;workqueue_struct&nbsp;*create_singlethread_workqueue(const&nbsp;char&nbsp;*name);<br /><br /><br /><br />每个工作队列有一个或多个专用的进程('内核线程'),&nbsp;这些进程运行提交给这个队列的函数。&nbsp;若使用&nbsp;create_workqueue,&nbsp;就得到一个工作队列它在系统的每个处理器上有一个专用的线程。在很多情况下,过多线程对系统性能有影响,如果单个线程就足够则使用&nbsp;create_singlethread_workqueue&nbsp;来创建工作队列。<br /><br />提交一个任务给一个工作队列,在这里《LDD3》介绍的内核2.6.10和我用的新内核2.6.22.2已经有不同了,老接口已经不能用了,编译会出错。这里我只讲2.6.22.2的新接口,至于老的接口我想今后内核不会再有了。从这一点我们可以看出内核发展。&nbsp;<br /><br />/*需要填充work_struct或delayed_work结构,可以在编译时完成,&nbsp;宏如下:&nbsp;*/<br /><br />struct&nbsp;work_struct&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;atomic_long_t&nbsp;data;<br />#define&nbsp;WORK_STRUCT_PENDING&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;T&nbsp;if&nbsp;work&nbsp;item&nbsp;pending&nbsp;execution&nbsp;*/<br />#define&nbsp;WORK_STRUCT_FLAG_MASK&nbsp;(3UL)<br />#define&nbsp;WORK_STRUCT_WQ_DATA_MASK&nbsp;(~WORK_STRUCT_FLAG_MASK)<br />&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;list_head&nbsp;entry;<br />&nbsp;&nbsp;&nbsp;&nbsp;work_func_t&nbsp;func;<br />};<br /><br />struct&nbsp;delayed_work&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;work_struct&nbsp;work;<br />&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;timer_list&nbsp;timer;<br />};<br /><br />DECLARE_WORK(n,&nbsp;f)&nbsp;&nbsp;&nbsp;&nbsp;<br />/*n&nbsp;是声明的work_struct结构名称,&nbsp;f是要从工作队列被调用的函数*/<br />DECLARE_DELAYED_WORK(n,&nbsp;f)<br />/*n是声明的delayed_work结构名称,&nbsp;f是要从工作队列被调用的函数*/<br /><br />/*若在运行时需要建立&nbsp;work_struct&nbsp;或&nbsp;delayed_work结构,&nbsp;使用下面&nbsp;2&nbsp;个宏定义:*/<br />INIT_WORK(struct&nbsp;work_struct&nbsp;*work,&nbsp;void&nbsp;(*function)(void&nbsp;*));&nbsp;<br />PREPARE_WORK(struct&nbsp;work_struct&nbsp;*work,&nbsp;void&nbsp;(*function)(void&nbsp;*));&nbsp;<br />INIT_DELAYED_WORK(struct&nbsp;delayed_work&nbsp;*work,&nbsp;void&nbsp;(*function)(void&nbsp;*));&nbsp;<br />PREPARE_DELAYED_WORK(struct&nbsp;delayed_work&nbsp;*work,&nbsp;void&nbsp;(*function)(void&nbsp;*));&nbsp;<br />/*&nbsp;INIT_*&nbsp;做更加全面的初始化结构的工作,在第一次建立结构时使用.&nbsp;PREPARE_*&nbsp;做几乎同样的工作,&nbsp;但是它不初始化用来连接&nbsp;work_struct或delayed_work&nbsp;结构到工作队列的指针。如果这个结构已经被提交给一个工作队列,&nbsp;且只需要修改该结构,则使用&nbsp;PREPARE_*&nbsp;而不是&nbsp;INIT_*&nbsp;*/<br /><br />/*有&nbsp;2&nbsp;个函数来提交工作给一个工作队列:*/<br />int&nbsp;queue_work(struct&nbsp;workqueue_struct&nbsp;*queue,&nbsp;struct&nbsp;work_struct&nbsp;*work);<br />int&nbsp;queue_delayed_work(struct&nbsp;workqueue_struct&nbsp;*queue,&nbsp;struct&nbsp;delayed_work&nbsp;*work,&nbsp;unsigned&nbsp;long&nbsp;delay);<br />/*每个都添加work到给定的workqueue。如果使用&nbsp;queue_delay_work,&nbsp;则实际的工作至少要经过指定的&nbsp;jiffies&nbsp;才会被执行。&nbsp;这些函数若返回&nbsp;1&nbsp;则工作被成功加入到队列;&nbsp;若为0,则意味着这个&nbsp;work&nbsp;已经在队列中等待,不能再次加入*/<br /><br /><br /><br />在将来的某个时间,&nbsp;这个工作函数将被传入给定的&nbsp;data&nbsp;值来调用。这个函数将在工作线程的上下文运行,&nbsp;因此它可以睡眠&nbsp;(你应当知道这个睡眠可能影响提交给同一个工作队列的其他任务)&nbsp;工作函数不能访问用户空间,因为它在一个内核线程中运行,&nbsp;完全没有对应的用户空间来访问。<br />取消一个挂起的工作队列入口项可以调用:&nbsp;<br /><br />int&nbsp;cancel_delayed_work(struct&nbsp;delayed_work&nbsp;*work);&nbsp;<br />void&nbsp;cancel_work_sync(struct&nbsp;work_struct&nbsp;*work)<br /><br /><br /><br />如果这个入口在它开始执行前被取消,则返回非零。内核保证给定入口的执行不会在调用&nbsp;cancel_delay_work&nbsp;后被初始化.&nbsp;如果&nbsp;cancel_delay_work&nbsp;返回&nbsp;0,&nbsp;但是,&nbsp;这个入口可能已经运行在一个不同的处理器,&nbsp;并且可能仍然在调用&nbsp;cancel_delayed_work&nbsp;后在运行.&nbsp;要绝对确保工作函数没有在&nbsp;cancel_delayed_work&nbsp;返回&nbsp;0&nbsp;后在任何地方运行,&nbsp;你必须跟随这个调用来调用:&nbsp;<br /><br />void&nbsp;flush_workqueue(struct&nbsp;workqueue_struct&nbsp;*queue);&nbsp;<br /><br /><br /><br /><br />在&nbsp;flush_workqueue&nbsp;返回后,&nbsp;没有在这个调用前提交的函数在系统中任何地方运行。<br />而cancel_work_sync会取消相应的work,但是如果这个work已经在运行那么cancel_work_sync会阻塞,直到work完成并取消相应的work。<br /><br />当用完一个工作队列,可以去掉它,使用:&nbsp;<br /><br />void&nbsp;destroy_workqueue(struct&nbsp;workqueue_struct&nbsp;*queue);&nbsp;<br /><br /><br /><br /><br />共享队列<br /><br />在许多情况下,&nbsp;设备驱动不需要它自己的工作队列。如果你只偶尔提交任务给队列,&nbsp;简单地使用内核提供的共享的默认的队列可能更有效。若使用共享队列,就必须明白将和其他人共享它,这意味着不应当长时间独占队列(不能长时间睡眠),&nbsp;并且可能要更长时间才能获得处理器。<br /><br />使用的顺序:<br />(1)&nbsp;建立&nbsp;work_struct&nbsp;或&nbsp;&nbsp;delayed_work&nbsp;<br /><br />static&nbsp;struct&nbsp;work_struct&nbsp;jiq_work;<br />static&nbsp;struct&nbsp;delayed_work&nbsp;jiq_work_delay;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;this&nbsp;line&nbsp;is&nbsp;in&nbsp;jiq_init()&nbsp;*/<br />INIT_WORK(&jiq_work,&nbsp;jiq_print_wq);<br />INIT_DELAYED_WORK(&jiq_work_delay,&nbsp;jiq_print_wq);<br /><br /><br />(2)提交工作<br /><br />int&nbsp;schedule_work(&jiq_work);/*对于work_struct结构*/<br />int&nbsp;schedule_delayed_work(&jiq_work_delay,&nbsp;delay);/*对于delayed_work结构*/<br /><br />/*返回值的定义和&nbsp;queue_work&nbsp;一样*/<br /><br /><br />若需取消一个已提交给工作队列入口项,&nbsp;可以使用&nbsp;cancel_delayed_work和cancel_work_sync,&nbsp;但刷新共享队列需要一个特殊的函数:&nbsp;<br /><br />void&nbsp;flush_scheduled_work(void);&nbsp;<br /><br /><br /><br />因为不知道谁可能使用这个队列,因此不可能知道&nbsp;flush_schduled_work&nbsp;返回需要多长时间。&nbsp;<br /><br /><br /><br />--------------------------------------------------------------------------------<br /><br /><br /><br />ARM9&nbsp;s3c2440AL&nbsp;实验<br /><br />jit模块:jit<br /><br />jiq模块:jiq<br /><br />实验数据:<br /><br />[Tekkaman2440@SBC2440V4]#cd&nbsp;/lib/modules/<br />[Tekkaman2440@SBC2440V4]#insmod&nbsp;jit.ko<br />[Tekkaman2440@SBC2440V4]#head&nbsp;-6&nbsp;/proc/currentime<br />0x0002b82f&nbsp;0x000000010002b82f&nbsp;1191.119051<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1191.115000000<br />0x0002b82f&nbsp;0x000000010002b82f&nbsp;1191.119204<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1191.115000000<br />0x0002b82f&nbsp;0x000000010002b82f&nbsp;1191.119230<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1191.115000000<br />[Tekkaman2440@SBC2440V4]#dd&nbsp;bs=20&nbsp;count=5&nbsp;&lt&nbsp;/proc/jitbusy<br />&nbsp;&nbsp;&nbsp;201604&nbsp;201804<br />&nbsp;&nbsp;&nbsp;201804&nbsp;202004<br />&nbsp;&nbsp;&nbsp;202004&nbsp;202204<br />&nbsp;&nbsp;&nbsp;202204&nbsp;202404<br />&nbsp;&nbsp;&nbsp;202404&nbsp;202604<br />5+0&nbsp;records&nbsp;in<br />5+0&nbsp;records&nbsp;out<br />[Tekkaman2440@SBC2440V4]#dd&nbsp;bs=20&nbsp;count=5&nbsp;&lt&nbsp;/proc/jitsched<br />&nbsp;&nbsp;&nbsp;212640&nbsp;212840<br />&nbsp;&nbsp;&nbsp;212840&nbsp;213040<br />&nbsp;&nbsp;&nbsp;213040&nbsp;213240<br />&nbsp;&nbsp;&nbsp;213240&nbsp;213440<br />&nbsp;&nbsp;&nbsp;213440&nbsp;213640<br />5+0&nbsp;records&nbsp;in<br />5+0&nbsp;records&nbsp;out<br />[Tekkaman2440@SBC2440V4]#dd&nbsp;bs=20&nbsp;count=5&nbsp;&lt&nbsp;/proc/jitqueue<br />&nbsp;&nbsp;&nbsp;218299&nbsp;218499<br />&nbsp;&nbsp;&nbsp;218499&nbsp;218699<br />&nbsp;&nbsp;&nbsp;218699&nbsp;218899<br />&nbsp;&nbsp;&nbsp;218899&nbsp;219099<br />&nbsp;&nbsp;&nbsp;219099&nbsp;219299<br />5+0&nbsp;records&nbsp;in<br />5+0&nbsp;records&nbsp;out<br />[Tekkaman2440@SBC2440V4]#dd&nbsp;bs=20&nbsp;count=5&nbsp;&lt&nbsp;/proc/jitschedto<br />&nbsp;&nbsp;&nbsp;228413&nbsp;228613<br />&nbsp;&nbsp;&nbsp;228613&nbsp;228813<br />&nbsp;&nbsp;&nbsp;228813&nbsp;229013<br />&nbsp;&nbsp;&nbsp;229013&nbsp;229213<br />&nbsp;&nbsp;&nbsp;229213&nbsp;229413<br />5+0&nbsp;records&nbsp;in<br />5+0&nbsp;records&nbsp;out<br />[Tekkaman2440@SBC2440V4]#cat&nbsp;/proc/jitimer<br />&nbsp;&nbsp;&nbsp;time&nbsp;delta&nbsp;inirq&nbsp;pid&nbsp;cpu&nbsp;command<br />&nbsp;&nbsp;&nbsp;236945&nbsp;0&nbsp;0&nbsp;832&nbsp;0&nbsp;cat<br />&nbsp;&nbsp;&nbsp;236955&nbsp;10&nbsp;1&nbsp;0&nbsp;0&nbsp;swapper<br />&nbsp;&nbsp;&nbsp;236965&nbsp;10&nbsp;1&nbsp;0&nbsp;0&nbsp;swapper<br />&nbsp;&nbsp;&nbsp;236975&nbsp;10&nbsp;1&nbsp;0&nbsp;0&nbsp;swapper<br />&nbsp;&nbsp;&nbsp;236985&nbsp;10&nbsp;1&nbsp;0&nbsp;0&nbsp;swapper<br />&nbsp;&nbsp;&nbsp;236995&nbsp;10&nbsp;1&nbsp;0&nbsp;0&nbsp;swapper<br />[Tekkaman2440@SBC2440V4]#cat&nbsp;/proc/jitasklet<br />&nbsp;&nbsp;&nbsp;time&nbsp;delta&nbsp;inirq&nbsp;pid&nbsp;cpu&nbsp;command<br />&nbsp;&nbsp;&nbsp;238437&nbsp;0&nbsp;0&nbsp;833&nbsp;0&nbsp;cat<br />&nbsp;&nbsp;&nbsp;238437&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;238437&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;238437&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;238437&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;238437&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />[Tekkaman2440@SBC2440V4]#cat&nbsp;/proc/jitasklethi<br />&nbsp;&nbsp;&nbsp;time&nbsp;delta&nbsp;inirq&nbsp;pid&nbsp;cpu&nbsp;command<br />&nbsp;&nbsp;&nbsp;239423&nbsp;0&nbsp;0&nbsp;834&nbsp;0&nbsp;cat<br />&nbsp;&nbsp;&nbsp;239423&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;239423&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;239423&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;239423&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;239423&nbsp;0&nbsp;1&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />[Tekkaman2440@SBC2440V4]#insmod&nbsp;jiq.ko<br />[Tekkaman2440@SBC2440V4]#cat&nbsp;/proc/jiqwq<br />&nbsp;&nbsp;&nbsp;&nbsp;time&nbsp;delta&nbsp;preempt&nbsp;pid&nbsp;cpu&nbsp;command<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;405005&nbsp;0&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />[Tekkaman2440@SBC2440V4]#cat&nbsp;/proc/jiqwqdelay<br />&nbsp;&nbsp;&nbsp;&nbsp;time&nbsp;delta&nbsp;preempt&nbsp;pid&nbsp;cpu&nbsp;command<br />&nbsp;&nbsp;&nbsp;406114&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406115&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406116&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406117&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406118&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406119&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406120&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406121&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406122&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406123&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />&nbsp;&nbsp;&nbsp;406124&nbsp;1&nbsp;0&nbsp;5&nbsp;0&nbsp;events/0<br />[Tekkaman2440@SBC2440V4]#cat&nbsp;/proc/jiqtimer<br />&nbsp;&nbsp;&nbsp;&nbsp;time&nbsp;delta&nbsp;preempt&nbsp;pid&nbsp;cpu&nbsp;command<br />&nbsp;&nbsp;&nbsp;420605&nbsp;0&nbsp;0&nbsp;853&nbsp;0&nbsp;cat<br />&nbsp;&nbsp;&nbsp;420805&nbsp;200&nbsp;256&nbsp;0&nbsp;0&nbsp;swapper<br />[Tekkaman2440@SBC2440V4]#cat&nbsp;/proc/jiqtasklet<br />&nbsp;&nbsp;&nbsp;&nbsp;time&nbsp;delta&nbsp;preempt&nbsp;pid&nbsp;cpu&nbsp;command<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0<br />&nbsp;&nbsp;&nbsp;431905&nbsp;0&nbsp;256&nbsp;3&nbsp;0&nbsp;ksoftirqd/0
zcying 发表于 2009-4-7 09:53 | 显示全部楼层

这么长啊

  
您需要登录后才可以回帖 登录 | 注册

本版积分规则

139

主题

185

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部