分类: LINUX
2011-09-02 18:37:10
在Linux驱动程序中,经常可以看到这样的代码:subsys_initcall(start_xxx); start_xxx是驱动模块中实现的一个初始化函数。这种写法有什么用途呢?函数又是如何调用的呢?这就是我们要讨论的initcall相关的内容。
我们先来看看subsys_initcall这个定义的出处。这是一个宏定义,在
#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 __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); 而属性 __attribute__((__section__())) 则表示把对象放在一个这个由括号中的名称所指代的section中。
所以__define_initcall这个宏定义的的含义是:
1) 声明一个名称为__initcall_##fn的函数指针(其中##表示替换连接);
2) 将这个函数指针初始化为fn;
3) 编译的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init"的section中(e.g. level="1",那么section的名称是".initcall1.init")。
比如,通过宏 core_initcall() 来声明的函数指针,将放置到名称为.initcall1.init的section中,而通过宏 postcore_initcall() 来声明的函数指针,将放置到名称为.initcall2.init的section中,那么我们之前的语句:subsys_initcall(start_xxx); start_xxx的意义就是
定义了initcall_t 类型的函数指针 __initcall_start_xxx_5s 并赋值为 start_xxx,并将这个函数指针放在命名为".initcall4s.init"的section中。
那么,注册好的函数是什么时候被调用到的呢?Linux系统启动后,初始化函数集的调用过程执行过程:从init/main.c中函数start_kernel()开始,
start_kernel()->rest_init()
在rest_init中会创建init内核线程kernel_init
kernel_init->do_basic_setup->do_initcalls
do_initcalls中会把.initcall.init.中的函数依次执行一遍:
static void __init do_initcalls(void)
{
initcall_t *call;
for (call = __early_initcall_end; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
__early_initcall_end和__initcall_end的定义在文件
#define INITCALLS \
*(.initcallearly.init) \
VMLINUX_SYMBOL(__early_initcall_end) = .; \
*(.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)
#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) = .; \
INITCALLS \
VMLINUX_SYMBOL(__initcall_end) = .;
这样,系统启动的时候,就会对相应的驱动模块,进行初始化。
另外需要注意的是,若驱动选择为内核模块的形式,那么subsys_initcall宏定义如下:
#ifndef MODULE
... ...
#else /* MODULE */
/* Don't use these in modules, but some people do... */
#define early_initcall(fn) module_init(fn)
#define core_initcall(fn) module_init(fn)
#define postcore_initcall(fn) module_init(fn)
#define arch_initcall(fn) module_init(fn)
#define subsys_initcall(fn) module_init(fn)
#define fs_initcall(fn) module_init(fn)
#define device_initcall(fn) module_init(fn)
#define late_initcall(fn) module_init(fn)
#endif
在定义MODULE的情况下对subsys_initcall的定义,等价于使用module_init,
#define module_init(initfn) \
static inline initcall_t __inittest(void) \ /*定义此函数用来检测传入函数的类型,并在编译时提供警告信息*/
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn))); /*声明init_modlue为 initfn的别名,insmod只查找名字为init_module函数并调用*/
typedef int (*initcall_t)(void); /*函数类型定义*/
在以模块方式编译一个模块的时候,会自动生成一个xxx.mod.c文件,在该文件里面定义一个struct module变量,并把init函数设置为上面的init_module() 而上面的这个init_module,被alias成模块的初始化函数(参考
也就是说,模块装载的时候(insmod,modprobe),sys_init_module()系统调用会调用module_init指定的函数(对于编译成模块的情况)
module的自动加载内核在启动时已经检测到了系统的硬件设备,并把硬件设备信息通过sysfs内核虚拟文件系统导出。sysfs文件系统由系统初始化脚本挂载到/sys上。udev扫描sysfs文件系统,根据硬件设备信息生成热插拔(hotplug)事件,udev再读取这些事件,生成对应的硬件设备文件。由于没有实际的硬件插拔动作,所以这一过程被称为coldplug。
udev完成coldplug操作,需要下面三个程序:
udevtrigger——扫描sysfs文件系统,生成相应的硬件设备hotplug事件。
udevd——作为deamon,记录hotplug事件,然后排队后再发送给udev,避免事件冲突(race conditions)。
udevsettle——查看udev事件队列,等队列内事件全部处理完毕才退出。
要规定事件怎样处理就要编写规则文件了.规则文件是udev的灵魂,没有规则文件,udev无法自动加载硬件设备的驱动模块。它一般位于
若应用程序中,需要在一个so加载的时候,做一些事情,可以仿照上述kernel驱动模块initcall的做法。下面结合例子说明。
$cat mk.sh
gcc -fPIC -g -c liba.c gcc -shared -g -o liba.so liba.o gcc -o test main.c -L./ -la export LD_LIBRARY_PATH+="$(pwd)" ./test |
$cat liba.c
#include typedef int (*fn) (void); int fn_a() { printf("fn_a() called\n"); return 0; } __attribute__((__section__(".init_array.2"))) static fn init_a = fn_a; int fn_b() { printf("fn_b() called\n"); return 0; } int fn_c() { printf("fn_c() called\n"); return 0; } __attribute__((__section__(".init_array.1"))) static fn init_c = fn_c; |
$cat main.c
#include extern int fn_b(); int main() { printf("main() called\n"); fn_b(); return 0; } |
结果如下:
fn_c() called
fn_a() called
main() called
fn_b() called