Linux的内核非常庞大,包含了很多的组件,我们通常有两种办法在内核中加入我们需要的功能。
第一种:把所有需要的功能都编译到内核中
这种方法对导致两个问题:一是生成的内核会很大。二是如果我们要在现有的内核中新增或删除功能,不得不重新编译内核,效率很低。而且如果我们编写的内核不完善的话很有可能会造成内核崩溃。
第二种:把需要的功能编译成单独的模块
Linux提供了模块这种机制。是我们可以实现编译出的内核本身并不包含所有功能,而在需要这些功能时,功能对应的代码可以被动态的加载到内核中。
注意:模块并不是驱动的必要形式,即:模块不一定必须是模块,有些驱动是直接编译进内核的;同时模块也不全是驱动,例如:我们写的一些很小的算法也可以作为模块加载进内核,但它并不是驱动。
一个简单的模块例子:
#include <linux/module.h>
#include <linux/kernel.h>
int hello_init(void)
{
printk("hello_init()\n");
return 0;
}
void hello_exit()
{
printk("hello_exit()\n");
return;
}
module_init(hello_init);
module_exit(hello_exit);
编译上述模块需要的makefile:
ifneq ($(KERNELRELEASE),)
$(info "2nd")
obj-m:=hello.o
else
KDIR := /lib/modules/$(shell uname -r)/build //内核路径,根据实际情况转换成自己的内核路径
PWD:= $(shell pwd)
all:
$(info "1st")
make -C $(KDIR) M=$(PWD) modules //-C表示进入KDIR表示的目录执行makefile,M= 表示返回PWD目录再执行makefile
clean:
rm -r *.ko *.o *.mod.c *.order *.symvers
endif
最终会编译得到hello.ko文件,使用insmod xxx.ko把模块插入内核,然后使用dmesg即可查看输出提示信息,即我们printk打印出的信息。
我们可以用_init和_exit来修饰函数,这是系统提供的两种宏,表示所修饰的函数在调用完成后会自动回收内存,即内核认为这种函数只会被执行一次,然后他所占用的资源就会被释放。