模块机制
模块是内核的一部分(通常是设备驱动程序),按需动态装入模块可以保证内核达到最小并且使内核非常灵活。一旦装入一个Linux内核模块,那么它就像任何标准的内核代码一样成为内核的一部分,具有相同的权限和职责。一方面凡是由内核“移出”的所有符号都可以在模块中引用;另一方面,除了这个特意移出的符号及系统调用外,应用程序别无途径直接访问内核中的资源。
在应用程序界面上,内核通过4个系统调用支持可安装模块的动态安装和拆卸,它们是create_module(),init_module(),query_module(),以及delete_module()。通常,用户不需要直接跟这些系统调用大交道,而可以用系统提供的工具/sbin/insmod(插入模块)和/sbin/rmmode(移走模块)来安装和拆卸模块。当然,这两个工具最终还是要通过这些系统调用完成有关操作的。/sbin/insmod 所调用的函数流程如图所示:
内核空间和用户空间
普通程序和内核模块的不同:对于模块的可执行代码时目标代码,缺少了一个连接过程——而insmod程序完成了这个连接过程。
1 处理器保护级
在i386处理器上实现了四层保护级别,依次是0级、1级、2级和3级,其中0级特权最高,3级特权最低。无论何时处理器总是在一个级别上运行。如果需要访问高特权级别的存储空间,只能通过由处理器规定的一些特权门完成操作。一般在0级运行的是操作系统的内核部分;在3级运行的是应用程序部分。在最低级运行的时候,操作系统禁止对硬件的直接访问和对内核运行内存空间的未授权访问。
2 用户空间和内核空间权限
对应于在0级别运行的内核程序,它所在的内存空间是内核空间;对应于在3级运行的应用程序,它所指的内存空间是用户空间。Linux通过系统调用或硬件中断完成从用户到内核空间的转换。在进程上下文中执行系统调用的内核代码,系统调用就可以访问原来进程的上下文数据。
对中断来说,它并不存在于任何进程上下文中,而是由内核来运行的。对模块来说,它就是在系统的用户空间定义的,然后通过用户空间的程序insmod将它插入到内核空间中运行,可以像真正的内核一样充分利用0级特权的功能执行程序。从图可以看出,insmod通过create_module()和init_module()两个系统调用将一个模块运行的空间从用户级切换到内核级。rmmod通过delete_module()系统调用将一个模块从内核空间中移出。
3 内核态和用户态之间数据传递
在内核空间与用户空间存在数据的传递,下面的函数与宏定义在内核中对这种传递进行操作。
函数copy_to_user从内核空间把数据拷贝到用户空间,拷贝成功返回0,否则,返回不能被拷贝的字节数。其参数to表示在用户空间的目标地址,参数from是在内核空间的源地址,参数n是需要拷贝数据的长度。函数原型如下:
unsigned long copy_to_user(void __user *to,const void *from,unsigned long n);
函数copy_from_user从用户空间拷贝一块数据,参数to表示在内核空间的目标地址,参数from是在用户空间的源地址,参数n是需要拷贝数据的长度。拷贝成功返回0,否则,返回不能被拷贝的字节数。如果有些数据没拷贝,函数copy_from_user将填充0到请求的长度n,函数copy_from_user原型如下:
unsigned long copy_from_user(void *to,const void __user *from,unsigned long n);
模块的使用过程
模块的使用过程如下:
打开待安装模块并将其读入到用户空间,所谓“模块”就是经过编译但未经连接的.o文件。
模块中有一些在模块内部无法落实的符号(函数名或变量名),这些符号的引用必须连接到内核中的相应符号,也就是必须把这些符号在内核映像中的地址填入模块中需要访问这些符号的指令,以及数据结构中。为此,需要通过系统调用query_module()函数向内核询问这些符号在内核中的地址。如果内核允许“移出”这些符号的地址,就会返回有关的“符号表”。有些符号可能并不属于内核本身,而属于已经安装的其他模块。
取得了内核“移出”的符号表“以后,模块中所有的符号引用就得到落实。这部分操作与编译后的连接相似。不过,常规的”连接“常常是双向的,而现在只是在模块中引用内核里的符号或者某些已经安装的模块中的符号,而内核不能反过来引用这个待安装模块中的符号。从这个意义上说,模块与内核的连接时单向连接。
通过系统调用sys_init_module(),把用户空间中模块目标文件解析后装入内核空间,建立在sysfs文件系统中的模块对象,再调用模块中一个名为init_module()的函数。每个模块的init_module()函数负责向内核”登记”本模块的一些包含着函数指针的数据结构。完成这种登记后,模块与内核之间的连接(另一个方向上的连接)才算真正完成。