📄 (ldd) ch06-时间流(转载).htm
字号:
color=#ffffff size=3>
<P>进入睡眠状态,就要被调出。还可以发现,kswapd和loadmonitor(这是我在我的系统上<BR>运行的一个程序)的执行时间都少于1个时钟滴答,而load50是在它的时间片耗尽后被抢<BR>先,这离它获得处理器有好几个时钟滴答。<BR> <BR> <BR> <BR>当系统中实际上没有任何进程在运行时,current进程总是空闲(idle)任务(0号进程,历<BR>史性的被称为"swapper"),任务队列或者是不断地运行或者是每隔1个时钟滴答运行一次<BR>。如果处理器不能进入“暂停”("halted")状态,调度器和任务队列就将不断运行;如<BR>果处理器能被0号进程暂停(halt),它们每隔1个时钟滴答才运行一次。暂停的(halted)<BR>处理器只能由中断唤醒。当中断发生时,空闲进程运行调度器(及相应的队列)。下面显<BR>示了在没有负载的系统运行运行命令head /proc/jiqsched得到的结果:<BR> <BR> <BR> <BR> time delta intr_count pid command<BR> <BR> 1704475 0 0 730 head<BR> <BR> 1704476 1 0 0 swapper<BR> <BR> 1704477 1 0 0 swapper<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> 1704478 1 0 0 swapper<BR> <BR> 1704478 0 0 6 loadmonitor<BR> <BR> 1704479 1 0 0 swapper<BR> <BR> 1704480 1 0 0 swapper<BR> <BR> 1704481 1 0 0 swapper<BR> <BR> 1704482 1 0 0 swapper<BR> <BR> <BR> <BR>定时器队列<BR>定时器队列的使用方法和调度器队列差不多。和调度器队列不同的是,定时器队列是在<BR>中断时间内执行的。另外,该队列一定在下一个时钟滴答被运行,因此就与系统负载无<BR>关了。下面是在我的系统在跑编译程序时运行命令head /proc/jiqtimer输出的结果:<BR> <BR> <BR> <BR> time delta intr_count pid command<BR></P></FONT><FONT
color=#ffffff size=3>
<P> time delta intr_count pid command<BR> <BR>1760712 1 1 945 cc1<BR> <BR>1760713 1 1 945 cc1<BR> <BR>1760714 1 1 945 cc1<BR> <BR>1760715 1 1 946 as<BR> <BR>1760716 1 1 946 as<BR> <BR>1760717 1 1 946 as<BR> <BR>1760718 1 1 946 as<BR> <BR>1760719 1 1 946 as<BR> <BR>1760720 1 1 946 as<BR> <BR> <BR> <BR>当前的任务队列实现有一个特性,一个任务可以将自己重新插回到它原先所在的队列。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>当前的任务队列实现有一个特性,一个任务可以将自己重新插回到它原先所在的队列。<BR>例如,定时器队列中的任务可以在运行时将自己插回到定时器队列中去,从而在下一个<BR>时钟滴答又再次被运行。在处理任务队列之前,由于是先用NULL指针替换队列的头指针<BR>,因此才可能进行不断的重新调度。这种实现可追溯到1.3.70版的内核。在早期的版本<BR>中(象1.2.13版),重调度是不可能的,因为内核在运行队列前不会先整理它。在1.2版的<BR>Linux中试图重新调度任务会因进入死循环(tight loop)而挂起系统。是否具有重新调度<BR>的能力是任务队列实现上1.2.13版和2.0.x各版本间唯一的一处差别。<BR> <BR> <BR> <BR>尽管一遍遍地重新调度同一个任务看起来似乎没什么意义,但有时这也有些用处。例如<BR>,我的计算机就是在到达目的地之前让任务在定时器队列上不断地重新调度自己,来实<BR>现步进马达的一步步移动的。其他的例子还有jiq模块,该模块中的打印函数都又重新调<BR>度了自己来显示对任务队列一遍扫描的结果。<BR> <BR>立即队列<BR>最后一个可由模块代码使用的预定义队列是立即队列。它和下半部中断处理程序工作机<BR>制相似,因此必须用mark_bh(IMMEDIATE_BH)标记该处理程序是活跃的。出于高效的目的<BR>,只有被标记为活跃的下半部处理程序才会被执行。注意必须在调用queue_task后才能<BR>标记下半部处理程序,否则会带来竞争。详情见第9章的“下半部处理”一节。<BR> <BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>立即队列是系统处理得最快的队列-在intr_count变量加1之后马上执行。该队列执行得<BR>如此“立即”以致于当你重新注册你的任务之后,它一返回就立即重新运行。该队列一<BR>遍遍执行直到队列为空。只要看看/proc/jiqimmed文件,就会明白它执行得如此只快的<BR>原因,在整个读过程中它完全控制了CPU。<BR> <BR> <BR> <BR>立即队列是由调度器执行的或者是在一个进程从系统调用返回时被执行的。值得注意的<BR>是,调度器(至少对于2.0版的内核)并不总会一直处理立即队列到它为空;只有从系统调<BR>用返回时才会这么做。这可以从下面的示例输出看出来-只有jiqimmed文件的第一行是<BR>当前进程head,而下面各行都不是了。<BR> <BR> <BR> <BR> time delta intr_count pid command<BR> <BR> 1975640 0 1 1060 head<BR> <BR> 1975641 1 1 0 swapper<BR> <BR> 1975641 0 1 0 swapper<BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> 1975641 0 1 0 swapper<BR> <BR> 1975641 0 1 0 swapper<BR> <BR> 1975641 0 1 0 swapper<BR> <BR> 1975641 0 1 0 swapper<BR> <BR> 1975641 0 1 0 swapper<BR> <BR> 1975641 0 1 0 swapper<BR> <BR> <BR> <BR> 显然该队列不能用于延迟任务的执行-它是个“立即”队列。相反,它的目的是<BR>使任务尽快地得以执行,但是要在“安全的时间”内。这对中断处理非常有用,因为它<BR>提供在实际的中断处理程序之外执行处理程序代码的一处入口。<BR> <BR> <BR> <BR> 尽管/proc/jiqimmed将任务重新注册到立即队列中,但这种技术在实际的实现代<BR>码中并不鼓励;象这种不肯合作的行为会在整个不断重等记的过程中独占住处理器,那<BR></P></FONT><FONT
color=#ffffff size=3>
<P>码中并不鼓励;象这种不肯合作的行为会在整个不断重等记的过程中独占住处理器,那<BR>还不如将整个任务一次性地完成。<BR> <BR>运行自己定制的工作队列<BR>声明新的任务队列不困难。驱动程序可以随意地声明任意多的新任务队列;这些队列的<BR>使用和tq_scheduler队列差不多。<BR> <BR> <BR> <BR>与预定义队列不同的,内核不会自动处理定制的任务队列。定制的任务队列要由程序员<BR>自己维护。<BR> <BR> <BR> <BR>下面的宏声明一个定制队列,在你需要任务队列声明处这个宏会被扩展:<BR> <BR> <BR> <BR>DECLARE_TASK_QUEUE(tq_custom);<BR> <BR> <BR> <BR>声明完队列,就可以调用下面的函数对任务进行排队。上面的宏和下面的调用相匹配:<BR></P></FONT><FONT
color=#ffffff size=3>
<P>声明完队列,就可以调用下面的函数对任务进行排队。上面的宏和下面的调用相匹配:<BR> <BR> <BR> <BR>queue_task(&custom_task,&tq_custom);<BR> <BR> <BR> <BR>然后就可以调用下面的函数运行tq_custom队列了:<BR> <BR> <BR> <BR>run_task_queue(&tq_custom);<BR> <BR> <BR> <BR>如果现在你想测试你定制的任务队列,你可以在预定义的队列中注册一个函数来处理这<BR>个队列。尽管看起来象绕了弯路,但其实并非如此。如果你希望累积任务以便同时得到<BR>执行,尽管需要用另一个队列来决定这个“同时”,定制的任务队列还是非常有用的。<BR> <BR>内核定时器<BR>内核中最终的计时资源还是定时器。定时器用于调度函数(定时器处理程序)在未来某个<BR>特定时间执行。与任务队列不同,你可以指定你的函数在未来何时被调用,但你不能确<BR></P></FONT><FONT
color=#ffffff size=3>
<P>特定时间执行。与任务队列不同,你可以指定你的函数在未来何时被调用,但你不能确<BR>定任务队列中的任务何时执行。另外,内核定时器与任务队列相似的,内核定时器注册<BR>的处理函数只执行一次-定时器不是循环执行的。<BR> <BR> <BR> <BR>有时你要执行的操作不在任何进程上下文内,比如关闭软驱马达和中止某个耗时的关闭<BR>操作。在这些情况下,延迟从close调用的返回对应用程序不公平。而且这时也没有必要<BR>使用任务队列,因为队中的任务在估算时间的同时还要不断重新注册自己。<BR> <BR> <BR> <BR>这里用定时器就更方便。你注册你的处理函数一次,当定时器超时后内核就调用它一次<BR>。这种处理一般较适合由内核完成,但有时驱动程序也需要,就象软驱马达的例子。<BR> <BR> <BR> <BR>Linux使用了两种定时器,所谓的“旧定时器”和新定时器。在介绍如何使用更好的新定<BR>时器前,我先简要介绍一下旧定时器。新定时器,实际上并不新;它们在1.0版之前的Li<BR>nux里就已经引入了。<BR> <BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR>旧定时器包括32个静态的定时器。它们的存在只是出于兼容性的考虑(因为替换旧定时器<BR>需要修改和测试大量的驱动程序代码)。<BR> <BR> <BR> <BR>旧定时器的数据结构包括一个标明活动的定时器的位屏蔽码和定时器数组,数组的每个<BR>成员又包括一个处理程序和该定时器的超时值。旧的定时器结构的主要问题在于,每个<BR>需要定时器来延迟操作的设备都要静态地分配给一个定时器。<BR> <BR> <BR> <BR>这种实现在几年前是可以接受的,当时支持的设备(因此需要的定时器)还很有限,但对<BR>当前的Linux版本就不够了。<BR> <BR> <BR> <BR>我不再介绍如何使用旧的定时器;我在这里提到它们只是为了满足那些好奇的读者。<BR> <BR>新的定时器列表<BR>新的定时器被组织成双向链接表。这意味着你加入任意多的定时器。定时器包括它的tim<BR>eout(超时)值(单位是jiffies)和超时时调用的函数。定时器处理程序需要一个参数,该<BR>参数和处理程序函数指针本身一起存放在一个数据结构中。<BR></P></FONT><FONT
color=#ffffff size=3>
<P>参数和处理程序函数指针本身一起存放在一个数据结构中。<BR> <BR> <BR> <BR>定时器列表的数据结构如下,抽取自头文件<linux/timer.h>:<BR> <BR> <BR> <BR>struct timer_list {<BR> <BR> struct timer_list *next; /*不要直接修改它 */<BR> <BR> struct timer_list *prev; /*不要直接修改它 */<BR> <BR> unsigned long expires; /* timeout超时值,以jiffies值为单位 */<BR> <BR> unsigned long data; /* 传递给定时器处理程序的参数 */<BR> <BR> void (*function)(unsigned long); /* 超时时调用的定时器处理程序 */<BR> <BR>};<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P><BR> <BR>可以看到,定时器的实现和任务队列有所不同,尽管两个表的表项有些相似。这两个数<BR>据结构是由两个编程者几乎在同时创建的,因此不大一样;它们并没有相互复制。所以<BR>,定时器处理程序的形参是unsigned long类型,而不是void *类型,而定时器处理程序<BR>的名字是function而非routine。<BR> <BR> <BR> <BR> 定时器的timeout值是个"jiffy"值,当jiffies值大于等于timer->expires时,<BR>就要运行timer->function函数。Timeout值是个绝对数值;它与当前时间无关,不需要<BR>更新。<BR> <BR> <BR> <BR> 一初始化完timer_list结构,add_timer函数就将它插入一张有序表中,该表每<BR>秒钟会被查询100次左右(尽管时钟滴答的频率有时要比这高,但这样节省CPU时间)。<BR> <BR> <BR> <BR> 简单说,操作定时器的有如下函数:<BR> <BR> <BR></P></FONT><FONT
color=#ffffff size=3>
<P>秒钟会被查询100次左右(尽管时钟滴答的频率有时要比这高,但这样节省CPU时间)。<BR> <BR> <BR> <BR> 简单说,操作定时器的有如下函数:<BR> <BR> <BR> <BR>void init_timer(struct timer_list *timer);<BR> <BR>该内联函数用来初始化新定时器队列结构。目前,它只将prev和next指针清零。建议程<BR>--<BR> <BR><FONT
color=#00ff00>※ 来源:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 202.38.196.234]</FONT><BR>--<BR><FONT
color=#00ffff>※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#0000ff>※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 211.80.41.106]</FONT><BR>--<BR><FONT
color=#ffff00>※ 转载:.南京大学小百合站 bbs.nju.edu.cn.[FROM: 211.80.41.106]</FONT><BR> <BR>--<BR><FONT
color=#ff0000>※ 转载:·饮水思源 bbs.sjtu.edu.cn·[FROM: 211.80.41.106]</FONT><BR></P></FONT>
<P align=center><A href="http://joyfire.net/lsdp/index.htm"><FONT
color=#ffffff size=2>目录页</FONT></A> | <A
href="http://joyfire.net/lsdp/7.htm"><FONT color=#ffffff
size=2>上一页</FONT></A> | <A href="http://joyfire.net/lsdp/9.htm"><FONT
color=#ffffff size=2>下一页</FONT></A></P></SPAN></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="90%" align=center border=0>
<TBODY>
<TR>
<TD colSpan=3 height=2>
<TABLE cellSpacing=0 cellPadding=0 width="100%" bgColor=#666666
border=0><TBODY>
<TR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -