实现简单的 Linux 内核模块

2018年7月26日 7.17k 次阅读 0 条评论 1 人点赞

Linux 内核包含了许多的驱动,这些驱动都是由一个个模块组合而成,那么怎么编写一个简单的内核驱动模块?

模块源码框架

内核的模块需要按照一定的框架进行搭建,通常情况下都需要模块入口和模块出口1。我们来演示一下怎么编写一个 Hello World 的模块。

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

static int __init hello_init(void)
{
        int i;

        for(i = 0; i < 10; i++ ) {
            printk(KERN_ALERT "Hey, How are you. digit %d\n", i);
        }

        return 0;
}

static void __exit hello_exit(void)
{
        printk(KERN_ALERT "Bye man, I have been unload.\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("Jackie Liu <liuyun01@kylinos.cn>");
MODULE_LICENSE("GPL");

首先需要定义一个 hello_init 的函数,然后通过 module_init 注册到内核中,同理的 hello_exit 也一样。MODULE_AUTHOR 并不是必须的,他仅仅告知使用者本模块的作者是谁,这个虽然对模块使用不产生什么影响,但是使用者尊重作者版权的态度还是要有的。MODULE_LICENSE 也并不是必须的,但是如果不显示声明模块采用 GPL 协议,那么很多的以 EXPORT_SYMBOL_GPL 导出的函数是无法使用的,通常情况下,建议大家都采用 GPL 授权模式,毕竟内核整个框架都是采用的 GPL 授权方案,如果你不懂什么是 GPL,那么请点击这里

好了,一个简单的模块已经编写完毕,无论多了复杂的驱动,他总是需要一个入口和一个出口,就像一个再复杂的 C 语言程序都需要一个 main 入口一样。

Makefile 的编写

尽管我们按照套路完成了驱动的编写,都这还不够,正确的书写配套的 Makefile 也是成功的关键,内核驱动模块的 Makefile 与其他核外的进程程序不同,他编译出来的对象并不是二进制可执行文件2

obj-m   := hello.o
DIRS    := /lib/modules/`uname -r`/build
all:
        make -C $(DIRS) M=$(PWD) modules
clean:
        rm -Rf*.o *.ko *.mod.c *.order *.symvers

编译、加载与卸载模块

准备好 Makefile 与 hello.c 源码文件,那么接下来可以演示如何加载与卸载模块

jackieliu@jackieliu-virtual-machine:~/modules$ make 
make -C /lib/modules/`uname -r`/build M=/home/jackieliu/modules modules
make[1]: Entering directory '/usr/src/linux-headers-4.11.0-14-generic'
  CC [M]  /home/jackieliu/modules/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/jackieliu/modules/hello.mod.o
  LD [M]  /home/jackieliu/modules/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.11.0-14-generic'

编译阶段需要保证没有报错,并且正确的生成 hello.ko。然后使用 root 权限来加载这个模块并打印内核输出信息。

jackieliu@jackieliu-virtual-machine:~/modules$ sudo insmod hello.ko
jackieliu@jackieliu-virtual-machine:~/modules$ dmesg
[ 1760.276063] Hey, How are you. digit 0
[ 1760.276068] Hey, How are you. digit 1
[ 1760.276069] Hey, How are you. digit 2
[ 1760.276070] Hey, How are you. digit 3
[ 1760.276071] Hey, How are you. digit 4
[ 1760.276072] Hey, How are you. digit 5
[ 1760.276073] Hey, How are you. digit 6
[ 1760.276074] Hey, How are you. digit 7
[ 1760.276075] Hey, How are you. digit 8
[ 1760.276075] Hey, How are you. digit 9

dmesg 信息将模块的 hello_init 中需要打印的信息展现出来,表示模块已经正确的加载。然后通过 rmmod 来卸载模块并打印信息。

jackieliu@jackieliu-virtual-machine:~/modules$ sudo rmmod hello
jackieliu@jackieliu-virtual-machine:~/modules$ dmesg
[ 1880.180709] Bye man, I have been unload.

模块正确卸载,并打印信息。至此,一个简单的模块已经介绍完毕,食用愉快。


  1. 模块出口并不是一定需要,也可以不写,那么这样的模块在正常模式下是无法卸载的。 ↩︎
  2. 这个说法其实页不完全对,可执行文件的组成方式是 ELF 格式,内核模块的文件组成格式也是 ELF 格式。 ↩︎
标签:
最后编辑:2020年12月30日