目录(每节开头都可以跳过本节,享受极致体验)
- 前言
- 一、编译生成模块
- 方法一(推荐)
- 方法二
- 步骤一:进入内核树目录
- 步骤二:编辑Makefile
- 步骤三:编辑Kconfig
- 步骤四:生成.config
- 步骤五:开始make
- 二、模块的加载和使用
- 步骤一:插入模块
- 步骤二:查询模块的`主编号(major number)`
- 步骤三:创建系统节点
- 三、使用模块
- 参考链接
前言
注:以下全部方法已经过测试。测试环境:
- 系统:Ubuntu 20.04 LTS
- 内核:
5.11.0-27-generic
与5.11.0-41-generic
编写好了驱动程序(.c
文件)后,下一步是编译、加载和运行。这里讲述如何实现这个操作。
这里默认已经构建好了Linux内核树
。如何查询电脑中有无内核树,或是构建新的内核树的具体步骤请参照这篇文章:【超详细】Linux内核树的构建。
一、编译生成模块
(点击跳过本章节)
这里介绍两种方法,任选一种即可。
- 方法一:在任意目录下编译模块,也是推荐方法。
- 方法二:在内核树目录下编译模块,步骤较为繁琐。
方法一(推荐)
随便进入一个文件夹,将驱动代码放进这个目录,比如helloworld.c
。
然后,创建一个Makefile
,文件内容如下
obj-m += helloworld.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
(!!注意make
前必须得是Tab
缩进符。)
代码解释:
obj-m += <文件>
:将指定的文件(需要是以.o
结尾)设为编译时以模块形式编译(这里并没有开始编译,make
那行才是真正的编译命令)make -C <目录>
:跳转到指定的目录,读取那里的Makefile
M=<目录>
:跳转到指定的目录,执行先前读取的Makefile
- 总结:先读取内核树下的
Makefile
,再读取我们写的源码,然后结合二者执行make modules
命令
最后,在源代码和Makefile
所在目录下运行make
,将在相同目录下生成模块helloworld.ko
。
方法二
(点击跳过方法二)
假设我们的内核树所在的目录为/lib/modules/5.11.22/build
。下面的步骤里都默认这个目录为根目录。如,/drivers
实际上是/lib/modules/5.11.22/build/drivers
步骤一:进入内核树目录
进入内核树所在根目录
cd /lib/modules/5.11.22/build
(其实这个build
文件夹是个链接,真实目录可以进入文件夹后用pwd
查看,或者在上一级文件夹用ll
查看)
将编写的驱动的源代码(比如helloworld.c
)放在内核树根目录的/drivers/char
文件夹下。
步骤二:编辑Makefile
编辑在同一个目录下的Makefile
,在末尾添加
obj-$(CONFIG_HELLO_WORLD) += helloworld.o
(helloworld.o
在这里指定的是编译时寻找的文件。由于示例源代码名为helloworld.c
,.c
文件将来生成的.o
文件就是Makefile
中需要指明的目标文件了。)
步骤三:编辑Kconfig
仍然在/drivers/char
目录下,编辑Kconfig
文件(这是make
的配置文件,相当于一般软件菜单栏里的选项
功能)。
在文末endmenu
之前添加
config HELLO_WORLD
tristate "HELLO WORLD"
注意,config
后面的名称需要和之前Makefile
里的对应,对应规则是Makefile
中的名称去掉CONFIG_
前缀。
tristate
意思是告知这个模块可以有三种选项:y
,n
和m
,分别对应编译时包括该模块
、不包括该模块
和使用模块化特性
(祥见下图红框中的黄色高亮部分,更多细节请看参考链接1
)。
!!!注意:
- 一定要找到
endmenu
所在位置(可能在文件中间,vi/vim
用户可以在命令模式输入/endmenu
后回车进行搜索),在之前添加,否则不会显示在menuconfig
里。 tristate
前面的是Tab
键而非空格。Makefile
中,空格和制表符的含义有所不同。
步骤四:生成.config
回到根目录(即前提中所说的/lib/modules/5.11.22/build
)。
cd ../..
设置menuconfig
(这里可以在末尾添加-j <最大并发数>
来提高效率,如6核CPU可以是-j 12
)
make menuconfig
在出现的菜单中进入Device Drivers
子目录
然后进入Character drivers
子目录
这时在目录最下面应该会出现一条入口
< > HELLO WORLD (NEW)
将光标移到其上,按M
键将当前驱动视为可装卸的模块。
然后,通过连按两次Esc
来返回上一页,直到退出UI
。
退出前会提示是否保存更改,选择Yes
即可。
(注:也可以不进行menuconfig
这一步,直接运行make
后,在显示到helloworld.ko
时,会询问驱动类型[n/y/m]
,输入m
后回车即可)
步骤五:开始make
回到内核树所在根目录(如,这里是/lib/modules/5.11.22/build
),开始编译我们的驱动。
运行
make
如果报错,说明驱动的源代码有bug
。按照报错的具体提示进行debug
,然后重新运行make
即可。
(在vi/vim
中输入:set number
可以开启行号显示)
持续这个过程直至成功。
成功后我们来检查一下是否生成了编译好的驱动程序。
首先,进入内核树的根目录下的/drivers/char
目录(我这里的完整路径是/lib/modules/5.11.22/build/drivers/char
)。
运行ls
命令后,在打印的文件清单中找到编译成功的.ko
结尾的驱动程序,就算成功了。
注:快速找到目标文件的方法
ls -t
可以按照更改时间对文件清单进行排序,最新修改的文件位于清单的开头ls | grep *,ko
只会打印清单中以.ko
结尾的所有文件。(想了解更多请查询grep
命令的用法)
二、模块的加载和使用
(点击跳过本章节)
步骤一:插入模块
在第一步生成的helloworld.ko
文件所在的目录下,利用insmod
命令,将我们的模块插入到当前操作系统中
sudo insmod helloworld.ko
现在我们来检查一下是否成功载入模块。
使用lsmod
命令。它会打印所有已经载入的模块(也可通过grep
命令快速找到我们的模块:lsmod | grep hello
)
```
$ lsmod
Module Size Used by
helloworld 16384 0
isofs 49152 1
vboxvideo 36864 0
... ... ...
```
删除的方法也很简单,用rmmod
即可:
sudo rmmod helloworld.ko
步骤二:查询模块的主编号(major number)
cat /proc/devices
会在打印所有已经载入的模块的同时,显示模块的主编号(major number)
(如,下面的情况中,我们的helloworld
模块的主编号
是237)。
(这里显示的模块名称,应该取决于源代码中alloc_chrdev_region()
的 第四个参数 所指定的模块名称。)
$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
... ...
237 hello_world
... ...
注意:
-
如果没有找到自己的驱动的话,可以参照下面的解释:
不是所有设备驱动都会有用到设备号的,除非驱动里有实现字符设备驱动接口(cdev). 而且还有可能是用了平台设备驱动模型,需要平台设备匹配上才会触发注册设备号等字符设备的操作
(摘自:https://bbs.csdn/topics/392191032) -
如果发现成功载入了模块,但是程序中的
printk
本应该在载入模块成功时打印消息,却没打印,那么可能是系统不支持printk
直接打印到终端。具体的原理及解决方法见这篇文章。
步骤三:创建系统节点
最后,如果想要在应用程序中使用这个模块(通常是通过/dev/<设备名称>
来调用),需要先用mknod
命令创建设备文件:mknod /dev/<自定义设备名称> <模块类型> <模块主编号> <模块次编号>
比如
sudo mknod /dev/helloworld c <主编号> <次编号>
(注:删除时使用rm -f /dev/helloworld
命令即可)
参数解释:
/dev/helloworld
:这里,/dev
后面的“helloworld”
是我为创建出的设备文件
起的名字。该名字与之前的步骤没有依赖关系,可以任意更换,只是在后续程序中使用该设备文件
的时候,需要与这里起的名字相同(如open("/dev/helloworld", O_RDONLY);
)c
:代表创建的是字符型设备(char device)
<主编号>
:我的模块的主编号(major number)
,可以在源代码中用MAJOR()
函数得到,也可通过cat /proc/devices
命令来找到<次编号>
:模块的次编号(minor number)
,通常是1
(详见这篇文章:linux设备驱动第三版 - 主次编号)
成功后,输入
ls /dev
就应该可以找到创建好的设备文件并且在应用程序的代码里使用了
(注:嫌输出太长可以用ls /dev | grep hello
命令过滤输出)。
此外,移除节点的方法是rm -f
命令:
sudo rm -f /dev/helloworld.c
三、使用模块
(还跳?都已经到文章结尾了)
使用Linux
系统自带的open
,read
等函数,就能够使用我们的驱动了。
如:
int buf[100];
int fd = open("/dev/helloworld", O_RDONLY);
read(fd, buf, 99);
参考链接
浅谈Kconfig、Makefile、.config之间的关系 - cnblog
如何让 printk 打印到终端 - Xav Pan
Linux lsmod 命令 - 菜鸟教程
更多推荐
【Linux】驱动模块的 编译与加载
发布评论