Linux驱动实践:带你一步一步编译内核驱动程序
扫描二维码
随时随地手机看文章
作 者:道哥,10 年嵌入式开发老兵,专注于:C/C 、嵌入式、Linux。目录关注下方公众号,回复【书籍】,获取 Linux、嵌入式领域经典书籍;回复【PDF】,获取所有原创文章( PDF 格式)。
学习的困惑
记得以前我在开始学习驱动开发的时候,找来很多文章、资料来学习,但是总是觉得缺少了点全局视角。
因此,这几篇文章我们就从最简单的驱动模块编译开始,然后介绍字符设备驱动程序。
- 每一篇文章的介绍都是正确的,但是如果把很多文章放在一起看,就会发现怎么说的都不一样啊?
- 有些文章注重函数的介绍,但是缺乏一个全局的视角,从整体上来观察驱动程序的结构;
- 对于一个新手来说,能够边学习、边实践,这是最好的学习方式,但是很多文章不会注意这方面。虽然文章内容很漂亮,但是不知道怎么去实践、验证。
- 编译进内核;
- 编译为一个独立的驱动模块;
实践环境
为了便于测试,以下操作都是在 Ubuntu16.04 操作系统里完成的。编译Linux驱动程序,肯定需要内核源码,这里选择的是 linux-4.15 版本,可以在官网下载。
文末有下载方式。下载之后,把linux-4.15.tar.gz解压到Ubuntu中任意目录即可,例如:解压到~/tmp/目录下:
编译进内核
创建驱动程序目录
linux中的驱动,一般都放在 linux-4.15/drivers/ 目录下,因此在这个目录中创建一个hello文件夹。
对于一个驱动来说,最重要的就是3个文件:
只要按照固定的格式来编写这3个文件,linux内核的编译脚本就可以确保把我们的驱动程序编译进去。
- 源代码
- Kconfig
- Makefile
创建源文件
首先是源码,在hello文件夹中创建源文件hello.c:
$ touch hello.c
源文件hello.c的内容是:
#include
// 当驱动被加载的时候,执行此函数
static int __init hello_init(void)
{
printk(KERN_ALERT "welcome, hello"\n");
return 0;
}
// 当驱动被卸载的时候,执行此函数
static void __exit hello_exit(void)
{
printk(KERN_ALERT "bye, hello\n");
}
// 版权声明
MODULE_LICENSE("GPL");
// 以下两个函数属于 Linux 的驱动框架,只要把驱动两个函数地址注册进去即可。
module_init(hello_init);
module_exit(hello_exit);
有两个小地方注意一下:
- 在内核中,打印函数是 printk,而不是 printf;
- 打印信息的级别有好几个,从 DEBUG 到 EMERG,这里使用的是 KERN_ALERT,方便查看打印信息。
创建 Kconfig 文件
这个文件是用来对内核进行配置的,当执行 make menuconfig 指令的时候,这个文件就被解析。
$ touch Kconfig
添加如下内容:
tristate "hello driver"
help
just a simplest driver.
default y
第一行内容 config HELLO ,在执行配置的时候,将会生成一个变量 CONFIG_HELLO ,而这个变量,将会在编译的时候,被 Makefile 引用。
endmenu // 加在这一句的上面
现在,可以来执行下面指令,看一下具体的配置界面:
$ make distclean
$ make ARCH=x86_64 defconfig
$ make ARCH=x86_64 menuconfig
第2条指令,是用来把默认的配置保存到当前目录下的 .config 配置文件,也就是把一个默认的配置文件复制过来,作为我们自己的配置文件。
创建 Makefile 文件
Makefile文件是make工具的脚本,首先创建它:
$ touch Makefile
其中的内容只有一行:
现在,hello驱动程序的Makefile已经创建好了,我们还要让linux内核的编译框架知道这个文件才行。
- CONFIG_HELLO 可以看做一个变量,在编译的时候,这个变量的值可能是:y, n 或者 m。
- 在刚才的 Kconfig 参数配置中,CONFIG_HELLO 被设置为 y,于是这句话就被翻译成:obj-y = hello,表示把 hello 驱动编译进内核。
编译
万事俱备,只欠编译!依次执行如下指令:
$ make -j4
make指令执行结束之后,编译得到的内核中(vmlinux)就包含了我们的hello驱动。
编译为驱动模块
编译为驱动模块,也有两种 操作方式:
编译所有的驱动模块
编译成功之后,就可以得到文件: linux-4.15/drivers/hello/hello.ko。
- 在执行 make ARCH=x86_64 menuconfig 指令的时候,把 hello 配置成 M;
- 然后在 linux-4.15 中执行编译模块指令:make -j4 modules。
只编译 hello 这一个驱动模块
另外一种编译驱动模块的方式是:进入hello目录,只编译这一个驱动模块。
可以把 hello 目录下的所有文件删除,只保留源文件 hello.c,然后新建 Makefile 文件。ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean
endif
然后,在hello文件夹中执行make指令,即可得到驱动模块 hello.ko 。
验证一下
加载驱动:
$ sudo insmod ./hello.ko
此时终端窗口是没有任何输出的,需要输入指令 dmesg | tail ,可以看到 hello_init 函数的输出内容:
再次输入 dmesg | tail ,可以看到 hello_exit 函数的输出内容:
资料下载
在公众号【IOT物联网小镇】的后台回复关键字:1112,获取下列文件的网盘地址:
linux-4.15.tar.gzhello文件夹压缩包
------ End ------