1. 导言
Linux体系开放源代码、体系漏洞少,在面临病毒和黑客侵略时能供给更好的安全性和安稳性,根据以上这些长处,近年来对Linux操作体系及其相关技能的运用和研讨越来越多。对Linux操作体系扩大或裁剪功用需求在从头编译内核上花费很多的时刻。LKM机制由于大大缩短了开发和测验的时刻,在 Linux开发、研讨的过程中起到了无足轻重的效果。
LKM首要包含内核模块在操作体系中的加载和卸载两部分功用,内核模块是一些在发动的操作体系内核需求时能够载入内核履行的代码块,不需求时由操作体系卸载。它们扩展了操作体系内核功用却不需求从头编译内核、发动体系。假如没有内核模块,就不得不重复编译生成操作体系的内核镜像来参加新功用,当附加的功用很多时,还会使内核变得臃肿。
2. LKM的编写和编译
2.1 内核模块的根本结构
一个内核模块至少包含两个函数,模块被加载时履行的初始化函数init_module()和模块
被卸载时履行的完毕函数cleanup_module()。在最新内核安稳版别2.6中,两个函数能够起
恣意的姓名,通过宏module_init()和module_exit()完结。仅有需求留意的当地是函数有必要在宏的运用前界说。例如:
static int __init hello_init(void){}
staTIc void __exit hello_exit(void ){}
module_init(hello_init);
module_exit(hello_exit);
这儿声明函数为staTIc的意图是使函数在文件以外不行见,__init的效果是在完结初始化后收回该函数占用的内存,宏__exit用于模块被编译进内核时疏忽完毕函数。这两个宏只针对模块被编译进内核的状况,而对动态加载模块是无效的。这是由于编译进内核的模块是没有整理收尾作业的,而动态加载模块却需求自己完结这些作业。
2.2 内核模块的编译
编译时需求供给一个makefile来躲藏底层很多的杂乱操作,运用户通过make指令就能够完结编译的使命。下面便是一个简略的编译hello.c的makefile文件:
obj-m += hello.ko
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
编译后取得可加载的模块文件hello.ko。
内核版别2.6中运用.ko文件后缀替代了.o,这是为了与一般可履行文件相差异。
3. LKM的首要功用
3.1 模块的加载
模块的加载有两种办法,榜首种是运用insmod指令加载,另一种是当内核发现需求加载某个模块时,恳求内核后台进程kmod加载恰当的模块。当内核需求加载模块时,kmod被唤醒并履行modprobe,一起传递需加载模块的姓名作为参数。modprobe像insmod相同将模块加载进内核,不同的是在模块被加载时检查它是否涉及到当时没有界说在内核中的任何符号。假如有,在当时模块途径的其他模块中查找。假如找到,它们也会被加载到内核中。但在这种状况下运用insmod,会以“未解析符号”信息完毕。
关于模块加载,能够用图3.1来扼要描绘:
insmod程序有必要找到要求加载的内核模块,这些内核模块是已链接的方针文件,与其他文件不同的是,它们被链接成可重定位映象即映象没有被链接到特定地址上。insmod将履行一个特权级体系调用来查找内核的输出符号,这些符号都以符号名和数值方法如地址值成对保存。内核输出符号表被保存在内核维护的模块链表的榜首个module结构中。只需特殊符号才被增加,它们在内核编译与链接时确认。insmod将模块读入虚拟内存并通过运用内核输出符号来修正其未解析的内核函数和资源的引证地址。这些作业采纳由insmod程序直接将符号的地址写入模块中相应地址来进行。
当insmod修正完模块对内核输出符号的引证后,它将再次运用特权级体系调用恳求满足的空间容纳新模块。内核将为其分配一个新的module结构以及满足的内核内存来保存新模块,并将其刺进到内核模块链表的尾部,最终将新模块标志为UNINITIALIZED。insmod将模块复制到已分配空间中,假如为它分配的内核内存已用完,将再次恳求,但模块被屡次加载必定处于不同的地址。别的此重定位作业包含运用恰当地址来修正模块映象。假如新模块也期望将其符号输出到体系中,insmod将为其结构输出符号映象表。每个内核模块有必要包含模块初始化和完毕函数,所以为了防止抵触它们的符号被规划成不输出,可是insmod有必要知道这些地址,这样能够将它们传递给内核。在一切这些作业完结今后,insmod将调用初始化代码并履行一个特权级体系调用将模块的初始化和完毕函数地址传递给内核。当将一个新模块加载到内核中时,内核有必要更新其符号表并修正那些被新模块运用的老模块。那些依靠于其他模块的模块有必要在其符号表尾部维护一个引证链表并在其module数据结构中指向它。内核调用模块的初始化函数,假如成功将装置此模块。模块的完毕函数地址被存储在其module结构中,将在模块卸载时由内核调用,模块的状况最终被设置成RUNNING。
3.2 模块的卸载
模块能够运用rmmod指令删去,可是恳求加载模块在其运用计数为0时,主动被体系删去。kmod在其每次idle定时器到期时都履行一个体系调用,将体系中一切不再运用的恳求加载模块删去。
关于模块卸载,能够用图3.2来描绘:
内核中其他部分还在运用的模块不能被卸载。例如体系中装置了多个VFAT文件体系则不能卸载VFAT模块。履行lsmod将看到每个模块的引证计数。模块的引证计数被保存在其映象的榜首个常字中,这个字还包含autoclean和visited标志。假如模块被符号成autoclean,则内核知道此模块能够主动卸载。visited标志表明此模块正被一个或多个文件体系部分运用,只需有其他部分运用此模块则这个标志被置位。每次体系要将没有被运用的恳求加载模块删去时,内核将在一切模块中扫描,可是一般只检查那些被标志为autoclean并处于running状况的模块。假如某模块的 visited符号被铲除则它将被删去。其他依靠于它的模块将修正各自的引证域,表明它们间的依靠联系不复存在。此模块占有的内核内存将被收回。
4. LKM的运用
零复制根本思想是:数据分组从网络设备到用户程序空间传递的过程中,削减数据复制次数,削减体系调用,完结CPU的零参加,彻底消除CPU在这方面的负载。零复制的完结分为完结DMA数据传输和地址映射两个部分。其间DMA数据传输与本文联系不大,就不具体叙说了,这儿首要介绍运用LKM机制完结的地址映射。
地址映射的根本原理是在内核空间恳求内存,通过proc文件体系和mmap函数将其映射到用户空间来答应运用程序拜访,这样就消除了内核空间到运用程序空间的数据复制。地址映射部分的完结首要分为以下三步:
榜首,树立LKM的根本结构,包含编写初始化和完毕函数等。
第二,声明完结映射功用所需求的函数,首要有分配和初始化内核内存函数init_mem(),开释内核内存函数del_mem(),向内核内存输入内容的函数put_mem()等。
第三,在初始化函数中运用第二步树立的函数分配一块内存空间、输入内容、树立proc文件体系进口。在完毕函数中开释已分配的内核内存,删去proc文件体系进口。
编写运用程序测验该LKM,发现现已到达了映射内核内存到运用程序空间的意图。在完结零复制的过程中选用LKM机制不光便于调试并且大大削减了开发时刻。
5. LKM与一般运用程序的比较
LKM与一般运用程序之间的差异首要体现在四个方面。
榜首,也是最重要的差异,一般运用程序工作在用户空间,而LKM工作在内核空间。通过区别不同的工作空间,操作体系能够安全地维护操作体系中一些重要数据结构的内容不被一般运用程序所修正,到达确保操作体系正常工作的意图。
第二,一般运用程序的方针很清晰,它们自始至终都是为了完结某一项特定使命。而LKM是在内核中注册并为后续运用程序的恳求供给服务的。
第三,一般运用程序能够调用并没有在其间界说的函数,但一个LKM是链接到内核上的,它所能调用的函数只需内核导出来的那些函数。
第四,一般运用程序和LKM处理过错的方法不同。当运用程序中呈现过错时并不会给体系形成很大的损伤。LKM则否则,在其间呈现的过错对子体系来说通常是丧命的,至少关于当时正在工作的进程而言。LKM中的一个过错常常会导致整个体系溃散。
6. 编写LKM需求留意的问题
LKM工作在内核空间,它们具有对整个体系一切资源的拜访权限,因而,编写LKM首要要留意便是安全问题,并且还应该防止将或许导致呈现安全问题的代码带到LKM中。
LKM加载后是作为操作体系内核的一部分工作的,因而,在规划、编写操作体系内核过程中应该留意的问题在LKM中也应该引起满足的注重。在这儿,首要指的是并发问题和指针引证问题。并发是指在同一时刻有多个进程在操作体系内核中一起工作。并发结合共享资源最终会导致竞态条件,在这种状况下应该对各个并发进程拜访共享资源进行严厉的操控。假如在LKM中呈现指针引证过错,内核将没有办法将内存的虚拟地址映射到物理地址,然后导致呈现内核中的意外,如内存拜访抵触、除0以及非法操作等。
7. LKM的缺乏之处
LKM尽管在设备驱动程序的编写和扩大内核功用中扮演着十分重要的人物,但它仍有许多缺乏的当地。
榜首,LKM关于内核版别的依靠性过强,每一个LKM都是靠内核供给的函数和数据结构组织起来的。当这些内核函数和数据结构由于内核版别改变而产生变化时,原先的LKM不通过修正就或许不能正常工作。
第二,尽管现在有针对内核编程调试的东西kgdb,可是在LKM编写过程中调试仍十分费事,并且在调试过程中,体系所能供给的犯错信息极为不流畅。
立异点:针对Linux内核,使用LKM,在完结了数据的零复制(Zero-copy)的过程中,将LKM与一般运用程序进行比较,提出了LKM的优势和缺乏。