Linux驱动:class_create、device_create
udev是什么
-
动态管理设备文件
传统的 Linux 系统通过静态创建 /dev 目录下的设备文件(如早期的 mknod 命令),但现代系统中硬件设备(如 USB 设备、存储设备、串口等)热插拔频繁,udev 可实时响应设备事件,自动生成或删除对应的设备文件。
例如:插入 U 盘时,udev 会自动在 /dev 下创建如 /dev/sdb 或 /dev/sdb1 等-设备节点。 -
设备属性与权限配置
udev 可根据设备的硬件信息(如 Vendor ID、Product ID、总线类型等)为设备文件设置 自定义权限、所有者、组属 或 符号链接(如 /dev/usb/dev_acm0 指向实际设备节点),方便用户或程序访问。
例如:为 USB 串口设备设置权限为 666,或为摄像头设备创建易读的符号链接 /dev/my_camera。 -
响应内核事件
udev 通过监听内核的 uevent 机制(内核向用户空间发送的设备事件通知),获取设备的插拔、状态变化等信息,并触发预设的规则进行处理。 -
udev 规则文件
规则文件定义了设备匹配条件和对应的操作,存储在 /etc/udev/rules.d/ 和 /lib/udev/rules.d/ 目录下,文件名通常以数字开头(如 50-udev-default.rules),数字越小优先级越高。
# 为 Vendor ID=046d、Product ID=c52f 的 USB 鼠标设置权限为 666,并创建符号链接
SUBSYSTEM=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52f", MODE="0666", SYMLINK+="usb_mouse"
SUBSYSTEM:设备所属子系统(如 usb、block、tty 等)。
ATTRS{…}:设备属性(如 Vendor ID、Product ID)。
MODE:设备文件权限。
SYMLINK:创建符号链接。
-
udevadm 工具
用于调试和管理 udev,常见命令:
udevadm monitor:监听 udev 事件。
udevadm info:查看设备的 udev 规则和属性。
udevadm trigger:手动触发 udev 规则(如插入新设备后刷新设备节点)。 -
udev 与设备驱动的配合
在 Linux 设备驱动开发中,若需自动创建设备文件,通常配合以下步骤:
使用 class_create(内核函数)创建一个设备类(用于 /sys/class/ 下的分类)。
使用 device_create(内核函数)在该类下创建具体设备,此时内核会通过 uevent 通知 udev。
udev 根据预设规则或动态生成规则,在 /dev 下创建对应的设备文件。
驱动中自动创建设备文件示例
#include <linux/device.h>static struct class *my_class;
static struct device *my_device;// 驱动初始化函数中
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class)) {return PTR_ERR(my_class);
}my_device = device_create(my_class, NULL, dev_num, NULL, "my_device");
if (IS_ERR(my_device)) {class_destroy(my_class);return PTR_ERR(my_device);
}// 驱动退出函数中
device_destroy(my_class, dev_num);
class_destroy(my_class);
执行后,udev 会根据 /sys/class/my_class/my_device 中的信息生成 /dev/my_device 设备文件。
class_create
struct class *class_create(struct module *owner, const char *name);
参数:
owner:一般为 THIS_MODULE(表示模块所有者)。
name:设备类名称(如 CLASS_NAME)。
返回值:
成功:指向 struct class 的指针。
失败:ERR_PTR(需通过 IS_ERR() 检查)。
device_create
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
参数:
class:指向 class_create 返回的 struct class 指针。
parent:父设备(通常为 NULL)。
devt:设备号(需与注册字符设备时的 dev_num 一致)。
drvdata:设备特定数据(通常为 NULL)。
fmt:设备名称(如 DEVICE_NAME)。
返回值:
成功:指向 struct device 的指针。
失败:ERR_PTR(需通过 IS_ERR() 检查)。
device_destroy:
void device_destroy(struct class *class, dev_t devt);
销毁设备节点,删除 /sys/class// 条目,并通知 udev 移除 /dev/ 下的设备文件。
class_destroy
void class_destroy(struct class *class);
销毁设备类,删除 /sys/class/ 目录。
module_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/uaccess.h> // copy_to_user, copy_from_user
#include <linux/kernel.h>
#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>#define DEVICE_NAME "mychartest" // 设备名(/dev/下的文件名)
#define CLASS_NAME "my_char_class" // 设备类名(/sys/class/下的目录名)#define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT#define rGPJ0CON *((volatile unsigned int *) GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *) GPJ0DAT)#define BUFFER_SIZE 100
#define MYMAJOR 250
#define MYNAME "mychartest"int mymajor;
static char buffer[BUFFER_SIZE];static dev_t dev_num; // 设备号(主设备号+次设备号)
static struct cdev my_cdev; // 字符设备结构static struct class *my_class; // 设备类指针
static struct device *my_device; // 设备指针int ctrled(int sta)
{if(sta){rGPJ0CON = 0x11111111;//rGPJ0DAT =((1<<3) | (1<<4) | (1<<5)) //灭1rGPJ0DAT =((1<<3) | (0<<4) | (0<<5)) ;//亮0printk(KERN_INFO "rGPJ0CON = %p\n",rGPJ0CON);printk(KERN_INFO "rGPJ0DAT = %d\n",rGPJ0DAT);printk(KERN_INFO "GPJ0CON = %p\n",GPJ0CON);printk(KERN_INFO "GPJ0DAT = %p\n",GPJ0DAT);}else{rGPJ0DAT =((0<<3) | (1<<4) | (1<<5)); //灭}}
// 打开设备函数
static int my_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0;
}// 释放设备函数
static int my_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device released\n");return 0;
}// 读取设备函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_read\n");// 确保用户缓冲区足够大if (count < sizeof(unsigned int)) {return -EINVAL;}// 读取寄存器值unsigned int reg_value = rGPJ0DAT;printk(KERN_INFO "reg_value = %d\n",reg_value);printk(KERN_INFO "sizeof(reg_value) = %d\n",sizeof(reg_value));if (copy_to_user(buf, ®_value, sizeof(reg_value))) {return -EFAULT;}// 更新文件位置*f_pos += sizeof(reg_value);// 返回实际传输的字节数return sizeof(reg_value);
}// 写入设备函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_write\n");memset(buffer,0,sizeof(buffer));if (copy_from_user(buffer, buf, count)) {return -EFAULT;}if(!strcmp(buffer,"1")){printk(KERN_INFO "1\n");ctrled(1);}else if(!strcmp(buffer,"0")){printk(KERN_INFO "0\n");ctrled(0);}return 0;
}// 定义file_operations结构体实例
static const struct file_operations my_fops = {.owner = THIS_MODULE,//全部一样,都这样写.open = my_open,.release = my_release,.write = my_write,.read = my_read,};// 模块安装函数
static int __init chrdev_init(void)
{ int ret;printk(KERN_INFO "chrdev_init helloworld init\n");// 动态分配设备号(主设备号自动分配,次设备号从0开始,分配1个)ret = alloc_chrdev_region(&dev_num, 0, 1, MYNAME);if (ret < 0) {printk(KERN_ERR "Failed to allocate char device number\n");return ret;}// 打印分配的主设备号和次设备号printk(KERN_INFO "Allocated major: %d, minor: %d\n", MAJOR(dev_num), MINOR(dev_num));// 初始化 cdev 结构cdev_init(&my_cdev, &my_fops);my_cdev.owner = THIS_MODULE;// 注册字符设备到内核ret = cdev_add(&my_cdev, dev_num, 1);if (ret < 0) {printk(KERN_ERR "Failed to add char device\n");unregister_chrdev_region(dev_num, 1); // 释放已注册的设备号return ret;}// 创建设备类(/sys/class/CLASS_NAME)my_class = class_create(THIS_MODULE, CLASS_NAME);if (IS_ERR(my_class)) {cdev_del(&my_cdev);unregister_chrdev_region(dev_num, 1);printk(KERN_ERR "Failed to create class\n");return PTR_ERR(my_class);}// 创建设备节点(触发 udev 生成 /dev/DEVICE_NAME)my_device = device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);if (IS_ERR(my_device)) {class_destroy(my_class);cdev_del(&my_cdev);unregister_chrdev_region(dev_num, 1);printk(KERN_ERR "Failed to create device\n");return PTR_ERR(my_device);}printk(KERN_INFO "Successfully created device: /dev/%s\n", DEVICE_NAME);printk(KERN_INFO "chrdev_init helloworld successs... mymajor = %d\n", MYMAJOR);
}// 模块下载函数
static void __exit chrdev_exit(void)
{// 销毁设备节点if (my_device) {device_destroy(my_class, dev_num);}// 销毁设备类if (my_class) {class_destroy(my_class);}// 移除字符设备cdev_del(&my_cdev);// 释放设备号unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "chrdev_exit helloworld exit\n");}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
sys 文件系统简介
sysfs(sys 文件系统) 是 Linux 内核提供的一种虚拟文件系统,用于在用户空间中暴露内核数据结构和设备信息。它挂载在 /sys 目录下,主要用于:
展示内核中设备、驱动、总线等硬件相关信息。
提供用户空间与内核交互的接口(如读取 / 修改内核参数)。
配合 udev 实现设备文件的自动创建和管理。
sysfs 的目录结构与内核中的对象模型紧密关联,主要包含以下几类核心目录:
/sys/bus:按总线类型(如 pci、usb)分类的设备和驱动信息。
/sys/devices:系统中所有设备的层次化列表。
/sys/class:按功能分类的设备抽象类(如 net、block、gpio),与用户空间交互最密切。
/sys/module:内核加载的模块信息。
/sys/class/xxx/ 目录中文件的作用
/sys/class/ 下的每个子目录(如 leds、tty、mydevice)代表一个 设备类(device class),用于将功能相似的设备分组。每个设备类包含多个设备实例的抽象信息,其下的文件和子目录主要用于:
设备类属性文件
name:设备类的名称(字符串)。
dev:记录该类下设备对应的主设备号和次设备号(格式为 major:minor),供 udev 自动创建设备节点时使用。
uevent:内核向用户空间发送设备事件(如设备插入 / 移除)的接口,udev 通过监听此文件触发规则。
设备实例文件(在设备类子目录中)
当设备驱动通过 class_create 和 device_create 创建设备实例后,会在设备类目录下生成以设备名称命名的子目录(如 /sys/class/myclass/mydev),其下常见文件包括:
dev:设备实例对应的主设备号和次设备号(用于 udev 创建设备节点)。
power:设备电源管理相关属性(如 status 表示电源状态)。
subsystem:指向设备所属的总线或类的符号链接(如 /sys/class/myclass)。
自定义属性文件:驱动程序可通过 sysfs_create_file 在内核中动态创建属性文件,用于暴露设备状态或配置参数(如 LED 亮度、GPIO 方向等)。
与 udev 配合自动创建设备节点
/sys/class/xxx/ 目录中的 dev 文件是 udev 自动创建设备节点的关键:
当内核检测到新设备时,会在 sysfs 中生成对应的设备类和实例目录,并写入 dev 文件(主 / 次设备号)。
udev 监听 sysfs 变化,根据预设规则(如 /etc/udev/rules.d/ 中的规则),结合 dev 文件中的设备号,在 /dev 目录下创建设备节点(如 /dev/ttyUSB0)。
/sys/class/my_char_class/mychartest
dev 文件
作用:
该文件存储设备的 设备号(包括主设备号和次设备号),用于标识内核中的具体设备。
设备号是字符设备或块设备的唯一标识,由主设备号(标识设备类型 / 驱动)和次设备号(标识具体设备实例)组成。
用户空间工具(如 mknod、udev)可通过读取此文件获取设备号,从而创建设备节点(如 /dev/xxx)。
内容格式:
文件内容为两个用逗号分隔的数字,例如:
power/ 目录
作用:
该目录包含与设备 电源管理 相关的属性文件,用于控制设备的电源状态(如休眠、唤醒、功耗设置等)。
这是 Linux 内核电源管理子系统(PM subsystem)的一部分,支持 ACPI、USB 等设备的电源控制。
常见子文件:
control:
控制设备的电源状态,可写入 on(开启)、auto(自动)、off(关闭)。
runtime_status:
显示设备的运行时电源状态(如 active、suspended)。
runtime_suspended_time:
记录设备处于挂起状态的总时间。
wakeup:
控制设备是否可以唤醒系统(on/off)。
echo off > /sys/class/xxx/power/control # 关闭设备电源
subsystem 文件
作用:
该文件存储设备所属的 子系统(Subsystem) 路径,用于标识设备在 Linux 设备模型中的分类。
子系统是内核中设备的逻辑分组(如 block、char、net、usb 等),同一子系统下的设备具有相似的操作接口。
内容格式:
文件内容为子系统在 /sys/subsystem/ 下的相对路径,例如:
char # 表示该设备属于字符设备子系统
uevent 文件
作用:
该文件用于内核向用户空间发送 设备热插拔事件(uevent) 的相关信息。
当设备插入、移除或状态改变时,内核会通过此文件传递事件参数(如设备类型、属性变更等),供用户空间工具(如 udev)处理。
内容格式:
文件内容为一系列 键值对,描述事件的具体信息,常见字段包括:
ACTION:事件类型(如 add、remove、change)。
DEVPATH:设备在 /sys 中的路径。
SUBSYSTEM:设备所属子系统。
DEVNAME:设备节点名称(如 /dev/ttyUSB0)。
MAJOR/MINOR:设备号的主 / 次部分。