beyes |
2009-08-21 10:30 |
__attribute__ 中的 section 属性对代码段起作用,其格式为:
引用
__attribute__ ((section("section_name"))) 其意是将作用的函数或数据放入指定名为 "section_name" 输入段中。
输入段和输出段是相对于要生成最终的 elf 或 binary 时的 link 过程来说的。link 过程的输入大都是由源代码编译生成的目标文件.o ,那么这些 .o 文件中包含的段相对 link 过程来说就是输入段,而 link 的输出一般是可执行文件 elf 或库等,这些输出文件中也包含段,这些输出文件中的段叫做输出段。输入段和输出段没有必然联系,为互相独立,只是在 link 过程中,link 程序会根据一定的规则 (这些规则来源于 link script),将不同的输入段组合到不同的输出段中。
测试代码-1:
引用
#include int main() {
int var __attribute__ ((section(".xxdata"))) = 9; printf ("%d\n", var); return 0; } 编译:
引用
beyes@linux-beyes:~/C/ELF> gcc -c test.c -o test.o test.c: In function ‘main’: test.c:7: error: section attribute cannot be specified for local variables 原来 section 属性不能用来声明局部变量。下面把 var 改为全局变量:
引用
#include int var __attribute__ ((section(".xxdata"))) = 9; int main() { printf ("%d\n", var); return 0; } 编译通过。下面查看一下 test.o 文件中的 section 信息:
引用
beyes@linux-beyes:~/C/ELF> objdump -x test.o
test.o: file format elf32-i386 test.o architecture: i386, flags 0x00000011: HAS_RELOC, HAS_SYMS start address 0x00000000
Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000034 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000000 00000000 00000000 00000068 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 00000068 2**2 ALLOC 3 .xxdata 00000004 00000000 00000000 00000068 2**2 CONTENTS, ALLOC, LOAD, DATA 4 .rodata 00000004 00000000 00000000 0000006c 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .comment 0000003a 00000000 00000000 00000070 2**0 CONTENTS, READONLY 6 .comment.SUSE.OPTs 00000005 00000000 00000000 000000aa 2**0 CONTENTS, READONLY 7 .note.GNU-stack 00000000 00000000 00000000 000000af 2**0 CONTENTS, READONLY SYMBOL TABLE: 00000000 l df *ABS* 00000000 test.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l d .xxdata 00000000 .xxdata 00000000 l d .rodata 00000000 .rodata 00000000 l d .comment.SUSE.OPTs 00000000 .comment.SUSE.OPTs 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .comment 00000000 .comment 00000000 g O .xxdata 00000004 var 00000000 g F .text 00000034 main 00000000 *UND* 00000000 printf
RELOCATION RECORDS FOR [.text]: OFFSET TYPE VALUE 00000012 R_386_32 var 0000001d R_386_32 .rodata 00000022 R_386_PC32 printf 上面,.xxdata 是自定义 section。像在 linux 驱动程序设计中,模块加载函数前有一个 __init 宏,也用了 attribute 的 section 属性,如:
引用
#define __init __attribute__ ((__section__(".init.text"))) 说明:在 linux 内核中,所有标识为 __init 的函数在链接的时候都放在 .init.text 这个区段内。此外,所有的 __init 函数在区段 .initcall.init 中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 __init 函数,并在初始化完成后释放 init 区段 (包括 .init.text, .initcall.iinit 等)。
不但是变量,函数也可以用 section 属性来声明:
引用
#include
int var __attribute__ ((section(".xdata.text"))) = 9; int __attribute__ ((section(".xxdata"))) func (int var) { printf ("%d\n", var); return 0; } int main() { func (var); return 0; } 编译后,同样用 objdump 查看一下 section 信息:
引用
beyes@linux-beyes:~/C/ELF> objdump -x test.o
test.o: file format elf32-i386 test.o architecture: i386, flags 0x00000011: HAS_RELOC, HAS_SYMS start address 0x00000000
Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000002c 00000000 00000000 00000034 2**2 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000000 00000000 00000000 00000060 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 00000000 00000000 00000060 2**2 ALLOC 3 .xdata.text 00000004 00000000 00000000 00000060 2**2 CONTENTS, ALLOC, LOAD, DATA 4 .rodata 00000004 00000000 00000000 00000064 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .xxdata 00000020 00000000 00000000 00000068 2**0 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 6 .comment 0000003a 00000000 00000000 00000088 2**0 CONTENTS, READONLY 7 .comment.SUSE.OPTs 00000005 00000000 00000000 000000c2 2**0 CONTENTS, READONLY 8 .note.GNU-stack 00000000 00000000 00000000 000000c7 2**0 CONTENTS, READONLY SYMBOL TABLE: 00000000 l df *ABS* 00000000 test.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l d .xdata.text 00000000 .xdata.text 00000000 l d .rodata 00000000 .rodata 00000000 l d .xxdata 00000000 .xxdata 00000000 l d .comment.SUSE.OPTs 00000000 .comment.SUSE.OPTs 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .comment 00000000 .comment 00000000 g O .xdata.text 00000004 var 00000000 g F .xxdata 00000020 func 00000000 *UND* 00000000 printf 00000000 g F .text 0000002c main
RELOCATION RECORDS FOR [.text]: OFFSET TYPE VALUE 00000012 R_386_32 var 0000001a R_386_PC32 func
RELOCATION RECORDS FOR [.xxdata]: OFFSET TYPE VALUE 00000010 R_386_32 .rodata 00000015 R_386_PC32 printf
在 linux 内核源代码中,与段相关的重要宏定义有: __init , __initdata, __exit, __exitdata 及类似的宏。
在 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 __init 宏常用的地方是驱动模块初始化函数的定义处; __initdata 常用于数据定义,目的是将数据放入名叫 .init.data 的输入段。 需要注意的是,上面的定义中,用 __section__ 代替了 section 。还有其他一些类似定义的宏,作用也类似。
........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ...........
关于 initcall 的宏定义 这条宏定义更为重要,它是一条可扩展的宏:
引用
#define __define_initcall(level,fn,id) static initcall_t __initcall_##fn##id __used __attribute__((__section__(".initcall" level ".init"))) = fn
上面 initcall_t 的定义为:
引用
typedef int (*initcall_t)(void);
__used 的定义在 include/linux/compiler-gcc4.h 中找到(根据编译器的不同,gcc4 中的 4 可能为 3)为:
引用
#define __used __attribute__((__used__)
initcall 宏定义带有 3 个参数: level, fn, id
分析一下这个宏: 由上面知道,initcall_t 是个用来函数指针定义类型,所以 __initcall_##fn##id 就是一个函数指针,fn 则是一个已经定义好了的函数。这里 ## 符号表示一个连接符的作用,它实际上负责一个新的函数名的定义。先不考虑 __used , __attribute__ 这些声明,假设fn 是一个定义好的函数 func() 的函数名 func,id 值为 9,level 值为 7,那么经过宏定义并展开后变成: static initcall_t __initcall_func9
这时,再考虑 __used , __attribute__ 这些声明的意义: __attribute__((__section__(".initcall" level ".init"))) 表示,函数(以上面的 __initcall_func9 为例)被放在 .initcall7.init 这个 section 中;__used 表示使用 .initcall7.init 这个 section 中的空间。
上面宏定义并不直接使用,同样在 init.h 文件中找到如下的宏定义:
引用
#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) #define subsys_initcall(fn) __define_initcall("4",fn,4) #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s) #define fs_initcall(fn) __define_initcall("5",fn,5) #define fs_initcall_sync(fn) __define_initcall("5s",fn,5s) #define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs) #define device_initcall(fn) __define_initcall("6",fn,6) #define device_initcall_sync(fn) __define_initcall("6s",fn,6s) #define late_initcall(fn) __define_initcall("7",fn,7) #define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
这些宏定义是为了方便使用 __define_initcall 宏的,上面每条宏第一次使用时都会产生一个新的输入段。
... ... ... ... ...... .... ... ... ... ... .... ... ... (转) __setup宏的来源及使用__setup这条宏在LinuxKernel中使用最多的地方就是定义处理Kernel启动参数的函数及数据结构,请看下面的宏定义: #define __setup_param(str, unique_id, fn,early) \ static char __setup_str_##unique_id[] __initdata__aligned(1) = str; \ static struct obs_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); 继续分解,将得到下面这段代吗: static char __setup_str_root_dev_setup_id[] __initdata__aligned(1) = "root="; static struct obs_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中。结构struct structobs_kernel_param也在该文件中定义如下: struct obs_kernel_param { const char *str; int (*setup_func)(char *); int early; }; 变量__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在处理每一个启动参数时,都会来查找这张表,与每一个数据项中的成员str进行比较,如果完全相同,就会调用该数据项的函数指针成员setup_func所指向的函数(该函数是在使用__setup宏定义该变量时传入的函数参数),并将启动参数如root=后面的内容传给该处理函数。 | |