当前位置: 首页 > news >正文

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, &reg_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:设备号的主 / 次部分。
在这里插入图片描述

相关文章:

  • 爱普生研发全新恒温晶体振荡器 “省、小、精”加速通信产业释放新质动能!
  • Dubbo学习(一):Dubbo介绍
  • 韩国IKS特价型号找游工IKS-LM-SN1G交换机
  • SpringMVC执行流程
  • Odoo 19 路线图(新功能)
  • C语言字符数组输入输出方法大全(附带实例)
  • Python训练day40
  • API标准的本质与演进:从 REST 架构到 AI 服务集成
  • DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
  • 双碳时代,能源调度的难题正从“发电侧”转向“企业侧”
  • LeetCode--23.合并k个升序链表
  • 【计算机网络】非阻塞IO——select实现多路转接
  • 量化面试绿皮书:5. 扑克牌游戏概率与期望值
  • Redis哨兵
  • 【C】-数据类型及存储空间长度
  • 使用python把json数据追加进文件,然后每次读取时,读取第一行并删除
  • 【时时三省】(C语言基础)局部变量和全局变量
  • 12.6Swing控件4 JSplitPane JTabbedPane
  • C++ if语句完全指南:从基础到工程实践
  • ​​TLV4062-Q1​​、TLV4082-Q1​​迟滞电压比较器应用笔记
  • 成都网络公司网站建设/营销方案推广
  • 网站建设 目标/晚上国网app
  • 如何投诉网站制作公司/网站建设选亿企网络
  • 网站推广的主要方法有哪些?/小程序制作一个需要多少钱
  • 互联网医院建设方案/昆山优化外包
  • 怎么找做网站的公司/seo人员培训