linux内核系列(1)——内核模块儿的编写

2019-04-14 11:59发布

linux内核有一个很强大的功能就是可以动态加载模块儿,这里说的模块儿其实就是一个内核支持的小程序。在真正进入linux内核世界之前,我们可以先过一把瘾,自己加载运行一个内核模块儿。 既然模块儿就是程序,我们就先来写一个小程序,这里还是拿最简单的“Hello World”程序来举例子。我们先上代码,然后再讲解。 #include #include #include MODULE_LICENSE("GPL"); static int __init lkp_init(void) { printk("<1>Hello World!...from the kernel! "); } static int __exit lkp_exit(void) { printk("<1>Goodbye World!...from the kernel! "); } module_init(lkp_init); module_exit(lkp_exit);首先是三个头文件,和以前看到的头文件不同的是它们都是linux目录下的,那么这个目录在哪儿呢?在ubuntu的系统中,它被放在/usr/src/linux-headers-x.x.x.-xx/include/linux/这个目录下,有兴趣的小伙伴可以去看一下,这里只做个简单的介绍:module.h里放的是对模块儿的版本控制信息,后面的宏定义MODULE_LICENSE就是在这里面定义的;kernel.h里放的是常用的内核函数,比如这里的printk;init.h里放的是宏__init(注意这里是两个下划线)、__exit和module_init、module_exit。 下面的宏定义MODULE_LICENSE("GPL");是告诉内核,我们编写的这这个模块儿是符合GNU通用公共许可证的,简单来说就是合法的。没有这句系统会给你你一个警告。 再下面是两个函数,static和int属于基本知识,就不多说了,后面的__init和__exit是两个宏定义,告诉系统你写的程序比较特殊,可以特殊处理一下。这主要是为了一些性能优化,其实不特殊处理也没关系。这两个函数都定义了一个printk函数,它来自内核,和printf用法类似,不过它会把内容输出到一个特定的文件内,而非标准输出,这个我们一会儿会看到。 最后也是两个宏定义,是这段代码最关键的地方。第一个宏定义可以被看做我们的小模块儿的入口,其实就是告诉系统模块执行时要先执行lkp_init这个函数,而另一个类似,代表模块的出口。它们的内部其实是由回调函数完成的,这里不详细讲了。 程序写好以后,我们可以给它起个名字保存起来,这里我们叫它hello.c。 程序有了,我们还需要一个指导编译的规则,就是Makefile文件,同样的,我们还是先来看代码。 obj_m:=hello.o CURRENT_PATH := $(shell pwd) LINUX_KERNEL := $(shell uname -r) LINUX_KERNEL_PAHT := /usr/src/linux-headers-(LINUX_KERNEL) all: make -C $(LINUX_KERNEL_PAHT) M=$(CURRENT_PATH) modules clean: make -C $(LINUX_KERNEL_PAHT) M=$(CURRENT_PATH) cleanmakefile的语法有很多教程,这里也不多说,关键说一下-C与M=的作用,这两点在编译内核中非常常见:当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。还有最后面的modules和clean为固定项,不能改变。 写完Makefile保存,然后执行make命令,可以生成很多个文件,其中一个后缀名为.ko的文件是我们需要插入内核的,在将其插入之前,我们先来看一下当前内核中已有的模块:执行命令# lsmod可以看到当前系统中的内核模块。然后执行命令# insmod hello.ko就可以把我们的模块插进入了,再执行一下# lsmod可以看到我们的模块已经插入了(一般第一个就是刚插入的)。这时候我们可以用命令#dmesg 来查看刚才插入模块的输出(一般是在最后面)。好了,这说明我们的模块儿已经可以运行了。当然,既然是随时可插拔的,也可以把它卸载掉。执行命令# rmmod hello就可以把它卸载了,再# dmesg看一下,可以看到程序退出时输出的那句话。 以上,我们已经完成了内核模块的编写和加载。