我使用的是系统是Ubuntu,事实上最开始进行驱动学习的时候是不需要用到开发板的,Ubuntu下是直接可以进行驱动编写测试的。省去了自己去编译内核源码的时间。
检查一下ubuntu的内核版本
执行如下命令查看:
uname -r
我的内核版本是:5.15.0-46-generic
在/lib/modules目录下能看到5.15.0-46-generic目录,这个在编译的时候会用到。
hello world驱动
不管是啥程序,反正先hello world。下面的是一个hello world的打印程序
#include <linux/module.h>
#include <linux/init.h>
/*所有的驱动程序都会包含上面的2个头文件*/
static int hello_init(void)
{
printk(KERN_EMERG "hello world\n");/*打印hello world*/
return 0;
}
static void hello_exit(void)
{
printk(KERN_EMERG "goodbay world\n");/*打印goodbay world*/
}
module_init(hello_init);/*制定模块加载函数*/
module_exit(hello_exit);/*制定模块卸载函数*/
MODULE_LICENSE("GPL");/*模块许可声明*/
释义:
hello_init()是模块的加载函数,当模块被加载的时候就会调用到这个函数。
hello_exit()是模块的卸载函数,当模块被卸载的时候就会调用到这个函数。
其中module_init()和module_exit()都是内核模块的一个宏,用来声明模块的加载和卸载函数的。
编译hello world模块
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ codre .depend .*.cmd *.ko *.mod *.mod.c *.tmp_versions *.order *.symvers .cache.mk .tmp_versions
endif
释义:
obj-m:hello.o意思就是将hello.o编译成hello.ko,如果模块名称改变只需要将hello修改为新的模块名称即可。
KDIR表示的内核源码路径,这个路径下的源码是要编译过的,也就是uname -r
显示的路径,在/lib/modules下。
modules编译命令的语法是:Make -C 内核路径 M=模块路径 modules该语句会执行内核模块的编译。
此Makefile执行命令make即可编译出hello.ko模块。
注意:如果有多个.c需要编译,例如一个hello.c一个world.c需要编译成hello_dev.ko,makefile写法如下:
ifneq ($(KERNELRELEASE),)
obj-m := hello_dev.o
hello_dev-y += hello.o
hello_dev-y += world.o
else
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ codre .depend .*.cmd *.ko *.mod *.mod.c *.tmp_versions *.order *.symvers .cache.mk .tmp_versions
endif
模块的加载和卸载
模块的加载和卸载通过命令来进行
sudo insmod hello.ko #模块加载命令,此时执行hello_init()
sudo rmmod hello.ko #模块卸载命令,此时执行hello_exit()
上述命令执行的时候就会分别调用到加载和卸载的函数,即hello_init()和hello_exit();可以通过终端打印Log来看到函数执行情况。
驱动Log的查看
可以通过dmesg命令来将驱动LOG输出到终端上,下面的脚本是不中断的输出到终端上,便于查看log输出
while true
do
sudo dmesg -c
sleep 1
done
打印Log如下:
模块信息查看
查看模块的信息通过如下命令
lsmod | grep hello
可以看到模块的名称、使用的内存、模块的引用计数(模块被多少程序使用)