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

《从系统调用到驱动回调:read() 如何映射到 chrdev_read()》

Linux 驱动中,为什么我们在用户程序里执行 read(fd, buf, size),内核就会自动去调用驱动中的 chrdev_read() 呢?

本文记录了我在学习字符设备驱动时,对用户态 read() / write() 如何触发内核态驱动回调函数的理解与分析。


一、字符设备驱动的基本组成

在 Linux 中,字符设备驱动(Character Device Driver) 是最常见的驱动类型之一。
每个字符设备驱动文件,基本都由以下几个核心模块组成:

模块作用示例
1. #include 头文件包含内核API与结构定义#include <linux/module.h>
2. 模块许可(License)指定驱动许可证,否则加载时会警告MODULE_LICENSE("GPL");
3. 文件操作集 (file_operations)定义驱动如何响应 open/read/write 等系统调用static struct file_operations my_fops = {...};
4. 主设备号与次设备号标识驱动类型与设备实例register_chrdev(200, "chrdev", &my_fops);
5. 模块加载与卸载函数定义驱动加载和卸载时的动作module_init() / module_exit()
6. 实际操作函数open、read、write、release 的实现ssize_t my_read(...) {...}
7. 元信息描述、作者等辅助信息MODULE_AUTHOR("Yunes");

这七个部分几乎出现在所有字符设备驱动中,是字符设备框架的“标准模板”。


二、内核态驱动代码示例

下面我们写一个最小可运行的字符设备驱动。

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/uaccess.h> // copy_to_user, copy_from_user#define CHRDEV_MAJOR 200        // 主设备号
#define CHRDEV_NAME  "chrdev"   // 设备名static char write_buf[100]; // 写缓冲(保存用户写入)
static char read_buf[100];  // 读缓冲(内核要返回的数据)
static char kernel_data[] = "kernel data!";// 打开设备
static int chrdev_open(struct inode *inode, struct file *filp)
{printk("chrdev open!\n");return 0;
}// 读取设备
static ssize_t chrdev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int ret;memcpy(read_buf, kernel_data, sizeof(kernel_data));ret = copy_to_user(buf, read_buf, cnt);if (ret == 0)printk("send successful!\n");elseprintk("send error!\n");return cnt;  // 返回实际读取的字节数
}// 写入设备
static ssize_t chrdev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int ret;ret = copy_from_user(write_buf, buf, cnt);if (ret == 0)printk("write successful: %s\n", write_buf);elseprintk("write error!\n");return cnt;
}// 关闭设备
static int chrdev_release(struct inode *inode, struct file *filp)
{printk("chrdev released.\n");return 0;
}// 设备操作函数集
static struct file_operations chrdev_fops = {.owner = THIS_MODULE,.open = chrdev_open,.read = chrdev_read,.write = chrdev_write,.release = chrdev_release,
};// 模块加载
static int __init chrdev_init(void)
{int ret = register_chrdev(CHRDEV_MAJOR, CHRDEV_NAME, &chrdev_fops);if (ret < 0)printk("register error!\n");elseprintk("chrdev init successful!\n");return 0;
}// 模块卸载
static void __exit chrdev_exit(void)
{unregister_chrdev(CHRDEV_MAJOR, CHRDEV_NAME);printk("chrdev exit.\n");
}module_init(chrdev_init);
module_exit(chrdev_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yunes");
MODULE_DESCRIPTION("Simple character device driver demo");

三、用户态测试程序

下面的程序用于测试 open/read/write/close 是否与驱动正确对应。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char const *argv[])
{char write_buf[100], read_buf[100];static char user_data[] = "user data!";int fd, tmp;if (argc != 3) {printf("Usage: %s <device> <0=read | 1=write>\n", argv[0]);return -1;}char *filename = (char *)argv[1];fd = open(filename, O_RDWR);if (fd < 0) {perror("open");return -1;}if (atoi(argv[2]) == 0) {tmp = read(fd, read_buf, sizeof(read_buf) - 1);if (tmp > 0) {read_buf[tmp] = '\0';printf("Read successful: %s\n", read_buf);} else if (tmp == 0)printf("EOF reached.\n");elseperror("read error");}if (atoi(argv[2]) == 1) {memcpy(write_buf, user_data, sizeof(user_data));tmp = write(fd, write_buf, sizeof(user_data));if (tmp < 0)perror("write error");}close(fd);return 0;
}

四、系统调用是如何映射到驱动函数的?

有人会问:

“为什么我在用户态执行 read(),驱动的 chrdev_read() 就自动被调用了?”

这是因为——内核在 file_operations 结构体里维护了函数指针映射关系。


内核中的调用流程如下:

用户态调用 read(fd, buf, size)↓
系统调用入口 sys_read()↓
根据 fd 查找 struct file 对象↓
找到 file->f_op 指针↓
调用 file->f_op->read(file, buf, size, &file->f_pos)↓
最终调用 chrdev_read()

所以,file_operations 就像是驱动的“函数接口表”,
内核通过它找到对应的驱动回调函数。


五、系统调用与驱动函数的对应关系

用户态函数驱动回调函数作用
open().open = chrdev_open打开设备
read().read = chrdev_read从设备读取数据
write().write = chrdev_write写入数据到设备
close().release = chrdev_release关闭设备文件

file_operations 就是把这些系统调用“挂接”到驱动的核心机制。


六、总结

Linux 字符设备驱动的核心机制,就是通过 struct file_operations
将用户态系统调用(open/read/write/close)映射到内核态驱动函数

🔹 register_chrdev() 告诉内核这个主设备号对应哪个函数集;
🔹 用户态执行 read(),系统调用进入内核;
🔹 内核通过 file_operations 找到 chrdev_read()
🔹 最终完成一次用户态与内核态的数据交互。


一句话总结:

file_operations 是用户态与内核态的“桥梁”,
每一个系统调用,最终都要在驱动中找到它的影子。

http://www.dtcms.com/a/481919.html

相关文章:

  • 【杂记】AI智能体产品开发中的多种语言混合编程
  • 财务开票的类型、异同点以及蓝字和红字的区别
  • 高阶数据结构-并查集
  • 从零开始的C++学习生活 8:list的入门使用
  • 平面设计师网站宁波制作网站哪个好
  • 简单的网站制作wordpress添加广告插件吗
  • 应用软件程序页面类型与核心元素解析
  • 从基金入门到长期主义:我如何建立自己的投资认知体系
  • 微算法科技MLGO推出隐私感知联合DNN模型部署和分区优化技术,开启协作边缘推理新时代
  • (20)100天python从入门到拿捏《JSON 数据解析》
  • 战略携手!沃飞长空与西门子数字化工业软件签约
  • 从零开始的C++学习生活 7:vector的入门使用
  • 队列数据结构详解:从原理到实现
  • JavaScript构造函数详解
  • 学习无刷直流电机驱动硬件
  • 案例应聘网络营销做网站推广网络营销的种类有哪些
  • 西安网站建设网莱芜网站推广
  • 从RNN到LSTM:深入理解循环神经网络与长短期记忆网络
  • AIDL 接口的定义与生成,使用
  • 深度解析过拟合与欠拟合:从诊断到正则化策略的全面应对
  • python - 装箱项目/3D Bin Packing problem
  • 【自动驾驶】自动驾驶概述 ⑨ ( 自动驾驶软件系统概述 | 预测系统 | 决策规划 | 控制系统 )
  • STM32F103C8T6 GY-906 MLX90614ESF 无线测温传感器模块的使用方法和代码驱动
  • 常规的紫外工业镜头有哪些?能做什么?
  • 香洲网站建设品牌形象设计方案
  • 突破AR视觉交互边界:Unity赋能Rokid AR眼镜实现高精度图像识别与实时跟踪
  • zabbix安装
  • 【VTK实战】vtkDepthImageToPointCloud:从2D深度图到3D点云,手把手教你落地3D扫描/AR场景
  • 【Git版本控制】Git初识、安装、仓库初始化与仓库配置(含git init、git config与配置无法取消问题)
  • 浅谈目前主流的LLM软件技术栈:Kubernetes + Ray + PyTorch + vLLM 的协同架构