⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 (ldd) ch06-时间流(转载).htm

📁 html格式
💻 HTM
📖 第 1 页 / 共 5 页
字号:
      color=#ffffff size=3>
      <P><BR>&nbsp;<BR>&nbsp;<BR>while&nbsp;(jiffies&lt;j)<BR>&nbsp;<BR>&nbsp;&nbsp;schedule();<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个例子和下面各例中的变量j应是延迟到达时的jiffies值,在忙等待时一般就<BR>是象这样使用的。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这种循环(可以通过读/proc/jitsched文件来测试它)延迟方法还不是最优的。系<BR>统可以调度其他任务;当前任务除了释放CPU之外不做任何工作,但是它仍在任务队列中<BR>。如果它是系统中唯一的可运行的进程,它还会被运行(系统调用调度器,调度器还是同<BR>一个进程,此进程又再调用调度器,然后...)。换句话说,机器的负载(系统中运行的进<BR>程个数)至少为1,而idle空闲进程(进程为0,历史性的被称为"swapper")绝不会被运行<BR>。尽管这个问题看来无所谓,当系统空闲时运行idle空闲进程可以减轻处理器负载,降<BR>低处理器温度,延长处理器寿命,如果是手提电脑,电池的寿命也可延长。而且,延迟<BR>期间实际上进程是在执行的,因此这段延迟还是记在它的运行时间上的。运行命令<BR>time&nbsp;cat&nbsp;/proc/jitsched就可以发现到这一点。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>time&nbsp;cat&nbsp;/proc/jitsched就可以发现到这一点。<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;尽管有些毛病,这种循环延迟还是提供了一种有点“脏”&nbsp;但比较快的监控驱动<BR>程序工作的途径。如果模块中的臭虫(bug)会锁死整个系统,在每个用于调试的printk语<BR>句后都添加一小段延迟,可以保证在处理器碰到令人厌恶的臭虫被锁死之前,所有的打<BR>印消息都能进入系统日志(system&nbsp;log)中。如果没有这样的延迟,这些消息能进入内存<BR>缓冲区,但在klogd得到运行前系统可能已经被锁住了。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;还有其他更好的获得延迟的方法。在内核态下让进程进入睡眠态的正确方式是设<BR>置current-&gt;timeout后睡眠在一个等待队列上。调度器每次运行时都会比较进程的timeo<BR>ut值和当前的jiffies值,如果timeout值小于等于当前时间,那么不管它的等待队列如<BR>何进程都会被唤醒。只要没有系统事件唤醒进程使它离开等待队列,那么一旦当前时间<BR>达到timeout值,调度器就唤醒睡眠进程。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这种延迟实现如下:<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;wait_queue&nbsp;*wait=NULL;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;wait_queue&nbsp;*wait=NULL;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;current-&gt;timeout=j;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;interruptible_sleep_on(&amp;wait);<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;注意要调用interruptible_sleep_on而不是sleep_on,因为调度器不检查不可中<BR>断的进程的timeout值-这种进程的睡眠即使超时也不被中断。因此,如果你调用sleep_<BR>on,就无法中断该睡眠进程。你可以通过读/proc/jitqueue文件来测试上面的代码。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Timeout域是个很有意思的系统资源。它可以用来实现阻塞的系统调用和计算延<BR>迟。如果硬件保证只要不出错就能在确定的时间内给出响应,那么驱动程序就可以在恰<BR>当设置timeout值后进入睡眠。例如,如果你有一个对海量存储的数据传输请求(读或者<BR>写),而磁盘响应该请求比如说要1秒。如果你设置了timeout值,并且当前时间达到它了<BR>,进程于是被唤醒,驱动程序开始处理这个请求。如果你使用这种技术,在进程被正常<BR>唤醒之后timeout值应被清为零。而如果进程是因为timeout超时而被唤醒的,调度器会<BR>清这个域,驱动程序就不必再做了。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>清这个域,驱动程序就不必再做了。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;你可能注意到了,如果目的只是插入延迟,这里并没有必要使用等待队列。实际<BR>上,如下所示,用current-&gt;timeout而不用等待队列就可以达到目的:<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;current-&gt;timeout=j;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;current-&gt;state=TASK_INTERRUPTIBLE;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;schedule();<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;current-&gt;timeout=0;/*&nbsp;重置timeout值*/<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这段语句是在调用调度器之前先改变进程的状态。进程的状态被标记为TASK_INT<BR>ERRUPTIBLE(与TASk_RUNNING相对应),这保证了该进程在超时前不会被再次运行(但其他<BR>系统事件如信号可能会唤醒它)。这种延迟方法在文件/proc/jitself中实现了-这个名<BR>字强调了,读进程是“自己进入睡眠的”,而不是通过调用sleep_on。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>字强调了,读进程是“自己进入睡眠的”,而不是通过调用sleep_on。<BR>&nbsp;<BR>短延迟<BR>有时驱动程序需要非常短的延迟来和硬件同步。此时,使用jiffies值就不能达到目的。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>这时就要用内核函数udelay*。它的原型如下:<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>#include&nbsp;&lt;linux/delay.h&gt;<BR>&nbsp;<BR>void&nbsp;udelay(unsigned&nbsp;long&nbsp;usecs);<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>该函数在绝大多数体系结构上是作为内联函数编译的,并且使用软件循环将执行延迟指<BR>定数量的微秒数。这里要用到BogoMips值:udelay利用了整数值loops_per_second,这<BR>个值是在启动时计算BogoMips时得到的。<BR>&nbsp;<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>&nbsp;<BR>udelay函数只能用于获取较短的时间延迟,因为loops_per_second值的精度就只有8位,<BR>所以当计算更长的延迟时会积累下相当大的误差。尽管运行的最大延迟将近1秒(因为更<BR>长的延迟就要溢出),推荐的udelay函数的参数的最大值是取1000微秒(1毫秒)。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>要特别注意的是udelay是个忙等待函数,在延迟的时间段内无法运行其他的任务。源码<BR>见头文件&lt;asm/delay.h&gt;。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>目前内核不支持大于1微秒而小于1个时钟滴答的延迟,但这不是个问题,因为延迟是给<BR>硬件或者人去识别的。百分之一秒的时间间隔对人来说延迟精度足够了,而1毫秒对硬件<BR>来说延迟时间也足够长。如果你真的需要其间的延迟间隔,你只要建立一个连续执行ude<BR>lay(1000)函数的循环。<BR>&nbsp;<BR>任务队列<BR>许多驱动程序需要将任务延迟到以后处理,但又不想占用中断。Linux为此提供了两种方<BR>法:任务队列和内核定时器。任务队列的使用很灵活,可以或长或短地延迟任务到以后<BR>处理,在写中断处理程序时任务队列非常有用,在第9章“中断处理”中,我们还将在“<BR>下半部处理”一节中继续讨论。内核定时器则用来调度任务在未来某个相对精确的时间<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>下半部处理”一节中继续讨论。内核定时器则用来调度任务在未来某个相对精确的时间<BR>执行,将在本章的“内核定时器”一节中讨论。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>要使用到任务队列的一个典型情形是,硬件不产生中断,但仍希望提供阻塞的读。此时<BR>需要对设备进行轮询,但要小心地不使CPU负担过多无谓的操作。将读进程到指定的时间<BR>后(例如,使用current-&gt;timeout变量)唤醒并不是个很好的方法,因为每次轮询需要两<BR>次上下文切换,而且通常轮询机制在进程上下文之外才可能较好地实现。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>类似的情形还有象不时地给简单的硬件设备提供输入。例如,有一个直接连接到并口的<BR>步进马达,要求该马达能一步步地移动。在这种情况下,由控制进程通知设备驱动程序<BR>进行移动,但实际上移动是在write返回后才一步步地进行的。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>快速完成这类不固定的任务的恰当方法是注册任务在未来执行。内核提供了对任务“队<BR>列”的支持,任务可以累积到队列上一块“运行”。你可以声明你自己的任务队列并且<BR>随意地操纵它,或者也可以将你的任务注册到预定义的任务队列中去,由内核来运行它<BR>。<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>&nbsp;<BR>&nbsp;<BR>下面一节将先概述任务队列,然后介绍预定义的任务队列,这让你可以开始进行一些有<BR>趣的测试(如果出错也可能挂起系统),最后介绍如何运行你自己的任务队列。<BR>&nbsp;<BR>任务队列的特性<BR>任务队列是任务的一张列表,每个任务用一个函数指针和一个参数表示。任务运行时,<BR>它接受一个void&nbsp;*类型的参数,返回值类型为void。而参数指针data可用来将一个数据<BR>结构传入函数,或者可以被忽略。队列本身是结构(任务)的列表,为声明和操纵它们的<BR>内核模块所拥有。这些模块全权负责这些数据结构的分配和释放;为此一般使用静态的<BR>数据结构。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>队列元素由下面这个结构来描述,这段代码是直接从头文件&lt;linux/tqueue.h&gt;拷贝下来<BR>的:<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>struct&nbsp;tq_struct&nbsp;{<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;tq_struct&nbsp;*next;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;激活的bh的链接表&nbsp;*/<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;tq_struct&nbsp;*next;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;激活的bh的链接表&nbsp;*/<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;sync;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;必须初始化为零&nbsp;*/<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;(*routine)(void&nbsp;*);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;调用的函数&nbsp;*/<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;*data;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;传递给函数的参数&nbsp;*/<BR>&nbsp;<BR>};<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;第一行注释中的bh指的是下半部处理程序(bottom-half)。下半部处理程序是“<BR>中断处理处理程序的下半部”;我们将在第9章的“下半部处理程序”一节介绍中断时详<BR>细讨论。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;任务队列是处理异步事件的重要资源,而且绝大多数的中断处理程序将它们的任<BR>务延迟到任务队列被处理时执行。另外,有些任务队列是下半部处理程序,通过调用do_<BR>bottom_half函数来处理。本章并不要求你理解下半部处理,但必要时我也会涉及到。<BR>&nbsp;<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;上面的数据结构中最重要的字段是routine和data。将要延迟的任务插入队列,<BR>必须先设置好结构的这些字段,并把next和sync两个字段清零。结构中的sync标志位用<BR>于避免同一任务被插入多次,这会破坏next指针。一旦任务被排入队列,该数据结构就<BR>被认为为内核“拥有”了,不能再被修改。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;与任务队列有关的其他数据结构还有task_queue,目前它实现为指向tq_struct<BR>结构的指针;如果将来需要扩充task_queue,只要用typedef将该指针定义为其他符号就<BR>可以了。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;下面的列表汇总了所有可以对tq_struct结构进行的操作;所有的函数都是内联<BR>的。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>void&nbsp;queue_task(struct&nbsp;tq_struct&nbsp;*task,&nbsp;task_queue&nbsp;*list);<BR>&nbsp;<BR>正如该函数的名字,本函数用于将任务排进队列中。它关闭了中断,避免了竞争,因此<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>正如该函数的名字,本函数用于将任务排进队列中。它关闭了中断,避免了竞争,因此<BR>可以被模块中任一函数调用。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>void&nbsp;queue_task_irq(struct&nbsp;tq_struct&nbsp;*task,&nbsp;task_queue&nbsp;*list);<BR>&nbsp;<BR>与前者类似,但本函数只能由不可重入的函数调用(象中断处理程序,所以本函数的名字<BR>带上了irq)。它比queue_task函数要快一些,因为它在排队前不关闭中断。如果你在一<BR>个可重入的函数内调用本函数,由于没有屏蔽资源竞争,是很危险的。但是,本函数排<BR>除了“运行时排队”的情形(也即将任务插入正在运行的那个任务的位置上)。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>void&nbsp;queue_task_irq_off(struct&nbsp;tq_struct&nbsp;*task,&nbsp;task_queue&nbsp;*list);<BR>&nbsp;<BR>本函数只能在中断已关闭的情况下调用。它比前两个函数要快,但没有防止象“并发排<BR>队”和“运行时排队”这样的资源竞争。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>void&nbsp;run_task_queue(struct&nbsp;tq_struct&nbsp;*task,&nbsp;task_queue&nbsp;*list);<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>run_task_queue函数用于运行累积在队列上的任务。除非你要声明和维护自己的任务队<BR>列,否则不必调用本函数。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.1.30版的内核已经不提供queue_task_irq和queue_task_irq_off这两个函数了<BR>,被认为得不偿失。详情见第17章“最近的发展”&nbsp;的“任务队列”一节。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在研讨任务队列的细节之前,最好还是先介绍一下内部的一些实现细节。任务队<BR>列与相应的系统调用是异步执行的;这种异步执行特别需要注意,必须先介绍一下。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;任务队列要在安全的时间内运行。这里安全的意思是在执行时没有什么特别严格<BR>的要求。因为在处理任务队列时允许硬件中断,任务代码也不要求执行的非常快。但队<BR>列中的函数执行得也不能太慢,毕竟在整个处理任务队列的期间,只有硬件中断才能被<BR>系统处理。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;另一个与任务队列有关的概念是中断时间。在Linux中,中断时间是个软件上的<BR>概念,取决于内核的全局变量intr_count。任一时候该变量都记录了正在执行的中断处<BR>理程序被嵌套的层数*。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;一般的计算流程中,当处理器允许某个进程时,intr_count值为0。当intr_coun<BR>t不为零时,,执行的代码就与系统的其他部分是异步的了。这些异步代码可以是硬件中<BR>断的处理或者是“软件中断”-与任何进程都无关的一个任务,我们称它在“中断时间<BR>内”运行。这种异步代码是不允许做某些操作的;特别的,它不能使当前进程进入睡眠<BR>,因为current指针的值与正在运行的软件中断代码无关。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;典型的例子是退出系统调用时要执行的代码。如果因为某个原因此时还有任务需<BR>要得到执行,内核可以一退出系统调用就处理它。这是个“软件中断”,intr_count值<BR>在处理这个待执行的任务之前会先加1。由于主线指令流被中断了,该函数算是在“中断<BR>时间”内被处理的。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当intr_count非零时,不能激活调度器。这也就意味着不允许调用kmalloc(GFP_<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;当intr_count非零时,不能激活调度器。这也就意味着不允许调用kmalloc(GFP_<BR>KERNEL)。在中断时间内,只能进行原子性的分配(见第7章“掌握内存”的“优先权参数<BR>”一节),而原子性的分配较“普通的”分配更容易失败。<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果运行在中断时间的代码调用了调度器,类似“Aiee:&nbsp;scheduling&nbsp;in<BR>interrupt”这样的错误信息和以16进制显示的调用点处的地址会打印到控制台上。2.1.<BR>37之后的版本,oops消息也会打印出来,通过分析寄存器的值可以进行调试。在中断时<BR>间内如果试图非原子性地按优先权分配内存,也会显示包括着调用者的调用点处地址的<BR>错误信息。<BR>&nbsp;<BR>预定义的任务队列<BR>延迟任务执行的简单方法是使用内核维护的任务队列。这种队列有下面描述的四种,但<BR>驱动程序只能用前三种。任务队列的定义在头文件&lt;linux/queue.h&gt;中,你的驱动程序代<BR>码要将它包含(include)进来。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>tq_scheduler队列<BR>&nbsp;<BR>当调度器被运行时该队列就会被处理。因为此时调度器在被调度出的进程的上下文中运<BR>行,所以该队列中的任务几乎可以做任何事;它们不会在中断时运行。<BR>&nbsp;<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>&nbsp;<BR>tq_timer队列<BR>&nbsp;<BR>该队列由定时器队列处理程序(timer&nbsp;tick)运行。因为该处理程序(见函数do_timer)是<BR>在中断时间运行的,该队列中的所有任务就也是在中断时间内运行的了。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>tq_immediate队列<BR>&nbsp;<BR>立即队列在系统调用返回时或调度器运行时尽快得到处理的(不管两种情况谁先发生了)<BR>。该队列是在中断时间内得到处理的。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>tq_disk队列<BR>&nbsp;<BR>1.2版的内核不再提供这种任务队列了,内存管理例程内部使用,模块不能使用。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>使用任务队列的一个设备驱动程序的执行流程可见图6-1。该图演示了设备驱动程序是如<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>使用任务队列的一个设备驱动程序的执行流程可见图6-1。该图演示了设备驱动程序是如<BR>何在中断处理程序中将一个函数插入tq_scheduler队列中的。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>被执行的代码<BR>&nbsp;<BR>中断<BR>&nbsp;<BR>从中断返回<BR>&nbsp;<BR>数据<BR>&nbsp;<BR>关键字<BR>&nbsp;<BR>处理器代码<BR>&nbsp;<BR>调度器<BR>&nbsp;<BR>驱动程序代码<BR>&nbsp;<BR>(指向任务)<BR>&nbsp;<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P><BR>"sync"位<BR>&nbsp;<BR>(指向next)<BR>&nbsp;<BR>图&nbsp;6-1:任务队列使用的执行流程<BR>&nbsp;<BR>示例程序是如何工作的<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;延迟计算的示例程序是jiq(Just&nbsp;In&nbsp;Queue)模块,本节中抽取了它的部分源码。<BR>该模块创建/proc文件,可以用dd或者其他工具来读;这与jit模块很相似。该示例程序<BR>使用了动态/proc文件因此不能在Linux1.2上运行。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;读jiq文件的进程进入睡眠状态直到缓冲区满*。缓冲区由不断运行的任务队列来<BR>填充。任务队列的每遍运行都将在要填充的缓冲区中添加一个字符串;该字符串记录了<BR>当前时间(jiffies值),该遍的current进程和intr_count值。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;该/proc文件最好是用dd&nbsp;count=1命令一次性地读进来;如果你用cat命令,read<BR>方法要被多次调用,输出结果会有重迭,详情可见第4章“调试技术”的“使用/proc文<BR>件系统”一节。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>件系统”一节。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;填充缓冲区的代码都在jiq_print函数中,任务队列的每遍运行都要调用它。打<BR>印函数没什么意思,不在这里列出;我们还是来看看插入队列的任务的初始化代码:<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>struct&nbsp;tq_struct&nbsp;jiq_task;&nbsp;/*&nbsp;全局变量;初始化为零&nbsp;*/<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;该行在init_module()中&nbsp;*/<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;jiq_task.routine&nbsp;=&nbsp;jiq_print;<BR>&nbsp;<BR>jiq_task.data&nbsp;=&nbsp;(void&nbsp;*)&amp;jiq_data;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>这里没必要清零jiq_task结构变量的sync域和next域,因为静态变量已由编译器初始化<BR>为零了。<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>为零了。<BR>&nbsp;<BR>调度器队列<BR>最容易使用的任务队列是tq_scheduler队列,因为该队列中的任务不会在中断时间内运<BR>行,因此少了很多限制。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>/proc/jiqsched文件是使用tq_scheduler队列的示例文件。该文件的read函数以如下的<BR>方式将任务jiq_task放进tq_scheduler队列中:<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>/*<BR>&nbsp;<BR>&nbsp;*&nbsp;使用调度器队列的例子&nbsp;&nbsp;--&nbsp;/proc/jiqsched<BR>&nbsp;<BR>&nbsp;*/<BR>&nbsp;<BR>int&nbsp;jiq_read_sched(char&nbsp;*buf,&nbsp;char&nbsp;**start,&nbsp;off_t&nbsp;offset,&nbsp;int&nbsp;len,&nbsp;int<BR>unused)<BR>&nbsp;<BR>{<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>{<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;jiq_data.len&nbsp;=&nbsp;0;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;还未打印,长度为0&nbsp;*/<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;jiq_data.buf&nbsp;=&nbsp;buf;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;打印到这个缓冲区中&nbsp;*/<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;jiq_data.jiffies&nbsp;=&nbsp;jiffies;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;开始时间&nbsp;*/<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;jiq_print会调用&nbsp;queue_task()&nbsp;使自己重新进入&nbsp;jiq_data.queue队列&nbsp;*/<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;jiq_data.queue&nbsp;=&nbsp;&amp;tq_scheduler;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;queue_task(&amp;jiq_task,&nbsp;&amp;tq_scheduler);&nbsp;/*&nbsp;准备运行*/<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;interruptible_sleep_on(&amp;jiq_wait);&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;进入睡眠队列只到任务完成&nbsp;*/<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;jiq_data.len;<BR></P></FONT><FONT color=#ffffff size=3>
      <P>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;jiq_data.len;<BR>&nbsp;<BR>}<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>读读/proc/jiqsched文件很有意思,因为它显示调度器在何时运行-jiffies值表明调度<BR>器激活的时间。如果系统中有些正在占用CPU的进程,那么队列中各任务的运行间会有些<BR>延迟;因为调度器要在若干时钟滴答后才会抢先那些进程。打开这个文件会花上好几秒<BR>钟,因为它长达100行(在Alpha机器上是200行)。<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>测试这些情形最简单方法是跑一个执行空循环的进程。load50是个增加机器负载的程序<BR>,它在用户空间执行50个并发的忙循环;你可以在示例程序中找到它的源码(misc-progs<BR>/load50.c)。当在系统中运行load50程序时,head命令将从/proc/jiqsched文件中抽取<BR>类似如下的信息:<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>time&nbsp;&nbsp;&nbsp;delta&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;intr_count&nbsp;pid&nbsp;command<BR>&nbsp;<BR>1643733&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;701&nbsp;head<BR></P></FONT><FONT 
      color=#ffffff size=3>
      <P>1643733&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;701&nbsp;head<BR>&nbsp;<BR>1643747&nbsp;&nbsp;14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;658&nbsp;load50<BR>&nbsp;<BR>1643747&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;3&nbsp;kswapd<BR>&nbsp;<BR>1643755&nbsp;&nbsp;&nbsp;8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;655&nbsp;load50<BR>&nbsp;<BR>1643761&nbsp;&nbsp;&nbsp;6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;666&nbsp;load50<BR>&nbsp;<BR>1643764&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;650&nbsp;load50<BR>&nbsp;<BR>1643767&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;661&nbsp;load50<BR>&nbsp;<BR>1643769&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;659&nbsp;load50<BR>&nbsp;<BR>1643769&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;6&nbsp;loadmonitor<BR>&nbsp;<BR>&nbsp;<BR>&nbsp;<BR>注意到调度队列是在进入schedule过程后就执行的,因此current进程就是刚刚被调度出<BR>去的进程。这就是为什么/proc/jiqsched文件的第一行总是读该文件的那个进程;它正<BR>进入睡眠状态,就要被调出。还可以发现,kswapd和loadmonitor(这是我在我的系统上<BR></P></FONT><FONT 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -