本章教你如何编写一个杂项设备(misc设备)驱动。杂项设备是 Linux 内核中非常常见的一种字符设备类型,适用于各种简单设备或工具类驱动。它让驱动开发更简单、繁琐的字符设备号管理都交给内核处理。
一、杂项设备简介
杂项设备(misc device)是 Linux 提供的一种特殊字符设备,使用统一的主设备号(一般是10),通过动态分配次设备号来区分不同设备。它简化了设备号分配与节点创建设备,驱动注册流程更简单。
常见的 /dev 目录下的 misc 设备:
/dev/rtc/dev/uinput/dev/nvram- ...等等
优点:
- 无需自己分配主设备号
- 注册流程更省事
- 适合大多数普通字符设备使用场景
二、杂项设备驱动结构
主要涉及两个结构体和三个操作:
struct miscdevice—— 杂项设备描述结构体(核心)struct file_operations—— 设备操作方法表- 注册:
misc_register() - 注销:
misc_deregister()
三、快速编写杂项设备驱动
创建一个 08_misc_driver/ 文件夹,在里面创建一个 misc_driver.c 文件,并编写如下代码:
c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
// 设备名称
#define DEVICE_NAME "mymiscdev"
/* 设备操作函数表 */
static ssize_t mymisc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
printk(KERN_INFO "mymiscdev: read called\n");
return 0;
}
static ssize_t mymisc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
printk(KERN_INFO "mymiscdev: write called\n");
return 0;
}
/* 设备操作接口 */
static struct file_operations mymisc_fops = {
.owner = THIS_MODULE,
.read = mymisc_read,
.write = mymisc_write,
};
/* 杂项设备描述 */
static struct miscdevice mymiscdev = {
.minor = MISC_DYNAMIC_MINOR, // 自动分配次设备号
.name = DEVICE_NAME, // 出现在 /dev 下的名称
.fops = &mymisc_fops,
};
/* 模块加载时自动调用 */
static int __init mymiscdev_init(void)
{
int ret = misc_register(&mymiscdev);
if (ret)
printk(KERN_ERR "mymiscdev: register failed\n");
else
printk(KERN_INFO "mymiscdev: /dev/%s registered\n", DEVICE_NAME);
return ret;
}
/* 卸载时自动调用 */
static void __exit mymiscdev_exit(void)
{
misc_deregister(&mymiscdev);
printk(KERN_INFO "mymiscdev: /dev/%s removed\n", DEVICE_NAME);
}
module_init(mymiscdev_init);
module_exit(mymiscdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LCKFB");
MODULE_DESCRIPTION("misc device driver");1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
1. 设备名和头文件
c
#define DEVICE_NAME "mymiscdev"1
用于指定设备节点的名称,最后节点为 /dev/mymiscdev。
2. 设备操作函数(file_operations)
c
static ssize_t mymisc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
printk(KERN_INFO "mymiscdev: read called\n");
return 0;
}1
2
3
4
5
2
3
4
5
.read和.write分别实现设备读写操作,目前只是打印日志,并未与用户交换数据,返回0表示操作结束。
3. file_operations结构体
c
static struct file_operations mymisc_fops = {
.owner = THIS_MODULE,
.read = mymisc_read,
.write = mymisc_write,
};1
2
3
4
5
2
3
4
5
- 描述本设备支持的操作(这里只实现了基本读写)。
4. 杂项设备结构体
c
static struct miscdevice mymiscdev = {
.minor = MISC_DYNAMIC_MINOR, // 自动分配次设备号
.name = DEVICE_NAME, // 节点名称
.fops = &mymisc_fops, // 操作方法表
};1
2
3
4
5
2
3
4
5
- 这是内核管理杂项设备的核心结构体。
minor设为MISC_DYNAMIC_MINOR即自动分配。.name=/dev下出现的名字。
5. 驱动加载与注销
c
static int __init mymiscdev_init(void) {
return misc_register(&mymiscdev);
}
static void __exit mymiscdev_exit(void) {
misc_deregister(&mymiscdev);
}1
2
3
4
5
6
2
3
4
5
6
- 加载时注册设备(自动生成
/dev/mymiscdev)。 - 卸载时注销,节点消失。
四、编译与测试
- 编写
Makefile
makefile
export ARCH=arm64
# 交叉编译器绝对路径前缀
export CROSS_COMPILE=/home/lckfb/TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
# 和源文件名一致
obj-m += misc_driver.o
# 内核源码目录
KDIR := /home/lckfb/TaishanPi-3-Linux/kernel-6.1
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
和之前编写的 Makefile 几乎一摸一样!
这不过这里变为了 misc_driver.o
CROSS_COMPILE:依旧是SDK中的编译器路径前缀。KDIR:依旧是内核源码目录。
- 编译驱动
在 08_misc_driver/ 目录下运行:
bash
make1
就会自动生成 .ko 文件,这就是我们所需要的。
- 加载模块
将 misc_driver.ko 复制到开发板中,挂载模块,就能看到 /dev 下生成了我们所需要的设备文件:
bash
sudo insmod misc_driver.ko1
五、快速问答
- Q: 杂项设备和普通字符设备驱动有什么不同? A: 普通字符设备要自己分配设备号、手动注册和创建设备节点,而杂项设备一切让内核自动化,代码更少更方便。
- Q: 支持多个杂项设备吗? A: 支持的,每个设备维护自己独立的
struct miscdevice即可。 - Q: 设备节点在哪里? A: 由内核自动在
/dev下创建,名字就是.name字段,比如上述示例是/dev/mymiscdev。
六、总结
- 杂项设备驱动极大简化了字符设备开发流程。
- 使用
struct miscdevice和misc_register()即可快速完成一个基础驱动。 - 适合新手开发常见小工具、组件型驱动。