微内核和宏内核
微内核
内核中只有最基本的调度、内存管理。其他的比如驱动、文件系统等都是用户态的守护进程去实现的。比如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> #include <linux/init.h>
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 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
|
这样就创建了一个虚拟的字符型驱动设备。
本文引用的文章: