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

Linux创建字符设备

Linux创建字符设备

驱动程序主要包括以下几个关键部分:

  1. 注册设备号和 cdev
  2. 实现 file_operations 结构体(包含 read/write 等操作)
  3. 创建设备类和设备节点
  4. 资源释放和模块卸载

file_operations 结构体模板:
static struct file_operations mydev_fops = {.owner   = THIS_MODULE,     // 模块所有者(必须填写).open    = mydev_open,      // 打开操作(自己实现的函数).release = mydev_release,   // 关闭/释放.read    = mydev_read,      // 读操作.write   = mydev_write,     // 写操作// 可以根据需要实现ioctl、poll等
};

程序实践

Makefile

export ARCH=arm64
#需要先设置交叉编译器环境变量(可以参考我另一篇博客https://www.cnblogs.com/tianwuyvlianshui/p/19571779)
export CROSS_COMPILE=aarch64-linux-gnu-obj-m += mychardev.oKDIR := $(HOME)/Desktop/SDK/kernel
PWD  := $(shell pwd)all:make -C $(KDIR) M=$(PWD) \ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modulesclean:make -C $(KDIR) M=$(PWD) clean

mychardev.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>// 设备名和类名的宏定义
#define DEV_NAME   "device_test"   // 设备节点名称
#define CLASS_NAME "class_test"    // 设备类名称static dev_t dev_num;                   // 保存设备号
static struct cdev cdev_test;           // 字符设备结构体
static struct class *class_test;        // 设备类指针// 打开设备时调用的函数
// inode: 指向文件的 inode 结构体指针
// file:  文件结构体指针
static int chrdev_open(struct inode *inode, struct file *file)
{printk(KERN_INFO "mychardev: chrdev_open called\n"); // 打印打开信息到内核日志return 0; // 返回0表示成功
}// 读设备时调用的函数
// file: 文件结构体指针
// buf:  用户空间的缓冲区指针
// size: 期望读取的字节数
// off:  偏移量指针
static ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{printk(KERN_INFO "mychardev: chrdev_read called\n"); // 打印读操作信息return 0; // 返回0表示没有数据可读
}// 写设备时调用的函数
// file: 文件结构体指针
// buf:  用户空间的缓冲区指针
// size: 要写入的字节数
// off:  偏移量指针
static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{printk(KERN_INFO "mychardev: chrdev_write called\n"); // 打印写操作信息return size; // 返回写入的字节数,表示写入成功
}// 关闭设备时调用的函数
// inode: 指向文件的 inode 结构体指针
// file:  文件结构体指针
static int chrdev_release(struct inode *inode, struct file *file)
{printk(KERN_INFO "mychardev: chrdev_release called\n"); // 打印关闭信息return 0; // 返回0表示成功
}// file_operations 结构体,指明本设备支持的操作
static struct file_operations cdev_fops_test = {.owner   = THIS_MODULE,      // 拥有者,一般为 THIS_MODULE.open    = chrdev_open,      // open 操作.read    = chrdev_read,      // read 操作.write   = chrdev_write,     // write 操作.release = chrdev_release,   // release 操作
};// 模块加载时自动调用的初始化函数
static int __init chrdev_fops_init(void)
{int ret;int major, minor;// 1. 自动申请设备号,主设备号和次设备号由内核分配ret = alloc_chrdev_region(&dev_num, 0, 1, DEV_NAME);if (ret < 0) {printk(KERN_ERR "mychardev: alloc_chrdev_region failed\n"); // 申请失败return ret;}major = MAJOR(dev_num); // 获取主设备号minor = MINOR(dev_num); // 获取次设备号printk(KERN_INFO "mychardev: alloc_chrdev_region ok: major=%d, minor=%d\n", major, minor);// 2. 初始化 cdev 结构体,并添加到内核cdev_init(&cdev_test, &cdev_fops_test); // 初始化 cdevret = cdev_add(&cdev_test, dev_num, 1); // 注册 cdev 到内核if (ret < 0) {printk(KERN_ERR "mychardev: cdev_add failed\n");unregister_chrdev_region(dev_num,1); // 失败时释放设备号return ret;}// 3. 创建设备类,便于自动创建设备节点class_test = class_create(THIS_MODULE, CLASS_NAME);if (IS_ERR(class_test)) {printk(KERN_ERR "mychardev: class_create failed\n");cdev_del(&cdev_test);unregister_chrdev_region(dev_num,1);return PTR_ERR(class_test);}// 4. 创建设备节点 /dev/device_testif (device_create(class_test, NULL, dev_num, NULL, DEV_NAME) == NULL) {printk(KERN_ERR "mychardev: device_create failed\n");class_destroy(class_test);cdev_del(&cdev_test);unregister_chrdev_region(dev_num,1);return -ENOMEM;}printk(KERN_INFO "mychardev: chrdev driver loaded successfully\n"); // 驱动加载成功return 0;
}// 模块卸载时自动调用的清理函数
static void __exit chrdev_fops_exit(void)
{device_destroy(class_test, dev_num);    // 删除设备节点class_destroy(class_test);              // 删除设备类cdev_del(&cdev_test);                   // 注销 cdevunregister_chrdev_region(dev_num, 1);   // 释放设备号printk(KERN_INFO "mychardev: chrdev driver unloaded\n"); // 卸载信息
}// 指定模块的初始化和退出函数
module_init(chrdev_fops_init);   // 加载模块时调用
module_exit(chrdev_fops_exit);   // 卸载模块时调用
MODULE_LICENSE("GPL");           // 模块许可证声明

装载,查看,卸载

image-20260203225810422

http://www.jsqmd.com/news/371309/

相关文章:

  • C#上位机
  • 概念完整性的力量——架构师与“外科手术队伍”
  • 【最小均方(LMS)算法和归一化最小均方(NLMS)算法进行了比较分析】NLMS比LMS更能抵抗输入相关性研究附Matlab代码
  • STM32 CubeIDE 读取模拟信号电压值
  • 一种基于单目相机的圆柱体/长方体体积测量方法
  • 【状态估计】【雷达】基于扩展卡尔曼滤波的雷达目标跟踪融合研究附Matlab代码
  • 用FastAPI打造LangChain生产级后端架构,小白也能轻松上手
  • 【状态估计】非线性受控动力系统的线性预测器——Koopman模型预测MPC附Matlab代码
  • 【综合能源】电热冷综合能源优化调度研究附Matlab代码
  • 适合转行大模型吗?大模型的未来前景怎么样?普通人转行大模型:收藏这份学习资料,开启高薪未来!
  • 零代码搭建RAG智能客服:7x24小时精准应答,提升效率,收藏必备!
  • 【状态估计】【卡尔曼滤波器】基本离散kalman、固定增益的kalman、平方根kalman、遗忘因子kalman、扩大P卡尔曼、自适应kalman、有限K减小kalman雷达轨迹附Matlab代码
  • 【状态估计】【扩展卡尔曼滤波算法的神经网络训练】BP神经网络、扩展卡尔曼滤波EKF+BP、粒子滤波PF轨迹估计研究附Matlab代码
  • 【最优无功功率分配】基于改进路径探索算法(PFA)的最优无功功率分配研究【IEEE30、IEEE57、IEEE118、IEEE300节点】附Matlab代码
  • 夹具板工作流程总结
  • C++之函数模板
  • 2026-02-11学习
  • ERP MES PLM SCM QMS EAM IOT WMS
  • 从群发文案到私人定制:手把手教你微调一个懂人情世故的拜年助手
  • 互联网大厂Java面试场景:Redis缓存与Spring Cloud微服务实战解析
  • Java 中的 封装、继承、多态
  • flask context (Application/Request)深度详解
  • 垂域大模型评估不再靠“感觉”:用结构化测试集+自动化打分实现效果可量化
  • B3871 [GESP202309 五级] 因数分解
  • MyBatis XML 里<![CDATA[ ]]>的使用
  • elasticSearch之API:基础命令及文档基本操作
  • 【Azure Event Hub】在VMSS中使用WAD(Window Azure Diagnostic)插件发送日志到Event Hub中报错分析
  • Flask-SocketIO深度详解
  • 数据可视化能将复杂的临床数据转换成直观的图形和图像,展示数据间的关联和趋势,通过人类视觉思维能力帮助理解大量数据信息,发现数据中的规律,从而提高数据使用效率
  • Flask-Migrate深度详解