Linux內核驅動加載過程

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查看當前加載到內核中的所有驅動模塊,同時提供其它一些信息,比如其它模塊是否在使用另一個模塊。

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