1. 程式人生 > >linux裝置和驅動載入的先後順序

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