Linux內核驅動加載過程
驅動加載分爲兩種情況:靜態加載和動態加載。
1. 靜態加載
靜態加載的方法是把驅動程序直接編譯進內核,然後內核在啓動過程中由do_initcall()函數加載。
do_initcalls()函數路徑在/init/main.c
過程如下:
start_kernel()--->rest_init()--->kernel_init()--->do_basic_setup()--->do_initcalls()
do_initcalls()函數內容如下:
staticvoid __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn <__initcall_end; fn++)
do_one_initcall(*fn);
}
該函數中會將在__initcall_start和__initcall_end之間定 義的各個模塊依次加載,其中.initcall.init段包含了這之間的內容。
在/arch/powerpc/kernel/vmlinux.lds文件,找到.initcall.init段:
.initcall.init: {
__initcall_start= .;
*(.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)
__initcall_end = .;
}
從中能夠了解到這兩個宏之間依次排列了14個等級的宏,由於這些宏是按先後順序鏈接的,所以也就表示,這14個宏有優先級:0>1>1s>2>2s………>7>7s,其具體的意義需要看/include/linux/init.h文件:
#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)
在靜態編譯時module_init就相當於device_initcall。在的內核 中,gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init,因爲arch_initcall的優先級大於module_init,所以gianfar設備驅動的device先於driver在總線上添加。
在linux內核代碼裏,通過調用subsys_initcall來進行各種子系統的初始化,在<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) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall"level ".init"))) = fn
subsys_initcall(fn) == __initcall_fn4 它將被鏈接器放於section .initcall4.init 中。
start_kernel->rest_init系統啓動後會在rest_init中會創建kernel_init內核線程
kernel_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. 動態加載
動態加載方式的初始化函數調用(模塊方式)。 在<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
① module_init分析先看看module_init宏究竟做了什麼
#definemodule_init(init fn) \
staticinline initcall_t __inittest(void) \ /*定義此函數用來檢測傳入函數的類型,並在編譯時提供警告信息*/
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));/*聲明init_modlue爲 initfn的別名,insmod只查找名字爲init_module函數並調用*/
typedefint (*initcall_t)(void); /*函數類型定義*/
在以模塊方式編譯一個模塊的時候,會自動生成一個xxx.mod.c文件,在該文件裏面定義一個struct module變量,並把init函數設置爲上面的init_module()而上面的這個init_module,被alias成模塊的初始化函數(參考<gcc關鍵字:__attribute__, alias, visibility, hidden>)。
也就是說,模塊裝載的時候(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,避免事件衝突(raceconditions)。
udevsettle——查看udev事件隊列,等隊列內事件全部處理完畢才退出。
要規定事件怎樣處理就要編寫規則文件了.規則文件是udev的靈魂,沒有規則文件,udev無法自動加載硬件設備的驅動模塊。它一般位於<etc/udev/rules.d>
③ 將first.c編譯成一個動態庫,其中,函數first()和函數three()放在兩個不同的初始化段裏,函數second()默認放置;編譯main.c,鏈接到由first.c編譯成的動態庫,觀察各函數的執行順序。
# cat first.c#include <stdio.h>
typedefint (*fn) (void);
int first(void)
{
printf("first\n");
return 0;
}
__attribute__((__section__(".init_array.2")))static fn init_first = first;
int three(void)
{
printf("three\n");
return 0;
}
__attribute__((__section__(".init_array.1")))static fn init_three = three;
int second()
{
printf("second\n");
return 0;
}
# catmain.c
#include<stdio.h>
int second();
intmain()
{
printf("main\n");
second ();
}
# catmk.sh
gcc-fPIC -g -c first.c
gcc-shared -g -o liba.so first.o
cp libfirst.so/lib/ -fr
gccmain.c libfirst.so
ldconfig
./first.out
# gcc-fPIC -g -c first.c
# gcc-shared -g -o libfirst.so first.o
# cp libfirst.so/lib/
# gccmain.c libfirst.so
#ldconfig
# ./first.out
first
three
main
second
3. 總結
① 靜態加載
⑴概念
在執行make menuconfig命令進行內核配置裁剪時,在窗口中可以選擇是否編譯入內核,還是放入/lib/modules/下相應內核版本目錄中,還是不選。
⑵操作步驟
linux設備一般分爲:字符設備、塊設備和網絡設備,每種設備在內核源代碼目錄樹drivers/下都有對應的目錄,其加載方法類似,下面以字符設備靜態加載爲例,假設驅動程序源代碼名爲ledc.c,具體操作步驟如下:
第一步:將ledc.c源程序放入內核源碼drivers/char/下;
第二步:修改drivers/char/Config.in文件,具體修改如下:
按照打開文件中的格式添加即可;
在文件的適當位置(這個位置隨便都可以,但這個位置決定其在make menuconfig窗口中所在位置)加入以下任一段代碼:
tristate 'LedDriver' CONFIG_LEDC
if [ "$CONFIG_LEDC" ="y" ];then
bool ' Support for led on h9200 board' CONFIG_LEDC_CONSOLE
fi
說明:以上代碼使用tristate來定義一個宏,表示此驅動可以直接編譯至內核(用*選擇),也可以編制至/lib/modules/下(用M選擇),或者不編譯(不選)。
bool 'LedDriver' CONFIG_LEDC
if [ "$CONFIG_LEDC" ="y" ];then
bool ' Support for led on h9200 board' CONFIG_LEDC_CONSOLE
fi
說明:以上代碼使用tristate來定義一個宏,表示此驅動只能直接編譯至內核(用*選擇)或者不編譯(不選),不能編制至/lib/modules/ 下(用M選擇)。
第三步:修改drivers/char/Makefile文件
在適當位置加入下面一行代碼:
obj-$(CONFIG_LEDC) += ledc.o
或者在obj-y一行中加入ledc.o,如:
obj-y += ledc.o mem.o 後面不變;
OK,經過以上的設置就可以在執行makemenuconfig命令後的窗口中的character devices---> 中進行選擇配置了,選擇後重新編譯就ok了。
② 動態加載
動態加載是將驅動模塊加載到內 核中,而不能放入/lib/modules/下。
在2.4內核中,加載驅動命令爲:insmod,刪除模塊爲:rmmod;
在2.6以上內核中,除了insmod與rmmod外,加載命令還有modprobe;
insmod與modprobe不同之處:
insmod 絕對路徑/××.o,而modprobe ××即可,不用加.ko或.o後綴,也不用加路徑;最重要的一點是:modprobe同時會加載當前模塊所依賴的其它模塊;
lsmod查看當前加載到內核中的所有驅動模塊,同時提供其它一些信息,比如其它模塊是否在使用另一個模塊。