设为首页 加入收藏

TOP

Linux Kernel代码分段分析尝试(一)
2014-11-24 00:39:10 来源: 作者: 【 】 浏览:7
Tags:Linux Kernel 代码 分段 分析 尝试

1. gcc的__attribute__编绎属性


要了解Linux Kernel代码的分段信息,需要了解一下gcc的__attribute__的编绎属性,__attribute__主要用于改变所声明或定义的函数或数据的特性,它有很多子项,用于改变作用对象的特性。比如对函数,noline将禁止进行内联扩展、noreturn表示没有返回值、pure表明函数除返回值外,不会通过其它(如全局变量、指针)对函数外部产生任何影响。但这里我们比较感兴趣的是对代码段起作用子项section。


__attribute__的section子项的使用格式为:


__attribute__((section("section_name")))


其作用是将作用的函数或数据放入指定名为"section_name"输入段。


这里还要注意一下两个概念:输入段和输出段


输入段和输出段是相对于要生成最终的elf或binary时的Link过程说的,Link过程的输入大都是由源代码编绎生成的目标文件.o,那么这些.o文件中包含的段相对link过程来说就是输入段,而Link的输出一般是可执行文件elf或库等,这些输出文件中也包含有段,这些输出文件中的段就叫做输出段。输入段和输出段本来没有什么必然的联系,是互相独立,只是在Link过程中,Link程序会根据一定的规则(这些规则其实来源于Link Script),将不同的输入段重新组合到不同的输出段中,即使是段的名字,输入段和输出段可以完全不同。


其用法举例如下:


int var __attribute__((section(".xdata"))) = 0;


这样定义的变量var将被放入名为.xdata的输入段,(注意:__attribute__这种用法中的括号好像很严格,这里的几个括号好象一个也不能少。)


static int __attribute__((section(".xinit"))) functionA(void)


{


.....
}


这个例子将使函数functionA被放入名叫.xinit的输入段。


需要着重注意的是,__attribute__的section属性只指定对象的输入段,它并不能影响所指定对象最终会放在可执行文件的什么段。


2. Linux Kernel源代码中与段有关的重要宏定义


A. 关于__init、__initdata、__exit、__exitdata及类似的宏


打开Linux Kernel源代码树中的文件:include/init.h,可以看到有下面的宏定议:


#define __init __attribute__ ((__section__ (".init.text"))) __cold


#define __initdata __attribute__ (( __section__ (".init.data")))


#define __exitdata __attribute__ (( __section__ (".exit.data")))


#define __exit_call __attribute_used__ __attribute__ (( __section__ (".exitcall.exit")))


#define __init_refok oninline __attribute__ ((__section__ (".text.init.refok")))


#define __initdata_refok __attribute__ ((__section__ (".data.init.refok")))


#define __exit_refok noinline __attribute__ ((__section__ (".exit.text.refok")))


.........


#ifdef MODULE


#define __exit __attribute__ (( __section__ (".exit.text"))) __cold


#else


#define __exit __attribute_used__ __attribute__ ((__section__ (".exit.text"))) __cold


#endif


对于经常写驱动模块或翻阅Kernel源代码的人,看到熟悉的宏了吧:__init, __initdata, __exit, __exitdata。


__init 宏最常用的地方是驱动模块初始化函数的定义处,其目的是将驱动模块的初始化函数放入名叫.init.text的输入段。对于__initdata来说,用于数据定义,目的是将数据放入名叫.init.data的输入段。其它几个宏也类似。另外需要注意的是,在以上定意中,用__section__代替了section。还有其它一些类似的宏定义,这里不一一列出,其作用都是类似的。


B. 关于initcall的一些宏定义


在该文件中,下面这条宏定议更为重要,它是一条可扩展的宏:


#define __define_initcall(level,fn,id) \


static initcall_t __initcall_##fn##id __attribute_used__ \


__attribute__ ((__section__(".initcall" level ".init"))) = fn


这条宏带有3个参数:level,fn, id,分析该宏可以看出:


 1.其用来定义类型为initcall_t的static函数指针,函数指针的名称由参数fn和id决定:__initcall_##fn##id,这就是函数指针的名称,它其实是一个变量名称。从该名称的定义方法我们其学到了宏定义的一种高级用法,即利用宏的参数产生名称,这要借助于"##"这一符号组合的作用。


 2. 这一函数指针变量放入什么输入段呢,请看__attribute__ ((__section__ (".initcall" levle ".init"))),输入段的名称由level决定,如果level="1",则输入段是.initcall1.init,如果level="3s",则输入段是.initcall3s.init。这一函数指针变量就是放在用这种方法决定的输入段中的。


 3. 这一定义的函数指针变量的初始值是什么叫,其实就是宏参数fn,实际使用中,fn其实就是真实定义好的函数。


该宏定义并不直接使用,请看接下来的这些宏定义:


#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#de

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇Linux内核阅读必备技能 下一篇Linux Kernel启动参数

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: