源程序hello.c
#include <linux/init.h>//用来指定程序的初始化和清理函数
#include <linux/module.h>//包含了大量加载模块需要的函数和符号的定义
#include <linux/kernel.h>
MODULE_LICENSE("GPL");//用于指定代码使用哪个许可
MODULE_AUTHOR("Hedwig");//声明谁编写了模块
MODULE_VERSION("V1");//代码修订版本号
MODULE_DESCRIPTION("Kernel example");//关于模块做什么的声明
static int year = 2021;
static int __init hello_init(void)//初始化函数应当声明成静态的
{
printk(KERN_WARNING "Hello Kernel, it's %d!\n", year);
return 0;
}
static void __exit hello_exit(void)//清理函数,注销接口、在模块被去除之前返回所有资源给系统
{
printk("Bye, kernel!\n");
}
module_init(hello_init);
module_exit(hello_exit);
- 最简单的模块,只包含模块加载函数,卸载函数和模块的一些描述信息
- printk是内核中的输出函数,类似与用户空间的printf
- _ _ init 标志的函数:如果模块编译进内核,在连接时都会放到.init.text这个区段内。 在.initcall.init中保留了一份函数指针,在初始化时内核会调用这些函数,初始化完成后释放init段内存
- _ _ exit 标志的函数:若编译成模块,模块退出时执行
- _ _ initdata 标志的数据:若编译到内核,初始化时使用。若编译为模块,模块初始化时使用
- _ _ exitdata 标志的数据:若编译到内核,初始化完成后释放数据
Makefile
模块编译:编写Makefile文件,用make编译模块得到ko文件
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
-
obi-m:=hello.o 表明有个模块要从目标文件 hello.o 建立,在从目标文件建立后结果模块命名为hello.ko。这里并没有输入 hello.c源文件,这得益于 makefile 的自动推导功能,需要编译生成 hello.o文件而没有显示指定 hello.c 文件位置时,make 查找hello.c 是否存在,如果存在就正常编译,如果不存在,则报错 (obj-m 表示编译生成可加载模块;obj-y表示直接将模块编译进内核)
-
all、clean 这一类的是makefile 中的伪目标,伪目标并不是-个真正的编泽目标,它代表着一系列你想要执行的命令集合,通常一个makefile 会对应多个操作,例如编译、清除编译结果、安装,就可以使用这些伪目标来进行标记。在执行时就可以键入make clean 或 make install,等指令来完成相应的指令操作,当命令make 后不带参数时,默认执行第一个伪目标的操作
-
C选项指定内核源码的位置,make 在编泽时将会进入内核源码目录,执行编译,编译完成时返回
-
KDIR: /lib/modules/(shell uname-r)/build/指定内核源码的位置
-
M=(PWD)需要编译的模块源文件地址
-
[target] modules 是个可选选项,默认行为是将源文件编译并生成内核模块,即module(s),它还支持以下选项: modules install:安装这个外部模块,默认安装地址是/lib/modules/(uname-n)/extral,同时可以由内建变量 INSTALL MOD PATH 指定安装目录; clean:卸载源文件目录下编译过程生成的文件; help:帮助信息;
编译安装
源程序与Makefile放在同一目录,make命令编译
make
加载模块、检查模块加载情况
sudo insmod ./hello.ko
lsmod | grep hello
卸载模块
sudo rmmod hello
检查日志输出
dmesg
内核编程基本特点
1,内核编程时既不能访问C库也不能访问标准的C头文件;
2,Linux内核编程就是开发Linux驱动程序,内核编程的语言仍是传统的C语言,但其编写方法和调用接口与传统应用程序的差别较大,必须了解如何处理中断、如何在内核态和用户态之间转换、PCI、DMA、内核地址映射、内核I/O等;
3,本例hello_init()是模块的入口点,通过module_init()注册到系统,在被装载时被调用;
4,通过makefile编译;
5,部分函数与用户编程写法不同,如printf改为printk;