Linux内核与驱动开发学习总结:initcall 和contain_of宏(十)

来源:互联网 发布:淘宝开服装店 编辑:程序博客网 时间:2024/05/01 01:02

内核驱动初始化顺序:

 main.c/start_kernel() ---> rest_init() ---> kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND) 

---> kernel_init() ---> do_basic_setup() ---> do_initcalls() ---> do_one_initcall()

 __define_initcall的作用:

宏定义__define_initcall(level,fn,id)对于内核的初始化很重要,他指示编译器在编译的时候,将一系列初始化函数的起始地址值按照一定的顺序放在一个section中。

在内核初始化阶段,do_initcalls() 将按顺序从该section中以函数指针形式取出这些函数的起始地址,来依次完成相应的初始化。由于内核某些部分的初始化需要依赖于其他某些部分的初始化的完成,因此这个顺序排列常常很重要。

[html] view plaincopy

1. #define module_init(x)  __initcall(x);  

[html] view plaincopy

1. #define __initcall(fn) device_initcall(fn)  

[html] view plaincopy

1. #define device_initcall(fn)     __define_initcall("6",fn,6)  

1. 分析__define_initcall(level, fn, id) 宏定义:

这个宏定义在 kernel/include/linux/init.h中:

[html] view plaincopy

1. #define __define_initcall(level,fn,id) \  

2.     static initcall_t __initcall_##fn##id __used \  

3.     __attribute__((__section__(".initcall" level ".init"))) = fn  

 其中initcall_t 是个函数指针类型:

[html] view plaincopy

1. typedef int (*initcall_t)(void);  

而属性 __attribute__((__section__())) 则表示把对象放在一个这个由括号中的名称所指代的section中。
所以这个宏定义的含义是:

a) 声明一个名称为__initcall_##fn的函数指针(其中##表示替换连接)

b) 将这个函数指针初始化为fn;

c) 编译的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init"section(比如level="1",代表这个section的名称是 ".initcall1.init")

d) level 值越小表示优先及更高。kernel中 level的范围是(0~7);

 2. 和初始化相关的section-initcall.init段:

 section-initcall.init 分成7个子section

[html] view plaincopy

1. __initcall_start = .;  

2. .initcall.init : {  

3. *(.initcall1.init)  

4. *(.initcall2.init)  

5. *(.initcall3.init)  

6. *(.initcall4.init)  

7. *(.initcall5.init)  

8. *(.initcall6.init)  

9. *(.initcall7.init)  

10. }  

Makefile中有:LDFLAGS_vmlinux += -T arch/$(ARCH)/kernel/vmlinux.lds.s

3. 内核初始化函数do_initcalls()

do_initcalls() 将从 .initcall.init 中,也就是这7section中依次取出任何的函数指针,并调用这些函数指针所指向的函数,来完成内核的一些相关的初始化。

总结:

1) __define_initcall(level,fn,id)的作用就是指示编译器把一些初始化函数的指针(:函数起始地址)按照顺序放置一个名为 .initcall.init section中,这个section又被分成了7个子section,它们按顺序排列。 在内核初始化阶段,这些放置到这个section中的函数指针将供 do_initcalls() 按顺序依次调用,来完成相应初始化。

2)  函数指针放置到的子section由宏定义的level确定,对应level较小的子section位于较前面。而位于同一个子section内的函数指针顺序不定,将由编译器按照编译的顺序随机指定。 

3) 因此,如果你希望某个初始化函数在内核初始化阶段就被调用,那么你 就应该使用宏__define_initcall(level,fn,id) 或 其7个衍生宏 把这个函数fn的对应的指针放置到按照初始化的顺序放置到相关的 section 中。同时,如果某个初始化函数fn_B需要依赖于另外一个初始化函数fn_A的完成,那么你应该把fn_B放在比fn_A对应的level值较大的子section中, 这样,do_initcalls()将在fn_A之后调用fn_B

Contain_of:

1.该宏定义在include/linux/kernel.h

439/**

440 * container_of - cast a member of a structure out to the containing structure

441 * @ptr:        the pointer to the member.

442 * @type:       the type of the container struct this is embedded in.

443 * @member:     the name of the member within the struct.

444 *

445 */

446 #define container_of(ptr, type, member) ({                      /

447        const typeof( ((type *)0)->member ) *__mptr = (ptr);    /

448      (type *)( (char *)__mptr - offsetof(type,member) );})

它的作用显而易见,那就是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。

1.#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

先分析一下这个宏的运行机理:

1. ( (TYPE *)0 ) 将零转型为TYPE类型指针;

2. ((TYPE *)0)->MEMBER 访问结构中的数据成员;

3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址;

4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型。巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址;

Reference:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26404201&id=3212360

 


0 0