API,第2部分可延迟函数、内核微线程以及工作.doc_第1页
API,第2部分可延迟函数、内核微线程以及工作.doc_第2页
API,第2部分可延迟函数、内核微线程以及工作.doc_第3页
API,第2部分可延迟函数、内核微线程以及工作.doc_第4页
API,第2部分可延迟函数、内核微线程以及工作.doc_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

API,第 2 部分 可延迟函数、内核微线程以及工作Linux 2.6 bottom halves简介简介:对于高频率线程操作,Linux内核提供微线程和工作队列。微线程和工作队列执行可延迟功能并替代驱动程序当中原有的bottom-half机制。本文研究如何在内核中使用微线程和工作队列,并展示如何利用这些APIs来构建可延迟函数。发布日期:2010年4月11日其他语言版本:英文联系Tim Tim是我们最受欢迎、作品最多的作者之一。查看并与他以及My developerWorks中的其他作者和相关读者取得联系。更多内容请参考Tim专栏。本文研究多个用于在内核环境当中延迟处理的方法(特别是在Linux内核版本4当中)。尽管这些方法针对Linux内核,但方法背后的理念,对于系统架构研究具有更广泛的意义。例如,可以将这些理念应用到传统的嵌入式系统当中,取代原有的调度程序来进行任务调度。在开始研究用于内核中的可延迟函数之前,让我们先了解一下相关问题的背景情况。操作系统会因为一个硬件事件而产生中断(例如出现了一个来自网卡的数据包),对该事件的处理过程从一个中断开始。通常,中断会导致大量任务停止。其中一些任务是在中断上下文完成的,然后任务被传递给软件栈来继续处理(参见图1)。问题在于,有多少任务需要在中断上下文完成?关于中断上下文的问题是,在此期间部分或者全部中断可以被禁止,这就增加了处理其他硬件问题的时延(并导致处理习惯的改变)。因此,有必要简化中断过程中要完成的任务,把一些任务转移到内核上下文中去完成(在该上下文,处理器资源更有可能被高效共享)。正如图1所示,在中断上下文所完成的处理过程称为top half,基于中断并被推出中断上下文之外的处理过程称为bottom half(top half要依据bottom half来安排后续的处理过程)。bottom-half处理过程在内核上下文完成,这意味着允许中断操作。因此通过延迟时间不敏感的任务,来更迅速处理高频率中断事件,能够带来性能的优化。Linux内核版本这里对微线程及工作队列的讨论采用了4版本的Linux内核。Linux能够快速响应各种功能需求,延迟功能也不例外。自Linux2.3版本内核开始,就提供了软中断功能,具有一组32个静态定义的bottom halves。作为静态元素,其定义在编译过程中完成(不同于新的动态机制)。软中断用于在内核线程上下文中处理时间要求严格的处理过程(软件中断)。可以在./kernel/softirq.c中找到软中断的来源。在2.3版本的Linux内核中还引入了微线程(参见./include/linux/interrupt.h)。微线程的构建基于软中断,用于允许动态生成可延迟函数。最终,在2.5版本Linux内核中引入了工作队列(参见./include/linux/workqueue.h)。工作队列允许将任务延迟到中断上下文之外,进入内核处理上下文。现在我们探讨一下任务延迟、微线程以及工作队列的动态机制。软中断最初为具有32个软中断条目的矢量,用来支持一系列的软件中断特性。当前,只有9个矢量被用于软中断,其中之一是TASKLET_SOFTIRQ(参见./include/linux/interrupt.h)。虽然软中断还存在于内核中,推荐采用微线程和工作队列,而不是分配新的软中断矢量。微线程是一个延迟方法,可以实现将已登记的函数进行推后运行。top half(中断处理程序)完成少量的任务,然后安排微线程在晚些的bottom half中执行。/*Declare aTasklet(the Bottom-Half)*/void tasklet_function(unsigned long data);DECLARE_TASKLET(tasklet_example,tasklet_function,tasklet_data);./*Schedule the Bottom-Half*/tasklet_schedule(&tasklet_example);一个给定的微线程只运行在一个CPU中(就是用于调用该微线程的那个CPU),同一微线程永远不会同时运行在多个CPU中。但是不同的微线程可以同时运行在不同的CPU中。微线程可由tasklet_struct结构体表示(参见图2),其中包含了用于管理和维护微线程的必要数据(状态,通过atomic_t来实现允许/禁止控制,函数指针,数据,以及链表引用)。通过软中断机制来调度微线程,当机器处于严重软件中断负荷之下时,可通过ksoftirqd(一种每CPU内核线程)软中断来调度。下面将探讨微线程应用编程接口(API)中支持的各类函数。嵌入式系统继承物微线程和工作队列背后的部分理念来自嵌入式系统。在很多嵌入式系统中,没有传统的调度程序,只有任务延迟(由输入/输出I/O或者内部处理过程来驱动)。在有调度程序的场合,中断和应用程序作为系统中其他单元调度处理过程的工具,来推延任务。这样,调度程序就变成了工作队列处理机(为处理器函数分配任务)或者位掩码(指示微线程所能完成的工作)。微线程通过宏调用来定义DECLARE_TASKLET(参见清单2)。在底层,该宏调用只是利用所提供的信息对结构体tasklet_struct进行初始化(微线程名,函数,以及微线程专有数据)。默认情况下,微线程处于允许状态,这意味着它可以被调度。还可以利用宏DECLARE_TASKLET_DISABLED将微线程默认声明为禁止状态。这时需要调用函数tasklet_enable来实现微线程可被调度。可以分别利用函数tasklet_enable和函数tasklet_disable实现允许和禁止一个微线程(从调度的角度)。函数tasklet_init也存在并利用用户提供的微线程数据来对tasklet_struct进行初始化。DECLARE_TASKLET(name,func,data);DECLARE_TASKLET_DISABLED(name,func,data);void tasklet_init(struct tasklet_struct*,void(*func)(unsigned long),unsigned long data);void tasklet_disable_nosync(struct tasklet_struct*);void tasklet_disable(struct tasklet_struct*);void tasklet_enable(struct tasklet_struct*);void tasklet_hi_enable(struct tasklet_struct*);有两个disable函数,每一个都对微线程发出disable请求,但是,微线程被终止后,只有tasklet_disable返回(其中tasklet_disable_nosync可能在终止发生之前返回)。disable函数允许微线程被掩码(也就是说,并不执行),直到enable函数被调用为止。存在两个enable函数:一个用于正常优先级调度(tasklet_enable),另一个用于允许高优先级调度(tasklet_hi_enable)。正常优先级调度通过TASKLET_SOFTIRQ-level软中断来执行,高优先级调度则通过HI_SOFTIRQ-level软中断执行。由于存在正常优先级和高优先级的enable函数,因此要有正常优先级和高优先级的调度函数(参见清单3)。每个函数利用特殊的软中断矢量来为微线程排队(tasklet_vec用于正常优先级,而tasklet_hi_vec用于高优先级)。来自高优先级矢量的微线程先得到服务,随后是来自正常优先级矢量的微线程。注意,每个CPU维持其自己的正常优先级和高优先级软中断矢量。void tasklet_schedule(struct tasklet_struct*);void tasklet_hi_schedule(struct tasklet_struct*);最后,微线程生成之后,就可以通过函数tasklet_kill来停止微线程(参见清单4)。函数tasklet_kill保证微线程不会再运行,并且,如果按进度该微线程应该运行,将会等到它运行完,然后再kill该线程。tasklet_kill_immediate只在指定的CPU处于dead状态时被采用。void tasklet_kill(struct tasklet_struct*);void tasklet_kill_immediate(struct tasklet_struct*,unsigned int cpu);通过该API,可见微线程API比较简单,实现也很简单。可以通过./kernel/softirq.c和./include/linux/interrupt.h来了解微线程的实现机制。我们来看一个使用微线程API的简单例子(参见清单5)。如这里所示,微线程函数(my_tasklet_function和my_tasklet_data)通过相关数据生成,然后由DECLARE_TASKLET来声明一个新的微线程。当该模块被插入后,微线程将被调度,这保证它在今后可执行。当该模块被卸载,函数tasklet_kill将被调用来保证微线程不处于可调度状态。#include linux/kernel.h#include linux/module.h#include linux/interrupt.h MODULE_LICENSE(GPL);char my_tasklet_data=my_tasklet_function was called;/*Bottom Half Function*/void my_tasklet_function(unsigned long data)printk(%sn,(char*)data);return;DECLARE_TASKLET(my_tasklet,my_tasklet_function,(unsigned long)&my_tasklet_data);int init_module(void)/*Schedule the Bottom Half*/tasklet_schedule(&my_tasklet);return 0;void cleanup_module(void)/*Stop the tasklet before we exit*/tasklet_kill(&my_tasklet);return;工作队列是实现延迟的新机制,从2.5版本Linux内核开始提供该功能。不同于微线程一步到位的延迟方法,工作队列采用通用的延迟机制,工作队列的处理程序函数能够休眠(这在微线程模式下无法实现)。工作队列可以有比微线程更高的时延,并为任务延迟提供功能更丰富的API。从前,延迟功能通过keventd对任务排队来实现,但是现在由内核工作线程events/X来管理。工作队列提供一个通用的办法将任务延迟到bottom halves。处于核心的是工作队列(结构体workqueue_struct),任务被安排到该结构体当中。任务由结构体work_struct来说明,用来鉴别哪些任务被延迟以及使用哪个延迟函数(参见图3)。events/X内核线程(每CPU一个)从工作队列中抽取任务并激活一个bottom-half处理程序(由处理程序函数在结构体work_struct中指定)。由于work_struct中指出了要采用的处理程序函数,因此可以利用工作队列来为不同的处理程序进行任务排队。现在,让我们看一下能够用于工作队列的API函数。工作队列API比微线程稍复杂,主要是因为它支持很多选项。我们首先探讨一下工作队列,然后再看一下任务和变体。通过图3可以回想工作队列的核心结构体是队列本身。该结构体用于将任务安排出top half,进入bottom half,从而延迟它的执行。工作队列通过宏调用生成create_workqueue,返回一个workqueue_struct参考值。可以通过调用函数destroy_workqueue来远程遥控工作队列(如果需要):struct workqueue_struct*create_workqueue(name);void destroy_workqueue(struct workqueue_struct*);通过工作队列与之通信的任务可以由结构体work_struct来定义。通常,该结构体是用来进行任务定义的结构体的第一个元素(后面有相关例子)。工作队列API提供三个函数来初始化任务(通过一个事先分配的缓存);参见清单6。宏INIT_WORK提供必需的初始化数据以及处理程序函数的配置(由用户传递进来)。如果开发人员需要在任务被排入工作队列之前发生延迟,可以使用宏INIT_DELAYED_WORK和INIT_DELAYED_WORK_DEFERRABLE。INIT_WORK(work,func);INIT_DELAYED_WORK(work,func);INIT_DELAYED_WORK_DEFERRABLE(work,func);任务结构体的初始化完成后,接下来要将任务安排进工作队列。可采用多种方法来完成这一操作(参见清单7)。首先,利用queue_work简单地将任务安排进工作队列(这将任务绑定到当前的CPU)。或者,可以通过queue_work_on来指定处理程序在哪个CPU上运行。两个附加的函数为延迟任务提供相同的功能(其结构体装入结构体work_struct之中,并有一个计时器用于任务延迟)。int queue_work(struct workqueue_struct*wq,struct work_struct*work);int queue_work_on(int cpu,struct workqueue_struct*wq,struct work_struct*work);int queue_delayed_work(struct workqueue_struct*wq,struct delayed_work*dwork,unsigned long delay);int queue_delayed_work_on(int cpu,struct workqueue_struct*wq,struct delayed_work*dwork,unsigned long delay);可以使用全局的内核全局工作队列,利用4个函数来为工作队列定位。这些函数(见清单8)模拟清单7,只是不需要定义工作队列结构体。int schedule_work(struct work_struct*work);int schedule_work_on(int cpu,struct work_struct*work);int scheduled_delayed_work(struct delayed_work*dwork,unsigned long delay);int scheduled_delayed_work_on(int cpu,struct delayed_work*dwork,unsigned long delay);还有一些帮助函数用于清理或取消工作队列中的任务。想清理特定的任务项目并阻塞任务,直到任务完成为止,可以调用flush_work来实现。指定工作队列中的所有任务能够通过调用flush_workqueue来完成。这两种情形下,调用者阻塞直到操作完成为止。为了清理内核全局工作队列,可调用flush_scheduled_ flush_work(struct work_struct*work);int flush_workqueue(struct workqueue_struct*wq);void flush_scheduled_work(void);还没有在处理程序当中执行的任务可以被取消。调用cancel_work_sync将会终止队列中的任务或者阻塞任务直到回调结束(如果处理程序已经在处理该任务)。如果任务被延迟,可以调用cancel_delayed_work_ cancel_work_sync(struct work_struct*work);int cancel_delayed_work_sync(struct delayed_work*dwork);最后,可以通过调用work_pending或者delayed_work_pending来确定任务项目是否在进行中。work_pending(work);delayed_work_pending(work);这就是工作队列API的核心。在./kernel/workqueue.c中能够找到工作队列API的实现方法,API在./include/linux/workqueue.h中定义。下面我们看一个工作队列API的简单例子。下面的例子说明了几个核心的工作队列API函数。如同微线程的例子一样,为方便起见,可将这个例子部署在内核模块上下文。首先,看一下将用于实现bottom half的任务结构体和处理程序函数(参见清单9)。首先您将注意到工作队列结构体参考的定义(my_wq)以及my_work_t的定义。my_work_t类型定义的头部包括结构体work_struct和一个代表任务项目的整数。处理程序(回调函数)将work_struct指针引用改为my_work_t类型。发送出任务项目(来自结构体的整数)之后,任务指针将被释放。#include linux/kernel.h#include linux/module.h#include linux/workqueue.h MODULE_LICENSE(GPL);static struct workqueue_struct*my_wq;typedef structstruct work_struct my_work;int x;my_work_t;my_work_t*work,*work2;static void my_wq_function(struct work_struct*work)my_work_t*my_work=(my_work_t*)work;printk(my_work.x%dn,my_work-x);kfree(void*)work);return;清单10是init_module函数,该函数从使用create_workqueue API函数生成工作队列开始。成功生成工作队列之后,创建两个任务项目(通过kmalloc来分配)。利用INIT_WORK来初始化每个任务项目,任务定义完成,接着通过调用queue_work将任务安排到工作队列中。top-half进程(在此处模拟)完成。如同清单10中所示,任务有时会晚些被处理程序处理。int init_module(void)int ret;my_wq=create_workqueue(my_queue);if(my_wq)/*Queue some work(item 1)*/work=(my_work_t*)kmalloc(sizeof(my_work_t),GFP_KERNEL);if(work)INIT_WORK(struct work_struct*)work,my_wq_function);work-x=1;ret=queue_work(my_wq,(struct work_struct*)work);/*Queue some additional work(item 2)*/work2=(my_work_t*)kmalloc(sizeof(my_work_t),GFP_KERNEL);if(work2)INIT_WORK(struct work_struct*)work2,my_wq_function);work2-x=2;ret=queue_work(my_wq,(struct work_struct*)work2);return 0;最终的元素在清单11中展示。在模块清理过程中,会清理一些特别的工作队列(它们将保持阻塞状态直到处理程序完成对任务的处理),然后销毁工作队列。void cleanup_module(void)flush_workqueue(my_wq);destroy_workqueue(my_wq);return;从对微线程和工作队列的简短介绍中,可以发现两个将任务从top halves延迟到bottom halves的不同方法。微线程提供低延迟机制,该方式简单而直接,而工作队列提供复杂的API来允许对多个任务项目进行排队。每种方法都在中断上下文延迟任务,但只有微线程采用run-to-complete的风格自动运行,而在此处,如果需要,工作队列允许处理程序休眠。为有效实现任务延迟,可根据具体需求来选择相应的方法。这里所探讨的任务延迟方法涉及了历史的和当前的应用在Linux内核中的延迟方法(除了计时器之外,这将在以后的文章中讨论)。它们当然不是新的-事实上,它们在过去已经以其他形式存在-但是它们代表了一种有趣的架构模式,这在Linux中和其他地方都很有用。从软中断到微线程再到任务队列再到延迟的工作队列,Linux在提供一致的和兼容的用户空间体验的同时,保持其内核各方面的持续发展。学习大部分来自互联网的有关微线程和工作队列的可用信息都有些过时。关于工作队列API返工的介绍,可以参考LWN.net。还可以从Linux Device Drivers一书当中学习一些有用的知识。这一重要的演示文稿出自Jennifer Hou(来自伊利诺伊大学厄本那-香槟分校计算机科学系),提供了一个对Linux内核的精彩论述,对软中断的介绍,以及关于利用微线程实现任务延迟的简短介绍。这一概要来自于旧金山大学的高级系统编程课程(由Emeritus教授和Allan Cruse博士讲授),提供了大量的有关工作延迟的资源(包括驱动着它们的理念)。Linux Device Drivers这本富有创意的图书提供了一个针对任务延迟的有益介绍(PDF)

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论