linux驱动程序编写基础.ppt
LinuxLinux操作系统分析与实践操作系统分析与实践第七讲:第七讲:LinuxLinux驱动程序编写基础驱动程序编写基础LinuxLinux操作系统分析与实践操作系统分析与实践课程建设小组课程建设小组北京大学北京大学二零零八年春季二零零八年春季*致谢:感谢致谢:感谢IntelIntel对本课程项目的资助对本课程项目的资助本讲主要内容本讲主要内容Linux内核模块内核模块中断和中断处理中断和中断处理下半部下半部LinuxLinux内核模块内核模块LinuxLinux操作系统的内核是单一体系结构(操作系统的内核是单一体系结构(monolithic kernelmonolithic kernel)有了模块机制后,有了模块机制后,提高提高LinuxLinux操作系统的可扩充性操作系统的可扩充性,内核编程,内核编程不再是一个恶梦不再是一个恶梦什么是模块呢?什么是模块呢?模块的全称是模块的全称是“动态可加载内核模块动态可加载内核模块”(Loadable Loadable Kernel ModuleKernel Module,LKMLKM)模块在内核空间运行模块在内核空间运行模块实际上是一种目标对象文件模块实际上是一种目标对象文件没有链接,不能独立运行没有链接,不能独立运行,但是其代码可以在运行时链接,但是其代码可以在运行时链接到系统中作为内核的一部分运行或从内核中取下,从而可到系统中作为内核的一部分运行或从内核中取下,从而可以以动态扩充内核的功能动态扩充内核的功能这种目标代码通常由一组函数和数据结构组成这种目标代码通常由一组函数和数据结构组成LinuxLinux内核模块的优点与缺点内核模块的优点与缺点优点优点使得内核更加使得内核更加紧凑和灵活紧凑和灵活修改内核时,修改内核时,不必全部重新编译不必全部重新编译整个内核。系统如果需要整个内核。系统如果需要使用新模块,只要编译相应的模块,然后使用使用新模块,只要编译相应的模块,然后使用insmodinsmod将模将模块装载即可块装载即可模块的目标代码一旦被链接到内核,它的作用域和静态链模块的目标代码一旦被链接到内核,它的作用域和静态链接的内核目标代码完全等价接的内核目标代码完全等价缺点缺点由于内核所占用的内存是不会被换出的,所以链接进内核由于内核所占用的内存是不会被换出的,所以链接进内核的模块会给整个系统带来一定的的模块会给整个系统带来一定的性能和内存利用方面的损性能和内存利用方面的损失失;装入内核的模块就成为内核的一部分,可以修改内核中的装入内核的模块就成为内核的一部分,可以修改内核中的其他部分,因此,模块的使用不当会导致系统崩溃其他部分,因此,模块的使用不当会导致系统崩溃;为了让内核模块能访问所有内核资源,为了让内核模块能访问所有内核资源,内核必须维护符号内核必须维护符号表表,并在装入和卸载模块时修改符号表,并在装入和卸载模块时修改符号表;模块会要求利用其它模块的功能,所以,模块会要求利用其它模块的功能,所以,内核要维护模块内核要维护模块之间的依赖性之间的依赖性.Linux内核模块与应用程序的区别内核模块与应用程序的区别 C C语言程序语言程序 LinuxLinux内核模块内核模块运行运行 用户空间用户空间 内核空间内核空间入口入口 main()main()module_init()module_init()指定指定;出口出口 无无 module_exit()module_exit()指定指定;编译编译 gccgcc c c MakefileMakefile连接连接 ld ld insmodinsmod运行运行 直接运行直接运行 insmodinsmod调试调试 gdbgdb kdbugkdbug,kdbkdb,kgdbkgdb等等 模块相关命令模块相关命令 insmod module parametersLoad the module注意,只有超级用户才能使用这个命令注意,只有超级用户才能使用这个命令RmmodUnload the modulelsmodList all modules loaded into the kernel这个命令和这个命令和cat/proc/modules等价等价modprobe-r Load the module specified and modules it depends模块依赖模块依赖一个一个模块模块A A引用另一个模块引用另一个模块B B所导出的符号,我们所导出的符号,我们就说就说模块模块B B被模块被模块A A引用引用。如果要装载模块如果要装载模块A A,必须先要装载模块必须先要装载模块B B。否则,否则,模块模块B B所导出的那些符号的引用就不可能被链接所导出的那些符号的引用就不可能被链接到模块到模块A A中。这种模块间的相互关系就叫做中。这种模块间的相互关系就叫做模块模块依赖依赖。最简单的最简单的内核模块内核模块例子例子#include#include#include static int _init hello_init(void)printk(KERN_INFO Hello worldn);return 0;static void _exit hello_exit(void)printk(KERN_INFO Goodbye worldn);module_init(hello_init);module_exit(hello_exit);staticint_inithello_init(void)staticvoid_exithello_exit(void)Static声明声明,因为这种函数在特定文件之外没有其它意义,因为这种函数在特定文件之外没有其它意义_initinit标记标记,该函数只在初始化期间使用。模块装载后,将该函数只在初始化期间使用。模块装载后,将该函数占用的内存空间释放该函数占用的内存空间释放_exit标记标记该代码仅用于模块卸载。该代码仅用于模块卸载。Init/exit宏:宏:module_init/module_exit声明模块初始化及清除函数所在的位置声明模块初始化及清除函数所在的位置装载和卸载模块时,内核可以自动找到相应的函数装载和卸载模块时,内核可以自动找到相应的函数module_init(hello_init);module_exit(hello_exit);编译编译内核内核模块模块Makefile文件文件obj-m:=hello.oall:make-C/lib/modules/$(shell uname-r)/build M=$(shell pwd)modulesclean:make-C/lib/modules/$(shell uname-r)/build M=$(shell pwd)cleanModuleincludesmorefilesobj-m:=hello.ohello-objs:=a.o b.o装载和卸载模块装载和卸载模块相关命令相关命令lsmodlsmodinsmod hello.koinsmod hello.kormmod hello.kormmod hello.ko模块参数传递模块参数传递有些模块需要传递一些参数有些模块需要传递一些参数参数在模块加载时传递参数在模块加载时传递#insmodinsmod hello.kohello.ko test=2 test=2参数需要使用参数需要使用module_parammodule_param宏来声明宏来声明 module_parammodule_param的参数:变量名称,类型以及访问许可掩码的参数:变量名称,类型以及访问许可掩码支持的参数类型支持的参数类型Byte,short,ushort,int,uint,long,ulong,bool,Byte,short,ushort,int,uint,long,ulong,bool,charpcharpArray(module_param_array(name,type,nump,perm)Array(module_param_array(name,type,nump,perm)#include#include#include#includestaticinttest;module_param(test,int,0644);staticint_inithello_init(void)printk(KERN_INFO“Helloworldtest=%dn”,test);return0;staticvoid_exithello_exit(void)printk(KERN_INFOGoodbyeworldn);MODULE_LICENSE(GPL);MODULE_DESCRIPTION(Test);MODULE_AUTHOR(xxx);module_init(hello_init);module_exit(hello_exit);导出符号表导出符号表如果一个模块需要向其他模块导出符号(方法或全局变如果一个模块需要向其他模块导出符号(方法或全局变量),需要使用:量),需要使用:EXPORT_SYMBOL(name);EXPORT_SYMBOL_GPL(name);*注意:符号必须在模块文件的全局部分导出,不能在函数部分导出。注意:符号必须在模块文件的全局部分导出,不能在函数部分导出。更多信息可参考更多信息可参考 文件文件ModulesModules仅可以使用由仅可以使用由KernelKernel或者其他或者其他ModulesModules导出的符导出的符号号不能使用不能使用LibcLibc/proc/kallsyms/proc/kallsyms 可以显示所有导出的符号可以显示所有导出的符号内核模块操作内核模块操作/proc/proc文件文件/proc文件系统文件系统,这是,这是内核模块和系统交互内核模块和系统交互的两种主要的两种主要方式之一。方式之一。/proc文件系统也是文件系统也是Linux操作系统的特色之一。操作系统的特色之一。/proc文件系统不是普通意义上的文件系统,它是一个文件系统不是普通意义上的文件系统,它是一个伪文件系统伪文件系统。通过通过/proc,可以用标准,可以用标准Unix系统调用系统调用(比如比如open()、read()、write()、ioctl()等等等等)访问进程地址空间访问进程地址空间可以用可以用cat、more等命令查看等命令查看/proc文件中的信息。文件中的信息。用户和应用程序可以通过用户和应用程序可以通过/proc得到系统的信息,并可得到系统的信息,并可以改变内核的某些参数。以改变内核的某些参数。当调试程序或者试图获取指定进程状态的时候,当调试程序或者试图获取指定进程状态的时候,/proc文件系统将是你强有力的支持者。通过它可以创建更文件系统将是你强有力的支持者。通过它可以创建更强大的工具,获取更多信息。强大的工具,获取更多信息。/proc/proc相关函数相关函数create_proc_entry()创创建一个文件建一个文件proc_symlink()创创建符号建符号链链接接proc_mknod()创创建建设备设备文件文件proc_mkdir()创创建目建目录录remove_proc_entry()删删除文件或目除文件或目录录Linux2.6Linux2.6内核中有关模块部分的改内核中有关模块部分的改变变模块引用计数器模块引用计数器Linux2.4中在中在linux/module.h中定义了中定义了三个宏三个宏来维护实用计数:来维护实用计数:_MOD_INC_USE_COUNT当前模块计数加一当前模块计数加一_MOD_DEC_USE_COUNT当前模块计数减一当前模块计数减一_MOD_IN_USE计数非计数非0时返回真时返回真在在Linux2.6中,模块引用计数器中,模块引用计数器由系统自动维护由系统自动维护,所以程序中有,所以程序中有关这些宏都关这些宏都可以注释掉。可以注释掉。关于符号导出列表(关于符号导出列表(listofexportedsymbols)Linux2.4中会用中会用EXPORT_NO_SYMBOLS宏宏,来表示不想,来表示不想导出任何变量或函数。导出任何变量或函数。在在Linux2.6中中这个宏也已经消失这个宏也已经消失。系统默认为不导出任何变。系统默认为不导出任何变量或函数。量或函数。模块程序编译方法的改变模块程序编译方法的改变Linux2.4中命令为:中命令为:gccWallDMODULED_KERNEL_-DLINUXc源文件名源文件名.c其中:其中:_KERNEL_:即告诉头文件这些代码将在内核模式下运行即告诉头文件这些代码将在内核模式下运行MODULE:即告诉头文件要给出适当的内核模块的定义即告诉头文件要给出适当的内核模块的定义LINUX:并非必要并非必要-Wall:显示所有显示所有warning信息。信息。Linux2.6中必须写中必须写makefile。通过。通过make命令编译程序。命令编译程序。Linux2.6中中makefile的写法:(以的写法:(以helloworld为例)为例)/Makefileobj-m+=hello.oall:make-C/lib/modules/$(shelluname-r)/buildM=$(PWD)modulesclean:make-C/lib/modules/$(shelluname-r)/buildM=$(PWD)clean*注意:注意:all下一行需要有一个下一行需要有一个“Tab”键,不要写成空格或略去,不键,不要写成空格或略去,不然系统无法识别,报错:然系统无法识别,报错:nothingtobedonefor“all”。用以加载的目标文件类型已经改变用以加载的目标文件类型已经改变Linux2.4Linux2.4中用以加载为模块的目标文件扩展名为中用以加载为模块的目标文件扩展名为.o.oLinux2.6Linux2.6中中用以加载为模块的目标文件扩展名为中中用以加载为模块的目标文件扩展名为.koko在在Linux2.4Linux2.4中,函数中,函数init_moduleinit_module()()和函数和函数cleanup_modulecleanup_module()()是必不可少的是必不可少的;而在;而在Linux2.6 Linux2.6 中,中,同样功能的函数并非同样功能的函数并非一定要起这两个函数名。一定要起这两个函数名。Lab Lab 模块编程模块编程WriteTwoModule:Module1:HelloWorldModule.Load/unloadthemodulecanoutputsomeinfo.Module2:Moduleacceptsaparameter.Loadthemodule,outputtheparametersvalue.ModifyModule1andModule2,letModule1exportssymbols,Module2usethesymbols.*注:详见实验指导注:详见实验指导二、中断和中断处理程序二、中断和中断处理程序中断处理的基本过程中断处理的基本过程When receiving an interrupt,CPU program counter jumps to a predefined address(interrupt vectors)The state of interrupted program is savedThe corresponding service routine is executedThe interrupting component is served,and interrupt signal is removedThe state of interrupted program is restoredResume the interrupted program at the interrupted address中断描述符表中断描述符表IDTIDT中断描述符表是一个系统表,它与每一个中断或者异常向量中断描述符表是一个系统表,它与每一个中断或者异常向量相联系相联系每个向量每个向量在表中有相应的在表中有相应的中断或者异常处理程序的入口中断或者异常处理程序的入口地址地址。每个描述符每个描述符8个字节,个字节,共共256项,占用空间项,占用空间2KB内核在允许中断发生前,必须适当的初始化内核在允许中断发生前,必须适当的初始化IDTCPU的的idtr寄存器寄存器指向指向IDT表的物理基地址表的物理基地址Interruptvectorsonx86初始化初始化IDTIDTLinuxLinux内核在系统的初始化阶段要内核在系统的初始化阶段要初始化可编程控制器初始化可编程控制器8259A8259A;将中断描述符表的起始地址装入将中断描述符表的起始地址装入IDTRIDTR寄存器寄存器,并初始化表中的每一项并初始化表中的每一项 当计算机运行在当计算机运行在实模式时实模式时IDTIDT被初始化,并由被初始化,并由BIOSBIOS使用使用 。真正真正进入了进入了LinuxLinux内核内核IDTIDT就被移到内存的另一个区域,并为就被移到内存的另一个区域,并为进入保护模式进行预初始化进入保护模式进行预初始化 中断处理程序中断处理程序注册中断处理程序注册中断处理程序intrequest_irq(unsignedintirq,irq_handler_t*handler,longirqflags,constchar*devname,void*dev_id)释放中断处理程序释放中断处理程序intfree_irq(unsignedintirq,void*dev_id)编写中断处理程序编写中断处理程序intirqreturn_thandler(intirq,void*dev_id,structpt_regs*regs);共享的中断处理程序共享的中断处理程序register_irq()withSA_SHIRQflagTheregistrationfailsifotherhandleralreadyregisterthesameIRQwithoutSA_SHIRQflagThedev_idargumentmustbeuniquetoeachhandlerTheinterrupthandlermustbeabletofindoutwhetheritsdeviceactuallygenerateaninterruptHardwaremustprovideastatusregisterforinquiry中断上下文中断上下文当执行当执行中断处理程序或下半部中断处理程序或下半部时时,内核处于中断上下文内核处于中断上下文中断上下文不同于进程上下文中断上下文不同于进程上下文中断或异常处理程序执行的代码中断或异常处理程序执行的代码不是一个进程不是一个进程它是一个它是一个内核控制路径内核控制路径,代表了中断发生时正在运行的进程执行,代表了中断发生时正在运行的进程执行,作为一个进程的内核控制路径,作为一个进程的内核控制路径,中断处理程序比一个进程要中断处理程序比一个进程要“轻轻”(中断上下文只包含了中断上下文只包含了很有限的几个寄存器很有限的几个寄存器,建立和终止这个,建立和终止这个上下文所需要的时间很少上下文所需要的时间很少)中断上下文不可以睡眠中断上下文不可以睡眠,也也不能调用某些函数不能调用某些函数,具有较为严格的时具有较为严格的时间限制间限制解决办法:解决办法:中断处理划分为上半部分和下半部中断处理划分为上半部分和下半部分分上半部上半部:(中断处理程序)(中断处理程序)内核立即执行内核立即执行Simpleandfast,dealingwithtime-criticalhardwaretasksE.g.packetstransmissionandreceiving下半部:留着稍后处理下半部:留着稍后处理DeferringworktoalaterpointwhereinterruptscanbeenabledProcessingtime-consumingandmaybesoftware-onlytasksEworkprotocolsprocessing下半部及推后执行的工作下半部及推后执行的工作中断处理程序的局限中断处理程序的局限中断处理程序必须非常快速结束来中断处理程序必须非常快速结束来避免打断其他重要代码的执行避免打断其他重要代码的执行中断实时任务中断实时任务或其他中断处理程序或其他中断处理程序当前当前IRQ被屏蔽被屏蔽或者或者CPU上所有的上所有的IRQ被屏蔽(如果设置了被屏蔽(如果设置了SA_INTERRUPT)运行在运行在中断上下文中断上下文(不是运行在进程上下文),(不是运行在进程上下文),不能被阻塞不能被阻塞BH2.6中去中去除除taskqueue(任务队列任务队列)2.6中去中去除除软中断软中断(softirq)2.4引入引入Tasklet2.4引入引入工作队列工作队列(workqueue)2.4引入引入下半部的环境下半部的环境下半部下半部外部设备外部设备TIMER_BH定时器定时器TQUEUE_BH周期性任务队列周期性任务队列SERIAL_BH串行端口串行端口IMMEDIATE_BH立即任务队列立即任务队列下半部可以通过多种机制实现,下半部可以通过多种机制实现,分别由不同的接口和子系统分别由不同的接口和子系统组成组成BH接口接口静态创建静态创建由由32个个Bottomhalf组成的链表组成的链表Taskqueue任务队列任务队列机制机制软中断软中断Tasklet工作队列工作队列SoftIRQSoftIRQ(软中断)(软中断)在编译期间静态分配在编译期间静态分配的的Only32softIRQscanexistonly6currentlyused.由由softirq_action结构结构表示表示:structsoftirq_actionvoid(*action)(structsoftirq_action*);/待执行的函数待执行的函数void*data;/传给函数的参数传给函数的参数;中中定义了一个包含定义了一个包含32个该结构体的数组个该结构体的数组staticstructsoftirq_actionsoftirq_vec32;6 6个当前使用的个当前使用的SoftIRQsSoftIRQsSoftIRQPriorityDescriptionHI0Highprioritytasklets.TIMER1Timerbottomhalf.NET_TX2Sendnetworkpackets.NET_RX3Receivenetworkpackets.SCSI4SCSIbottomhalf.TASKLET5Tasklets.include/linux/interrupt.h109enum110111HI_SOFTIRQ=0,112TIMER_SOFTIRQ,113NET_TX_SOFTIRQ,114NET_RX_SOFTIRQ,115BLOCK_SOFTIRQ,116TASKLET_SOFTIRQ117;软中断处理程序软中断处理程序注册注册软中断处理程序软中断处理程序(kernel/softirq.c)205voidopen_softirq(intnr,void(*action)(structsoftirq_action*),void*data)206207softirq_vecnr.data=data;208softirq_vecnr.action=action;209当当软中断处理程序运行软中断处理程序运行时,当前处理器上的时,当前处理器上的软中断被禁软中断被禁止止。其它处理器仍可以执行别的软中断。其它处理器仍可以执行别的软中断。引入软中断的原因就是其可扩展性。引入软中断的原因就是其可扩展性。如果不需要扩展到如果不需要扩展到多个处理器,那么就使用多个处理器,那么就使用tasklet.软中断不能睡眠。软中断不能睡眠。raise_softirqraise_softirq调用调用open_softirq()进行注册后,新的软中断就可以运行了。进行注册后,新的软中断就可以运行了。调用调用raise_softirq()可以将一个软中断设置为可以将一个软中断设置为挂起状态挂起状态,使它在,使它在下一次调用下一次调用do_softirq()函数函数投入运行。投入运行。do_softirqdo_softirq在下列地方,待处理的在下列地方,待处理的软中断会被检查和执行软中断会被检查和执行从一个从一个硬件中断代码处返回硬件中断代码处返回时时在在ksoftirqd内核线程内核线程中中在那些在那些显式检查显式检查和执行和执行待处理的软中断待处理的软中断的代码中。的代码中。无论什么方式,无论什么方式,软中断都要在软中断都要在do_softirq()中执行中执行。遍历每一个软中断遍历每一个软中断,调用他们的处理程序。,调用他们的处理程序。TaskletsTaskletsTasklets是是利用软中断实现的一种下半部机制利用软中断实现的一种下半部机制。Tasklet结构体结构体structtasklet_structstructtasklet_struct*next;/*队列指针队列指针*/unsignedlongstate;/*tasklet的状态的状态*/atomic_tcount;/*引用计数,通常用引用计数,通常用1表示表示disabled*/void(*func)(unsignedlong);/*函数指针函数指针*/unsignedlongdata;/*func(data)*/;在软中断中在软中断中相关的向量相关的向量:softirq_vecHI_SOFTIRQsoftirq_vecHI_SOFTIRQ softirq_vecTASKLET_SOFTIRQsoftirq_vecTASKLET_SOFTIRQ 触发触发(激活、调度)(激活、调度)tasklettasklet:HI:HI:tasklet_hi_scheduletasklet_hi_schedule()()TASKLET:TASKLET:tasklet_scheduletasklet_schedule()()通过通过do_softirqdo_softirq()()调度调度tasklettasklet的运的运行行HIHI action:action:tasklet_hi_actiontasklet_hi_action()()TASKLETTASKLET action:action:tasklet_actiontasklet_action()()Tasklet实现的软中断向量表实现的软中断向量表使用使用tasklettasklet大多数情况下,大多数情况下,tasklet机制是实现下半部的最佳选择机制是实现下半部的最佳选择编写编写tasklet处理程序处理程序声明声明tasklet调度调度tasklet编写编写tasklet处理程序处理程序定义一个定义一个小任务的处理函数小任务的处理函数并把用户的代码写到其中。并把用户的代码写到其中。voidmy_tasklet_fun(unsignedlong)用户代码;用户代码;声明声明takslet使用使用DECLARE_TASKLET()宏宏DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);调用调用tasklet_schedule()函数函数系统会在适当的时候系统会在适当的时候调度并运行这个调度并运行这个tasklet.tasklet_schedule(&my_tasklet);工作队列工作队列(work queue)(work queue)工作队列使用工作队列使用内核线程来执行驱动中需延迟执行的工作内核线程来执行驱动中需延迟执行的工作(BottomHalf),这些内核线程被称为这些内核线程被称为工作线程工作线程。在在Linux2.6内核中内核中,系统除了提供系统除了提供默工作认线程默工作认线程来帮助驱动方便来帮助驱动方便的执行延迟操作,还允许驱动自己产生的执行延迟操作,还允许驱动自己产生自定义的工作线程自定义的工作线程来执行来执行某些特殊的延迟工作。某些特殊的延迟工作。默认工作线程被称作默认工作线程被称作events/n,其中其中n为为CPU个数个数,也就是说也就是说,每每个个CPU都有一个默认工作线程。都有一个默认工作线程。一般情况下一般情况下,大多数驱动都使用默认工作线程大多数驱动都使用默认工作线程来执行自己的来执行自己的Bottom-Half工作。工作。某些情况下某些情况下,驱动产生自己的自定义工作线程驱动产生自己的自定义工作线程可以满足更高的性可以满足更高的性能要求能要求,并可以减轻默认工作线程的负担。并可以减轻默认工作线程的负担。工作线程工作线程每种类型的工作线程都有一个这样的每种类型的工作线程都有一个这样的结构与其关联结构与其关联。它有一个重要的成员它有一个重要的成员CPU_wq,即元素类型为即元素类型为CPUworkqueue_struct的数组的数组,表示系统中的表示系统中的每个每个CPU都有自己都有自己的工作线程的工作线程假设需要在有假设需要在有2个个CPU的计算机上创建类型为的计算机上创建类型为myworker的工作的工作线程线程,则系统除了有则系统除了有2个类型为个类型为events的默认工作线程的默认工作线程外外,还有还有2个类型为个类型为myworker的工作线程的工作线程,每一个每一个events或或myworker都有都有一个一个CPU_workqueue_struct结构结构与之关联。与之关联。structworkqueue_structstructCPU_workqueue_structCPU_wqNR_CPUS;constchar*name;structlist_headlist;structCPU_workqueue_structspinlock_tlock;/*lockprotectingthisstructure*/longremove_sequence;/*least-recentlyadded(nexttorun)*/longinsert_sequence;/*nexttoadd*/structlist_headworklist;/*该该CPU上的所需处理的工作队列上的所需处理的工作队列*/wait_queue_head_tmore_work;wait_queue_head_twork_done;structworkqueue_struct*wq;/*所属的所属的workqueue_struct,*/task_t*thread;/*所关联的工作线程所关联的工作线程,*/intrun_depth;/*run_workqueue()recursiondepth*/工作单元工作单元每个每个CPU的的同一类型工作单元同一类型工作单元被连接成一个被连接成一个工作队列工作队列work_struct用来表示每一个需要被延迟处理的用来表示每一个需要被延迟处理的工作单元工作单元。structwork_structunsignedlongpending;/*isthisworkpending?*/structlist_headentry;/*linklistofallwork*/void(*func)(void*);/*handlerfunction*/void*data;/*argumenttohandler*/void*wq_data;/*usedinternally*/structtimer_listtimer;/*timerusedbydelayedworkqueues*/worker_threadworker_thread()()线程函数线程函数所有的所有的工作线程都是普通内核线程工作线程都是普通内核线程,使用,使用worker_thread()作为线程函数作为线程函数该线程函数在进行一段初始化操作后便进入该线程函数在进行一段初始化操作后便进入无限循环无限循环,并睡眠等待并睡眠等待。当有需处理的延迟工作被加入到工作队列中时当有需处理的延迟工作被加入到工作队列中时,该线该线程函数被唤醒程函数被唤醒并循环处理对应工作队列中的并循环处理对应工作队列中的每一个工每一个工作单元作单元;当工作队列中没有工作单元需要被处理时当工作队列中没有工作单元需要被处理时,它又它又重新重新进入睡眠状态进入睡眠状态,等待下一次的唤醒。等待下一次的唤醒。主要任务就是主要任务就是遍历工作队列上所有需要被处理的工作单元遍历工作队列上所有需要被处理的工作单元,如果如果队列非空队列非空,则调用则调用run_workqueue()处理具体的延迟工作。处理具体的延迟工作。通过通过list_entry取得每一个工作单元的取得每一个工作单元的work_struct结构结构,并以并以work-data为参数调用其具体处理函数为参数调用其具体处理函数work-func()。阅读代码阅读代码/kernel/workqueue.c?v=2.6.17.13#L188使用工作队列使用工作队列驱动驱动为需要延迟处理的工作建立一为需要延迟处理的工作建立一work_struct结构结构,该该结构即为结构即为工作单元工作单元,它还包含一它还包含一函数指针函数指针用来处理具体的用来处理具体的延迟工作延迟工作;该工作单元被添加到当前该工作单元被添加到当前CPU的默认工作线程的默认工作线程或自定义或自定义工作线程的工作队列中等待处理工作线程的工作队列中等待处理在某一时刻在某一时刻,工作线程被唤醒工作线程被唤醒,它将循环处理工作队列中它将循环处理工作队列中的每一个的每一个工作单元工作单元。使用系统中的默认工作队列使用系统中的默认工作队列首先首先,要为需要延迟处理的工作单元建立一个要为需要延迟处理的工作单元建立一个work_struct结构结构内核提供了下面内核提供了下面2个宏来方便地建立该结构个宏来方便地建立该结构:DECLARE_WORK(name,void(*func)(void*),void*data);/静态创建静态创建INIT_WORK(structwork_struct*work,void(*func)(void*),void*data)/动态初始化动态初始化将该结构放入到将该结构放入到默认工作线程的工作队列默认工作线程的工作队列中去中去,内核提供以下内核提供以下2个宏个宏操作操作:schedule_work(work);schedule_delayed_work(work,delay)。这这2个宏操作的主要区别就在于一个是个宏操作的主要区别就在于一个是立即被调度立即被调度,一个是一个是延迟延迟delay个时钟周期后被调度个时钟周期后被调度。使用自定义工作队列使用自定义工作队列创建工作线程创建工作线程使用使用structworkqueue_struct*create_workqueue(constchar*name)。其中其中,name为该类型工作工作线程的名字为该类型工作工作线程的名字创建类型为创建类型为myworker的的工作线程的代码工作线程的代码如下如下:structworkqueue_struct*myworker;myworker=create_workqueue(“myworker”)。将将work_struct加入到加入到自定义工作线程的工作队列自定义工作线程的工作队列中可以中可以采用以下接口采用以下接口:intqueue_work(structworkqueue_struct*wq,structwork_struct*work);intqueue_delayed_work(structworkqueue_struct*wq,structwork_struct*work,unsignedlongdelay)。Q&A本讲结束本讲结束 !