05、创建设备节点实验
在Linux系统中,所有硬件设备都会被当作文件来管理。这些设备对应的特殊文件叫做设备节点,都存放在/dev文件夹里。它们的作用就像桥梁:一边连接着系统内部识别的硬件设备,另一边让应用程序能像操作普通文件一样读写设备数据。这样程序就能通过访问这些特殊文件,间接控制硬件完成操作。
一、SYSFS 文件系统
从Linux 2.6版本开始,内核引入了一个叫sysfs的系统,它默认挂载在/sys目录下。这其实是一个存在内存里的文件系统,和/proc类似。它的主要作用是让内核和应用程序能方便地交换信息。
内核会通过sysfs把一些内部参数"暴露"出来。每个被暴露的参数在/sys目录里都会对应一个文件,这个文件里直接保存着参数的当前值。比如,当某个硬件驱动要显示自己的设备号时,sysfs里就会自动生成一个对应的文件(比如uevent),文件内容就是这个设备号的值。
简单来说,sysfs就像一个"翻译器":内核把参数变成文件格式,用户程序则能像读普通文件一样直接获取这些参数,操作起来非常方便。
二、自动创建设备文件的工具
1. devtmpfs 这是Linux系统启动时自动创建设备文件的工具。开机时,系统会把devtmpfs挂载到/dev
目录下,自动生成所有硬件设备对应的设备文件(比如键盘、鼠标、硬盘等)。这些文件让软件能和硬件正常通信。
2. udev udev是设备管理的"智能管家"。系统运行时,只要硬件插入(比如U盘)或拔出,udev就会收到通知,自动创建/删除对应的设备文件。它还能执行用户写的脚本,比如插U盘后自动备份文件,或者给设备改个更容易识别的名字。
3. mdev mdev是轻量级的设备管理工具(常用于嵌入式设备)。它在系统启动时创建设备文件,也能在硬件插拔时执行用户脚本。相比udev,它更简单、占用资源少,但功能有限(比如不支持复杂规则或网络设备管理)。
总结区别
- devtmpfs:开机时快速生成基础设备文件,由内核直接管理。
- udev:功能强大,实时管理硬件变化,适合复杂系统。
- mdev:轻量简单,适合资源有限的设备(如路由器),但功能较少。
三、手动创建设备节点
使用 mknod
命令手动创建设备节点的语法如下:
mknod 设备名称 类型 主编号 次编号
参数说明:
- 设备名称:你想创建的节点文件名(如
/dev/device_test
) - 类型:用
b
(块设备)、c
(字符设备)或p
(管道)表示设备类型 - 主编号:设备的主识别号
- 次编号:设备的次级识别号
例如:
mknod /dev/device_test c 511 0
这条命令会创建一个名为 device_test
的字符设备节点(c
表示字符设备),它关联到主编号 511
和次编号 0
的硬件设备。
简单来说,就是通过命令告诉系统: "在 /dev/
目录下新建一个设备文件,类型是字符设备,对应硬件的主编号是511,次编号是0"。
四、自动创建设备节点
设备文件的自动创建主要由udev或mdev机制完成,系统通常会自动处理。
udev的工作方式是:随时关注硬件变化,一旦检测到硬件插入、移除或状态变化,就自动在系统中创建或删除对应的设备文件。
具体过程分两步:
- 驱动程序先在系统中注册设备类别(这会在/sys/class文件夹里生成一个类别标识)
- 接着驱动程序创建具体设备信息。当驱动加载时,udev会立刻感知到这个动作。
根据已注册的类别信息,udev会自动在/dev目录下生成对应的设备文件,比如/dev/sda这样的设备访问入口。整个过程就像系统自动给新硬件配发"身份证"一样,方便程序直接使用。
4.1、创建类
①class_create**(…)**函数
include/linux/device/class.h
该函数用于在Linux内核中动态创建设备类,并初始化部分字段后添加到系统中。参数说明:
- owner:指向模块的指针(struct module类型),表示该设备类所属的模块,通常填
THIS_MODULE
。 - name:设备类的名称字符串(如
"my_device"
)。
函数返回创建的设备类结构体指针(struct class*)。
② class_destroy**(...)**函数
该函数用于在Linux内核中注销创建设备类,并初始化部分字段后添加到系统中。参数说明:
- owner:指向模块的指针(struct module类型),表示该设备类所属的模块,通常填
THIS_MODULE
。
4.2、创建设备
③ device_create**(...)**函数
device_create(struct class *cls, struct device *parent, dev_t devt,
void *drvdata, const char *fmt, ...);
2
函数作用:
用来在 class 类中下创建一个设备属性文件,udev 会自动识别从而进行设备节点的创建
参数含义:
- cls:指定所要创建的设备所从属的类。
- parent:指定该设备的父设备,如果没有就指定为 NULL。
- devt:指定创建设备的设备号。
- drvdata:被添加到该设备回调的数据,没有则指定为 NULL。
- fmt:添加到系统的设备节点名称。
返回值:
- structdevice*类型结构体
④ **device_destroy(...)**函数
void device_destroy(struct class *cls, dev_t devt);
函数作用:
用来删除 class 类中的设备属性文件,udev 会自动识别从而进行设备节点的删除。
参数含义:
- cls:指定所要创建的设备所从属的类。
- devt:指定创建设备的设备号。
**返回值:**无
五、实验代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
static dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号
static struct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备
static struct file_operations cdev_fops_test = {
.owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
};//定义file_operations结构体类型的变量cdev_test_ops
static struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
static int __init chrdev_fops_init(void)//驱动入口函数
{
int ret;//定义int类型的变量ret,用来对函数返回值进行判断
int major,minor;//定义int类型的主设备号major和次设备号minor
ret = alloc_chrdev_region(&dev_num,0,1,"chrdev_name");//自动获取设备号,设备名chrdev_name
if (ret < 0){
printk("alloc_chrdev_region is error \n");
}
printk("alloc_chrdev_region is ok \n");
major = MAJOR(dev_num);//使用MAJOR()函数获取主设备号
minor = MINOR(dev_num);//使用MINOR()函数获取次设备号
printk("major is %d\n",major);
printk("minor is %d\n",minor);
cdev_init(&cdev_test,&cdev_fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到cdev_test_ops结构体
cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
ret = cdev_add(&cdev_test,dev_num,1); //使用cdev_add()函数进行字符设备的添加
if (ret < 0){
printk("cdev_add is error \n");
}
printk("cdev_add is ok \n");
class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_test
device_create(class_test,NULL,dev_num,NULL,"device_test");//使用device_create进行设备的创建,设备名称为device_test
return 0;
}
static void __exit chrdev_fops_exit(void)//驱动出口函数
{
cdev_del(&cdev_test);//删除添加的字符设备cdev_test
unregister_chrdev_region(dev_num,1);//释放字符设备所申请的设备号
device_destroy(class_test,dev_num);//删除创建的设备
class_destroy(class_test);//删除创建的类
printk("module exit \n");
}
module_init(chrdev_fops_init);//注册入口函数
module_exit(chrdev_fops_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意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