系统初始化函数集(subsys_initcall)和初始化段应用

来源:互联网 发布:c语言书籍推荐 编辑:程序博客网 时间:2024/06/06 05:58

系统初始化函数集(subsys_initcall)和初始化段应用

前言:前段时间做一个项目需要设计一个动态库,并希望在加载库的同时自动执行一些初始化动作,于是联想到了linux内核众子系统的初始化,于是研究之,并在过这程中发现了初始化段的存在,利用初始化段实现了该功能。工作一年,笔记积累多了,慢慢变得杂乱无章,于是开博,一方面整理笔记,梳理知识,另一方面和大家交流,共同进步。



keywordsubsys_initcall,init, init_call


1系统初始化调用函数集分析(静态)

1.1函数定义

linux内核代码里,运用了subsys_initcall来进行各种子系统的初始化,具体怎么初始化的呢?其实并不复杂。以2.6.29内核作为例子。在<include/linux/init.h>下就能找到subsys_initcall的定义:

#definepure_initcall(fn) __define_initcall("0",fn,0)

#definecore_initcall(fn) __define_initcall("1",fn,1)

#definecore_initcall_sync(fn) __define_initcall("1s",fn,1s)

#definepostcore_initcall(fn) __define_initcall("2",fn,2)

#definepostcore_initcall_sync(fn) __define_initcall("2s",fn,2s)

#definearch_initcall(fn) __define_initcall("3",fn,3)

#definearch_initcall_sync(fn) __define_initcall("3s",fn,3s)

#definesubsys_initcall(fn) __define_initcall("4",fn,4)

#definesubsys_initcall_sync(fn) __define_initcall("4s",fn,4s)

#definefs_initcall(fn) __define_initcall("5",fn,5)

#definefs_initcall_sync(fn) __define_initcall("5s",fn,5s)

#definerootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)

#definedevice_initcall(fn) __define_initcall("6",fn,6)

#definedevice_initcall_sync(fn) __define_initcall("6s",fn,6s)

#definelate_initcall(fn) __define_initcall("7",fn,7)

#definelate_initcall_sync(fn) __define_initcall("7s",fn,7s)


__define_initcall又被定义为

#define__define_initcall(level,fn,id) \

staticinitcall_t __initcall_##fn##id __used \

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


所以 subsys_initcall(fn)== __initcall_fn4 它将被链接器放于section .initcall4.init中。(attribute将会在另一篇文章中介绍)


1.2初始化函数集的调用过程执行过程:

start_kernel->rest_init

系统在启动后在rest_init中会创建init内核线程

init->do_basic_setup->do_initcalls

do_initcalls中会把.initcall.init.中的函数依次执行一遍:


for(call = __initcall_start; call < __initcall_end; call++) {

. .....

result= (*call)();

. ........

}


这个__initcall_start是在文件<arch/xxx/kernel/vmlinux.lds.S>定义的:

.initcall.init: AT(ADDR(.initcall.init) - LOAD_OFFSET) {

__initcall_start= .;

INITCALLS

__initcall_end= .;

}


INITCALLS被定义于<asm-generic/vmlinux.lds.h>:

#defineINITCALLS \

*(.initcall0.init) \

*(.initcall0s.init) \

*(.initcall1.init) \

*(.initcall1s.init) \

*(.initcall2.init) \

*(.initcall2s.init) \

*(.initcall3.init) \

*(.initcall3s.init) \

*(.initcall4.init) \

*(.initcall4s.init) \

*(.initcall5.init) \

*(.initcall5s.init) \

*(.initcallrootfs.init) \

*(.initcall6.init) \

*(.initcall6s.init) \

*(.initcall7.init) \

*(.initcall7s.init)



2基于模块方式的初始化函数(动态)2.1函数定义subsys_initcall的静态调用方式应该讲清楚来龙去脉了,现在看看动态方式的初始化函数调用(模块方式)。在<include/linux/init.h>里,如果MODULE宏被定义,说明该函数将被编译进模块里,在系统启动后以模块方式被调用。

#definecore_initcall(fn) module_init(fn)

#definepostcore_initcall(fn) module_init(fn)

#definearch_initcall(fn) module_init(fn)

#definesubsys_initcall(fn) module_init(fn)

#definefs_initcall(fn) module_init(fn)

#definedevice_initcall(fn) module_init(fn)

#definelate_initcall(fn) module_init(fn)

这是在定义MODULE的情况下对subsys_initcall的定义,就是说对于驱动模块,使用subsys_initcall等价于使用module_init



2.2module_init分析下面先看看module_init宏究竟做了什么

#definemodule_init(initfn) \

staticinline initcall_t __inittest(void) \/*定义此函数用来检测传入函数的类型,并在编译时提供警告信息*/

{return initfn; } \

intinit_module(void) __attribute__((alias(#initfn))); /*声明init_modlueinitfn的别名,insmod只查找名字为init_module函数并调用*/


typedefint (*initcall_t)(void); /*函数类型定义*/


在以模块方式编译一个模块的时候,会自动生成一个xxx.mod.c文件,在该文件里面定义一个structmodule变量,并把init函数设置为上面的init_module()而上面的这个init_module,被alias成模块的初始化函数(参考<gcc关键字:__attribute__,alias, visibility, hidden>)


也就是说,模块装载的时候(insmodmodprobe)sys_init_module()系统调用会调用module_init指定的函数(对于编译成>模块的情况)。


2.3module的自动加载内核在启动时已经检测到了系统的硬件设备,并把硬件设备信息通过sysfs内核虚拟文件系统导出。sysfs文件系统由系统初始化脚本挂载到/sys上。udev扫描sysfs文件系统,根据硬件设备信息生成热插拔(hotplug)事件,udev再读取这些事件,生成对应的硬件设备文件。由于没有实际的硬件插拔动作,所以这一过程被称为coldplug

udev完成coldplug操作,需要下面三个程序:

udevtrigger——扫描sysfs文件系统,生成相应的硬件设备hotplug事件。

udevd——作为deamon,记录hotplug事件,然后排队后再发送给udev,避免事件冲突(raceconditions)。

udevsettle——查看udev事件队列,等队列内事件全部处理完毕才退出。

要规定事件怎样处理就要编写规则文件了.规则文件是udev的灵魂,没有规则文件,udev无法自动加载硬件设备的驱动模块。它一般位于<etc/udev/rules.d>



3初始化段的应用这里给出一个简单的初始化段的使用例子,将a.c编译成一个动态库,其中,函数a()和函数c()放在两个不同的初始化段里,函数b()默认放置;编译main.c,链接到由a.c编译成的动态库,观察各函数的执行顺序。

#cat a.c #include <stdio.h>

typedefint (*fn) (void);

inta(void)

{

printf("a\n");

return0;

}

__attribute__((__section__(".init_array.2")))static fn init_a = a;

intc(void)

{

printf("c\n");

return0;

}

__attribute__((__section__(".init_array.1")))static fn init_c = c;


intb()

{

printf("b\n");

return0;

}


#cat main.c

#include<stdio.h>

intb();

intmain()

{

printf("main\n");

b();

}


#cat mk.sh


gcc-fPIC -g -c a.c

gcc-shared -g -o liba.so a.o

cpliba.so /lib/ -fr

gccmain.c liba.so

ldconfig

./a.out


#gcc -fPIC -g -c a.c

#gcc -shared -g -o liba.so a.o

#cp liba.so /lib/

#gcc main.c liba.so

#ldconfig

#./a.out

a

c

main

b


原创粉丝点击