hello.c源文件
- module.h包含可装载模块需要的大量符号和函数定义
- init.h指定初始化和清除函数
- MODULE_LICENSE宏告诉内核,该模块采用的协议
- printk可能不会将信息打印在终端上,可用dmesg命令查看(dmsg | tail -5只读最后5行)
- module_init该宏在模块目标代码中增加一个特殊的段,用于说明内核初始化函数所在的位置。
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, world\n");
}
module_init(hello_init);
module_exit(hello_exit);
编译生成hello.ko模块
方法一、Makefile+命令行
Makefile
obj-m := hello.o
命令行
- make modules 忽略中间参数是编译模块的意思
- -C $(KDIR)指明跳转到内核源码目录下读取那里的Makefile
- M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile
make -C /usr/src/linux-headers-4.13.0-16-generic M=`pwd` modules
方法二、完整的Makefile
KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容,当从内核源码目录返回时,KERNELRELEASE已被定义
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /usr/src/linux-headers-$(shell uname -r)
PWD := $(shell pwd)
module:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.ko *~ core .depend .tmp_version .*.cmd
else
obj-m := hello.o
endif
安装、卸载模块
- sudo insmod hello.ko安装之后,调用dmesg可以看到hello,world
- sudo rmmod hello 卸载后,调用dmesg可以看到goodbye,world
- lsmod 通过读取/proc/modules虚拟文件获得已安装模块
内核编译的注意事项
- 内核使用非常小的栈,可能只有4096B(一页),自己编写的驱动函数必须和整个内核空间共享这个一栈。因此需要大的结构,使用动态分配kmalloc。
- “_ ”双下划綫前缀的函数通常是接口的底层组件。 _ init标记表示,模块装载之后将扔掉初始化函数。 _ _exit标记表示该代码用于模块卸载,如果模块直接编译进内核或不允许卸载这该函数被丢弃。
- 用户空间的驱动程序可以实现为一个服务器进程,其任务是代替内核作为硬件控制的唯一代理,客户应用程序可连接到该服务器并和设备执行实际通信。