gnu c for linux,GNU C 扩展

__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=后面的内容传给该处理函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值