树莓派使用教程6:linux内核模块编程
0.引言
在linux中,设备分为三种:
-
字符设备:以字符为单位进行传输
-
块设备: 以块为单位进行传输,通常一个块为4K
-
网络设备:以网络接口进行传输
其中,网络设备不像字符设备或块设备那样通过对应的设备文件进行操作,也不能通过read/write进行数据请求,而是通过socket接口函数进行访问;
这些设备驱动文件加载到内核中,我们才能在linux系统中查看到对应的设备文件,才能通过上层程序去操作硬件;
设备驱动文件的编写,也就是内核模块编程的一种;
本文简单介绍了内核模块编程的基本步骤
1.内核模块三要素:
-
入口函数
-
出口函数
-
GPL声明文件
入口函数(加载):
module_init() //入口函数 int __init XXX_func(void){ // return 0; } //module_init在头文件linux/init.h中被定义,使用时需include
出口函数(卸载):
module_exit() //卸载函数 void __exit xxx_func(void){ }
GPL声明文件:
#include <linux/module.h> MODULE_LICENSE("GPL") //MODULE_LICENSE在头文件linux/module.h中被定义,使用时需include
2.测试实例
创建demo.c文件,输入如下内容:
int __init demo_init(void){
printk("----%s----%s----%d",__FILE__,__func__,__LINE);
printk("hello world");
return 0;
}
void __exit demo_exit(void){
printk("----%s----%s----%d",__FILE__,__func__,__LINE);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
3.编译
编译器:gcc 交叉编译工具;
编写编译内核模块的Makefile;
-
内部编译:将内核模块源文件放在内核源码中进行编译,Kconfig, Makefile, make menuconifg
-
静态编译:将内核模块编译进uImage中
-
外部编译:将内核模块源文件放在内核源文件外进行编译
-
动态编译:编译生成动态模块xxx.ko
一般采用外部编译,动态加载的方式.
编写Makefile文件:
与demo.c同目录下创建Makefile文件
KERNELDIR := /lib/modules/$(shell uname -r)/build PWD:=$(shell pwd) obj-m:=hello.o all: make -C $(KERNELDIR) M=$(PWD) modules clean: make -C $(KERLNEDIR) M=$(PWD) clean
提示:$(shell uname -r) 与`uname -r`是等价的
-C 进入到内核的目录执行Makefile ;
M=$(PWD)表示返回当前目录;
再次执行makefile;
modules 编译成模块的意思
KERNELDIR
指的是内核库文件的路径;
uname -r查看内核版本号;
一般来说我们构造内核树时,它把内核库统一保存在/lib/modules/内核版本号/build目下;
没有build文件:
需要安装linux-headers
//树莓派 sudo apt install raspberrypi-kernel-headers //ubuntu sudo apt install linux-headers-`uname -r`
安装后的内核文件在:/usr/src目录下,然后创建软链接:
ln -s /usr/src/`uname -r` /lib/modules/`uname -r`/build
4.使用模块
在demo.c的文件夹下执行make
指令,会生成demo.ko文件
-
将内核模块插入内核中
sudo insmod demo.ko
-
查看内核中被插入的模块
lsmod
-
卸载模块
sudo rmmod demo
-
查看内核的日志信息命令
dmesg //清除内核日志信息 dmesg -c