一.背景
a) 在進行JZ2440的一個小demo開發的時候,使用本身編譯的內核(3.4.2)及lcd模塊進行加載時,insmod會提示加載失敗由於內核版本不匹配(提示當前內核版本爲空),而且顯示模塊的內核版本爲空。linux
b) 嘗試過修改編譯的Makefile文件的內核目錄,及從新編譯內核及模塊並從新燒寫,均無效。數組
c) 網上方法,使用統一的gcc編譯文件系統一樣無效,編譯較新版本的busybox後命令能夠成功使用。數據結構
d) 開始着手分析insmod加載過程,但願發現真正緣由函數
e) 內核模塊編譯時嘗試繞過insmod的版本檢查(還沒有實驗)ui
二.概述
模塊是做爲ELF對象文件存放在文件系統中的,並經過執行insmod程序連接到內核中。對於每一個模塊,系統都要分配一個包含如下數據結構的內存區。this
一個module對象,表示模塊名的一個以null結束的字符串,實現模塊功能的代碼。在2.6內核之前,insmod模塊過程主要是經過modutils中的insmod加載,大量工做都是在用戶空間完成。但在2.6內核之後,系統使用busybox的insmod指令,把大量工做移到內核代碼處理,不管邏輯上仍是代碼量上都比原來精簡了不少,經過busybox的insmod命令與內核進行接入。spa
三.insmod調用過程分析
入口函數在busybox的insmod.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) debug
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中。
- 模塊依賴關係
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.結束。