linux設備和驅動加載的先後順序

Linux驅動先註冊總線,總線上可以先掛device,也可以先掛driver,那麼究竟怎麼控制先後的順序呢。

Linux系統使用兩種方式去加載系統中的模塊動態靜態

靜態加載:將所有模塊的程序編譯到Linux內核中,由do_initcall函數加載

核心進程(/init/main.c)kernel_inità do_basic_setup()àdo_initcalls()該函數中會將在__initcall_start和__initcall_end之間定義的各個模塊依次加載。那麼在__initcall_start 和 __initcall_end之間都有些什麼呢?

找到/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文件:

 #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)

 

這裏就定義了具體的宏,我們平時用的module_init在靜態編譯時就相當於device_initcall。舉個例子,在2.6.24的內核中:gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init,因爲arch_initcall的優先級大於module_init,所以gianfar設備驅動的device先於driver在總線上添加。

 

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

前言:前段時間做一個項目需要設計一個動態庫,並希望在加載庫的同時自動執行一些初始化動作,於是聯想到了linux內核衆子系統的初始化,於是研究之,並在過這程中發現了初始化段的存在,利用初始化段實現了該功能。工作一年,筆記積累多了,慢慢變得雜亂無章,於是開博,一方面整理筆記,梳理知識,另一方面和大家交流,共同進步。

 

keyword:subsys_initcall, init, init_call

 

1 系統初始化調用函數集分析(靜態)

1.1 函數定義

 在linux內核代碼裏,運用了subsys_initcall來進行各種子系統的初始化,具體怎麼初始化的呢?其實並不複雜。以2.6.29內核作爲例子。在<include/linux/init.h>下就能找到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


所以 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>:

#define INITCALLS       \

   *(.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宏被定義,說明該函數將被編譯進模塊裏,在系統啓動後以模塊方式被調用。

#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)

這是在定義MODULE的情況下對subsys_initcall的定義,就是說對於驅動模塊,使用subsys_initcall等價於使用module_init

 

 

2.2 module_init 分析下面先看看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成模塊的初始化函數(參考<gcc關鍵字:__attribute__, alias, visibility, hidden>)。

 

也就是說,模塊裝載的時候(insmod,modprobe),sys_init_module()系統調用會調用module_init指定的函數(對於編譯成>模塊的情況)。

 

2.3 module的自動加載內核在啓動時已經檢測到了系統的硬件設備,並把硬件設備信息通過sysfs內核虛擬文件系統導出sysfs文件系統由系統初始化腳本掛載到/sys上udev掃描sysfs文件系統,根據硬件設備信息生成熱插拔(hotplug)事件,udev再讀取這些事件,生成對應的硬件設備文件。由於沒有實際的硬件插拔動作,所以這一過程被稱爲coldplug。

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

udevtrigger——掃描sysfs文件系統,生成相應的硬件設備hotplug事件。

udevd——作爲deamon,記錄hotplug事件,然後排隊後再發送給udev,避免事件衝突(race conditions)。

udevsettle——查看udev事件隊列,等隊列內事件全部處理完畢才退出。

要規定事件怎樣處理就要編寫規則文件了.規則文件是udev的靈魂,沒有規則文件,udev無法自動加載硬件設備的驅動模塊。它一般位於<etc/udev/rules.d>

 

 

3初始化段的應用這裏給出一個簡單的初始化段的使用例子,將a.c編譯成一個動態庫,其中,函數a()和函數c()放在兩個不同的初始化段裏,函數b()默認放置;編譯main.c,鏈接到由a.c編譯成的動態庫,觀察各函數的執行順序。

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

typedef int (*fn) (void);

int a(void)

{

    printf("a\n");

    return 0;

}

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

int c(void)

{

    printf("c\n");

    return 0;

}

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

 

int b()

{

    printf("b\n");

    return 0;

}

 

# cat main.c

#include<stdio.h>

int b();

int main()

{

    printf("main\n");

    b();

}

 

# cat mk.sh

 

gcc -fPIC -g -c a.c

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

cp liba.so /lib/ -fr

gcc main.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

 

 

在類unix操作系統中,驅動加載方式一般分爲:動態加載和靜態加載,下面分別對其詳細論述。
一、動態加載
動態加載是將驅動模塊加載到內 核中,而不能放入/lib/modules/下。
    在2.4內核中,加載驅動命令爲:insmod ,刪除模塊爲:rmmod
    在2.6以上內核中,除了insmod與rmmod外,加載命令還有modprobe;
    insmod與modprobe不同之處:
    insmod 絕對路徑/××.o,而modprobe ××即可,不用加.ko或.o後綴,也不用加路徑;最重要的一點是:modprobe同時會加載當前模塊所依賴的其它模塊;
    lsmod查看當前加載到內核中的所有驅動模塊,同時提供其它一些信息,比如其它模塊是否在使用另一個模塊。
二、靜態加載
(一)概念
    在執行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,經過以上的設置就可以在執行make menuconfig命令後的窗口中的character devices---> 中進行選擇配置了。選擇後重新編譯就ok了。

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