需要了解知识:
Linux
C语言
Makefile
Linux是宏内核(或单内核)的操作体系统。(Windows微内核)
宏内核是所有的内核功能被整体编译在一起,形成一个单独的内核镜像,内核中各模块的交互是通过直接的函数调用,效率非常高。
微内核是实现内核中相当关键和核心的一部分,其他功能模块被单独编译,功能模块之间的交互通过微内核提供的某种通信机制来建立的。
宏内核如果要增加、删除、修改内核的某个功能,就不得不重新编译整个内核,为了解决这一问题,引入了内核模块。内核模块是被单独编译的一段内核代码,它可以在需要的时候动态加载到内核,不需要时,动态卸载(需要内核配置了可卸载的选项),不需要重启整个系统,这种特性非常适合驱动程序开发。
#include
#include
#include
int init_module(void){
printk("module init\n");
return 0;
}
void cleanup_module(void){
printk("cleanup module\n");
}
一个模块程序几乎都要直接或间接包含上述三个模块。
init_module函数时模块的初始化函数(非必要),不接受参数,成功返回0;失败返回一个负数。
printk函数类似于printf,但支持额外的打印级别,用于调试目的。
cleanup_module函数是模块的清除函数(非必要),在模块从内核中卸载时调用。但要是提供了模块初始化函数,则清除函数是要提供的(除非内核模块不允许卸载)。
Makefile
简单的做法是把刚才的代码添加到内核源码树中,然后修改对应的Makefile文件即可,但这回修改内核的源码;另一种是将c文件放到内核源码树外一个单独的目录中,然后在该目录下编写一个对应的Makefile。
ifeq($(KERNELRELEASE),)
ifeq($(ARCH),arm)
KERNELDIR?=/home/farsight/fs4412/linux-3.14.25-fs4412
ROOTFS?=/nfs/rootfs
else
KERNELDIR?=/lib/moudle/$(shell uname -r)/build
endif
PWD:=$(shell pwd)
module:
$(MAKE) -C $(KERNELDIR)M=$(PWD)modules
modules_install:
$(MAKE) -C $(KERNELDIR)M=$(PWD)INSTALL_MOD_PATH=$(ROOTFS)modules_install
clean:
rm -rf *.0 *.ko *.cmd *.mod modules.order Module.symvers.tmp_versions
else
obj-m:=vser.o
endif
略
模块加载
insmod:加载指定目录下的一个.ko文件到内核
模块加载成功后使用dmesg命令可以看到控制台输出信息
modprobe:自动加载模块岛内和,相比insmod更智能,推荐使用。但莫要要执行安装操作,在运行该命令前最好运行一次depmod命令来更新模块的依赖信息。modeprobe不指定路径及后缀
模块信息
modinfo:查看模块信息,在安装了模块并运行depmod命令后,可以不指定路径和后缀,也可以指定查看某一特定的.ko文件的信息
模块卸载
rmod:如果内核配置允许卸载模块,那么rmmod将指定的模块从内核中卸载。
GPL许可协议略。在代码中需要添加代码表示改代码接收响应的许可协议。
MODULE_LICENSE("GPL");
MOUDLE_LICENSE是一个宏,里面的参数是一个字符串,代表相应的许可证协议。可以是GPL,GPL v2,GPL and additional rights等。这个宏将会生成一些模块信息,放在ELF文件中的一个特殊的段中,模块在加载时会将该信息复制到内存中,并检查该信息。不加这段代码。会导致内核报警告或关闭某些调试功能,某些功能函数是不能调用的,开发驱动调用内核的一些基础设施(一些内核的API函数)是必不可少的。
模块的初始化函数和清楚函数的名字是固定的,入口函数基本都为main。内核借助于GUN的函数别名机制,可以灵活的指定模块的初始化函数和清楚函数的别名。
module_init(vser_init);
module_exit(vser_exit);
module_init和module_exit是两个宏,分别用于指定init_module和函数别名vser_init和cleanup_exit的别名是vser_exit。
为了避免因为重名带来重读定义的问题,函数可以加static关键字修饰,经过static修饰后的函数的链接属性为内部。这也是几乎所有驱动程序的函数前都要加static关键字修饰的原因。
Linux是节约内存的操作系统。上面的初始化函数仅会被调用1次,之后就可以释放掉这函数占用的内存,在函数名前加__init可以达到此目的。__init是把这些标记的函数放到ELF文件的特定代码段,在模块加载这些段时将会单独分配内存,这些函数调用成功后,模块的加载程序会释放这部分内存。__exit用于修饰清除函数,类似于__init,如果模块不允许且在,那么这段代码完全就不用加载。
#include
#include
#include
static int __init vser_init(void){
printk("init\n");
return 0;
}
static void __exit vser_exit(void){
printk("exit\n");
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XXX");
MODULE_DESCRIPTION("XXXXXX");
MODULE_ALIAS("virtual-serial");
对于一个较复杂的驱动程序,将所有代码写在一个源文件不太现实,通常会把程序功能拆分,由不同的源文件来实现对应的功能。
使用make,将多个目标文件共同生成的。
模块的初始化函数在模块被加载时调用,但该函数不接受参数。模块提供了另外一种形式来支持参数传递信息,叫做模块参数。
模块参数允许用户在加载模块时通过命令行指定参数,并转换成相应类型的值,然后赋值给对应的辩论,这个过程发生在调用模块初始化函数之前。内核支持的参数类型有:bool,inbool(反转值bool)、charp(字符串指针)、short、int、long、ushort、uint、ulong这些类型又可以复合成对应的数组类型。
module_param(name,type,perm)
module_param_array(name,type,nump,perm)
name:变量的名字
type:变量或数组元素的类型
nump:数组元素个数的指针(可选)
perm:在sysfs文件系统中对应文件的权限属性,如果为0,则sysfs文件系统中不会出现任何对应文件。
虽然代码中增加模块参数的写权限可以通过sysfs文件系统来修改模块参数的值,但不推荐。因为这种方式对模块参数惊醒的修改模块本身是一无所知。
使用file命令和nm命令可以得到ELF目标文件的相关细节信息
Linux内核是由全世界的志愿者开发的,不同版本函数接口可能完全不一样。
内核模块和普通应用程序的差异: