10、杂项设备
杂项设备属于特殊的一种字符型设备, 是对字符设备的一种封装, 为最简单的字符设备。 为什么从字符设备中单独提取出了杂项设备呢? 杂项设备又要如何进行使用呢?
一、杂项设备节点
二、什么是杂项设备驱动
在Linux系统中,杂项设备是一种处理特殊设备的通用方案。相比普通字符设备,它主要有两大优势:
- 设备号分配更高效 杂项设备统一使用主设备号10,无需像普通字符设备那样每次申请新主号。当系统有多个杂项设备时,只需通过子设备号区分,避免了主设备号的浪费。
- 开发更省事 普通字符设备需要单独注册驱动、创建设备类别并生成/dev设备节点。而杂项设备只需把基本信息填入结构体,调用
misc_register()
函数即可快速完成注册,省去繁琐步骤。
在驱动程序中,我们使用miscdevice结构体来描述辅助设备。这个结构体定义在内核源码的include/linux/miscdevice.h头文件里,在编写代码时需要包含这个头文件。
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
2
3
4
5
6
7
8
9
10
11
定义一个misc设备时,需要设置三个关键参数:次设备号、设备名称和操作函数集合。
- 次设备号(minor) 可以选择系统预定义好的设备号(在内核头文件里已定义好的数值),也可以自己选一个未被占用的数字。但最简单的方式是填
MISC_DYNAMIC_MINOR
,这样系统会自动帮你分配一个空闲的设备号,不用自己操心。 - 设备名称(name) 这个名字决定了设备驱动注册成功后,在
/dev
目录下会生成一个同名的设备文件(比如填"my_device",就会出现/dev/my_device
)。这是用户程序访问设备的入口。 - 操作函数集合(fops) 指向一个操作函数表(
file_operations
结构体),里面包含驱动的具体功能,比如打开、读写、关闭等操作对应的函数。这相当于告诉系统如何控制你的设备。
三、杂项设备的注册和卸载
杂项设备的注册和卸载比字符设备简单很多。只需要用misc_register
函数注册设备,用misc_deregister
函数卸载设备即可。这两个函数都定义在头文件<linux/miscdevice.h>
里。
杂项设备注册函数
函数名称: int misc_register(struct miscdevice *misc)
功能说明: 该函数用于向系统注册一个杂项设备。具体操作包括:
- 根据传入的设备信息创建设备节点
- 将设备信息添加到系统管理列表中
- 完成设备在Linux设备模型中的初始化工作
参数说明: misc:指向设备信息结构体的指针(包含设备名称、操作函数等基本信息)
返回值: 成功返回0,失败返回负数(如-ENOMEM表示内存不足)
杂项设备注销函数
函数名称: int misc_deregister(struct miscdevice *misc)
功能说明: 该函数用于从系统中移除已注册的杂项设备。具体操作包括:
- 从设备管理列表中删除设备信息
- 释放相关资源
- 完成设备的反向初始化操作
参数说明: misc:指向需要移除设备信息结构体的指针
返回值: 成功返回0,失败返回负数
关键点说明:
- 注册时需要提前填写好设备信息结构体(设备名称、文件操作函数等)
- 注销前需要确保没有其他进程正在使用该设备
- 这两个函数是杂项设备驱动的核心操作接口
这样改写后的内容更突出核心操作步骤,用更生活化的动词(如"创建设备节点"替代"构造设备"),并用分点方式明确各步骤关系,同时保持技术准确性。
四、misc_register实现
misc_register
与 device_create 的底层逻辑是一样的
int misc_register(struct miscdevice *misc)
{
dev_t dev;
int err = 0;
bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
//链表
INIT_LIST_HEAD(&misc->list);
//互斥锁
mutex_lock(&misc_mtx);
if (is_dynamic) {
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
if (i >= DYNAMIC_MINORS) {
err = -EBUSY;
goto out;
}
misc->minor = DYNAMIC_MINORS - i - 1;
set_bit(i, misc_minors);
} else {
struct miscdevice *c;
list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {
err = -EBUSY;
goto out;
}
}
}
//dev_t
//#define MISC_MAJOR 10
dev = MKDEV(MISC_MAJOR, misc->minor);
//重要
misc->this_device =
device_create_with_groups(misc_class, misc->parent, dev,
misc, misc->groups, "%s", misc->name);
//对指针的判断
if (IS_ERR(misc->this_device)) {
if (is_dynamic) {
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
misc->minor = MISC_DYNAMIC_MINOR;
}
err = PTR_ERR(misc->this_device);
goto out;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
list_add(&misc->list, &misc_list);
out:
mutex_unlock(&misc_mtx);
return err;
}
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
60
61
62
device_create_with_groups
base/core.c
struct device *device_create_with_groups(struct class *class,
struct device *parent, dev_t devt,
void *drvdata,
const struct attribute_group **groups,
const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_groups_vargs(class, parent, devt, drvdata, groups,
fmt, vargs);
va_end(vargs);
return dev;
}
EXPORT_SYMBOL_GPL(device_create_with_groups);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static __printf(6, 0) struct device *
device_create_groups_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata,
const struct attribute_group **groups,
const char *fmt, va_list args)
{
struct device *dev = NULL;
int retval = -ENODEV;
if (class == NULL || IS_ERR(class))
goto error;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}
device_initialize(dev);
dev->devt = devt;
dev->class = class;
dev->parent = parent;
dev->groups = groups;
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata);
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval)
goto error;
retval = device_add(dev);
if (retval)
goto error;
return dev;
error:
put_device(dev);
return ERR_PTR(retval);
}
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
static __printf(6, 0) struct device *
device_create_groups_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata,
const struct attribute_group **groups,
const char *fmt, va_list args)
{
struct device *dev = NULL;
int retval = -ENODEV;
if (class == NULL || IS_ERR(class))
goto error;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}
device_initialize(dev);
dev->devt = devt;
dev->class = class;
dev->parent = parent;
dev->groups = groups;
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata);
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval)
goto error;
retval = device_add(dev);
if (retval)
goto error;
return dev;
error:
put_device(dev);
return ERR_PTR(retval);
}
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
kobject_set_name_vargs:
设置 kobject 的名称, 并将父节点赋值给 kobject 的 parent 字段。
device_add
五、使用案例
六 、实验代码
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/fs.h> //注册设备节点的文件结构体
#include <linux/uaccess.h>
#include <asm/ptrace.h>
/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
/*本章实验重点******/
char kbuf[32] = {0}; //定义写入缓存区kbuf
if (copy_from_user(kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据
{
printk("copy_from_user error\r\n");//打印copy_from_user函数执行失败
return -1;
}
printk("This is cdev_test_write\r\n");
printk("kbuf is %s\r\n", kbuf);
return 0;
}
/**从设备读取数据*/
static ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
/*本章实验重点******/
char kbuf[32] = "This is cdev_test_read!";//定义内核空间数据
if (copy_to_user(buf, kbuf, strlen(kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据
{
printk("copy_to_user error\r\n"); //打印copy_to_user函数执行失败
return -1;
}
printk("This is cdev_test_read\r\n");
return 0;
}
struct file_operations misc_fops = { //文件操作集
.owner = THIS_MODULE, ////将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模
.read = cdev_test_read, //将open字段指向chrdev_read(...)函数
.write = cdev_test_write //将open字段指向chrdev_write(...)函数
};
struct miscdevice misc_dev = { //杂项设备结构体
.minor = MISC_DYNAMIC_MINOR, //动态申请的次设备号
.name = "miscdriver", //杂项设备名字是hello_misc
.fops = &misc_fops, //文件操作集
};
static int __init misc_init(void)
{
int ret;
dump_stack();
ret = misc_register(&misc_dev); //在初始化函数中注册杂项设备
if (ret < 0)
{
printk("misc registe is error \n"); //打印注册杂项设备失败
}
printk("misc registe is succeed \n");//打印注册杂项设备成功
return 0;
}
static void __exit misc_exit(void)
{
misc_deregister(&misc_dev); //在卸载函数中注销杂项设备
printk(" misc goodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
Makefile
export ARCH=arm64
export CROSS_COMPILE=/home/book/rk/tspi/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
obj-m += misc.o #此处要和你的驱动源文件同名
KDIR := /home/book/rk/tspi/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make#操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
2
3
4
5
6
7
8
9
七、运行结果
驱动加载成功之后会生成/dev/test 设备驱动文件, 输入以下命令查看杂项设备的主次设备号。
从上图可以看出, /dev/test 这个杂项设备的主设备号为 10, 次设备号为 61 。