1. 程式人生 > >Insmod模塊加載過程分析

Insmod模塊加載過程分析

rec cto struct const 描述 skip cut 字段 聲明

一.背景

  a) 在進行JZ2440的一個小demo開發的時候,使用自己編譯的內核(3.4.2)及lcd模塊進行加載時,insmod會提示加載失敗因為內核版本不匹配提示當前內核版本為空,並且顯示模塊的內核版本為空。

  b) 嘗試過修改編譯的Makefile文件的內核目錄,及重新編譯內核及模塊並重新燒寫,均無效。

  c) 網上方法,使用統一的gcc編譯文件系統同樣無效編譯較新版本的busybox後命令可以成功使用。

  d) 開始著手分析insmod加載過程,希望發現真正原因

  e) 內核模塊編譯時嘗試繞過insmod的版本檢查(尚未實驗)

二.概述

  模塊是作為ELF對象文件存放在文件系統中的,並通過執行

insmod程序鏈接到內核中。對於每個模塊,系統都要分配一個包含以下數據結構的內存區。

  一個module對象,表示模塊名的一個以null結束的字符串,實現模塊功能的代碼。在2.6內核以前,insmod模塊過程主要是通過modutils中的insmod加載,大量工作都是在用戶空間完成。但在2.6內核以後,系統使用busyboxinsmod指令,把大量工作移到內核代碼處理,無論邏輯上還是代碼量上都比原來精簡了很多,通過busyboxinsmod命令與內核進行接入。

三.insmod調用過程分析

  入口函數在busyboxinsmod.c文件中

int insmod_main(int argc UNUSED_PARAM, char **argv)
{
	char *filename;
	int rc;

	/* Compat note:
	 * 2.6 style insmod has no options and required filename
	 * (not module name - .ko can‘t be omitted).
	 * 2.4 style insmod can take module name without .o
	 * and performs module search in default directories
	 * or in $MODPATH.
	 */

	IF_FEATURE_2_4_MODULES(
		getopt32(argv, INSMOD_OPTS INSMOD_ARGS);
		argv += optind - 1;
	);
	//去的加載模塊的路徑名
	filename = *++argv;
	if (!filename)
		bb_show_usage();

	rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0));
	if (rc)
		bb_error_msg("can‘t insert ‘%s‘: %s", filename, moderror(rc));

	return rc;
}

  

初始化函數bb_init_module中調用的函數parse_cmdline_module_options用來parse傳入參數中的模塊相關參數(文件為modutils.c  

char* FAST_FUNC parse_cmdline_module_options(char **argv, int quote_spaces)
{
    char *options;
    int optlen;

    options = xzalloc(1);
    optlen = 0;
    //便利模塊名後面的模塊參數
    while (*++argv) {
        const
char *fmt; const char *var; const char *val; var = *argv; //為option分配空間 options = xrealloc(options, optlen + 2 + strlen(var) + 2); fmt = "%.*s%s "; val = strchrnul(var, =); if (quote_spaces) { /* * modprobe (module-init-tools version 3.11.1) compat: * quote only value: * var="val with spaces", not "var=val with spaces" * (note: var *name* is not checked for spaces!) */ if (*val) { /* has var=val format. skip ‘=‘ */ val++; if (strchr(val, )) fmt = "%.*s\"%s\" "; } } optlen += sprintf(options + optlen, fmt, (int)(val - var), var, val); } /* Remove trailing space. Disabled */ /* if (optlen != 0) options[optlen-1] = ‘\0‘; */ return options; }

  初始化函數bb_init_module會通過系統調用,調用內核的sys_init_module(syscalls.h聲明,實現在module.c)

/* Return:
 * 0 on success,
 * -errno on open/read error,
 * errno on init_module() error
 */
int FAST_FUNC bb_init_module(const char *filename, const char *options)
{
    size_t image_size;
    char *image;
    int rc;
    bool mmaped;

    if (!options)
        options = "";

//TODO: audit bb_init_module_24 to match error code convention
#if ENABLE_FEATURE_2_4_MODULES
    if (get_linux_version_code() < KERNEL_VERSION(2,6,0))
        return bb_init_module_24(filename, options);
#endif

    image_size = INT_MAX - 4095;
    mmaped = 0;
    image = try_to_mmap_module(filename, &image_size);
    if (image) {
        mmaped = 1;
    } else {
        errno = ENOMEM; /* may be changed by e.g. open errors below */
        image = xmalloc_open_zipped_read_close(filename, &image_size);
        if (!image)
            return -errno;
    }

    errno = 0;
//調用內核的系統調用
    init_module(image, image_size, options);
    rc = errno;
    if (mmaped)
        munmap(image, image_size);
    else
        free(image);
    return rc;
}

  系統調用在內核中的實現(系統調用的調用過程分析以後補上):

SYSCALL_DEFINE3(init_module, void __user *, umod,
        unsigned long, len, const char __user *, uargs)
{
    int err;
    struct load_info info = { };

    err = may_init_module();
    if (err)
        return err;

    pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
           umod, len, uargs);

    err = copy_module_from_user(umod, len, &info);
    if (err)
        return err;

    return load_module(&info, uargs, 0);
}

四.內核中的相關結構體

  以Linux-3.8.2為例,相關結構定義代碼在include/linux/module.h中。

  1. 模塊依賴關系
struct module_use {
    struct list_head source_list;
    struct list_head target_list;
    struct module *source, *target;
};

  2.模塊狀態信息

enum module_state {
    MODULE_STATE_LIVE,    /* Normal state. */
    MODULE_STATE_COMING,    /* Full formed, running module_init. */
    MODULE_STATE_GOING,    /* Going away. */
    MODULE_STATE_UNFORMED,    /* Still setting it up. */
};

  3.模塊計數

/**
 * struct module_ref - per cpu module reference counts
 * @incs: number of module get on this cpu
 * @decs: number of module put on this cpu
*/
struct module_ref {
    unsigned long incs;
    unsigned long decs;
} __attribute((aligned(2 * sizeof(unsigned long))));

  4.module結構(一個長度可怕的結構)

  module對象描述一個模塊。一個雙向循環鏈表存放所有module對象,鏈表頭部存放在modules變量中,而指向相鄰單元的指針存放在每個module對象的list字段中。

Struct module
{
    enum module_state state;    //存放模塊當前狀態
                            //裝載期間狀態為MODULE_STATE_COMING.
                            //正常運行後,狀態變為 MODULE_STATE_LIVE
                            //正在卸載時,狀態為 MODULE_STATE_GOING

    /* Member of list of modules */
    struct list_head list;        //模塊鏈表指針,所有加載的模塊保存在雙向鏈表中,鏈表頭部為定義的全局變量modules。

    /* Unique handle for this module */
    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 */
/*這三個用於管理模塊導出符號,syms是一個數組,有num_syms個數組項,數組項類型為kernel_symbol,負責將標識符(name)分配到內存地址(value)
 struct kernel_symbol 
 { 
     unsigned long value; 
     const char *name; 
 }; 
    //crcs也是一個num_syms個數組項的數組,存儲了導出符號的校驗和,用於實現版本控制
*/    
        const struct kernel_symbol *syms;        //指向導出符號數組的指針
    const unsigned long *crcs;            //指向導出符號CRC值數組的指針
    unsigned int num_syms;                //導出符號數目

    /* Kernel parameters. */
    struct kernel_param *kp;    //內核參數
    unsigned int num_kp;        //內核參數個數

    /* GPL-only exported symbols. */
 /*在導出符號時,內核不僅考慮了可以有所有模塊(不考慮許可證類型)使用的符號,還要考慮只能由 GPL 兼容模塊使用的符號。 第三類的符號當前仍然可以有任意許可證的模塊使用,但在不久的將來也會轉變為只適用於 GPL 模塊。gpl_syms,num_gpl_syms,gpl_crcs 成員用於只提供給 GPL 模塊的符號;gpl_future_syms,num_gpl_future_syms,gpl_future_crcs 用於將來只提供給 GPL 模塊的符號。unused_gpl_syms 和 unused_syms 以及對應的計數器和校驗和成員描述。 這兩個數組用於存儲(只適用於 GPL)已經導出, 但 in-tree 模塊未使用的符號。在out-of-tree 模塊使用此類型符號時,內核將輸出一個警告消息。 
*/ 
 unsigned int num_gpl_syms;            //GPL格式導出符號數
 const struct kernel_symbol *gpl_syms;    //指向GPL格式導出符號數組的指針
 const unsigned long *gpl_crcs;        //指向GPL格式導出符號CRC值數組的指針
#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

#ifdef CONFIG_MODULE_SIG
    /* Signature was verified. */
    bool sig_ok;
#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 */
 /*如果模塊定義了新的異常,異常的描述保存在 extable數組中。 num_exentries 指定了數組的長度。 */
    unsigned int num_exentries;
    struct exception_table_entry *extable;

  /*模塊的二進制數據分為兩個部分;初始化部分和核心部分。 
 前者包含的數據在轉載結束後都可以丟棄(例如:初始化函數),後者包含了正常運行期間需要的所有數據。   
 初始化部分的起始地址保存在 module_init,長度為 init_size 字節; 
 核心部分有 module_core 和 core_size 描述。 
 */
    /* Startup function. */
    int (*init)(void);

    /* If this is non-NULL, vfree after init() returns */
    void *module_init; //用於模塊初始化的動態內存區指針

    /* Here is the actual code + data, vfree‘d on unload. */
    void *module_core; //用於模塊核心函數與數據結構的動態內存區指針

    /* Here are the sizes of the init and core sections */
    unsigned int init_size, core_size;  //用於模塊初始化的動態內存區大小和用於模塊核心函數與數據結構的動態內存區指針

    /* The size of the executable code in each section.  */
 //模塊初始化的可執行代碼大小,模塊核心可執行代碼大小,只當模塊鏈接時使用
    unsigned int init_text_size, core_text_size;

    /* Size of RO sections of the module (text+rodata) */
    unsigned int init_ro_size, core_ro_size;

    /* Arch-specific module values */
    struct mod_arch_specific arch; //依賴於體系結構的字段
 /*如果模塊會汙染內核,則設置 taints.汙染意味著內核懷疑該模塊做了一個有害的事情,可能妨礙內核的正常運作。 
 如果發生內核恐慌(在發生致命的內部錯誤,無法恢復正常運作時,將觸發內核恐慌),那麽錯誤診斷也會包含為什麽內核被汙染的有關信息。 
 這有助於開發者區分來自正常運行系統的錯誤報告和包含某些可疑因素的系統錯誤。 
 add_taint_module 函數用來設置 struct module 的給定實例的 taints 成員。  
模塊可能因兩個原因汙染內核: 
 1,如果模塊的許可證是專有的,或不兼容 GPL,那麽在模塊載入內核時,會使用 TAINT_PROPRIETARY_MODULE. 
   由於專有模塊的源碼可能弄不到,模塊在內核中作的任何事情都無法跟蹤,因此,bug 很可能是由模塊引入的。 
  
   內核提供了函數 license_is_gpl_compatible 來判斷給定的許可證是否與 GPL 兼容。 
 2,TAINT_FORCED_MODULE 表示該模塊是強制裝載的。如果模塊中沒有提供版本信息,也稱為版本魔術(version magic), 
   或模塊和內核某些符號的版本不一致,那麽可以請求強制裝載。  
 */
    unsigned int taints;    /* same bits as kernel:tainted */

#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).
     */
    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 */
    struct module_notes_attrs *notes_attrs;
#endif

    /* The command line arguments (may be mangled).  People like
       keeping pointers to this stuff */
    char *args;

#ifdef CONFIG_SMP
    /* Per-cpu data. */
    void __percpu *percpu;
    unsigned int percpu_size;
#endif

#ifdef CONFIG_TRACEPOINTS
    unsigned int num_tracepoints;
    struct tracepoint * const *tracepoints_ptrs;
#endif
#ifdef HAVE_JUMP_LABEL
    struct jump_entry *jump_entries;
    unsigned int num_jump_entries;
#endif
#ifdef CONFIG_TRACING
    unsigned int num_trace_bprintk_fmt;
    const char **trace_bprintk_fmt_start;
#endif
#ifdef CONFIG_EVENT_TRACING
    struct ftrace_event_call **trace_events;
    unsigned int num_trace_events;
#endif
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
    unsigned int num_ftrace_callsites;
    unsigned long *ftrace_callsites;
#endif

#ifdef CONFIG_MODULE_UNLOAD
    /* What modules depend on me? */
    struct list_head source_list;
    /* What modules do I depend on? */
    struct list_head target_list;

    /* Who is waiting for us to be unloaded */
    struct task_struct *waiter; //正卸載模塊的進程

    /* Destruction function. */
    void (*exit)(void);
 /*module_ref 用於引用計數。系統中的每個 CPU,都對應到該數組中的數組項。該項指定了系統中有多少地方使用了該模塊。 
內核提供了 try_module_get 和 module_put 函數,用對引用計數器加1或減1,如果調用者確信相關模塊當前沒有被卸載, 
也可以使用 __module_get 對引用計數加 1.相反,try_module_get 會確認模塊確實已經加載。
 struct module_ref { 
   unsigned int incs; 
   unsigned int decs; 
  } 
*/ 
    struct module_ref __percpu *refptr; //模塊計數器,每個cpu一個
#endif

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

五.模塊鏈接過程

  用戶可以通過執行insmod外部程序把一個模塊鏈接到正在運行的內核中。該過程執行以下操作:
  1.從命令行中讀取要鏈接的模塊名
  2.確定模塊對象代碼所在的文件在系統目錄樹中的位置。
  3.從磁盤讀入存有模塊目標代碼的文件。
  4.調用init_module()系統調用。函數將模塊二進制文件復制到內核,然後由內核完成剩余的任務。
  5.init_module函數通過系統調用層,進入內核到達內核函數 sys_init_module,這是加載模塊的主要函數。
  6.結束。

Insmod模塊加載過程分析