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

深入理解 Linux 驱动中的 file_operations:从 C 语言函数指针到类比 C++ 虚函数表

深入理解 Linux 驱动中的 file_operations:从 C 语言函数指针到类比 C++ 虚函数表

在 Linux 字符设备驱动开发中,struct file_operations 是最核心的结构之一。它是用户空间和内核驱动之间的桥梁。很多初学者第一次看到下面这样的代码时,往往会一头雾水:

static const struct file_operations fops = {.owner   = THIS_MODULE,.open    = led_open,.write   = led_write,.release = led_close,
};

为什么要定义这样一个结构体?为什么里面全是函数指针?这些函数什么时候会被调用?这一切其实和 C 语言的函数指针机制密切相关,甚至可以和 C++ 的虚函数表对应起来。本文就来一步步讲清楚。

一、C 语言函数指针的基础

在 C 语言里,函数本质上也是一段内存中的代码,因此函数也有自己的地址。函数指针就是用来保存函数地址的变量。

例如,我们先定义一个普通函数:

#include <stdio.h>// 定义一个普通函数,接受两个整数,返回它们的和
int add(int a, int b) {return a + b;
}

然后定义一个函数指针,并指向这个函数:

int main() {// 定义一个函数指针,能指向 “接受两个 int 参数并返回 int”的函数int (*func_ptr)(int, int);// 让函数指针指向 add 函数func_ptr = add;// 通过函数指针调用 add,相当于执行 add(2, 3)int result = func_ptr(2, 3);printf("result = %d\n", result);  // 输出 result = 5return 0;
}

可以看到,函数指针就是把函数名的“地址”存到一个变量里,然后通过这个变量间接调用函数

二、结构体中的函数指针

函数指针不仅可以单独使用,还可以放在结构体中。这样就能把一组操作方法“打包”在一个结构体里。

例如,我们模拟一个“操作”结构体:

#include <stdio.h>// 定义一个结构体,里面放两个函数指针
struct operations {void (*say_hello)(void);   // 指向一个打印 hello 的函数void (*say_bye)(void);     // 指向一个打印 bye 的函数
};// 定义两个具体的函数
void hello_func(void) {printf("Hello!\n");
}void bye_func(void) {printf("Bye!\n");
}int main() {// 定义一个结构体变量 ops,并填充函数指针struct operations ops = {.say_hello = hello_func,.say_bye   = bye_func,};// 调用函数指针,就像虚函数一样实现了“动态调用”ops.say_hello(); // 输出 Hello!ops.say_bye();   // 输出 Bye!return 0;
}

这样,一个结构体就能代表“一套操作方法”。这和 Linux 驱动里的 struct file_operations 是完全一样的思路。

三、Linux 驱动中的 file_operations

Linux 内核中,struct file_operations 就是专门用来描述一个设备文件能做什么操作的结构体。它里面放了很多函数指针,例如:

  • .open:当用户调用 open("/dev/xxx") 时会执行的函数
  • .read:当用户调用 read(fd, buf, size) 时会执行的函数
  • .write:当用户调用 write(fd, buf, size) 时会执行的函数
  • .release:当用户调用 close(fd) 时会执行的函数

我们来看一个实际的驱动代码片段(以 LED 灯驱动为例),并加上详细注释:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>#define DEV_NAME "led_drv"// 1. open 函数,当应用层 open("/dev/led_drv") 时调用
static int led_open(struct inode *inode, struct file *file) {printk("led_open called\n");return 0;
}// 2. write 函数,当应用层 write(fd, buf, size) 时调用
static ssize_t led_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos) {printk("led_write called, user wants to send %zu bytes\n", count);return count; // 假装写成功
}// 3. release 函数,当应用层 close(fd) 时调用
static int led_close(struct inode *inode, struct file *file) {printk("led_close called\n");return 0;
}// 定义 file_operations 结构体,并把函数指针填进去
static const struct file_operations fops = {.owner   = THIS_MODULE,   // 一般写 THIS_MODULE,用来防止模块被卸载.open    = led_open,      // open 对应 led_open.write   = led_write,     // write 对应 led_write.release = led_close,     // close 对应 led_close
};static int major; // 保存主设备号// 模块加载时执行
static int __init led_init(void) {major = register_chrdev(0, DEV_NAME, &fops); printk("led driver loaded, major = %d\n", major);return 0;
}// 模块卸载时执行
static void __exit led_exit(void) {unregister_chrdev(major, DEV_NAME);printk("led driver unloaded\n");
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");

这段代码的核心就在于 fops,它把 openwriterelease 等操作函数“挂”到了设备上。以后应用层调用相应的系统调用时,内核就会根据这个表找到驱动里的对应函数。

四、和 C++ 虚函数表的类比

如果你熟悉 C++,会发现这和“虚函数表”的机制非常类似。

  • 在 C++ 里,一个含有虚函数的类,编译器会为它生成一张“虚函数表”(vtable),里面存放虚函数的地址。通过基类指针调用虚函数时,实际上就是查这张表,然后跳转到对应的实现。

  • 在 Linux 驱动里,file_operations 就是一张“函数指针表”。当用户空间调用系统调用时,内核就会查这张表,然后调用驱动里对应的函数。

两者的共同点是:

  • 都是通过函数指针实现“动态绑定”。
  • 都允许外部调用在运行时决定真正执行哪个函数。

区别在于:

  • C++ 的虚函数表是编译器自动生成的。
  • Linux 驱动的 file_operations 完全由程序员手动填写。

可以说,Linux 驱动就是用最原始的 C 语言机制,手工实现了类似 C++ 多态的功能

五、总结

  1. C 语言里可以用函数指针保存函数地址,并通过指针调用函数。
  2. 函数指针可以放在结构体中,形成一套“操作方法表”。
  3. Linux 内核提供的 struct file_operations 就是这样一张“方法表”,它把应用层的系统调用映射到驱动里的具体实现函数。
  4. file_operations 和 C++ 的虚函数表非常类似,本质上都是通过函数指针来实现运行时的动态调用。

(完)

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

相关文章:

  • centos7中MySQL 5.7.32 到 5.7.44 升级指南:基于官方二进制包的原地替换式升级
  • 有个需求:切换车队身份实现Fragment的Tab隐藏显示(车队不显示奖赏)
  • SNMPv3开发--简单使用
  • 【Linux基础】深入理解Linux环境下的BIOS机制
  • Python - 机器学习:从 “教电脑认东西” 到 “让机器自己学规律”
  • 项目管理和产品管理的区别
  • docker,mysql安装
  • vector的学习和模拟
  • 揭秘表格推理的“思维革命”:RoT模型介绍
  • 【机器学习基础】机器学习中的容量、欠拟合与过拟合:理论基础与实践指南
  • Vue生命周期、工程化开发和脚手架、组件化开发
  • 学习日志41 python
  • 打工人日报#20250830
  • 内网后渗透攻击--跨域攻击
  • 给某个conda环境安装CUDA 12.4版本 全局CUDA不变
  • Mybatis 动态sql
  • 【树形数据结构】李超线段树 (Li-Chao Tree)
  • 【深度学习新浪潮】有没有什么方法可以将照片变成线描稿,比如日式漫画的那种?
  • 嵌入式学习日记(38)HTTP
  • Ansible主机模式与文件导入技巧
  • 开发环境全面配置指南:语言环境与数据库工具
  • 【面试场景题】订单超时自动取消功能如何设计
  • 【机器学习入门】3.3 FP树算法——高效挖掘频繁项集的“树状神器”
  • 11 C 语言 sizeof 与指针实战指南:一维 / 二维数组计算注意事项 + 笔试真题解析 + sizeof strlen 对比
  • 谈谈线程的中断退出
  • nginx(自写)
  • [Windows] 剪映国际版CapCut 6.7.0 视频编辑处理,免费使用素材和滤镜
  • 倾斜摄影是选择RGB图像还是多光谱影响进行操作?
  • RestTemplate工具类用法总结
  • AI融合高等教育:从通识到专业 - 学科+AI人才培养白皮书(下)