本文共 1648 字,大约阅读时间需要 5 分钟。
转自:
本文详细讲解了利用__attribute__((section()))构建初始化函数表,以及Linux内核各级初始化的原理。
作者简介:
廖威雄,2016年本科毕业于暨南大学,目前就职于珠海全志科技股份有限公司从事linux嵌入式系统(Tina Linux)的开发,主要负责文件系统和存储的开发和维护,兼顾linux测试系统的设计和持续集成的维护。
拆书帮珠海百岛分舵的组织长老,二级拆书家,热爱学习,热爱分享。
欢迎投稿:
2018年给Linuxer投稿原创Linux技术文章,一经录取,赠送人民邮电出版社任意在售图书,获得读者红包打赏,和公众号站长宋宝华200元微信红包。
欢迎关注Linuxer
问题导入
传统的应用编写时,每添加一个模块,都需要在main中添加新模块的初始化
使用__attribute__((section()))构建初始化函数表后,由模块告知main:“我要初始化“,添加新模块再也不需要在main代码中显式调用模块初始化接口。
以此实现main与模块之间的隔离,main不再关心有什么模块,模块的删减也不需要修改main。
那么,如何实现这个功能呢?如何实现DECLARE_INIT呢?联想到内核驱动,所有内核驱动的初始化函数表在哪里?为什么添加一个内核驱动不需要修改初始化函数表?
下文会从 构建初始化函数表的原理分析、分析内核module_init实现、演练练习 的3个角度给小伙伴分享。
构建初始化函数表的原理分析__attribute__((section(”name“)))是gcc编译器支持的一个编译特性(arm编译器也支持此特性),实现在编译时把某个函数/数据放到name的数据段中。因此实现原理就很简单了: 1. 模块通过__attribute__((section("name")))的实现,在编译时把初始化的接口放到name数据段中2. main在执行初始化时并不需要知道有什么模块需要初始化,只需要把name数据段中的所有初始化接口执行一遍即可 首先: gcc -c test.c -o test.o此时编译过程中处理了__atribute__((section(XXX))),把标记的变量/函数放到了test.o的XXX的数据段,可用 readelf命令查询。最后:ld -Ttest.o -otest.bin链接时,test.o的XXX数据段(输入段),最终保存在test.bin的XXX数据段(输出段),如此在bin中构建了初始化函数表。由于自定义了一个数据段,而默认链接脚本缺少自定义的数据段的声明,因此并不能使用默认的链接脚本。ld链接命令有两个关键的选项:ld -T
执行:gcc -c section.c -o section.o 编译应用源码。
执行:readelf -S section.o 查看段信息,截图如下:
可以看到,段[6]是我们自定义的数据段
执行:gcc -T ldscript.lds section.o -o section 链接成可执行的bin文件
执行:readelf -S section 查看bin文件的段分布情况,部分截图如下:
在我链接成的可执行bin中,在[25]段中存在我们自定义的段
执行结果:
本文后面跟着的一篇文章是关于这篇文章对应的高清思维导图。