系統初始化函數集(subsys_initcall)和初始化段應用

系統初始化函數集(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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章