看过Linux内核源码的人都知道内核中到处充斥着类似: arch_initcall(),fs_initcall(), module_init()等等, 也都知道它们的作用主要
是将各个不同模块的初始化函数的地址放到指定的段,然后方便内核在同一个地方集中初始化,同时解决了调用者和被调函数
之间的耦合。唯一不太方便的地方就是每新定义一个段就需要在链接脚本中同步加入类似下列描述代码:
__xxx_section_start = .; // 表示段的起始地址
*(.xxx)
__xxx_section_end = .; // 表示段的结束地址
那有没有什么简单点的方法尽量少改甚至不改链接脚本呢? 答案是肯定的。 请看如下代码:
#define __RO_SECTION_DECLARE(name, order, index_1, index_2, sym) \
__section(".rodata1.symbol." #name "." #order "." #index_1 "." #index_2 "." #sym) \
__used __aligned(4)
struct sysinit_item {
void (*handler)(void);
}
#define _SYSINIT_INDEX_ITEM(sname, handler, nsec, index_1, index_2) \
__RO_SECTION_DECLARE(sname, nsec, index_1, index_2, handler) \
static const struct sysinit_item __##sname##_item_##nsec = {handler}
#define SYS_INIT(handler, module, order) \
enum { _Sysinit_##handler = order*module }; \
_SYSINIT_INDEX_ITEM(sysinit, handler, 1, module, order)
#define SYSINIT_BOUNDARY(_name) \
_SYSINIT_INDEX_ITEM(_name, NULL, 0, 0, 0); \
_SYSINIT_INDEX_ITEM(_name, NULL, 2, 0, 0)
#define FOREACH_ITEM(_var, _name) \
for (_var = &__##_name##_item_##0, ++_var; \
_var < &__##_name##_item_##2; \
_var++)
调用指定段的所有函数:
SYSINIT_BOUNDARY(sysinit);
void sys_init(void) {
const struct sysinit_item *item;
FOREACH_ITEM(item, sysinit)
item->handler();
}
使用例程:
#define SYSINIT_DEVICE 0x10
#define DEVICE_ORDER 0x03
static void xxx_device_setup(void)
{
//TODO
}
SYS_INIT(xxx_device_setup, SYSINIT_DEVICE, DEVICE_ORDER);
上面的实现代码中有两个关键宏:
#define SYS_INIT(handler, module, order) \
enum { _Sysinit_##handler = order*module }; \
_SYSINIT_INDEX_ITEM(sysinit, handler, 1, module, order)
这里的枚举类型 enum { _Sysinit_##handler = order*module }; 的主要作用并不是定义一个类型,而是为了强制触发编译器的预处理程序的替换操
作,即:如果module和order是宏则将他们替换成数字。
#define SYSINIT_BOUNDARY(_name) \
_SYSINIT_INDEX_ITEM(_name, NULL, 0, 0, 0); \
_SYSINIT_INDEX_ITEM(_name, NULL, 2, 0, 0)
这个宏的作用主要用于定义段的起始地址和结束地址。让我们来看下,如果将这个宏展开应该是这样的,以SYSINIT_BOUNDARY(sysinit)为例:
__section(".rodata1.symbol." sysinit "."0 "." 0 "." 0 "." "NULL") __used __aligned(4)\
static const struct sysinit_item __sysinit_item_0 = {NULL} ; \
__section(".rodata1.symbol." #name "."2 "." 0 "." 0 "." "NULL") __used __aligned(4) \
static const struct sysinit_item __sysinit_item_2 = {NULL}
那这又代表什么意思呢? 聪明的你们可能早已看破了一切, 但我还是啰嗦一遍,SYSINIT_BOUNDARY(sysinit)的含义如下:
定义并将变量__sysinit_item_0(初始值为NULL)放在段 .rodata1.symbol.sysinit .0 .0 .0 .NULL
定义并将变量__sysinit_item_2(初始值为NULL) 放在段 .rodata1.symbol.sysinit .2 .0 .0 .NULL
同理: SYS_INIT(xxx_device_setup, SYSINIT_DEVICE, DEVICE_ORDER);表示:
定义并将变量__sysinit_item_1(初始值为xxx_device_setup)放在段 .rodata1.symbol.sysinit .1 .0x10 .0x03.xxx_device_setup
再利用链接器对段的排序功能(GNU-LINKER: SORT(.xxx*)),最终生成的可执行文件的内存映像应该如下:
0xxxxxxx0: .rodata1.symbol.sysinit .0 .0 .0 .NULL
0xxxxxxx4: .rodata1.symbol.sysinit .1 .0x10 .0x03.xxx_device_setup
0xxxxxxx8: .rodata1.symbol.sysinit .2 .0 .0 .NULL
再看sys_init()的实现, 主要就是遍历段(&__sysinit_item_0, &__sysinit_item_2), 不包含__sysinit_item_0和__sysinit_item_2,因为这两个变量为
空且主要作用是标识段的边界。 另外之所以选择选择段.rodata1*主要是为了利用C/C++的基本段.rodata*, 如果链接器默认对段rodata进行排序,
则我们完全不用更改链接脚本,反之则需要在脚本中加入排序指令。
链接脚本之软件初始化解耦
最新推荐文章于 2023-06-10 22:10:29 发布