13 工程扩展:自动注册与 Linux 内核中的对象组织
13 工程扩展:自动注册与 Linux 内核中的对象组织
C 语言面向对象系列
前面的 LED 框架已经能够通过统一接口管理不同设备。最后一篇讨论模块和设备数量增加后的组织方式:如何安排初始化、注册对象,以及如何对照这些概念阅读 Linux 内核代码。
模块增多后的初始化管理
简单项目可以在 main() 中依次调用初始化函数:
1 | int main(void) |
模块较少时,这种方式清楚直接。模块增多后,新增模块需要修改这份调用列表,初始化顺序和不同子系统之间的关系也需要集中维护。
一种扩展方式是让各模块声明自己的初始化入口,再由系统统一调用。
收集并执行初始化函数
先定义初始化函数指针类型。假设这些指针已被收集到一段连续区域,系统可以遍历调用:
1 | typedef int (*InitCall_t)(void); |
__initcall_start 和 __initcall_end 分别表示这段区域的开始和结束。循环每次取出一个函数指针,再执行对应初始化函数。
下面用编译器的 section 属性,将每个模块的函数指针放入指定段:
1 | static int led_driver_init(void) |
这里的 section 可以先理解为目标文件中按名称组织的一段内容。宏将指针放入 .initcall 段,链接脚本再负责收集、保留这些内容,并提供起止位置。
这是一个简化模型,编译器支持、链接脚本、初始化顺序和失败处理还需要配套设计。单独使用这段宏,并不会自动解决模块依赖或调用顺序。
Linux 的 module_init() 也用于声明初始化入口:编入内核的代码在启动阶段调用,可加载模块在装入时调用。上面的同名宏只演示收集入口的思路,实际行为见 Linux 内核的驱动初始化文档。
从 LED 操作表理解文件操作接口
前面的 LED 操作表包含一组函数指针:
1 | typedef struct { |
文件操作也可以采用类似组织方式。下面定义的是教学模型,用来说明 file_operations 一类接口的结构:
1 | typedef struct { |
文件对象保存操作表和私有数据:
1 | typedef struct { |
统一入口先检查指针,再调用操作表中的读取函数:
1 | int vfs_read(File_t *file, char *buf, int len) |
这个过程与 led_on(led) → led->ops->on(led) 相同:入口负责公共处理,操作表提供具体实现。这里的类型和函数签名经过简化,阅读 Linux 源码时应以实际定义为准。
在内核代码中辨认相似结构
可以用前面掌握的概念理解以下结构:
| 内核中的名称 | 可以关注的设计内容 |
|---|---|
file_operations |
文件操作对应的一组函数指针 |
| I2C driver | 驱动描述及 probe/remove 等回调 |
gpio_chip |
GPIO 控制器数据和操作回调 |
net_device_ops |
网络设备的操作表 |
container_of |
从成员指针取得所属结构体 |
module_init |
模块初始化入口的声明 |
阅读时先区分结构体保存的数据、回调的参数,以及初始化和注册入口。掌握这些关系后,再继续看各个子系统的具体约定。
从公共成员访问具体对象
回调参数经常只提供公共部分,具体实现则需要访问完整对象。第 10 篇使用的简化宏如下:
1 |
例如,PWM 实现需要从 LedBase_t * 取得 PwmLed_t *,再读取通道:
1 | static int pwm_led_on(LedBase_t *base) |
宏根据成员偏移计算对象地址,因此传入指针必须确实指向对应类型的成员。实际内核提供了自己的宏实现,类型限定等细节应查对应版本;这里保留简化形式用于理解地址关系。相关接口见 Linux 内核的 container_of 文档。
将设备对象注册到框架
初始化入口负责安排模块启动,设备注册接口则负责把具体对象加入框架。可以先定义设备描述:
1 | typedef struct { |
框架内部保存基础指针,之后统一调用 LED 接口:
1 | static LedBase_t *g_leds[16]; |
这里的注册过程只保存了对象指针,没有复制完整对象。注册后,对象需要在框架使用期间保持有效。
示例数组只能保存 16 个指针,代码省略了容量检查。用于实际工程时,应在添加前检查 g_led_count,并处理容量已满的情况。
设备注册与第 10 篇的管理器类似,都让框架通过公共类型使用设备。对象如何创建和配置,则由具体驱动或板级代码负责。
回顾对象与框架的分工
整个系列中的主要组成可以对应为:
| 本系列中的内容 | 职责 |
|---|---|
Led_t |
保存某个设备实例的数据 |
LedBase_t |
保存不同实现共用的部分 |
LedOps_t |
描述一组可调用操作 |
led_on() |
提供统一调用入口 |
gpio_led_on() |
完成具体硬件操作 |
container_of |
从公共成员取得具体对象 |
board_init() |
创建、初始化并组织板上的设备 |
module_init() |
声明模块初始化入口 |
Linux 内核中的相似结构还会配合各子系统的规则使用。理解这些基本职责,有助于沿着对象、接口、回调和注册关系阅读代码。
按项目需要选择组织方式
可以根据项目规模和变化需求逐步增加机制:
| 项目情况 | 可以采用的做法 |
|---|---|
| 小项目、实现较固定 | struct + init/deinit,先明确数据和调用顺序 |
| 中等项目、同类设备有多种实现 | base + ops + board_init |
| 需要支持多种芯片或仿真平台 | 增加 platform 分层 |
| 需要接入多种驱动或模块 | 使用注册表,按需要安排自动注册 |
| 接近内核或大型框架的组织规模 | 结合子系统、操作表和成员到对象的转换 |
已有模块能够清楚表达数据、接口和生命周期时,可以先保持简单。需要统一管理更多实现时,再增加操作表、注册或分层,并把相应的调用条件写清楚。









