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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/85958.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【vLLM 学习】Cpu Offload Lmcache

vLLM 是一款专为大语言模型推理加速而设计的框架&#xff0c;实现了 KV 缓存内存几乎零浪费&#xff0c;解决了内存管理瓶颈问题。 更多 vLLM 中文文档及教程可访问 →https://vllm.hyper.ai/ *在线运行 vLLM 入门教程&#xff1a;零基础分步指南 源码 examples/offline_inf…

基于深度强化学习的Scrapy-Redis分布式爬虫动态调度策略研究

在大数据时代&#xff0c;网络数据的采集与分析变得至关重要&#xff0c;分布式爬虫作为高效获取海量数据的工具&#xff0c;被广泛应用于各类场景。然而&#xff0c;传统的爬虫调度策略在面对复杂多变的网络环境和动态的抓取需求时&#xff0c;往往存在效率低下、资源浪费等问…

openlayers实现可拖拽的节点(类似知识图谱)

/** * 本文介绍了实现知识图谱可视化的技术方案&#xff0c;主要分为两个图层实现&#xff1a; * 1、线图层 不拖动 * 2、点图层 需要拖动 */ 线图层 - 负责绘制静态连接线&#xff0c;使用LineString创建线要素并添加到矢量图层&#xff1b; // 线图层 export function add…

酷黑NBA足球赛事直播源码体育直播M39模板赛事源码

源码名称&#xff1a;NBA足球赛事直播源码酷黑体育直播M39模板赛事源码 开发环境&#xff1a;帝国cms7.5 空间支持&#xff1a;phpmysql 带软件采集&#xff0c;可以挂着自动采集发布&#xff0c;无需人工操作&#xff01; 演示地址&#xff1a;https://www.52muban.com/shop…

Verilog编程技巧01——如何编写三段式状态机

前言 Verilog编程技巧系列文章将聚焦于介绍Verilog的各种编程范式或者说技巧&#xff0c;编程技巧和编程规范有部分重合&#xff0c;但并非完全一样。规范更注重编码的格式&#xff0c;像变量命名、缩进、注释风格等&#xff0c;而编程技巧则更偏重更直观易读、更便于维护、综合…

豆包和deepseek 元宝 百度ai区别是什么

豆包、DeepSeek、元宝和百度 AI 有以下区别&#xff1a; 开发公司 豆包5&#xff1a;由字节跳动公司基于云雀模型开发。DeepSeek4&#xff1a;是深度求索打造的开源多模态大模型。元宝1&#xff1a;是腾讯混元模型的落地产品&#xff0c;整合了 DeepSeek - R1 与混元模型。百…

网页端 js 读取发票里的二维码信息(图片和PDF格式)

起因 为了实现在报销流程中&#xff0c;发票不能重用的限制&#xff0c;发票上传后&#xff0c;希望能读出发票号&#xff0c;并记录发票号已用&#xff0c;下次不再可用于报销。 基于上面的需求&#xff0c;研究了OCR 的方式和读PDF的方式&#xff0c;实际是可行的&#xff…

读文献先读图:GO弦图怎么看?

GO弦图&#xff08;Gene Ontology Chord Diagram&#xff09;是一种用于展示基因功能富集结果的可视化工具&#xff0c;通过弦状连接可以更直观的展示基因与GO term&#xff08;如生物过程、分子功能等&#xff09;之间的关联。 GO弦图解读 ①内圈连线表示基因和生物过程之间的…

pandas随笔

主要操作两个对象&#xff1a;一维带标签数组 和 二维表格DataFrame 一维带标签数组Series pd.Series([1, 3, 5, np.nan, 6, 8]) &#xff0c;结果如下&#xff1a; 可指定索引&#xff0c;pd.Series([1, 3, 5], index[a, b, c]) 二维表格DataFrame 创建时需要指定列名&a…

java教程笔记(十一)-泛型

Java 泛型&#xff08;Generics&#xff09;是 Java 5 引入的重要特性之一&#xff0c;它允许在定义类、接口和方法时使用类型参数。泛型的核心思想是将类型由具体的数据类型推迟到使用时再确定&#xff0c;从而提升代码的复用性和类型安全性。 1.泛型的基本概念 1. 什么是泛…

力扣刷题(第四十九天)

灵感来源 - 保持更新&#xff0c;努力学习 - python脚本学习 反转链表 解题思路 迭代法&#xff1a;通过遍历链表&#xff0c;逐个改变节点的指针方向。具体步骤如下&#xff1a; 使用三个指针&#xff1a;prev&#xff08;初始为None&#xff09;、curr&#xff08;初始为…

设置应用程序图标

(1)找一张图片 (2)然后转ico图片 在线生成透明ICO图标——ICO图标制作 验证16x16就可以 降低exe大小 (3) 在xxx.pro修改 添加 &#xff08;4&#xff09; 删除 build 和 xxxpro_user文件 (5)编译project 和运行xx.exe (6)右键 设置快捷方式

免费wordpress模板下载

西瓜红色的免费wordpress模板&#xff0c;简洁实用又容易上手&#xff0c;适合新手使用。 下载 https://www.waimaoyes.com/moban/2231.html

【React】React 18 并发特性

React 18 引入了 并发特性&#xff08;Concurrent Features&#xff09;&#xff0c;这是一次对 React 渲染机制的重大升级&#xff0c;让 React 更加智能、响应更流畅、资源更节省。 我们来详细讲解一下它的原理、特性、API 以及实际应用。 &#x1f9e0; 一、什么是并发特性…

FFMPEG 提取视频中指定起始时间及结束时间的视频,给出ffmpeg 命令

以下是提取视频中指定起始时间及结束时间的 ffmpeg 命令示例: bash 复制 ffmpeg -i input.mp4 -ss 00:01:30.00 -to 00:05:00.00 -c copy output.mp4 其中,-i input.mp4 是指定要处理的输入视频文件为 “input.mp4”。 -ss 00:01:30.00 表示指定视频的起始时间为 1 分 30 …

mybatis的if判断==‘1‘不生效,改成‘1‘.toString()才生效的原因

mybatis的xml文件中的if判断‘1’不生效&#xff0c;改成’1’.toString()才生效 Mapper接口传入的参数 List<Table> queryList(Param("state") String state);xml内容 <where><if test"state ! null and state 1">AND EXISTS(select…

AI 模型分类全解:特性与选择指南

人工智能&#xff08;AI&#xff09;技术正以前所未有的速度改变着我们的生活和工作方式。AI 模型作为实现人工智能的核心组件&#xff0c;种类繁多&#xff0c;功能各异。从简单的线性回归模型到复杂的深度学习网络&#xff0c;从文本生成到图像识别&#xff0c;AI 模型的应用…

01-python爬虫-第一个爬虫程序

开始学习 python 爬虫 第一个获取使用最多的网站-百度 源代码 并将源代码保存到文件中 from urllib.request import urlopenurl https://www.baidu.com resp urlopen(url)with open(baidu.html, w, encodingutf-8) as f:f.write(resp.read().decode(utf-8))知识点&#xf…

四六级监考《培训学习》+《培训考试》

1 线上注册 &#xff08;网址&#xff1a; https://passport.neea.edu.cn 2 登录培训平台参加线上必修课程学习和考核 &#xff08;平台网址&#xff1a; https://kwstudy.neea.edu.cn 注意选择学员入口&#xff09; 3 考试要求&#xff1a;考试成绩须达应到80分以上&#xf…

回顾Java与数据库的30年历程

当 Java 1.0 于 1996 年推出时&#xff0c;语言和互联网都与今天大不相同。当时&#xff0c;网络主要是静态的&#xff0c;而 Java 承诺通过注入交互式游戏和动画来为网络注入活力&#xff0c;这一承诺极具前景。根据 1995 年写给《连线》杂志的 David Banks 的说法&#xff0c…