如果你第一次看 Linux 内核、Android HAL、Binder 驱动、FFmpeg 源码,很容易有一个强烈感受:
👉 怎么全是 struct + 函数指针?几乎看不到“类”。
struct file_operations { int (*open)(struct inode*, struct file*); ssize_t (*read)(struct file*, char*, size_t, loff_t*); ssize_t (*write)(struct file*, const char*, size_t, loff_t*);};
struct hw_module_methods_t { int (*open)(const struct hw_module_t*, const char*, struct hw_device_t**);};
binder_device->ops->ioctl(...);
看起来像“面向过程”,但实际上:
👉这是操作系统世界的“对象模型”。
一、先给结论:系统层的“对象”,本质是 struct + 函数表
在系统层:
也就是说:
👉 struct + 函数指针 = 对象 + 接口 + 多态。
二、为什么系统层不用 C++ / Java 的类?
不是“不会”,是不能随便用。
系统层面对的不是“开发效率”,而是:
而 C 风格结构刚好满足:
👉 这是“操作系统级接口”,不是“应用层接口”。
三、系统层对象模型的真实结构
典型模型:
struct DeviceOps { int (*open)(void*); int (*close)(void*);};struct Device { struct DeviceOps* ops; int fd;};
调用:
dev->ops->open(dev);
这在机制上,等价于:
dev->open(); // C++ 虚函数
或:
dev.open(); // Java 接口调用
区别只是:
👉 抽象模型完全一致。
四、Linux 为什么大量使用这种模型?
1️⃣ 驱动是“可插拔模块”
Linux 设备模型:
struct file_operations;struct net_device_ops;struct bus_type;
驱动注册时:
运行时:
👉 这本质就是:
加载一个实现 → 挂一张函数表 → 内核统一调度。
这就是“插件架构”。
2️⃣ 内核不能依赖 C++ 运行时
内核里不能:
所以它必须:
👉 用最小语义单位搭最大抽象系统。
3️⃣ 内核要控制每一个字节
struct 模型可以:
这是面向硬件的能力。
五、Android 为什么也是这一套?
你会在 Android 各层看到同构设计:
1️⃣ HAL
struct hw_module_methods_t { int (*open)(...);};
厂商只要:
👉 系统通过接口调用,不关心厂商实现。
2️⃣ Binder 驱动
Binder 驱动是纯 C:
- struct binder_driver_return
而 libbinder / system_server 是 C++:
👉 C 提供稳定模型 👉 C++ 提供工程封装
3️⃣ Android framework
Camera / Audio / Sensor:
HAL 是 struct + ops
Framework 是 C++ 虚类
App 层是 Java 接口
👉 同一模型,三种实现。
六、这套模型解决了系统层三大核心问题
✅ 1. 稳定接口(ABI)
函数表 = 固定协议 可升级、可替换、可回滚
✅ 2. 模块解耦
内核 / framework 不依赖具体设备 设备只实现接口
✅ 3. 运行时多态
同一个调用点 不同设备、不同实现
七、为什么说“回调 / 接口 / 虚函数 / 驱动模型”是同一件事?
普通回调:
系统对象模型:
区别只是:
👉 对象模型 = 一组“语义相关的回调”。
所以:
八、一句话总结(系统工程师版)
👉 系统层不是“有没有对象”,而是“怎么实现对象”。 👉 struct + 函数表,是操作系统世界最稳定的对象模型。
九、你再看系统源码,会发生什么变化?
你会从:
❌ “好多结构体,看不懂”
变成:
✅ “这是接口” ✅ “这是实现” ✅ “这是设备实例” ✅ “这是多态调度点”
👉 系统源码可读性瞬间上一个层级。
十、终极总结
硬件世界 → struct 行为抽象 → 函数表 系统架构 → 对象模型
Linux / Android 不是“面向过程”, 而是**“用 C 实现的面向对象系统”。**
当你真正理解 struct + 函数表,
你就已经站在:
👉 系统接口设计层。