attribute用法section部分1.gcc的__attribute__编译属性要了解LinuxKernel代码的分段信息,需要了解一下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程序会根据一定的规则(这些规则其实来源于LinkScript),将不同的输入段重新组合到不同的输出段中,即使是段的名字,输入段和输出段可以完全不同。其用法举例如下:intvar__attribute__((section(.xdata)))=0;这样定义的变量var将被放入名为.xdata的输入段,(注意:__attribute__这种用法中的括号好像很严格,这里的几个括号好象一个也不能少。)staticint__attribute__((section(.xinit)))functionA(void){.....}这个例子将使函数functionA被放入名叫.xinit的输入段。需要着重注意的是,__attribute__的section属性只指定对象的输入段,它并不能影响所指定对象最终会放在可执行文件的什么段。2.linuxKernel源代码中与段有关的重要宏定义A.关于__init、__initdata、__exit、__exitdata及类似的宏打开LinuxKernel源代码树中的文件: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_refokoninline__attribute__((__section__(.text.init.refok)))#define__initdata_refok__attribute__((__section__(.data.init.refok)))#define__exit_refoknoinline__attribute__((__section__(.exit.text.refok))).........#ifdefMODULE#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)\staticinitcall_t__initcall_##fn##id__attribute_used__\__attribute__((__section__(.initcalllevel.init)))=fn这条宏带有3个参数:level,fn,id,分析该宏可以看出:1.其用来定义类型为initcall_t的static函数指针,函数指针的名称由参数fn和id决定:__initcall_##fn##id,这就是函数指针的名称,它其实是一个变量名称。从该名称的定义方法我们其学到了宏定义的一种高级用法,即利用宏的参数产生名称,这要借助于##这一符号组合的作用。2.这一函数指针变量放入什么输入段呢,请看__attribute__((__section__(.initcalllevle.init))),输入段的名称由level决定,如果level=1,则输入段是.initcall1.init,如果level=3s,则输入段是.initcall3s.init。这一函数指针变量就是放在用这种方法决定的输入段中的。3.这一定义的函数指针变量的初始值是什么叫,其实就是宏参数fn,实际使用中,fn其实就是真实定义好的函数。该宏定义并不直接使用,请看接下来的这些宏定义:#definepure_initcall(fn)__define_initcall(0,fn,0)#definecore_initcall(fn)__define_initcall(1,fn,1)#definecore_initcall_sync(fn)__define_initcall(1s,fn,1s)#definepostcore_initcall(fn)__define_initcall(2,fn,2)#definepostcore_initcall_sync(fn)__define_initcall(2s,fn,2s)#definearch_initcall(fn)__define_initcall(3,fn,3)#definearch_initcall_sync(fn)__define_initcall(3s,fn,3s)#definesubsys_initcall(fn)__define_initcall(4,fn,4)#definesubsys_initcall_sync(fn)__define_initcall(4s,fn,4s)#definefs_initcall(fn)__define_initcall(5,fn,5)#definefs_initcall_sync(fn)__define_initcall(5s,fn,5s)#definerootfs_initcall(fn)__define_initcall(rootfs,fn,rootfs)#definedevice_initcall(fn)__define_initcall(6,fn,6)#definedevice_initcall_sync(fn)__define_initcall(6s,fn,6s)#definelate_initcall(fn)__define_initcall(7,fn,7)#definelate_initcall_sync(fn)__define_initcall(7s,fn,7s)这些宏定义出来是为了方便的使用__define_initcall宏定义的,上面每条宏第一次使用时都会产生一个新的输入段。接下来还有一条#define__initcall(fn)device_initcall(fn)这一条其实只是定义了另一个别名,即平常使用的__initcall其实就是这儿的device_initcall,用它定义的函数指定位于段.initcall6.init中。C.__setup宏的来源及使用__setup这条宏在LinuxKernel中使用最多的地方就是定义处理Kernel启动参数的函数及数据结构,请看下面的宏定义:#define__setup_param(str,unique_id,fn,early)\staticchar__setup_str_##unique_id[]__initdata__aligned(1)=str;\staticstructobs_kernel_param__setup_##unique_id\__used__section(.init.setup)\__attribute__((aligned((sizeof(long)))))\={__setup_str_##unique_id,fn,early}#define__setup(str,fn)\__setup_param(str,fn,fn,0)使用Kernel中的例子分析一下这两条定义:__setup(root=,root_dev_setup);这条语句出现在init/do_mounts.c中,其作用是处理Kernel启动时的像root=/dev/mtdblock3之类的参数的。分解一下这条语句,首先变为:__setup_param(root=,root_dev_setup,root_dev_setup,0);继续分解,将得到下面这段代吗:staticchar__setup_str_root_dev_setup_id[]__initdata__aligned(1)=root=;staticstructobs_kernel_param__setup_root_dev_setup_id__used__section(.init.setup)__attribute__((aligned((sizeof(long)))))={__setup_str_root_dev_setup_id,root_dev_setup,0};这段代码定义了两个变量:字符数组变量__setup_str_root_dev_setup_id,其初始化内容为root=,由于该变量用__initdata修饰,它将被放入.init.data输入段;另一变量是结构变量__setup_root_dev_setup_id,其类型为structobs_kernel_param,该变理被放入输入段.init.setup中。结构structobs_kernel_param也在该文件中定义如下:structobs_kernel_param{constchar*str;int(*setup_func)(char*);intearly;};变量__setup_root_dev_setup_id的三个成员分别被初始化为:__setup_str_root_dev_setup_id--前面定义的字符数组变量,初始内容为root=。root_dev_setup--通过宏传过来的处理函数。0--常量0,该成员的作用以后分析。现在不难想像内核启动时怎么处理启动参数的了:通过__setup宏定义obs_kernel_param结构变量都被放入.init.setup段中,这样一来实际是使.init.setup段变成一张表,Kernel在处理每一个启动参数时,都会来查找这张表,