Linux Module


catalog

1. 概述
2. 使用模塊
3. 插入和刪除模塊
4. 自動化與熱插拔
5. 版本控制

 

1. 概述

模塊(module)是一種向Linux內核添加設備驅動程序、文件系統及其他組件的有效方法,而無需連編新內核或重啟系統,模塊消除了宏內核的許多限制,模塊有許多優點

1. 通過使用模塊,內核發布者能夠預先編譯大量驅動程序,但並不會造成內核鏡像發生膨脹,在自動檢測硬件(例如USB)或用戶提示之后,安裝例程選擇適當的模塊並將其添加到內核中
2. 內核開發者可以將試驗性的代碼打包到模塊中,模塊可以卸載、修改代碼或重新打包后可以重新加載,這使得可以快速測試新特性,無需每次都重啟系統

模塊幾乎可以無縫地插入到內核,模塊代碼導出一些函數,可以由其他核心模塊(包括持久編譯到內核中的代碼)使用,同樣,在模塊代碼需要卸載時,模塊和內核剩余部分之間的關聯,也會相應終止

Relevant Link:

 

2. 使用模塊

添加和移除模塊涉及幾個系統調用,這些通常由modtils工具包調用

0x1: 添加和移除

從用戶的角度來看,模塊可通過兩個不同的系統程序添加到運行的內核中

1. modprobe: 考慮了各個模塊之間可能出現的依賴性,即在一個模塊依賴於一個或多個合作者模塊的功能,modprobe在識別出目標模塊所依賴的模塊之后,在內核也會使用Insmod
2. insmod: 只加載一個單一的模塊到內核中,而該模塊可能只信賴內核中已存在的代碼,並不關注所依賴的代碼是通過模塊動態加載,還是持久編譯到內核中

模塊文件是可重定位的,可重定位文件的函數都不會引用絕對地址,而只是指向代碼中的相對地址,因此可以在內存的任意偏移地址加載,當然在映像加載到內存中時,映像中的地址要由動態鏈接器ld.so進行適當的修改,內核模塊也是一樣的,內核模塊中的地址也是相對的,而不是絕對的,但重定位的工作由內核自身執行,而不是動態裝載器
在處理init_module系統調用時,模塊代碼首先復制到內核內存中,接下來是重定位工作和解決模塊中未定義的引用,因為模塊使用了持久編譯到內核中的函數,在模塊本身編譯時無法確定這些函數的地址,所以需要在這里處理未定義的引用,這些函數定義在內核的基礎代碼中,因為已經加載到內存,為此,內核提供了一個所有導出函數的列表,該列表給出所有導出函數的內存地址和對應函數名,可以通過proc文件系統訪問

cat /proc/kallsyms

這是對於內核中持久編譯的代碼和動態添加的模塊導入的代碼的數組,其數組項用於將符號分配到虛擬地址空間中對應的地址

0x2: 依賴關系

一個模塊還可以依賴一個或多個其他模塊,在向內核添加模塊時,還需要考慮下列問題

1. 內核提供的函數符號表,可以在模塊加載時動態擴展其長度,模塊可以指定其代碼中哪些函數可以導出,哪些函數僅供內部使用
2. 如果模塊之間有依賴,那么向內核添加模塊的順序很重要,否則會造成函數引用的地址無法解析,modutils標准工具集中的depmod工具可用於計算系統的各個模塊之間的依賴關系,每次系統啟動時或新模塊安裝后,通常會運行該程序,找到的依賴關系保存在一個列表中,默認情況下,寫入/lib/modules/2.6.18-308.el5(對應ersion)/modules.dep中,該信息由modprobe處理,該工具在現存的依賴關系能夠自動解決的情況下向內核插入模塊

depmod分析所有可用的模塊的二進制代碼,對每個模塊建立一個列表,包含所有已定義符號和未解決的引用,最后將各個模塊的列表彼此進行比較,如果模塊A包含的一個符號在模塊B中是未解決的引用,則意味着模塊B依賴模塊A,接下來在依賴文件中以B:A的形式增加一項,即確認了上述事實,模塊引用的大多數符號都定義在內核中,而不是定義在其他模塊中

0x3: 查詢模塊信息

有一些額外信息,是直接存儲在模塊二進制文件中,並且指定了模塊用途的文本描述,這可以使用modinfo工具查詢

1. 驅動的開發者
2. 驅動程序的簡短描述
3. 可以傳遞給模塊的配置參數
4. 指定支持的設備
5. 該模塊按何種許可證分發(重要)

0x4: 自動加載

通常,模塊的裝載發起於用戶空間,由用戶或者自動化腳本啟動,在處理模塊時,為達到更大的靈活性並提高透明度,內核自身也能夠請求加載模塊,由於在用戶空間完成這些比內核空間容易得多,內核將工作委托給一個輔助進程kmod,kmod並不是一個永久性的守護進程,內核會按需啟動它
內核源代碼中,很多不同地方調用了request_module,借助該函數,內核試圖通過在沒有用戶介入的情況下自動加載代碼,使得盡可能透明地訪問那些委托給模塊的功能

 

3. 插入和刪除模塊

用戶空間和內核的模塊實現之間的接口,包括以下幾個系統調用

1. init_module: 將一個新模塊插入到內核中,用戶空間工具只需要提供二進制數據,所有其他工作(特別是重定位和解決引用)由內核自身完成
2. delete_module: 從內核移除一個模塊(前提是該模塊的代碼不再使用,並且其他模塊也不再使用該模塊導出的函數,否則強行卸載會造成內核地址引用錯誤PANIC)
3. reque_module(非系統調用): 用於從內核端加載模塊,它不僅僅用於加載模塊,還用於實現熱插拔功能  

0x1: 模塊的表示

我們接下來討論在內核中表示模塊(及其屬性),其中,module是最重要的數據結構,內核中駐留的每個模塊,都分配了該結構的一個實例
\linux-2.6.32.63\include\linux\module.h

struct module
{
    /*
    state表示模塊的當前狀態
    enum module_state
    {
        MODULE_STATE_LIVE,        //正常運行
        MODULE_STATE_COMING,    //裝載期間
        MODULE_STATE_GOING,        //正在移除
    };
    */
    enum module_state state;

    /* 
    Member of list of modules,    用作模塊鏈表的鏈表元素 
    list是一個標准的鏈表元素,在內核使用,將所有加載模塊保存到一個雙鏈表中,鏈表的表頭定義為全局變量modules
    */
    struct list_head list;

    /* 
    Unique handle for this module 該模塊的唯一句柄 
    name指定了模塊的名稱,該名稱必須是唯一的,內核中會使用該名稱來引用模塊
    */
    char name[MODULE_NAME_LEN];

    /* Sysfs stuff. */
    struct module_kobject mkobj;
    struct module_attribute *modinfo_attrs;
    const char *version;
    const char *srcversion;
    struct kobject *holders_dir;

    /* Exported symbols 導出的符號 */
    const struct kernel_symbol *syms;
    //crcs也是num_syms個數組項的數組,存儲了導出符號的校驗和,用於實現版本控制
    const unsigned long *crcs;
    unsigned int num_syms;

    /* Kernel parameters. */
    struct kernel_param *kp;
    unsigned int num_kp;

    /* GPL-only exported symbols. 只適用於GPL的導出符號 */
    unsigned int num_gpl_syms;
    const struct kernel_symbol *gpl_syms;
    const unsigned long *gpl_crcs;

#ifdef CONFIG_UNUSED_SYMBOLS
    /* unused exported symbols. */
    const struct kernel_symbol *unused_syms;
    const unsigned long *unused_crcs;
    unsigned int num_unused_syms;

    /* GPL-only, unused exported symbols. */
    unsigned int num_unused_gpl_syms;
    const struct kernel_symbol *unused_gpl_syms;
    const unsigned long *unused_gpl_crcs;
#endif

    /* symbols that will be GPL-only in the near future. */
    const struct kernel_symbol *gpl_future_syms;
    const unsigned long *gpl_future_crcs;
    unsigned int num_gpl_future_syms;

    /* Exception table 異常表 */
    unsigned int num_exentries;
    struct exception_table_entry *extable;

    /* Startup function. 初始化函數 */
    int (*init)(void);

    /*
    模塊的二進制數據分為連個部分
    1. 初始化部分: 裝載結束后都可以丟棄,例如初始化函數
    2. 核心部分: 包含了正常運行期間需要的所有數據
    */
    /* If this is non-NULL, vfree after init() returns 如果不是NULL,則在init()返回后調vfree釋放 */
    void *module_init;
    /* Here is the actual code + data, vfree'd on unload. 這是實際的代碼和數據,在卸載時調用vfree釋放*/
    void *module_core;

    /* Here are the sizes of the init and core sections module_init、module_core兩個內存區的長度 */
    unsigned int init_size, core_size;

    /* The size of the executable code in each section. 上述兩個內存區中可執行代碼的長度 */
    unsigned int init_text_size, core_text_size;

    /* Arch-specific module values 特定於體系結構的值 */
    struct mod_arch_specific arch;

    /* 
    same bits as kernel:tainted
    如果模塊會污染內核,則設置taints,污染意味着內核懷疑該模塊做了一些有害的事情可能妨礙內核的正確運行,模塊可能因為兩個原因污染內核
    1. 如果模塊的許可證是專有的,或不兼容GPL(即沒有聲明GPL),那么在模塊載入內核時,會使用TAINT_PROPRIETARY_MODULE
    2. TAINT_FORCED_MODULE表示該模塊是強制裝載的,如果模塊中沒有提供版本信息,也稱作版本魔數(version magic),或模塊和內核某些符號的版本不一致,那么可以請求強制裝載
    */
    unsigned int taints;    

#ifdef CONFIG_GENERIC_BUG
    /* Support for BUG */
    unsigned num_bugs;
    struct list_head bug_list;
    struct bug_entry *bug_table;
#endif

#ifdef CONFIG_KALLSYMS
    /*
     * We keep the symbol and string tables for kallsyms.
     * The core_* fields below are temporary, loader-only (they
     * could really be discarded after module init).
     kallsyms的符號表和字符串表
     KALLSYMS是一個配置選項(但只用於嵌入式系統,在普通計算機上總是啟用的),啟用該選項后,將在內存中建立一個列表,保存內核自身和加載模塊中定義的所有符號(否則只存儲導出的函數)
     如果oops消息(內核檢測到背離常規的行為,例如反引用NULL指針),不僅輸出16進制數字(地址),還要輸出涉及函數的名稱,那么該選項就很有用    
     */
    Elf_Sym *symtab, *core_symtab;
    unsigned int num_symtab, core_num_syms;
    char *strtab, *core_strtab;

    /* Section attributes 模塊中各段的屬性 */
    struct module_sect_attrs *sect_attrs;

    /* Notes attributes note屬性 */
    struct module_notes_attrs *notes_attrs;
#endif

    /* Per-cpu data. per-cpu數據,它在模塊裝載時初始化 */
    void *percpu;

    /* The command line arguments (may be mangled).  People like keeping pointers to this stuff */
    char *args;
#ifdef CONFIG_TRACEPOINTS
    struct tracepoint *tracepoints;
    unsigned int num_tracepoints;
#endif

#ifdef CONFIG_TRACING
    const char **trace_bprintk_fmt_start;
    unsigned int num_trace_bprintk_fmt;
#endif
#ifdef CONFIG_EVENT_TRACING
    struct ftrace_event_call *trace_events;
    unsigned int num_trace_events;
#endif
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
    unsigned long *ftrace_callsites;
    unsigned int num_ftrace_callsites;
#endif

#ifdef CONFIG_MODULE_UNLOAD
    /* 
    What modules depend on me? 依賴當前模塊的模塊 
    modules_which_use_me用作一個鏈表元素,將模塊連接到內核用於描述模塊間依賴關系的數據結構中
    */
    struct list_head modules_which_use_me;

    /*
    Who is waiting for us to be unloaded 等待當前模塊卸載的進程 
    waiter是一個指針,指向導致模塊卸載並且正在等待該操作結束的進程的task_struct實例
    */
    struct task_struct *waiter;

    /* Destruction function. 析構函數 */
    void (*exit)(void);

/*
引用計數
系統中每個CPU,都對應到該數組中的一個數組項,該項指定了系統中有多少地方使用了該模塊
*/
#ifdef CONFIG_SMP
    char *refptr;
#else
    local_t ref;
#endif
#endif

#ifdef CONFIG_CONSTRUCTORS
    /* Constructor functions. */
    ctor_fn_t *ctors;
    unsigned int num_ctors;
#endif
};

0x2: 依賴關系和引用

如果模塊B使用了模塊A提供的函數,那么模塊A和模塊B之間就存在依賴關系,可以用兩種不同的方式來看這種關系

1. 模塊B依賴模塊A,除非模塊A已經駐留在內核內存,否則模塊B無法裝載
2. 模塊B引用模塊A,除非模塊B已經移除,否則模塊A無法從內核移除,即所有引用模塊A的模塊都已經從內核移除。在內核中,這種關系稱之為模塊B使用模塊A

為了正確管理這些依賴關系,內核需要引入另一個數據結構
\linux-2.6.32.63\kernel\module.c

/* modules using other modules */
struct module_use
{
    struct list_head list;
    struct module *module_which_uses;
};

依賴關系的網絡通過module_use、module->modules_which_usr_me成員共同建立起來

1. 對每個使用了模塊A中的函數的模塊B,都會創建一個module_use的新實例
/*
struct module_use
{
    struct list_head list;
    struct module *module_which_uses;
};
*/
2. 將新module_use實例的module_which_uses指針指向模塊B的module實例
3. 該新module_use實例將添加到模塊A的module實例中的modules_which_use_me鏈表

根據這些信息,內核很容易計算出使用特定模塊的其他內核模塊

如果試圖裝載一個模塊,卻因為依賴的模塊不存在,而導致一部分未定義的符號無法解決,內核將返回錯誤碼並放棄裝載,需要明白的是,依賴關系的處理,需要由用戶空間的modprobe來處理
內核提供了already_uses函數,來判斷模塊A是否需要另一個模塊B
\linux-2.6.32.63\kernel\module.c

/* Does a already use b? */
static int already_uses(struct module *a, struct module *b)
{
    struct module_use *use;

    /*
    如果模塊A依賴模塊B,則模塊B的modules_which_use_me鏈表中必定至少有一個鏈表元素的module_which_uses成員指向了模塊A的module實例的指針
    */
    list_for_each_entry(use, &b->modules_which_use_me, list) 
    {
        if (use->module_which_uses == a) 
        {
            DEBUGP("%s uses %s!\n", a->name, b->name);
            //如果找到一個匹配項,則依賴關系確實存在
            return 1;
        }
    }
    DEBUGP("%s does not use %s!\n", a->name, b->name);
    return 0;
}

use_module用於建立模塊A和模塊B之間的關系,即模塊A需要模塊B才能正確運行

int use_module(struct module *a, struct module *b)
{
    struct module_use *use;
    int no_warn, err;

    //already_uses首先檢查該關系是否已經建立
    if (b == NULL || already_uses(a, b)) return 1;

    /* 
    If we're interrupted or time out, we fail.  
    將模塊B的引用計數器加1,使之不能從內核移除,因為模塊A堅決要求模塊B駐留在內存中
    static inline int strong_try_module_get(struct module *mod)
    {
        if (mod && mod->state == MODULE_STATE_COMING)
            return -EBUSY;
        if (try_module_get(mod))
            return 0;
        else
            return -ENOENT;
    }
    */
    if (wait_event_interruptible_timeout( module_wq, (err = strong_try_module_get(b)) != -EBUSY, 30 * HZ) <= 0) 
    {
        printk("%s: gave up waiting for init of module %s.\n",
               a->name, b->name);
        return 0;
    }

    /* If strong_try_module_get() returned a different error, we fail. */
    if (err)
        return 0;

    DEBUGP("Allocating new usage for %s.\n", a->name);
    use = kmalloc(sizeof(*use), GFP_ATOMIC);
    if (!use) 
    {
        printk("%s: out of memory loading\n", a->name); 
        module_put(b);
        return 0;
    }

    use->module_which_uses = a;
    list_add(&use->list, &b->modules_which_use_me);
    no_warn = sysfs_create_link(b->holders_dir, &a->mkobj.kobj, a->name);
    return 1;
}
EXPORT_SYMBOL_GPL(use_module);

對於內核模塊的依賴的概念,我們需要明白本質包含的兩種概念層次

1. 模塊安裝/卸載的依賴關系
模塊A、模塊B依次安裝,模塊B使用了模塊A中的某個函數,則模塊B依賴於模塊A,模塊A如果要卸載則必須等待模塊B卸載完成之后才可以繼續進行

2. 模塊運行期間函數調用的引用依賴關系
模塊A裝載進內核內存,並導出了一些函數,用戶態或內核態發起了對這個函數的調用,每次調用對增加了一次對這個模塊A的引用計數,模塊A如果想卸載必須等待這個引用計數降為0時才可以繼續進行

0x3: 模塊的二進制結構

模塊使用ELF二進制結構,模塊中包含了幾個額外的段,普通的程序或庫中不會出現,我們着重討論這些額外的段

1. __ksymtab、__ksymtab_gpl、__ksymtab_gpl_future: 包含一個符號表,包括了模塊導出的所有符號
2. __param: 存儲了模塊可接受的參數有關信息
3. .modinfo: 存儲哎加載當前模塊之間,內核中必須先行加載的所有其他模塊的名稱,即該特定模塊所依賴的其他模塊
4. .exit.text: 包含了在該模塊從內核移除時,所需使用的代碼(和可能的數據),該信息並未保存在普通代碼段中,這樣如果內核配置中未啟用移除模塊的選項,就不必將該段載入內存
5. .init_text: 初始化函數(和數據)使用一個獨立的段,因為初始化完成后,相關的代碼和數據就不再需要,因而可以從內存移除
6. .gnu_linkonce_this_module: 提供了struct module的一個實例,其中存儲了模塊的名稱(name)、和指向二進制文件中的初始化函數和清理函數(init、cleanup)的指針,根據本段,內核即可判斷特定的二進制文件是否為模塊,如果沒有該段,則拒絕裝載文件

在模塊自身和所依賴的所有其他內核模塊都已經編譯完成之前,上述的一些段是無法生成的,例如列出模塊所有依賴關系的段,因為源代碼中沒有明確給出依賴關系信息,內核必須通過分析目標模塊的未解決引用和所有其他模塊導出的符號,來獲取該信息
生成模塊需要執行下述的幾個步驟

1. 模塊源代碼中的所有C文件都編譯為普通的.o目標文件
2. 在為所有模塊產生目標文件后,內核可以分析它們,找到附加信息(例如模塊依賴關系),保存在一個獨立文件中,也編譯為一個二進制文件
3. 將前述兩個步驟產生的二進制文件鏈接起來,生成最終的模塊

1. 初始化和清理函數

模塊的初始化函數和清理函數,保存在.gnu.linkonce.module段中的module實例中
<init.h>中的module_init宏、module_exit宏用於定義init函數、exit函數,每個模塊都包含上述兩個宏定義的代碼,用於定義init、exit函數
__init、__exit前綴有助於將這兩個函數放置到二進制代碼的正確的段中
\linux-2.6.32.63\include\linux\init.h

#define __init        __section(.init.text) __cold notrace
#define __initdata    __section(.init.data)
#define __initconst    __section(.init.rodata)
#define __exitdata    __section(.exit.data)
#define __exit_call    __used __section(.exitcall.exit)

2. 導出符號

內核為導出符號提供了兩個宏

1. EXPORT_SYMBOL: 用於一般的導出符號
2. EXPORT_SYMBOL_GPL: 只用於GPL兼容代碼的導出符號

\linux-2.6.32.63\include\linux\module.h

/* For every exported symbol, place a struct in the __ksymtab section */
#define __EXPORT_SYMBOL(sym, sec)                \
    extern typeof(sym) sym;                    \
    __CRC_SYMBOL(sym, sec)                    \
    static const char __kstrtab_##sym[]            \
    __attribute__((section("__ksymtab_strings"), aligned(1))) \
    = MODULE_SYMBOL_PREFIX #sym;                        \
    static const struct kernel_symbol __ksymtab_##sym    \
    __used                            \
    __attribute__((section("__ksymtab" sec), unused))    \
    = { (unsigned long)&sym, __kstrtab_##sym }

#define EXPORT_SYMBOL(sym)                    \
    __EXPORT_SYMBOL(sym, "")

#define EXPORT_SYMBOL_GPL(sym)                    \
    __EXPORT_SYMBOL(sym, "_gpl")

#define EXPORT_SYMBOL_GPL_FUTURE(sym)                \
    __EXPORT_SYMBOL(sym, "_gpl_future")

3. 一般模塊信息

模塊的.modinfo段包含了一般信息,使用MODULE_INFO設置

/* Generic info of form tag = "info" */
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)

這個段中並沒有什么特別重要的元素,我們來重點關注"基本的版本控制"這個元素信息,".modinfo"段中總是會存儲某些必不可少的版本控制信息,無論內核的版本控制特性是否啟用,這使得可以從各種內核配置中區分出特別影響整個內核源代碼的那些配置,這些可能需要一個單獨的模塊集合,在模塊編譯的第二階段期間,下列代碼會鏈接到每個模塊中

module.mod.c
MODULE_INFO(vermagic, VERMAGIC_STRING);
//VERMAGIC_STRING是一個字符串,表示內核配置的關鍵特性

\linux-2.6.32.63\include\linux\vermagic.h
#define VERMAGIC_STRING                         \
    UTS_RELEASE " "                            \
    MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT             \
    MODULE_VERMAGIC_MODULE_UNLOAD MODULE_VERMAGIC_MODVERSIONS    \
    MODULE_ARCH_VERMAGIC

內核自身和每個模塊中都會存儲VERMAGIC_STRING的一份副本,因為不同處理器可用的特性可能相差很多,例如,如果模塊編譯時特意對Pentium4處理器進行優化,那么可能無法插入為Athlon處理器編譯的內核中

0x4: 插入模塊

init_module系統調用是用戶空間和內核之間用於裝載新模塊的接口,關於init_module的相關知識,請參閱另一篇文章

http://www.cnblogs.com/LittleHann/p/3920387.html
//搜索:3. LKM模塊加載原理

0x5: 移除模塊

從內核移除模塊比插入模塊簡單得多,關於delete_module的相關知識,請參閱另一篇文章

http://www.cnblogs.com/LittleHann/p/3920387.html
//搜索:4. LKM模塊卸載原理

 

4. 自動化與熱插拔

模塊不僅可以根據用戶指令或自動化腳本裝載,還可以由內核自身請求裝載,這種裝載機制,在下面兩種情況很有用處

1. 內核確認一個需要的功能當前不可用,例如,需要裝載一個文件系統,但內核不支持,內核可以嘗試加載所需的模塊,然后重試裝載文件系統
2. 一個新設備連接到可熱插拔的總線(USB、FireWire、PCI等),內核檢測到新設備並自動裝載包含適當驅動程序的模塊
//在這兩種情況下,內核都依賴用戶空間的實用程序,根據內核提供的信息,實用程序找到適當的模塊並按慣例將其插入內核

0x1: kmod實現的自動加載

在內核發起的模塊自動裝載特性中,\linux-2.6.32.63\kernel\kmod.c中的__request_module是主要的函數,模塊的名稱(或一般占位符)需要傳遞給該函數。請求模塊的操作必須顯式建立在內核中,邏輯上一般出現在以下場合

1. 內核因為沒有可用的驅動程序而導致分配特定的資源失敗
2. 內核中此類場景有很多,例如IDE驅動程序在探測現存的設備時會嘗試加載設備所需的驅動程序,為此必須直接指定所需驅動程序的模塊名

0x2: 熱插拔

在新設備連接到可熱插拔的總線(或移除)時,內核再次借助用戶空間應用程序來確保裝載正確的驅動程序,與通常插入模塊的過程相比,這里有必要執行幾個額外的任務,例如

1. 根據設備標識字符串,找到正確的驅動程序
2. 進行一些配置工作
//這里使用/sbin/udevd完成這些工作

需要明白的是,內核不僅在設備插入與移除時會向用戶空間提供消息,實際上內核在很多一般事件發生時,都會發送消息,例如

1. 在一個新硬盤連接到系統時,內核不僅提供有關該事件的信息,還發送通知,提供該設備上已經找到的分區信息
2. 設備模型的每部分都可以向用戶層發送注冊和撤銷注冊事件
//實際上內核可能發送的消息,組成餓了一個相當龐大和廣泛的集合

我們通過一個具體的USB的例子來更好地說明這個問題,考慮一個USB存儲棒附接到系統,但此時提供USB海量存儲(mass storage)支持的模塊尚未載入內核,系統想要自動地將設備裝載到文件系統,以便用戶可以立即訪問它,為此,需要執行以下步驟

1. USB宿主機控制器在總線上檢測到一個新設備並報告給其設備驅動程序,宿主機控制器分配一個新的device實例並調用usb_new_device注冊它
2. usb_new_device觸發對kobject_uevent的調用,該函數對所述對象kobject實例,調用其中注冊的特定於子系統的事件通知程序
3. 對USB設備對象,usb_uevent用作通知函數,該函數准備一個消息,其中包含了所有必要的信息,使得udevd能夠對新的USB海量存儲設備的插入、作出適當反應
//udevd守護進程可以檢查來源於內核的所有消息

 

5. 版本控制

不斷改變的內核源代碼對驅動程序的程序設計有一定影響,特別是只提供二進制代碼的專有驅動程序,在實現新特性或修訂總體設計時,通常必須修改內核各個部分之間的接口,以處理新的情況或支持性能和設計方面的改進。當然,開發者會盡可能將改動限制到驅動程序不直接使用的那些內部函數,但這並不能排除內核偶爾修改"公開的"接口,很顯然,模塊接口也會受到此類修改的影響。但是對於廠商發布、只提供二進制代碼的驅動程序來說,情況會有所不同,用戶不得不等待新驅動的開發和發布,這種情況會引起一整套問題,包括

1. 如果模塊使用一個廢棄的接口,不僅會損害模塊的正常功能,而且系統很可能PANIC
2. SMP和單處理器系統的接口不同,需要兩個二進制版本,如果裝載了錯誤的版本同樣可能導致系統崩潰

因此,引入了一個細粒度的方法,從而考慮到內核中各個例程的改變,我們無需考慮實際的模塊和內核實現,需要考慮的問題是,如果模塊要再不同的內核版本下運作,那么其調用的接口不能改變,所用的方法很簡單,但卻能很好地解決版本控制問題

0x1: 校驗和方法

基本思想是使用函數或過程的參數,生成一個CRC校驗和,該校驗和是一個4字節數字,如果函數接口修改了,校驗和也會發生變化,這使得內核能夠推斷出新版本已經不再兼容舊版本

1. 生成校驗和

內核源代碼附帶的genksym工具在編譯時自動創建,用於生成函數的校驗和
2. 將校驗和編譯到模塊和內核中

內核必須將genksym提供的信息合並到模塊的二進制代碼中,供后續使用,CRC版本控制檢測本質上是比較兩個東西

1. 掃描當前模塊中所使用的內核導出函數,這是從裝載目標機器的內核中動態獲取並計算的,當然這里面還要處理模塊間依賴關系
2. 模塊自身保存的一份CRC Magic數值,這是模塊在編譯的時候計算並保存的

0x2: 版本控制函數

我們知道,內核使用輔助函數check_version確定模塊所需版本的符號是否與內核中可用符號的版本匹配
\linux-2.6.32.63\kernel\module.c

/*
1. sechdrs: 模塊段頭的一個指針
2. versindex: __version段的索引
3. symname: 將要處理符號的名稱
4. mod: 指向模塊數據結構的一個指針
5. crc: 指向內核提供的對應符號校驗和的一個指針,該校驗和在解析該符號時由__find_symbol動態提供
*/
static int check_version(Elf_Shdr *sechdrs,
             unsigned int versindex,
             const char *symname,
             struct module *mod, 
             const unsigned long *crc,
             const struct module *crc_owner)
{
    unsigned int i, num_versions;
    struct modversion_info *versions;

    /* 
    Exporting module didn't supply crcs?  OK, we're already tainted. 
    導出模塊沒有提供校驗和,那么內核已經被污染了,函數直接返回1,這意味着版本檢查已經成功,因為如果沒有信息可用,那么檢查也不會失敗
    */
    if (!crc)
        return 1;

    /* No versions at all?  modprobe --force does this. */
    if (versindex == 0)
        return try_to_force_load(mod, symname) == 0;

    versions = (void *) sechdrs[versindex].sh_addr;
    num_versions = sechdrs[versindex].sh_size
        / sizeof(struct modversion_info);

    /*
    內核遍歷該模塊所引用的所有符號,從中搜索對應項,並比較模塊中存儲的校驗和與內核返回的校驗和
    1. 如果兩者匹配,則內核返回1
    2. 否則發出一條警告信息,並且函數返回0
    */
    for (i = 0; i < num_versions; i++) 
    {
        if (strcmp(versions[i].name, symname) != 0)
            continue;

        if (versions[i].crc == maybe_relocated(*crc, crc_owner))
            return 1;
        DEBUGP("Found checksum %lX vs module %lX\n", maybe_relocated(*crc, crc_owner), versions[i].crc);
        goto bad_version;
    }

    printk(KERN_WARNING "%s: no symbol version for %s\n", mod->name, symname);
    return 0;

bad_version:
    printk("%s: disagrees about version of symbol %s\n", mod->name, symname);
    return 0;
}

 

Copyright (c) 2015 LittleHann All rights reserved

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM