尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

linux驱动-字符设备

linux驱动-字符设备
📅 发布时间:2026/6/26 16:37:25

目录

一、概念

核心特点

典型应用场景

与块设备的区别

二、代码实现

1、设备号

1.1 组成:主设备号 + 次设备号

1.2 设备号两种分配方式:静态分配、动态分配

1.2.1 静态分配(手动指定固定主设备号)

1.2.2 动态分配(推荐,内核自动分配空闲主号)

2、stuct cdev (字符设备核心结构体)

2.1 结构体原型(内核源码简化版)

2.2 配套核心函数(固定配对流程)

2.2.1 cdev_init

2.2.2 cdev_add

2.2.3. cdev_del

2.3 标准使用流程

3、创建设备容器

函数作用

4、创建单个设备实例,触发自动生成 /dev 节点

函数作用

只调用 device_create 不先 class_create?

三、完整实现


一、概念

字符设备是Linux系统中一种以字符为单位进行数据传输的设备类型,与块设备(以固定大小的数据块为单位)相对。字符设备通常用于需要逐字节或非结构化数据流传输的场景,例如键盘、鼠标、串口、终端等。

核心特点

  1. 按字节访问:数据以字符流形式传输,不支持随机访问(如直接跳转到指定位置)。
  2. 无缓存机制:数据通常直接传输,不经过系统缓冲区(少数例外可通过设置实现)。
  3. 实时性高:适用于对延迟敏感的设备,如传感器或交互式输入设备。

典型应用场景

  1. 输入设备:键盘、鼠标、触摸屏。
  2. 输出设备:串口终端、打印机。
  3. 虚拟设备:/dev/null、/dev/random等特殊文件。

与块设备的区别

特性字符设备块设备
数据传输单位字节(字符流)固定大小的数据块(如512B)
访问方式顺序访问支持随机访问
缓存机制通常无缓存通常带缓存
典型设备串口、终端硬盘、SSD

二、代码实现

1、设备号

1.1 组成:主设备号 + 次设备号

内核中每个字符设备唯一标识 = 设备号 dev_t

dev_t 是 32 位无符号整数:

  • 高 12 位:主设备号 major
  • 低 20 位:次设备号 minor

宏操作(内核代码)

MAJOR(dev_t dev); // 提取主设备号 unsigned int MINOR(dev_t dev); // 提取次设备号 unsigned int MKDEV(maj, min); // 拼接主次生成dev_t dev_t

1.2 设备号两种分配方式:静态分配、动态分配

1.2.1 静态分配(手动指定固定主设备号)

核心api

// 拼接主次号 dev_t MKDEV(unsigned int maj, unsigned int min); int register_chrdev_region(dev_t from, unsigned count, const char *name);

参数说明:

  • from:起始设备号(用 MKDEV 拼接好)
  • count:连续占用多少个次设备
  • name:驱动名,存到 /proc/devices

使用步骤

  1. 自己选一个未被占用的主设备号(查看/proc/devices)
  2. MKDEV(指定主号, 起始次号)生成 dev_t
  3. 调用 register_chrdev_region 注册
#define MY_MAJ 200 dev_t devno = MKDEV(MY_MAJ, 0); // 占用次设备0,共1个设备 register_chrdev_region(devno, 1, "static_dev");

释放接口

void unregister_chrdev_region(dev_t from, unsigned count);

优缺点

  • 优点:设备号固定,不用 mknod 每次换号;
  • 缺点:容易和系统已有驱动主设备号冲突,加载失败,嵌入式不推荐。
1.2.2 动态分配(推荐,内核自动分配空闲主号)

核心 api

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

参数说明:

  • dev:输出参数,内核回填分配好的起始 dev_t;
  • baseminor:起始次设备号,一般填 0;
  • count:连续次设备数量;
  • name:驱动名称。

返回值:成功 0,失败负错误码。

使用步骤

dev_t devno; // 自动分配主设备,次设备从0开始,1个设备 int ret = alloc_chrdev_region(&devno, 0, 1, "auto_dev"); if(ret < 0) return ret; // 提取打印主次号 printk("major:%d minor:%d", MAJOR(devno), MINOR(devno));

释放同样用

unregister_chrdev_region(devno, 1);

优缺点

  • 优点:不会冲突,不用手动查空闲主设备号,通用驱动首选;
  • 缺点:每次开机加载主设备号可能变化,搭配device_create自动生成 /dev 节点可规避该问题。

2、stuct cdev (字符设备核心结构体)

2.1 结构体原型(内核源码简化版)

struct cdev { struct kobject kobj; // 内核对象,sysfs 驱动管理 const struct file_operations *ops; // 绑定读写open/read/write接口 struct module *owner; // 所属模块 THIS_MODULE dev_t dev; // 该设备对应的完整设备号 unsigned int count; // 占用次设备数量 };

2.2 配套核心函数(固定配对流程)

2.2.1 cdev_init
void cdev_init(struct cdev *cdev, const struct file_operations *fops);

功能:只做结构体初始化,把file_operations函数集绑定到cdev->ops

2.2.2 cdev_add
int cdev_add(struct cdev *p, dev_t dev, unsigned count);

功能:把 cdev 注册进内核,系统能识别该字符设备

返回值:成功返回 0;失败负数错误码

2.2.3. cdev_del
void cdev_del(struct cdev *p);

功能:从内核注销 cdev

2.3 标准使用流程

// 1. 定义全局cdev对象 struct cdev mycdev; dev_t devno; // 初始化文件操作集 struct file_operations my_fops = { .open = dev_open, .read = dev_read, .write = dev_write, .release = dev_release, }; static int __init drv_init(void) { // 分配设备号 alloc_chrdev_region(&devno, 0, 1, "mydev"); // 2. 初始化cdev,绑定fops cdev_init(&mycdev, &my_fops); mycdev.owner = THIS_MODULE; // 标记所属模块,防卸载崩溃 // 3. 注册cdev到内核 cdev_add(&mycdev, devno, 1); return 0; } static void __exit drv_exit(void) { // 注销cdev cdev_del(&mycdev); // 释放设备号 unregister_chrdev_region(devno, 1); }

3、创建设备容器

函数作用

  1. 在/sys/class/下创建一个分类文件夹,用来归类同一类型的设备;
  2. 生成struct class结构体,是device_create必须依赖的父容器;
  3. 向内核设备模型注册设备分类,为后续自动生成/dev节点做前置准备。

版本区分

// Linux 5.x / 6.2及更早 struct class *cls = class_create(THIS_MODULE, "my_class"); // Linux 6.3+ struct class *cls = class_create("my_class");

执行后生成目录:/sys/class/my_class/

销毁配对

class_destroy(cls);

4、创建单个设备实例,触发自动生成 /dev 节点

必须依赖 class_create 返回的 class 指针才能调用。

函数作用

  1. 在上面创建好的 class 分类下,新建一个具体设备;
  2. 内核发送 uevent 事件给用户空间udev/mdev;
  3. udev 收到消息后自动执行类似mknod /dev/xxx c 主号 次号,生成设备文件;
  4. 在/sys/class/my_class/下生成对应设备的属性目录,存放设备信息。
struct device *dev = device_create(cls, NULL, devno, NULL, "mydev");

执行后两处产物:

  1. /dev/mydev应用程序操作的设备节点
  2. /sys/class/my_class/mydev/设备 sysfs 目录

销毁配对

device_destroy(cls, devno);

只调用 device_create 不先 class_create?

编译直接报错,缺少struct class参数,无法运行。

三、完整实现

char_demo.c

#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/uaccess.h> // 1. 自定义参数 #define DEV_MAJOR 230 #define DEV_MINOR 0 #define DEV_COUNT 1 #define DEV_NAME "mychar" // 2. 全局字符设备结构体 static struct cdev char_dev; static struct class *dev_class; static char buf[128] = "char device test data"; // read:用户层read()触发,把内核数据拷贝到用户空间 static ssize_t char_read(struct file *file, char __user *ubuf, size_t size, loff_t *off) { int ret; // 如果偏移超过缓冲区总长度,返回0,cat读到0就停止循环 if (*off >= sizeof(buf)) return 0; // 计算本次能读多少:剩余字节 和 用户传入size 取小值 size_t read_len = min(size, sizeof(buf) - *off); ret = copy_to_user(ubuf, buf + *off, read_len); if (ret != 0) return -EFAULT; // 关键:更新文件偏移,光标向后移动 *off += read_len; return read_len; } // write:用户层write()触发,用户数据拷贝进内核 static ssize_t char_write(struct file *file, const char __user *ubuf, size_t size, loff_t *off) { int ret; ret = copy_from_user(buf, ubuf, size); if(ret != 0) return -EFAULT; return size; } static int char_open(struct inode *inode, struct file *file) { printk(KERN_INFO "char dev open\n"); return 0; } static int char_release(struct inode *inode, struct file *file) { printk(KERN_INFO "char dev close\n"); return 0; } // 绑定操作函数 static struct file_operations char_fops = { .owner = THIS_MODULE, .open = char_open, .read = char_read, .write = char_write, .release = char_release, }; // 模块加载入口 static int __init char_dev_init(void) { dev_t devno = MKDEV(DEV_MAJOR, DEV_MINOR); int ret; // 1. 注册设备号 ret = register_chrdev_region(devno, DEV_COUNT, DEV_NAME); if(ret < 0){ printk("register dev fail\n"); return ret; } // 2. 初始化cdev,绑定操作集 cdev_init(&char_dev, &char_fops); // 3. 添加cdev到内核 ret = cdev_add(&char_dev, devno, DEV_COUNT); if(ret < 0){ unregister_chrdev_region(devno, DEV_COUNT); return ret; } // 4. 创建/class 新版Linux6.x class_create 只传名字 dev_class = class_create("char_class"); if (IS_ERR(dev_class)) { ret = PTR_ERR(dev_class); cdev_del(&char_dev); unregister_chrdev_region(devno, DEV_COUNT); return ret; } // 5. 生成/dev设备节点 device_create(dev_class, NULL, devno, NULL, DEV_NAME); printk("char device init ok /dev/%s\n", DEV_NAME); return 0; } // 模块卸载入口 static void __exit char_dev_exit(void) { dev_t devno = MKDEV(DEV_MAJOR, DEV_MINOR); // 销毁设备节点、类 device_destroy(dev_class, devno); class_destroy(dev_class); // 删除cdev、注销设备号 cdev_del(&char_dev); unregister_chrdev_region(devno, DEV_COUNT); printk("char device exit\n"); } module_init(char_dev_init); module_exit(char_dev_exit); MODULE_LICENSE("GPL");

Makefile

obj-m += char_demo.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

insmod char_demo.ko

root@1:/sys/class/char_class/mychar# ls
dev power subsystem uevent

root@1:/sys/class/char_class/mychar# cat uevent
MAJOR=230 主设备号
MINOR=0 次设备号
DEVNAME=mychar 设备名

root@1:/dev# xxd mychar
00000000: 6368 6172 2064 6576 6963 6520 7465 7374 char device test
00000010: 2064 6174 6100 0000 0000 0000 0000 0000 data...........
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
root@1:/dev#

相关新闻

  • HS-PEG-Silane 合成副产物产生机理与实操规避方案
  • Deep3D终极指南:如何用AI将普通2D视频变成立体3D大片?
  • AI音乐作品怎么发行

最新新闻

  • 企业微信AI Agent:企微官方能力+企业微信服务商方案+AI SCRM选型指南解读
  • AI 核算真的能降碳吗? - 蓝色星球
  • Adobe-GenP终极指南:三步解锁Adobe全家桶专业功能
  • Win11 OpenClaw全流程报错排查指南|解压 / 安装 / 启动问题优化方案
  • 高温工况下,温度变送器为什么总是电路板先挂?
  • 【Springboot毕设全套源码+文档】基于SpringBoot的学生评奖评优管理系统的设计与实现(丰富项目+远程调试+讲解+定制)

日新闻

  • Qwen2.5-Turbo百万上下文实战指南:百炼平台长文本处理全解析
  • 怎么监控对标账号更新,2026年作者监控工作流,5款深度对比
  • EdgeRemover:专业级Windows Edge浏览器管理工具,彻底解决顽固软件卸载难题

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号