Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2685773
  • 博文数量: 877
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5921
  • 用 户 组: 普通用户
  • 注册时间: 2013-12-05 12:25
个人简介

技术的乐趣在于分享,欢迎多多交流,多多沟通。

文章分类

全部博文(877)

文章存档

2021年(2)

2016年(20)

2015年(471)

2014年(358)

2013年(26)

分类: LINUX

2014-05-22 20:03:28

http://gong301.blog.163.com/blog/static/183885907201172175248/

声明的特殊属性

==============

GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代

码检查。要指定一个声明的属性,在声明后写

__attribute__ (( ATTRIBUTE ))

其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。GNU C 支持十几个属性,这

里介绍最常用的:

* noreturn

属性 noreturn 用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的

代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如:

++++ include/linux/kernel.h

47: # define ATTRIB_NORET __attribute__((noreturn)) ....

61: asmlinkage NORET_TYPE void do_exit(long error_code)

ATTRIB_NORET;

* format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)

属性 format 用于函数,表示该函数使用 printf, scanf 或 strftime 风格的参

数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定 format 属性可以

让编译器根据格式串检查参数类型。例如:

++++ include/linux/kernel.h?

89: asmlinkage int printk(const char * fmt, ...)

90: __attribute__ ((format (printf, 1, 2)));

表示第一个参数是格式串,从第二个参数起根据格式串检查参数。

* unused

属性 unused 用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免

编译器产生警告信息。

* section ("section-name")

属性 section 用于函数和变量,通常编译器将函数放在 .text 节,变量放在

.data 或 .bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的

节中。

例如:

++++ include/linux/init.h

78: #define __init __attribute__ ((__section__ (".text.init")))

79: #define __exit __attribute__ ((unused, __section__(".text.exit")))

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

81: #define __exitdata __attribute__ ((unused, __section__ (".data.exit")))

82: #define __initsetup __attribute__ ((unused,__section__ (".setup.init")))

83: #define __init_call __attribute__ ((unused,__section__ (".initcall.init")))

84: #define __exit_call __attribute__ ((unused,__section__ (".exitcall.exit")))

连接器可以把相同节的代码或数据安排在一起,Linux 内核很喜欢使用这种技术,

例如系统的初始化代码被安排在单独的一个节,在初始化结束后就可以释放这部分

内存。

* aligned (ALIGNMENT)

属性 aligned 用于变量、结构或联合类型,指定变量、结构域、结构或联合的对

齐量,以字节为单位,例如:

++++ include/asm-i386/processor.h

294: struct i387_fxsave_struct {

295: unsigned short cwd;

296: unsigned short swd;

297: unsigned short twd;

298: unsigned short fop;

299: long fip;

300: long fcs;

301: long foo;

......

308: } __attribute__ ((aligned (16)));

表示该结构类型的变量以 16 字节对齐。通常编译器会选择合适的对齐量,显示指

定对齐通常是由于体系限制、优化等原因。



* packed

属性 packed 用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用

于枚举、结构或联合类型时表示该类型使用最小的内存。例如:

++++ include/asm-i386/desc.h

51: struct Xgt_desc_struct {

52: unsigned short size;

53: unsigned long address __attribute__((packed));

54: };

域 address 将紧接着 size 分配。属性 packed 的用途大多是定义硬件相关的结

构,使元素之间没有因对齐而造成的空洞。

当前函数名

==========

GNU CC 预定义了两个标志符保存当前函数的名字,__FUNCTION__ 保存函数在源码

中的名字,__PRETTY_FUNCTION__ 保存带语言特色的名字。在 C 函数中,这两个

名字是相同的,在 C++ 函数中,__PRETTY_FUNCTION__ 包括函数返回类型等额外

信息,Linux 内核只使用了 __FUNCTION__。

++++ fs/ext2/super.c

98: void ext2_update_dynamic_rev(struct super_block *sb)

99: {

100: struct ext2_super_block *es = EXT2_SB(sb)->s_es;

101:

102: if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV)

103: return;

104:

105: ext2_warning(sb, __FUNCTION__,

106: "updating to rev %d because of new feature flag, "

107: "running e2fsck is recommended",

108: EXT2_DYNAMIC_REV);

这里 __FUNCTION__ 将被替换为字符串 "ext2_update_dynamic_rev"。虽然

__FUNCTION__ 看起来类似于标准 C 中的 __FILE__,但实际上 __FUNCTION__

是被编译器替换的,不象 __FILE__ 被预处理器替换。

 

 

 

要了解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)
#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宏定义的,上面每条宏第一次使用时都会产生一个新的输入段。

接下来还有一条

#define __initcall(fn) device_initcall(fn)
这一条其实只是定义了另一个别名,即平常使用的__initcall其实就是这儿的device_initcall,用它定义的函数指定位于段.initcall6.init中。

C. __setup宏的来源及使用

__setup这条宏在Linux Kernel中使用最多的地方就是定义处理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,其类型为struct obs_kernel_param, 该变理被放入输入段.init.setup中。结构struct struct obs_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=后面的内容传给该处理函数

阅读(642) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~