linux驱动程序编写基础.ppt
《linux驱动程序编写基础.ppt》由会员分享,可在线阅读,更多相关《linux驱动程序编写基础.ppt(45页珍藏版)》请在装配图网上搜索。
Linux操作系统分析与实践第七讲:Linux驱动程序编写基础,Linux操作系统分析与实践课程建设小组北京大学二零零八年春季*致谢:感谢Intel对本课程项目的资助,本讲主要内容,Linux内核模块中断和中断处理下半部,Linux内核模块,Linux操作系统的内核是单一体系结构(monolithickernel)有了模块机制后,提高Linux操作系统的可扩充性,内核编程不再是一个恶梦什么是模块呢?模块的全称是“动态可加载内核模块”(LoadableKernelModule,LKM)模块在内核空间运行模块实际上是一种目标对象文件没有链接,不能独立运行,但是其代码可以在运行时链接到系统中作为内核的一部分运行或从内核中取下,从而可以动态扩充内核的功能这种目标代码通常由一组函数和数据结构组成,Linux内核模块的优点与缺点,优点使得内核更加紧凑和灵活修改内核时,不必全部重新编译整个内核。系统如果需要使用新模块,只要编译相应的模块,然后使用insmod将模块装载即可模块的目标代码一旦被链接到内核,它的作用域和静态链接的内核目标代码完全等价缺点由于内核所占用的内存是不会被换出的,所以链接进内核的模块会给整个系统带来一定的性能和内存利用方面的损失;装入内核的模块就成为内核的一部分,可以修改内核中的其他部分,因此,模块的使用不当会导致系统崩溃;为了让内核模块能访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改符号表;模块会要求利用其它模块的功能,所以,内核要维护模块之间的依赖性.,Linux内核模块与应用程序的区别,C语言程序Linux内核模块运行用户空间内核空间入口main()module_init()指定;出口无module_exit()指定;编译gcccMakefile连接ldinsmod运行直接运行insmod调试gdbkdbug,kdb,kgdb等,模块相关命令,insmodmoduleparametersLoadthemodule注意,只有超级用户才能使用这个命令RmmodUnloadthemodulelsmodListallmodulesloadedintothekernel这个命令和cat/proc/modules等价modprobe-rLoadthemodulespecifiedandmodulesitdepends,模块依赖,一个模块A引用另一个模块B所导出的符号,我们就说模块B被模块A引用。如果要装载模块A,必须先要装载模块B。否则,模块B所导出的那些符号的引用就不可能被链接到模块A中。这种模块间的相互关系就叫做模块依赖。,最简单的内核模块例子,#include#include#includestaticint_inithello_init(void)printk(KERN_INFOHelloworldn);return0;staticvoid_exithello_exit(void)printk(KERN_INFOGoodbyeworldn);module_init(hello_init);module_exit(hello_exit);,staticint_inithello_init(void)staticvoid_exithello_exit(void)Static声明,因为这种函数在特定文件之外没有其它意义_init标记,该函数只在初始化期间使用。模块装载后,将该函数占用的内存空间释放_exit标记该代码仅用于模块卸载。Init/exit宏:module_init/module_exit声明模块初始化及清除函数所在的位置装载和卸载模块时,内核可以自动找到相应的函数module_init(hello_init);module_exit(hello_exit);,编译内核模块,Makefile文件obj-m:=hello.oall:make-C/lib/modules/$(shelluname-r)/buildM=$(shellpwd)modulesclean:make-C/lib/modules/$(shelluname-r)/buildM=$(shellpwd)cleanModuleincludesmorefilesobj-m:=hello.ohello-objs:=a.ob.o,装载和卸载模块,相关命令lsmodinsmodhello.kormmodhello.ko,模块参数传递,有些模块需要传递一些参数参数在模块加载时传递#insmodhello.kotest=2参数需要使用module_param宏来声明module_param的参数:变量名称,类型以及访问许可掩码支持的参数类型Byte,short,ushort,int,uint,long,ulong,bool,charpArray(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);*注意:符号必须在模块文件的全局部分导出,不能在函数部分导出。更多信息可参考文件Modules仅可以使用由Kernel或者其他Modules导出的符号不能使用Libc/proc/kallsyms可以显示所有导出的符号,内核模块操作/proc文件,/proc文件系统,这是内核模块和系统交互的两种主要方式之一。/proc文件系统也是Linux操作系统的特色之一。/proc文件系统不是普通意义上的文件系统,它是一个伪文件系统。通过/proc,可以用标准Unix系统调用(比如open()、read()、write()、ioctl()等等)访问进程地址空间可以用cat、more等命令查看/proc文件中的信息。用户和应用程序可以通过/proc得到系统的信息,并可以改变内核的某些参数。当调试程序或者试图获取指定进程状态的时候,/proc文件系统将是你强有力的支持者。通过它可以创建更强大的工具,获取更多信息。,/proc相关函数,create_proc_entry()创建一个文件proc_symlink()创建符号链接proc_mknod()创建设备文件proc_mkdir()创建目录remove_proc_entry()删除文件或目录,Linux2.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.4中用以加载为模块的目标文件扩展名为.oLinux2.6中中用以加载为模块的目标文件扩展名为.ko在Linux2.4中,函数init_module()和函数cleanup_module()是必不可少的;而在Linux2.6中,同样功能的函数并非一定要起这两个函数名。,Lab模块编程,WriteTwoModule:Module1:HelloWorldModule.Load/unloadthemodulecanoutputsomeinfo.Module2:Moduleacceptsaparameter.Loadthemodule,outputtheparametersvalue.ModifyModule1andModule2,letModule1exportssymbols,Module2usethesymbols.*注:详见实验指导,二、中断和中断处理程序,中断处理的基本过程Whenreceivinganinterrupt,CPUprogramcounterjumpstoapredefinedaddress(interruptvectors)ThestateofinterruptedprogramissavedThecorrespondingserviceroutineisexecutedTheinterruptingcomponentisserved,andinterruptsignalisremovedThestateofinterruptedprogramisrestoredResumetheinterruptedprogramattheinterruptedaddress,中断描述符表IDT,中断描述符表是一个系统表,它与每一个中断或者异常向量相联系每个向量在表中有相应的中断或者异常处理程序的入口地址。每个描述符8个字节,共256项,占用空间2KB内核在允许中断发生前,必须适当的初始化IDTCPU的idtr寄存器指向IDT表的物理基地址,Interruptvectorsonx86,初始化IDT,Linux内核在系统的初始化阶段要初始化可编程控制器8259A;将中断描述符表的起始地址装入IDTR寄存器,并初始化表中的每一项当计算机运行在实模式时IDT被初始化,并由BIOS使用。真正进入了Linux内核IDT就被移到内存的另一个区域,并为进入保护模式进行预初始化,中断处理程序,注册中断处理程序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引入,下半部的环境,下半部可以通过多种机制实现,分别由不同的接口和子系统组成BH接口静态创建由32个Bottomhalf组成的链表Taskqueue任务队列机制软中断Tasklet工作队列,SoftIRQ(软中断),在编译期间静态分配的Only32softIRQscanexistonly6currentlyused.由softirq_action结构表示:structsoftirq_actionvoid(*action)(structsoftirq_action*);/待执行的函数void*data;/传给函数的参数;中定义了一个包含32个该结构体的数组staticstructsoftirq_actionsoftirq_vec32;,6个当前使用的SoftIRQs,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_softirq,调用open_softirq()进行注册后,新的软中断就可以运行了。调用raise_softirq()可以将一个软中断设置为挂起状态,使它在下一次调用do_softirq()函数投入运行。,do_softirq,在下列地方,待处理的软中断会被检查和执行从一个硬件中断代码处返回时在ksoftirqd内核线程中在那些显式检查和执行待处理的软中断的代码中。无论什么方式,软中断都要在do_softirq()中执行。遍历每一个软中断,调用他们的处理程序。,Tasklets,Tasklets是利用软中断实现的一种下半部机制。Tasklet结构体structtasklet_structstructtasklet_struct*next;/*队列指针*/unsignedlongstate;/*tasklet的状态*/atomic_tcount;/*引用计数,通常用1表示disabled*/void(*func)(unsignedlong);/*函数指针*/unsignedlongdata;/*func(data)*/;,在软中断中相关的向量:softirq_vecHI_SOFTIRQsoftirq_vecTASKLET_SOFTIRQ触发(激活、调度)tasklet:HI:tasklet_hi_schedule()TASKLET:tasklet_schedule()通过do_softirq()调度tasklet的运行HIaction:tasklet_hi_action()TASKLETaction:tasklet_action(),Tasklet实现的软中断向量表,使用tasklet,大多数情况下,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(,工作队列(workqueue),工作队列使用内核线程来执行驱动中需延迟执行的工作(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_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,本讲结束!,- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 驱动程序 编写 基础
装配图网所有资源均是用户自行上传分享,仅供网友学习交流,未经上传用户书面授权,请勿作他用。
关于本文