




已阅读5页,还剩22页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),我们简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的最大优点是效率高,因为所有的内容都集成在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。一、 什么是模块模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。二、 编写一个简单的模块模块和内核都在内核空间运行,模块编程在一定意义上说就是内核编程。因为内核版本的每次变化,其中的某些函数名也会相应地发生变化,因此模块编程与内核版本密切相关。以下例子针对2.6内核1程序举例hellomod.c001/ hello world driver for Linux 2.6004 #include 005 #include 006 #include /* 必要的头文件*/009 static int _init lkp_init( void )printk(“Hello,World! from the kernel spacen”);return 0;013 015 static void _exit lkp_cleanup( void )printk(“Goodbye, World! leaving kernel spacen”);018 020 module_init(lkp_init);021 module_exit(lkp_cleanup);022 MODULE_LICENSE(“GPL”);说明 第4行: 所有模块都要使用头文件module.h,此文件必须包含进来。第5行: 头文件kernel.h包含了常用的内核函数。第6行: 头文件init.h包含了宏_init和_exit,它们允许释放内核占用的内存。建议浏览一下该文件中的代码和注释。第9-12行: 这是模块的初始化函数,它必需包含诸如要编译的代码、初始化数据结构等内容。第11行使用了printk()函数,该函数是由内核定义的,功能与C库中的printf()类似,它把要打印的信息输出到终端或系统日志。字符串中的是输出的级别,表示立即在终端输出。 第15-18行: 这是模块的退出和清理函数。此处可以做所有终止该驱动程序时相关的清理工作。第20行: 这是驱动程序初始化的入口点。对于内置模块,内核在引导时调用该入口点;对于可加载模块则在该模块插入内核时才调用。第21行: 对于可加载模块,内核在此处调用module_cleanup()函数,而对于内置的模块,它什么都不做。第22行: 提示可能没有GNU公共许可证。有几个宏是在2.4版的内核中才开发的(详情参见modules.h)。 函数module_init()和cleanup_exit()是模块编程中最基本也是必须的两个函数。module_init()向内核注册模块所提供的新功能,而cleanup_exit()注销由模块提供的所有功能。模块编程属于内核编程,因此,除了对内核相关知识有所了解外,还需要了解与模块相关的知识。1应用程序与内核模块的比较为了加深对内核模块的了解,表一给出应用程序与内核模块程序的比较。表一 应用程序与内核模块程序的比较C语言应用程序内核模块程序使用函数Libc库内核函数运行空间用户空间内核空间运行权限普通用户超级用户入口函数main()module_init()出口函数exit()module_exit()编译Gcc cMakefile连接Gccinsmod运行直接运行insmod调试Gdbkdbug, kdb,kgdb等从表一我们可以看出,内核模块程序不能调用libc库中的函数,它运行在内核空间,且只有超级用户可以对其运行。另外,模块程序必须通过module_init()和module-exit()函数来告诉内核“我来了”和“我走了”。2内核符号表(如果对以下第24点理解上有困难,可以越过)如 前所述,Linux内核是一个整体结构,像一个圆球,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起见,Linux把内核也看作 一个“母”模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共享,内核只把各个模块中 主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号。到低哪些符号可以被共享? Linux内核有自己的规定。对于内核这个特殊的母模块,在kernel/ksyms.c中定义了从中可以“移出”的符号,例如进程管理子系统可以“移出”的符号定义如下:/* 进程管理 */EXPORT_SYMBOL(do_mmap_pgoff);EXPORT_SYMBOL(do_munmap);EXPORT_SYMBOL(do_brk);EXPORT_SYMBOL(exit_mm);EXPORT_SYMBOL(schedule);EXPORT_SYMBOL(jiffies);EXPORT_SYMBOL(xtime);你可能对这些变量和函数已经很熟悉。其中宏定义EXPORT_SYMBOL()本身的含义是“移出符号”。为什么说是“移出”呢?因为这些符号本来是内核内部的符号,通过这个宏放在一个公开的地方,使得装入到内核中的其他模块可以引用它们。实际上,仅仅知道这些符号的名字是不够的,还得知道它们在内核地址空间中的地址才有意义。因此,内核中定义了如下结构来描述模块的符号:struct module_symbolunsigned long value; *符号在内核地址空间中的地址*/const char *name; /*符号名*/;我们可以从/proc/ksyms文件中读取所有内核模块“移出”的符号,这所有符号就形成内核符号表,其格式如下:内存地址 符号名 所属模块在模块编程中,可以根据符号名从这个文件中检索出其对应的地址,然后直接访问该地址从而获得内核数据。第三列“所属模块”指符号所在的模块名,对于从内核这一母模块移出的符号,这一列为空。模块加载后,2.4内核下可通过 /proc/ksyms、 2.6 内核下可通过/proc/kallsyms查看模块输出的内核符号3模块依赖如前所述,内核符号表记录了所有模块可以访问的符号及相应的地址。当一个新的模块被装入内核后,它所申明的某些符号就会被登记到这个表中,而这些符号可能被其他模块所引用,这就引出了模块依赖这个问题。一个模块A引用另一个模块B所移出的符号,我们就说模块B被模块A引用,或者说模块A依赖模块B。如果要链接模块A,必须先链接模块B。这种模块间相互依赖的关系就叫模块依赖。4模块引用计数器为 了确保模块安全地卸载,每个模块都有一个引用计数器。当执行模块所涉及的操作时就递增计数器,在操作结束时就递减这个计数器;另外,当模块B被模块A引用 时,模块B的引用计数就递增,引用结束,计数器递减。什么时候可以卸载这个模块?当然只有这个计数器值为0的时候,例如,当一个文件系统还被安装在系统上 时就不能将其卸载,当这个文件系统不再被使用时,引用计数器就为0,于是可以卸载。四模块编译Linux 中最重要的软件开发工具是 GCC。GCC 是 GNU 的 C 和 C+ 编译器。但是,在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工键入 gcc 命令进行编译的话,则会非常不方便。因此,人们通常利用 make 工具来自动完成编译工作。利用这种自动编译可大大简化开发工作,避免不必要的重新编译。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。1编译工具make实际上,make 工具通过一个称为 Makefile 的文件来完成并自动维护编译工作。Makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。下面给出2.6 内核模块的Makefile模板(请参看Makefile的写法)# Makefile2.6obj-m += hellomod.o # 产生hellomod 模块的目标文件CURRENT_PATH := $(shell pwd) #模块所在的当前路径LINUX_KERNEL := $(shell uname -r) #Linux内核源代码的当前版本LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL) #Linux内核源代码的绝对路径all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块了clean:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #清理注意: 在每个命令前(例如make命令前)要键入一个制表符(按TAB键产生)有了Makefile,执行make命令,会自动形成相关的后缀为.o和.ko文件。到此,模块编译好了,该把它插入到内核了:如:$insmod hellomod.ko 当然,要以系统员的身份才能把模块插入。 成功插入后,可以通过dmesg命令查看,屏幕最后几行的输出就是你程序中输出的内容:Hello,World! from the kernel space当模块不再需要时,可以通过rmmod命令移去,例如$rmmod hellomodTags: Add new tagmodutils是管理内核模块的一个软件包。可以在任何获得内核源代码的地方获取Modutils(modutils-x.y.z.tar.gz)源代码,然后选择最高级别的patch.x.y.z等于或小于当前的内核版本,安装后在/sbin目录下就会有insomod、rmmod、ksyms、lsmod、modprobe等实用程序。当然,通常我们在加载Linux内核时,modutils已经被载入。1Insmod命令调用insmod程序把需要插入的模块以目标代码的形式插入到内核中。在插入的时候,insmod自动调用init_module()函数运行。注意,只有超级用户才能使用这个命令,其命令格式为:# insmodpath modulename.c2. rmmod命令调用rmmod程序将已经插入内核的模块从内核中移出,rmmod会自动运行cleanup_module()函数,其命令格式为:#rmmodpath modulename.c3lsmod命令调用lsmod程序将显示当前系统中正在使用的模块信息。实际上这个程序的功能就是读取/proc文件系统中的文件/proc/modules中的信息,其命令格式为:#lsmod4ksyms命令ksyms这个程序用来显示内核符号和模块符号表的信息。与lsmod相似,它的功能是读取/proc文件系统中的另一个文件/proc/kallsyms。在此,我们将编写一个模块,其中有一个中断函数,当内核接收到某个 IRQ 上的一个中断时会调用它。先给出全部代码,读者自己调试,把对该程序的理解跟到本贴后面。-#include #include #include static int irq;static char *interface;/MODULE_PARM_DESC(interface,”A network interface”);2.4内核中该宏的用法molule_parm(interface,charp,0644) /2.6内核中的宏/MODULE_PARM_DESC(irq,”The IRQ of the network interface”);module_param(irq,int,0644);static irqreturn_t myinterrupt(int irq, void *dev_id, struct pt_regs *regs)static int mycount = 0;if (mycount 10) printk(“Interrupt!n”);mycount+;return IRQ_NONE;static int _init myirqtest_init(void)printk (“My module worked!11111n”);if (request_irq(irq, &myinterrupt, SA_SHIRQ,interface, &irq) printk(KERN_ERR “myirqtest: cannot register IRQ %dn”, irq);return -EIO;printk(“%s Request on IRQ %d succeededn”,interface,irq);return 0;static void _exit myirqtest_exit(void)printk (“Unloading my module.n”);free_irq(irq, &irq);printk(“Freeing IRQ %dn”, irq);return;module_init(myirqtest_init);module_exit(myirqtest_exit);MODULE_LICENSE(“GPL”);-这里要说明的是,在插入模块时,可以带两个参数,例如insmod myirq.ko interface=eth0 irq=9其中 具体网卡 irq的值可以查看 cat /proc/interrupts动手吧!以此为例,可以设计出各种各样有价值的内核模块,贴出来体验分享的快乐吧。在上一部分“编写带有参数的中断模块”中,这个看似简单的程序,你调试并运行以后思考了哪些方面的问题?(1)给模块传递参数,使得这个模块的扩展和应用有了空间,例如,在我的机器上查看/proc/interruptsCPU00: 10655925 IO-APIC-edge timer1: 9148 IO-APIC-edge i80426: 4 I O-APIC-edge floppy7: 0 IO-APIC-edge parport08: 3 IO-APIC-edge rtc9: 0 IO-APIC-fasteoi acpi12: 41970 IO-APIC-edge i804215: 106157 IO-APIC-edge ide116: 57823 IO-APIC-fasteoi ioc017: 8090 IO-APIC-fasteoi eth018: 245 IO-APIC-fasteoi uhci_hcd:usb1, Ensoniq AudioPCI, usbNMI: 0LOC: 10249542ERR: 0MIS: 0然后,在插入模块时,你对每个中断都作为参数试运行一下,看看会出现什么问题?思考一下irq为0,3等值时,为什么插入失败?这就引出中断的共享和非共享问题,从而促使你分析Linux对共享的中断到底如何处理,共享同一个中断号的中断处理程序到底如何执行?2. 对于myinterrupt()函数,可以进行怎样的改进,使得这个自定义的中断处理程序变得有实际意义?static irqreturn_t myinterrupt(int irq, void *dev_id, struct pt_regs *regs)static int mycount = 0;if (mycount 10) printk(“Interrupt!n”);mycount+;return IRQ_NONE;比如,对于网卡中断,在此收集每一次中断发生时,从网卡接收到的数据,把其存入到文件中。以此思路,随你考虑应用场景了。3. 模块机制给Linux内核的扩展和应用提供了方便的入口,在我们内核之旅 的电子杂志部分,针对内核相关的内容,每一部分都有相对比较实际的内核应用题目,感兴趣者可以去实践,前提是对内核相关内容的彻透理解。在调试该程序的时候请保证调试了带参数的中断程序实例内的程序,并且对中断有了一定的学习。#include #include #include #include static int irq;static char *interface;module_param(interface,charp,0644);module_param(irq,int,0644);static int mycount = 0;static long mytime = 0;static unsigned long data=0;static struct tasklet_struct mytasklet;/定义小任务/小任务函数static void mylet(unsigned long data)printk(“tasklet running.n”);if(mycount=0)mytime=jiffies;if (mycount 10)mytime=jiffies-mytime;printk(“Interrupt number %d time %ld n”,irq,mytime);mytime=jiffies;mycount+;return;/中断服务程序static irqreturn_t myinterrupt(int intno,void *dev_id)tasklet_schedule(&mytasklet);/调度小任务,让它运行return IRQ_NONE;static int _init mytasklet_init(void)printk(“initn”);tasklet_init(&mytasklet, mylet,data);/初始化小任务tasklet_schedule(&mytasklet);if (request_irq(irq,&myinterrupt,IRQF_SHARED,interface,&irq)printk(KERN_ERR “myirqtest: cannot register IRQ %dn”, irq);tasklet_kill(&mytasklet);/删除小任务free_irq(irq,&irq);/释放中断return -EIO;printk(“%s Request on IRQ %d succeededn”,interface,irq);return 0;static void _exit mytasklet_exit(void)tasklet_kill(&mytasklet);/删除小任务free_irq(irq,&irq);/释放中断printk(“Freeing IRQ %dn”, irq);printk(“exitn”);return;MODULE_AUTHOR(“Helight.Xu”);MODULE_LICENSE(“GPL”);module_init(mytasklet_init);module_exit(mytasklet_exit);在调试该程序的时候请保证调试了2.6内核模块编程之内的程序,并且对中断有了一定的学习。/*myirq.c*/#include #include #include static int irq;static char *interface;module_param(interface,charp,0644);module_param(irq,int,0644);/static irq_handler_t myinterrupt(int irq, void *dev_id, struct pt_regs *regs)static irqreturn_t myinterrupt(int irq, void *dev_id)static int mycount = 0;static long mytime = 0;struct net_device *dev=(struct net_device *)dev_id;if(mycount=0)mytime=jiffies;/count the interval between two irqsif (mycount name,dev-irq);mycount+;return IRQ_NONE;static int _init myirqtest_init(void)printk (“My module worked!n”);/regist irq/if (request_irq(irq,&myinterrupt,SA_SHIRQ,interface,&irq) /early than 2.6.23if (request_irq(irq,&myinterrupt,IRQF_SHARED,interface,&irq) /later than 2.6.23printk(KERN_ERR “myirqtest: cannot register IRQ %dn”, irq);return -EIO;printk(“%s Request on IRQ %d succeededn”,interface,irq);return 0;static void _exit myirqtest_exit(void)printk (“Unloading my module.n”);free_irq(irq, &irq); /release irqprintk(“Freeing IRQ %dn”, irq);return;module_init(myirqtest_init);module_exit(myirqtest_exit);MODULE_AUTHOR(“Helight.Xu”);MODULE_LICENSE(“GPL”);编译使用该模块:使用Makefile文件的内容如下obj-m := myirq.oKERNELDIR := /usr/src/kernels/linux-2.6.24/all:make -C $(KERNELDIR) M=$(PWD) modulesclean:rm -rf *.o * core .depend .*.cmd *.ko *.mod.c .tmp_versions在查看 /proc/interrupts文件后,确定要共享的中断号(应为该程序是共享中断号的),使用下面的命令插入模块。insmod myirq.ko irq=2 interface=myirq中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。因此,内核把中断处理分为两部分:上半部(top half)和下半部(bottom half),上半部(就是中断服务程序)内核立即执行,而下半部(就是一些内核函数)留着稍后处理:首先,一个快速的“上半部”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲区(如果你的设备用到了DMA,就不止这些)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。 下半部运行时是允许中断请求的,而上半部运行时是关中断的,这是二者之间的主要区别。 但是,内核到底什时候执行下半部,以何种方式组织下半部?这就是我们要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫做bottom half(简称bh),在2.4以后的版本中有了新的发展和改进,改进的目标使下半部可以在多处理机上并行执行,并有助于驱动程序的开发者进行驱动程序的开发。下面主要介绍常用的小任务(Tasklet)机制及2.6内核中的工作队列机制。除此之外,还简要介绍2.4以前内核中的下半部和任务队列机制。1 小任务机制 这里的小任务是指对要推迟执行的函数进行组织的一种机制。其数据结构为tasklet_struct,每个结构代表一个独立的小任务,其定义如下:struct tasklet_struct struct tasklet_struct *next; /*指向链表中的下一个结构*/ unsigned long state; /* 小任务的状态 */ atomic_t count; /* 引用计数器 */ void (*func) (unsigned long); /* 要调用的函数 */ unsigned long data; /* 传递给函数的参数 */;结构中的func域就是下半部中要推迟执行的函数 ,data是它唯一的参数。State域的取值为TASKLET_STATE_SCHED或TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示小任务已被调度,正准备投入运行,TASKLET_STATE_RUN表示小任务正在运行。TASKLET_STATE_RUN只有在多处理器系统上才使用,单处理器系统什么时候都清楚一个小任务是不是正在运行(它要么就是当前正在执行的代码,要么不是)。Count域是小任务的引用计数器。如果它不为0,则小任务被禁止,不允许执行;只有当它为零,小任务才被激活,并且在被设置为挂起时,小任务才能够执行。2 声明和使用小任务大多数情况下,为了控制一个寻常的硬件设备,小任务机制是实现下半部的最佳选择。小任务可以动态创建,使用方便,执行起来也比较快。我们既可以静态地创建小任务,也可以动态地创建它。选择那种方式取决于到底是想要对小任务进行直接引用还是一个间接引用。如果准备静态地创建一个小任务(也就是对它直接引用),使用下面两个宏中的一个:DECLARE_TASKLET(name, func, data)DECLARE_TASKLET_DISABLED(name, func, data)这两个宏都能根据给定的名字静态地创建一个tasklet_struct结构。当该小任务被调度以后,给定的函数func会被执行,它的参数由data给出。这两个宏之间的区别在于引用计数器的初始值设置不同。第一个宏把创建的小任务的引用计数器设置为0,因此,该小任务处于激活状态。另一个把引用计数器设置为1,所以该小任务处于禁止状态。例如:DECLARE_TASKLET(my_tasklet, my_tasklet_handler, dev);这行代码其实等价于struct tasklet_struct my_tasklet = NULL, 0, ATOMIC_INIT(0), tasklet_handler, dev;这样就创建了一个名为my_tasklet的小任务,其处理程序为tasklet_handler,并且已被激活。当处理程序被调用的时候,dev就会被传递给它。3 编写自己的小任务处理程序小任务处理程序必须符合如下的函数类型:void tasklet_handler(unsigned long data)由于小任务不能睡眠,因此不能在小任务中使用信号量或者其它产生阻塞的函数。但是小任务运行时可以响应中断。4 调度自己的小任务通过调用tasklet_schedule()函数并传递给它相应的tasklt_struct指针,该小任务就会被调度以便适当的时候执行:tasklet_schedule(&my_tasklet); /*把 my_tasklet 标记为挂起 */在小任务被调度以后,只要有机会它就会尽可能早的运行。在它还没有得到运行机会之前,如果一个相同的小任务又被调度了,那么它仍然只会运行一次。 可以调用tasklet_disable()函数来禁止某个指定的小任务。如果该小任务当前正在执行,这个函数会等到它执行完毕再返回。调用tasklet_enable()函数可以激活一个小任务,如果希望把以DECLARE_TASKLET_DISABLED()创建的小任务激活,也得调用这个函数,如:tasklet_disable(&my_tasklet); /* 小任务现在被禁止,这个小任务不能运行 */tasklet_enable(&my_tasklet); /* 小任务现在被激活 */也可以调用tasklet_kill()函数从挂起的队列中去掉一个小任务。该函数的参数是一个指向某个小任务的tasklet_struct的长指针。在小任务重新调度它自身的时候,从挂起的队列中移去已调度的小任务会很有用。这个函数首先等待该小任务执行完毕,然后再将它移去。5 tasklet的简单用法 下面是tasklet的一个简单应用, 以模块的形成加载。#include #include #include #include #include #include #include static struct tasklet_struct my_tasklet;static void tasklet_handler (unsigned long data)printk(KERN_ALERT tasklet_handler is running.n);static int _init test_init(void)tasklet_init(&my_tasklet, tasklet_handler, 0);tasklet_schedule(&my_tasklet);return 0;static void _exit test_exit(void)tasklet_kill(&my_tasklet);printk(KERN_ALERT test_exit running.n);MODULE_LICENSE(GPL);module_init(test_init);module_exit(test_exit);module_init从这个例子可以看出,所谓的小任务机制是为下半部函数的执行提供了一种执行机制,也就是说,推迟处理的事情是由tasklet_handler实现,何时执行,经由小任务机制封装后交给内核去处理。Linux 可加载内核模块(从内核的 1.2 版本开始引入)是 Linux 内核的最重要创新之一。它们提供了可伸缩的、动态的内核。探索隐藏在可加载模块后面的原理,并学习这些独立的对象如何动态地转换成 Linux 内核的一部分。Linux 就是通常所说的单内核(monolithic kernel),即操作系统的大部分功能都被称为内核,并在特权模式下运行。它与微型内核不同,后者只把基本的功能(进程间通信 IPC、调度、基本的输入/输出 I/O 和内存管理)当作内核运行,而把其他功能(驱动程序、网络堆栈和文件系统)排除在特权空间之外。因此,您可能认为 Linux 是一个完全静态的内核,但事实恰恰相反。通过 Linux 内核模块(LKM)可以在运行时动态地更改 Linux。 Linux flash 文件系统剖析 Security-Enhanced Linux(SELinux)剖析 实时 Linux 架构剖析 Linux SCSI 子系统剖析 Linux 文件系统剖析 Linux 网络栈剖析 Linux 内核剖析 Linux slab 分配器剖析 Linux 同步方法剖析 Tim 所著的所有剖析系列文章 可动态更改是指可以将新的功能加载到内核、从内核去除某个功能,甚至添加使用其他 LKM 的新 LKM。LKM 的优点是可以最小化内核的内存占用,只加载需要的元素(这是嵌入式系统的重要特性)。 Linux 不是可以进行动态更改的惟一(也不是第一个)单内核。Berkeley Software Distribution(BSD)的变体、Sun Solaris、更老的内核(比如 OpenVMS),以及其他流行的操作系统(比如 Microsoft Windows 和 Apple Mac OS X)都支持可加载模块。 LKM 与直接编译到内核或典型程序的元素有根本区别。典型的程序有一个 main 函数,其中 LKM 包含 entry 和 exit 函数(在 2.6 版本,您可以任意命名这些函数)。当向内核插入模块时,调用 entry 函数,从内核删除模块时则调用 exit 函数。因为 entry 和 exit 函数是用户定义的,所以存在module_init和module_exit宏,用于定义这些函数属于哪种函数。LKM 还包含一组必要的宏和一组可选的宏,用于定义模块的许可证、模块的作者、模块的描述等等。图 1 提供了一个非常简单的 LKM 的视图。 2.6 版本的 Linux 内核提供了一个新的更简单的方法,用于构建 LKM。构建 LKM 时,可以使用典型的用户工具管理模块(尽管内部已经改变):标准insmod(安装 LKM),rmmod(删除 LKM),modprobe(insmod和rmmod的包装器),depmod(用于创建模块依赖项),以及modinfo(用于为模块宏查找值)。更多关于为 2.6 版本内核构建 LKM 的信息,请查看参考资料。 回页首LKM 只不过是一个特殊的可执行可链接格式(Executable and Linkable Format,ELF)对象文件。通常,必须链接对象文件才能在可执行文件中解析它们的符号和结果。由于必须将 LKM 加载到内核后 LKM 才能解析符号,所以 LKM 仍然是一个 ELF 对象。您可以在 LKM 上使用标准对象工具(在 2.6 版本中,内核对象带有后缀.ko,)。例如,如果在 LKM 上使用objdump实用工具,您将发现一些熟悉的区段(section),比如.text(说明)、.data(已初始化数据)和.bss(块开始符号或未初始化数据)。 您还可以在模块中找到其他支持动态特性的区段。.init.text 区段包含module_init代码,.exit.text 区段包含module_exit代码(参见图 2)。.modinfo 区段包含各种表示模块许可证、作者和描述等的宏文本。 了解 LKM 的基础知识之后,现在我们进一步探索模块是如何进入内核的,以及在内核内部是如何管理模块的。 回页首在用户空间中,insmod(插入模块)启动模块加载过程。insmod命令定义需要加载的模块,并调用init_module用户空间系统调用,开始加载过程。2.6 版本内核的insmod命令经过修改后变得非常简单(70 行代码),可以在内核中执行更多工作。insmod并不进行所有必要的符号解析(处理kerneld),它只是通过init_module函数将模块二进制文件复制到内核,然后由内核完成剩余的任务。 init_module函数通过系统调用层,进入内核到达内核函数sys_init_module(参见图 3)。这是加载模块的主要函数,它利用许多其他函数完成困难的工作。类似地,rmmod命令会使delete_module执行system call调用,而delete_module最终会进入内核,并调用sys_delete_module将模块从内核删除。 在模块的加载和卸载期间,模块子系统维护了一组简单的状态变量,用于表示模块的操作。加载模块时,状态为MODULE_STATE_COMING。如果模块已经加载并且可用,状态为MODULE_STATE_LIVE。此外,卸载模块时,状态为MODULE_STATE_GOING。 回页首现在,我们看看加载模块时的内部函数(参见图 4)。当调用内核函数sys_init_module时,会开始一个许可检查,查明调用者是否有权执行这个操作(通过capable函数完成)。然后,调用load_module函数,这个函数负责将模块加载到内核并执行必要的调试(后面还会讨论这点)。load_module函数返回一个指向最新加载模块的模块引用。这个模块加载到系统内具有双重链接的所有模块的列表上,并且通过 notifier 列表通知正在等待模块状态改变的线程。最后,调用模块的init()函数,更新模块状态,表明模块已经加载并且可用。 加载模块的内部细节是 ELF 模块解析和操作。load_module函数(位于 ./linux/kernel/module.c)首先分配一块用于容纳整个 ELF 模块的临时内存。然后,通过copy_from_user函数将 ELF 模块从用户空间读入到临时内存。作为一个 ELF 对象,这个文件的结构非常
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年秋季初级经济师考试 经济基础知识押题冲刺训练试卷
- 现代婚姻法律知识培训内容课件
- 广西钟山中学2026届高三化学第一学期期末综合测试试题含解析
- 兖州中考题目及答案大全
- 烟台教招必背题目及答案
- 民法典婚姻培训课件
- 无人机产业链全面分析及未来发展
- 玉米速写课件
- 2026届河南省开封十中化学高二第一学期期中经典模拟试题含解析
- 2025年度绿色建筑认证生态办公场地租赁合同(绿色装修标准)
- 2025年残联招聘笔试大纲解读与备考指南
- 2025版厂房装修施工安全责任合同模板
- GB 16808-2025可燃气体报警控制器
- 医疗机构重点部门感染预防与控制标准WST860-2025解读宣贯
- 气体灭火系统日常维护管理手册
- 2025年汽车后市场行业当前市场规模及未来五到十年发展趋势报告
- 德育副校长工作总结课件
- 业财一体化课件
- 退伍留疆考试题库及答案
- 2025年安管人员继续教育试题及答案
- 超声弹性成像技术规范
评论
0/150
提交评论