linux内核及驱动开发中有关__init,__exit和__initdata的用法 .

来源:互联网 发布:淘宝需要3c认证类目 编辑:程序博客网 时间:2024/06/05 10:31
 这些宏包括 __init、__initdata、__initfunc()、asmlinkage、ENTRY()、FASTCALL()等等。它们的定义主要位于 Include\linux\linkage.h和 include\asm-i386\Init.h以及其他一些.h文件中。

  1) __init位置:include\asm-i386\Init.h

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

  注释:这个标志符和函数声明放在一起,表示gcc编译器在编译的时候需要把这个函数放.text.init section中,而这个section在内核完成初始化之后,会被释放掉。

  举例:asmlinkage void __init start_kernel(void){...}

  2) __initdata

  位置:include\asm-i386\Init.h

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

  注释:这个标志符和变量声明放在一起,表示gcc编译器在编译的时候需要把这个变量放在.data.init section中,而这个section在内核完成初始化之后,会被释放掉。

  举例:static struct kernel_param raw_params[] __initdata = {....}

  3) __initfunc()

  位置:include\asm-i386\Init.h

  定义: #define __initfunc(__arginit) \

  __arginit __init; \

  __arginit

  注释: 这个宏用来定义一个 __init 函数。

  举例: __initfunc(void mem_init(unsigned long start_mem, unsigned long e

  nd_mem)) {....}

  4) asmlinkage

  位置:Include\linux\linkage.h

  定义:#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

  注释:这个标志符和函数声明放在一起,告诉gcc编译器该函数不需要通过任何寄存器来传递参数,参数只是通过堆栈来传递。

  举例:asmlinkage void __init start_kernel(void){...}

  5) ENTRY()

  位置:Include\linux\linkage.h

  定义: #define ENTRY(name) \

  .globl SYMBOL_NAME(name); \

  ALIGN; \

  SYMBOL_NAME_LABEL(name)

  注释: 将name声明为全局,对齐,并定义为标号。

  举例: ENTRY(swapper_pg_dir)

  .long 0x00102007

  .fill __USER_PGD_PTRS-1,4,0

  /* default: 767 entries */

  .long 0x00102007

  /* default: 255 entries */

  .fill __KERNEL_PGD_PTRS-1,4,0

  等价于

  .globl swapper_pg_dir

  .align 16,0x90

  /* if i486 */

  swapper_pg_dir:

  .long 0x00102007

  .fill __USER_PGD_PTRS-1,4,0

  /* default: 767 entries */

  .long 0x00102007

  /* default: 255 entries */

  .fill __KERNEL_PGD_PTRS-1,4,0

  6) FASTCALL()

  位置:Include\linux\kernel.h

  定义:#define FASTCALL(x) x __attribute__((regparm(3)))

  注释:这个标志符和函数声明放在一起,带regparm(3)的属性声明告诉gcc编译器这个函数可以通过寄存器传递多达3个的参数,这3个寄存器依次为EAX、EDX 和 ECX。更多的参数才通过堆栈传递。这样可以减少一些入栈出栈操作,因此调用比较快。

  举例:extern void FASTCALL(__switch_to(struct task_struct *prev, struct t

  ask_struct *next));

  这个例子中,prev将通过eax,next通过edx传递

  7)_sched 存在于kernel/sched.h文件中

  Attach to any functions which should be ignored in wchan output

  #define _sched _attribute_ ((_section_(".sched.text")))

  Reference:

 

要了解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。还有其它一些类似的宏定义,这里不一一列出,其作用都是类似的。

在ADS编译器中,“__irq”专门用来声明IRQ中断服务程序,如果用“__irq”来声明一个函数,那么该函数表示一个IRQ中断服务程序,编译器便会自动在该函数内部增加中断现场保护的代码,如图 1.15中的阴影所示。

同样一个函数,如果将关键字“__irq”去掉,那么编译器便不会增加现场保护的代码,而只是作为一个普通函数来处理,如下面的代码段所示(红色的部分为不同的部分)。
现在大家应该对“__irq”关键字有了一定的了解,那么,是不是所有的IRQ中断服务程序都需要使用“__irq”关键字声明呢?其实,这取决于获取“中断服务程序地址”的方法:
    
    如果在执行中断服务函数之前没有对中断现场进行保护,那么中断服务函数必须要使用“__irq”关键字进行声明。例如,在0x0000 0018处执行指令“LDR PC, [PC, #-0xff0]”,此时对应的中断服务函数必须要使用“__irq”关键字进行声明;
    如果在执行中断服务函数之前已经对中断现场进行了保护(比如说有处理中断的宏定义之类的处理),那么中断服务函数不能使用“__irq”关键字进行声明。

加入关键字__irq的反汇编代码

void __irq TimerTickHandle(void)

{

[0xe92d500f]   stmfd    r13!,{r0-r3,r12,r14}

       Uart_Printf("T4 counter interrupt!!!");  

[0xe28f001c]   add      r0,pc,#0x1c ; #0x30001b44

[0xebfffc8f]   bl       Uart_Printf

       ClearPending(BIT_TIMER3); }

[0xe8bd500f]   ldmfd    r13!,{r0-r3,r12,r14}

[0xe25ef004]  subs     pc,r14,#4

 

不加关键字__irq的反汇编代码

void TimerTickHandle(void)

{

[0xe92d4008]   stmfd    r13!,{r3,r14}

       Uart_Printf("T4 counter interrupt!!!");

[0xe28f0018]   add      r0,pc,#0x18 ; #0x30001b40

[0xebfffc8f]   bl       Uart_Printf

       ClearPending(BIT_TIMER3); I

}

[0xe8bd8008]   ldmfd    r13!,{r3,pc}