第一个内核模块:hello world

内核是个大箩筐,什么都可以往里装。Linux内核因为是宏内核,硬件驱动、网络协议、文件系统都放到内核空间执行,导致内核的体积越来越大。好就好在,Linux内核通过模块化设计,支持模块的配置和裁剪,甚至在内核运行阶段,也可以动态加载和卸载一个模块的运行。

接下来我们编写一个最简单的hello world内核模块,在ARM开发板下运行:

#include <linux/init.h>
#include <linux/module.h>

static int __init hello_init(void)
{
    printk("Hello world\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk("Goodbye world\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wit@zhaixue.cc");

内核模块和应用程序的最大区别是入口函数,内核模块的入口函数不再是main()函数,而是通过module_init指定,当一个内核被加载到内核运行时,首先要执行由module_init指定的hello_init。内核的打印使用 printk() 函数,使用格式和应用层的 printf() 函数一样。当一个模块卸载时,会运行由module_exit指定的函数hello_exit,一般在这个exit函数,会释放申请的各种系统资源,最后退出,整个模块从内核中卸载。

内核是GNU开源工程的一部分,遵循GPL协议,使用MODULE_LICENSE可以声明你编写的这段代码使用设什么协议发布,一般声明GPL就可以了,不声明的话,内核可能不会接受,运行时的打印可能打不出来。MODULE_AUTHOR声明代码作者,一般可以留个邮箱或者名字,对这块代码感兴趣的人可以通过该联系方式找到你。

因为我们在内核模块中使用了module_init、module_exit函数,按照C语言先声明后使用的传统,所以要包含module.h头文件,这个头文件位于include/linux目录下,include是路径起点,所以要给头文件价格前缀:linux/module.h。init.h头文件主要定义了__init、__exit宏,用于将这个内核模块在编译时放到指定的section中,统一管理。

对应的Makefile:c

ifneq ($(KERNELRELEASE),)
obj-m := hello.o

else
EXTRA_CFLAGS += -DDEBUG 
KDIR := /home/linux-5.10-rc3
ARCH_ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-

all:
        make  $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules
clean:
        make  $(ARCH_ARGS) -C $(KDIR) M=$(PWD) modules clean
endif

Makefile中,obj-m表示要将指定的目标文件hello.o编译为内核模块hello.ko,KDIR表示模块编译要依赖的内核源码路径。我们的内核模块虽然在内核源码目录外编译,但也有可能需要引用内核中的一些接口函数,因此需要使用KDIR指定内核源码位置,否则在编译时遇到引用的内核符号时就会报错。

编译上面的内核模块,并拷贝到ARM虚拟开发板的/home/nfs共享文件系统目录下:

root@ubuntu:/home/workplace# make
make  ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -C /home/linux-5.10-rc3 M=/home/workplace modules
make[1]: Entering directory '/home/linux-5.10-rc3'
  CC [M]  /home/workplace/hello.o
  MODPOST /home/workplace/Module.symvers
  CC [M]  /home/workplace/hello.mod.o
  LD [M]  /home/workplace/hello.ko
make[1]: Leaving directory '/home/linux-5.10-rc3'
root@ubuntu:/home/workplace# cp hello.ko /home/nfs/

然后在开发板的控制台下就可以直接使用insmod命令加载hello.ko到内核执行了:

[root@vexpress ]# insmod hello.ko 
[ 1761.645729] Hello world
[root@vexpress ]# rmmod hello.ko 
[ 1769.171359] Goodbye world
[root@vexpress ]#

若在编译过程中,出现编译错误,信息提示缺少:Module.sysvers文件,需要先生成这个Module.sysvers文件,然后才能在Linux内核源码外,编译out-of-tree 内核模块。可以通过以下的make modules命令,来生成这个Module.sysvers文件:

# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules
驱动开发核心理论,Linux内核开发入门实战视频教程:《Linux内核编程》,具有一线芯片原厂开发经验的驱动工程师录制,详情点击:王利涛老师个人淘宝店:Linux内核编程