对Linux内核而言, 模块是可有可无的特色。 这项特色必得在编译内核时,选择适当的选项才得以成功。 就我所知道的, 所有发行系統的内核,都是以模块化定为预设选项的。
我们甚至于可以替系统设计新的模块, 并且有内核载入执行,而不用重新编译内核, 也不用重新启动系统。
当一模块载入内核时, 便成了内核的一部份, 因此:
这个模块可以使用内核中所有的函數,而且可以存取所有的参数和数据结构。
这个模块会以处理器最高层次的档位来执行。 就 i386 的体系结构来说,就是第 0层的环 (ring level 0); 这么一来, 这个模块就可以针对 I/O,做各式各样的存取动作, 并执行一般程序所无法执行的指令。
模块的代码段和数据段, 都是直接映射到物理内存上去的,也就是说这种模块不可能做"分页 (paging)" 的动作。这么说来, 在模块执行期间, 就不可能会产生分页错误的问题了。
如同我们所看到的, 动态载入模块已经有一些实时系统的特点:动态载入模块, 会避免由分页错误所造成的时间延迟,而且动态载入模块, 可以存取所有的硬件资源。
模块可以用 "C语言" 来写。 这里举个例子(要执行下面所提到的命令,最好是以 su、root 的身份登陆系统):
example1.c
#define MODULE
#include <linux/module.h>
#include <linux/cons.h>
static int output=1;
int init_module(void) {
printk("Output= %dn",output);
return 0;
}
void cleanup_module(void){
printk("Adi, Bye, Chao, Ovuar, n");
}
以下一行的参数来编译 example1.c:
# gcc -I /usr/src/linux/include/linux -O2 -Wall -D__KERNEL__ -c example1.c
选项 -c 是要求 gcc 在产生目标文件之后就停下來,不要再做连接的动作了。 最后的結果是一个目标文件, example1.o。
内核缺少标准输出函数, 因此我们不可以用 printf() 这个函数,而要以内核所提供的 printk()函数来代替。 printk() 和 printf() 几乎没有什么两样, 唯一的差別是 printk() 会把输出的结果,送到内核的环缓冲区 (ring buffer)里面。这个缓冲区是系统所有信息集中的地方, 就像开机时所看到的信息,都可以在这个环缓冲区找到。 任何时候, 我们都可以用 dmseg 命令查看环缓冲区的內容, 或是直接检验 /proc/kmsg 这个文件。
注意到这个模块里并没有main() 函数, 反而有个不带任何参数的 init_module()函数。 cleanup_module() 是最后一个释放模块前,所必须呼叫的函数。 insmod 是用来载入模块,然后执行模块的。
# insmod example1.o
现在我们已经安装好 example1 模块了, 而且也执行了 example1 的 init_module()函数。 要看结果, 请用下列的命令:
# dmesg | tail -1
Output= 1
命令 lsmod 会列出当前所有载入核心中的模块:
# lsmod
Module Pages Used by:
example1 1 0
sb 6 1
uart401 2 [sb] 1
sound 16 [sb uart401] 0 (autoclean)
最后呢,我们用 rmmod 来释放模块:
# rmmod example1
# dmesg | tail -2
Output= 1
Adi, Bye, Chao, Orvua,
dmesg 显示了函数 cleanup_module() 已经执行了。
现在我们只差还不知道怎么把参数传递给模块了。 这个方法出奇的简单,只要指定数值給参数, 再由 insmod 把参数传递给模块就行了。例如:
# insmod ejemplo1.o output=4
# dmesg | tail -3
Output= 1
Ads, Bye, Chao, Orvua,
Output= 4
现在模块大大小小的事情该知道的都知道了,还愣着干什么,赶紧学着写一个啊!