http://blog.csdn.net/w4and0er96/article/details/60603152
分类:
版权声明:本文为博主原创文章,未经博主允许不得转载。
- 我的环境时 Linux 4.10.0+, Ubuntu 16.04 LTS, Mac OS X 10.12.2, VMware Fusion 8
- 在这个 project 里, 我们要做的是编写一个 linux 内核模块, 并把它加载到内核里面去.
- 内核模块的开发过程中, 我们会直接和内核函数打交道, 要提醒自己现在正在编写的是内核代码, 一不小心就可能造成系统崩溃, 这里建议初次尝试时使用虚拟机, 这样即使系统崩溃也可以通过快照的技术快速恢复
Part 1. 创建一个内核模块
- 在 Linux 下, 我们可以通过lsmod
命令来查看当前已经加载的内核模块
- 接下来我们在任意目录(例如: ~/workspace)下创建 simple.c 文件, 文件内容如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 函数
simple_init
是模块的入口(module entry point), 当模块被加载进内核时会被调用; 类似的, 函数simple_exit
是模块的出口(module exit point), 当模块从内核中被移除时会被调用. - 模块的入口函数必须返回一个整型值(integer value), 0代表加载成功, 其他的值代表加载失败; 模块的出口函数必须返回 void. 以上两个函数都不应该传递任何参数.
- 下面两个宏用来向内核把函数注册为模块的入口函数和出口函数:
module_init()
module_exit()
- 注意到这两个函数都调用了
printk()
函数, 可以把这个函数看作内核版的printf()
(因为在内核编程中默认是没有printf()
函数的). 它的输出定向到内核的日志缓冲区里(kernel log buffer). 这个缓冲区可以使用dmesg
命令来查看. printf()
和printk()
的不同之处包括printk()
支持我们提供一个级别位(priority flag), 它的可选值定义在<linux/printk.h>
中, 在上面例子中, 我们使用的是KERN_INFO
, 它代表 informational message.- 最后的几行以 MODULE_ 开头, 显示了这个模块使用的证书(LICENSE), 模块的描述(DESCRIPTION)和作者(AUTHOR). 这些信息对于模块本身并没有什么作用, 但它是内核模块开发的标准要求之一.
- 以下是编译这个内核模块源文件所需要的 Makefile:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
- 这里提醒第3行和第5行前面都空白都应该是一个 TAB, 此外我多次在网上复制 Makefile 的时候出现
-
这个符号格式不对的情况, 这个 Makefile 建议手写. - 我们在把源文件 simple.c 和 Makefile 放到同一个目录下, 在命令行下进入该目录, 输入
make
指令, 以完成编译. 在我的环境中, 编译结束后当前目录应该有以下文件:
- 1
- 2
- 1
- 2
Part 2. 加载和移除内核模块
- 接着上一步, 确认我们在当前目录下看到了 simple.ko 这个文件. 我们可以通过下面这个指令简单的把刚刚写好的模块加载到内核中sudo insmod simple.ko
- 现在, 可以来确认我们的成果了!
- 首先我们输入lsmod
命令, 在输出的文本中找到我们刚刚加载进去的 simple 模块. 一般而言它会出现在 title 信息后的第一行. 在我的环境中此时执行lsmod
命令, 其前几行输出为:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
- 可以看到 simple 模块已经加载成功.
- 此外我们还可以通过
dmesg
命令来查看内核日志缓冲区, 我们期望看到加载 simple 模块时应当写进日志的信息 “Loading Module.” 下面是我的输出的最后几行:
- 1
- 2
- 3
- 1
- 2
- 3
- 看到 module verification failed 的提示信息不用怕, 这不代表我们刚才的步骤有哪一步失败了, 只是表示当前内核在编译的时候选择支持内核签名机制, 而我们目前这个简单的模块并没有处理它的要求而已.
- 好了, 现在我们把这个演示用模块从内核中移除吧, 这需要我们使用
sudo rmmod simple
这个命令, 注意参数是<module name>
的形式, 不像insmod
命令的参数是 .ko 文件. - 再调用一次
dmesg
, 在输出文本的末尾应该可以看到:
- 1
- 2
- 1
- 2
- 表示模块已经被移除(模块出口函数被调用).
- 使用
sudo dmesg -c
命令可以清空内核日志缓冲区.