GoDm@'s Blog

Linux文件操作调用链探究

简单来说,底层的核心逻辑是基于C语言的“多态”实现,通过函数指针表 (Function Pointer Table) 进行的“间接调用”

内核本身(VFS层)并不“认识” 你的 my_led_write 函数。它只知道一个通用的“合同”或“接口”(即 struct file_operations),你的驱动在注册时,把自己的函数地址“登记”在了这份合同上。

当用户调用 write() 时,内核会通过一系列的查找,最终找到你登记的那个函数地址,然后调用它。


🏛️ 底层逻辑的三大组件

内核的这种设计,依赖于三个关键的结构体,它们共同构成了这个调用框架:

1. struct file_operations (fops)

这是**“合同” (Interface)**。

2. struct cdev (字符设备)

这是**“登记处” (Registry)**。

3. struct filestruct inode

这是**“上下文” (Context)**。


⛓️ 完整调用链 (以 write() 为例)

现在,我们把一个用户空间的 write() 调用串联起来,看看内核是如何一步步找到你的 my_led_write 函数的。

前提: 你的驱动已加载 (my_led_init 已执行)。alloc_chrdev_region 分配了设备号 (假设 240:0),cdev_add 已将 led_cdev (绑定了 led_fops) 注册到内核。

阶段一:用户空间 open() (准备工作)

  1. 用户空间: 应用程序调用 fd = open("/dev/my_led", O_RDWR);

  2. 系统调用: 触发 sys_open() 系统调用,进入内核空间。

  3. VFS (内核): 内核的 VFS (虚拟文件系统) 层开始工作。

    • 它沿着路径名找到 /dev/my_led 对应的 inode

    • VFS 发现这个 inode 是一个字符设备文件 (S_IFCHR)。

    • VFS 从 inode 中读取设备号 (dev_t),即 240:0。

  4. cdev 查找: VFS 使用这个设备号 240:0,去 cdev 登记处(一个哈希表)查找。

  5. 找到驱动: VFS 找到了你注册的 led_cdev

  6. 创建 file 对象: 内核创建一个新的 struct file 对象来代表这个打开的实例。

  7. 核心步骤 (绑定): 内核从 led_cdev 中取出 file_operations 指针 (即 led_fops),并将其赋值给 file->f_op

// VFS 内部的伪代码
struct file *new_file = create_file_struct();
new_file->f_op = led_cdev->ops; // led_cdev->ops 就是你的 led_fops
  1. 调用驱动的 open VFS 检查 file->f_op->open 是否存在。如果存在(即你的 led_fops.open),就调用它。

  2. 返回 fd open 完成,sys_open 返回文件描述符 fd 给用户空间。这个 fd 在进程中唯一标识了第 6 步创建的 struct file 对象。

阶段二:用户空间 write() (执行调用)

  1. 用户空间: 应用程序调用 write(fd, "1", 1);

  2. 系统调用: 触发 sys_write() 系统调用,进入内核空间。

  3. VFS (内核):

    • VFS 使用 fd 找到对应的 struct file 对象 (就是阶段一第 6 步创建的那个)。

    • VFS 检查这个 struct file 对象,确保它可写。

  4. 核心分发 (Dispatch): VFS 执行类似如下的逻辑(这是关键!):

// VFS 内部的伪代码 (sys_write -> vfs_write)
struct file *filp = find_file_by_fd(fd);

// 检查 "合同" 上有没有 write 方法
if (filp->f_op && filp->f_op->write) {
    // 如果有,就调用它!
    ret = filp->f_op->write(filp, user_buf, count, &filp->f_pos);
}
  1. 执行驱动代码:

    • 因为 filp->f_op 指向的就是你的 led_fops

    • 所以 filp->f_op->write 指向的就是你的 my_led_write 函数。

    • 内核间接调用了你的 my_led_write 函数,CPU 控制权转移到你的驱动代码。

  2. 驱动工作: 你的 my_led_write 函数被执行。它使用 copy_from_user 拷贝数据,然后(虚拟地)点亮 LED。

  3. 返回: 你的 my_led_write 返回 1 (写入的字节数)。

  4. VFS (内核): VFS 拿到返回值,sys_write 随之返回。

  5. 用户空间: 应用程序的 write() 调用返回 1


📈 调用链图示

这个流程可以简化为:

User Space -> System Call -> VFS (Generic Layer) -> Function Pointer Call -> Device Driver (Specific Code)

[用户空间]
   write(fd, ...);
       |
       v
[内核空间 - 系统调用]
   sys_write(fd, ...);
       |
       v
[内核空间 - VFS]
   vfs_write(...) {
     // 1. 根据 fd 找到 struct file *filp
     struct file *filp = fget(fd);
     
     // 2. 找到 filp->f_op (这个在 open 时已被设为 led_fops)
     // 3. 执行函数指针
     filp->f_op->write(filp, ...);  <---- 核心跳转!
   }                                    |
                                        |
       +--------------------------------+
       |
       v
[内核空间 - 你的驱动]
   my_led_write(struct file *filp, ...) {
     // ...
     // copy_from_user(...)
     // 控制硬件
     // ...
     return count;
   }

核心总结

内核调用驱动的逻辑,就是**“注册与回调”**:

  1. 注册 (Registration): 你的驱动 init 时,通过 cdev_add 把一个包含函数指针的 “合同” (file_operations) 注册到内核的 cdev 系统中。

  2. 回调 (Callback): 当 VFS 需要对你的设备进行操作时,它不关心你的驱动叫什么,它只通过 file 对象找到那份 “合同”,然后调用 “合同” 上约定的函数指针。


共计约1.5k字。于2025/10/09首次发布,最后更新于2025/11/26。

本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

AI辅助创作:本文部分内容由 Gemini 3 Pro 生成,最终版本由作者审核与修改。了解该AI模型

#Linux | #调用链 |
  1. 🏛️ 底层逻辑的三大组件
    1. 1. struct file_operations (fops)
    2. 2. struct cdev (字符设备)
    3. 3. struct file 和 struct inode
  2. ⛓️ 完整调用链 (以 write() 为例)
    1. 阶段一:用户空间 open() (准备工作)
    2. 阶段二:用户空间 write() (执行调用)
  3. 📈 调用链图示
  4. 核心总结