Linux内核模块编程 HelloWorld

微内核和宏内核

微内核

内核中只有最基本的调度、内存管理。其他的比如驱动、文件系统等都是用户态的守护进程去实现的。比如Windows NT、OS X

优点是超级稳定,驱动等的错误只会导致相应进程死掉,不会导致整个系统都崩溃,做驱动开发时,发现错误,只需要kill掉进程,修正后重启进程就行了,比较方便。

缺点是效率低。典型代表QNX,QNX的文件系统是跑在用户态的进程,称为resmgr的东西,是订阅发布机制,文件系统的错误只会导致这个守护进程挂掉。

宏内核

简单来说,就是把很多东西都集成进内核,例如Linux内核,除了最基本的进程、线程管理、内存管理外,文件系统,驱动,网络协议等等都在内核里面。优点是效率高。缺点是稳定性差,开发过程中的bug经常会导致整个系统挂掉。做驱动开发的应该经常有按电源键强行关机的经历。


内核模块

由于 Linux 内核是宏内核,集成性比较高,随着内核版本的迭代,内核变得非常大(Linux内核约50M),我们想定制自己的内核时,需要整个重新编译,比较繁琐。而且,定制内核时,有些功能我们是不需要的。

因此 Linux 内核采用了模块化的方式。当我们编译内核时,可以只选择我们需要的模块。而且,我们自己编写的模块,可以采用安装/卸载的方式,集成到内核里。

内核模块的优点是:开发效率更高,而且可以在内核运行时动态加载。由于Linux内核模块是动态加载的,所以它也叫可加载内核模块(Loadable Kernel Module, LKM)。Linux内核镜像位于/boot目录下,启动时最先加载,LKM总是在内核启动之后加载。

LKM主要用于:设备驱动、文件系统驱动和系统调用。

  • 注意:LKM是内核空间程序,不是用户空间程序,你可以把它看成是内核的一部分。也就是LKM没有任何保护,一不小心可能就会导致系统崩溃。

编译LKM

安装C编译器和Linux内核头文件

1
sudo apt-get install build-essential linux-headers-$(uname -r)

Hello World内核模块代码(hello.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <linux/module.h>     /* 模块头文件,必不可少 */
#include <linux/kernel.h> /* KERN_INFO在这里 */
#include <linux/init.h> /* 使用的宏 */

// LICENSE
MODULE_LICENSE("GPL");

// 作者
MODULE_AUTHOR("blog.topspeedsnail.com");

// 描述
MODULE_DESCRIPTION("hello world");

// 模块版本
MODULE_VERSION("3.14");

static int __init hello_start(void)
{
printk(KERN_INFO "Hello World\n");
return 0;
}

static void __exit hello_end(void)
{
printk(KERN_INFO "Go to Hell\n");
}

module_init(hello_start);
module_exit(hello_end);
  • module_init 定义了模块的入口函数,在模块加载 insmoded (插入模块)时执行
  • module_exit 定义了模块的退出函数,在模块卸载 rmmoded (移除模块)时执行

创建Makefile

1
2
3
4
5
6
obj-m = hello.o
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
# make命令前是tab,不是空格

编译

make命令

1
make

输出

1
2
3
4
5
6
7
8
9
jerrysheh@ubuntu:~/Desktop$ make
make -C /lib/modules/4.13.0-31-generic/build/ M=/home/jerrysheh/Desktop modules
make[1]: Entering directory '/usr/src/linux-headers-4.13.0-31-generic'
CC [M] /home/jerrysheh/Desktop/helloworld.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/jerrysheh/Desktop/helloworld.mod.o
LD [M] /home/jerrysheh/Desktop/helloworld.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.13.0-31-generic'

查看

使用modinfo hello.ko来查看模块信息

使用sudo insmod hello.ko来加载模块到内核

使用lsmod来查看已加载的内核(结合管道lsmod | grep hello

使用sudo rmmod hello来卸载模块

输出

使用tail /var/log/kern.log来查看模块的输出

1
2
3
4
5
6
7
8
9
10
11
jerrysheh@ubuntu:~/Desktop$ tail /var/log/kern.log
Mar 7 12:53:59 ubuntu NetworkManager[863]: <info> [1520398439.9770] gateway 192.168.224.2
Mar 7 12:53:59 ubuntu NetworkManager[863]: <info> [1520398439.9770] server identifier 192.168.224.254
Mar 7 12:53:59 ubuntu NetworkManager[863]: <info> [1520398439.9770] lease time 1800
Mar 7 12:53:59 ubuntu NetworkManager[863]: <info> [1520398439.9770] nameserver '192.168.224.2'
Mar 7 12:53:59 ubuntu NetworkManager[863]: <info> [1520398439.9770] domain name 'localdomain'
Mar 7 12:53:59 ubuntu NetworkManager[863]: <info> [1520398439.9770] wins '192.168.224.2'
Mar 7 12:53:59 ubuntu NetworkManager[863]: <info> [1520398439.9771] dhcp4 (ens33): state changed bound -> bound
Mar 7 12:56:41 ubuntu kernel: [ 1170.100050] helloworld: loading out-of-tree module taints kernel.
Mar 7 12:56:41 ubuntu kernel: [ 1170.100085] helloworld: module verification failed: signature and/or required key missing - tainting kernel
Mar 7 12:56:41 ubuntu kernel: [ 1170.100760] Hello World

看到最后一行输出了 Hello World,正是我们编写的 helloworld.ko 模块输出的。

可以使用tail -f /var/log/kern.lo来动态监控内核的输出

作为字符型驱动

在 insmod 的时候把设备的主设备号打印出来。

如果要把内核模块作为字符型驱动设备,首先

1
tail -f /var/log/messgaes

查看设备的主设备号,比如是 254

然后创建目录和节点

1
2
mkdir /dev/demo
mknod /dev/demo/newdev c 254 0

这样就创建了一个虚拟的字符型驱动设备。


本文引用的文章: