天天看點

核心子產品的加載

版權聲明:本文為CSDN部落客「[email protected]」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。

原文連結:https://blog.csdn.net/lidan113lidan/article/details/45313535

更多内容可關注微信公衆号

核心子產品的加載

###概述

  • 一般linux中有兩個程式可以添加核心子產品,modprobe和insmod,前者考慮到了各個子產品之間可能出現的依賴關系,被依賴的子產品會被自動載入,而insmod隻是簡單的嘗試載入目前的子產品。二者最終都是通過linux系統調用sys_init_module載入核心子產品的。
  • 編譯好的核心子產品一般是以*.ko結尾的,這類檔案都是可重定位檔案。
  • elf的基本結構如圖:
    核心子產品的加載

###sys_init_module

/* 子產品加載主要就三步:
1. elf->核心
2. 檔案映像->記憶體映像
3. 調用子產品的init函數
子產品的加載首先會在使用者空間将子產品的檔案映射到記憶體(後面稱之為檔案映像),然後調用sys_init_module,核心會将檔案映像複制到核心态,然後根據各個節的屬性,再重新配置設定子產品的空間,重新配置設定的空間我們後面稱之為記憶體映像。
*/
SYSCALL_DEFINE3(init_module, 
		//一個指向使用者位址空間的指針,子產品的二進制代碼位于其中。
		void __user *, umod,  
		unsigned long, len, 		//使用者位址空間的長度
		const char __user *, uargs)	//子產品的參數
{
	struct module *mod;	//核心用struct module來表示一個子產品
	int ret = 0;
	
	//selinux檢查, 目前程序是否有insmod的權限
	if (!capable(CAP_SYS_MODULE) || modules_disabled)
		return -EPERM;

	//載入elf子產品,傳回初始化好的module結構體(最麻煩的一個函數)
	mod = load_module(umod, len, uargs);
	if (IS_ERR(mod))
		return PTR_ERR(mod);

	//利用核心通知鍊,告訴核心其他子系統,有子產品将要裝入
	blocking_notifier_call_chain(&module_notify_list,
			MODULE_STATE_COMING, mod);

	//設定這兩段虛拟位址空間中所有頁的屬性
	/* Set RO and NX regions for core */
	set_section_ro_nx(mod->module_core,
				mod->core_text_size,
				mod->core_ro_size,
				mod->core_size);

	/* Set RO and NX regions for init */
	set_section_ro_nx(mod->module_init,
				mod->init_text_size,
				mod->init_ro_size,
				mod->init_size);
				
	//調用mod中的ctors中的所有函數,這個ctors是啥?
	do_mod_ctors(mod);

	//調用子產品的init函數
	if (mod->init != NULL)
		//基本上等于直接調用mod->init
		ret = do_one_initcall(mod->init);
	
	if (ret < 0) {
		//init函數運作失敗,子產品狀态改為正在解除安裝中
		mod->state = MODULE_STATE_GOING;
		synchronize_sched();
		//減小子產品的使用計數
		module_put(mod);
		//調用核心通知連,通知子產品正在解除安裝中
		   blocking_notifier_call_chain(
				&module_notify_list,
				MODULE_STATE_GOING, mod);
		//釋放子產品
		free_module(mod);
		
		//喚醒module_wq隊列中的所有元素,由于核心的整個modules
		//清單需要原子操作,如果有多個程序同時對modules清單進行
		//操作,後來的就會進入這個module_wq隊列。
		wake_up(&module_wq);
		return ret;
	}
	
	if (ret > 0) {
		//如果加載子產品成功
		dump_stack();	//此函數可以列印目前cpu的堆棧資訊
	}
	
	//設定子產品狀态為加載成功,喚醒所有等待此子產品加載完成的程序。
	mod->state = MODULE_STATE_LIVE;
	wake_up(&module_wq);
	//調用核心通知連,通知子產品已加載
	blocking_notifier_call_chain(&module_notify_list,
				     MODULE_STATE_LIVE, mod);

	//等待所有的異步函數調用完成 
	async_synchronize_full();
	mutex_lock(&module_mutex);
	//減少引用計數
	module_put(mod);
	
	//應該是處理異常表
	trim_init_extable(mod);
#ifdef CONFIG_KALLSYMS
	//導出符号表,在/proc/kallsyms中能看到的應該是mod->symtab
	mod->num_symtab = mod->core_num_syms;
	mod->symtab = mod->core_symtab;
	mod->strtab = mod->core_strtab;
#endif
	//對init相關的region 取消 RO和NX屬性的設定
	//free init段
	unset_module_init_ro_nx(mod);
	module_free(mod, mod->module_init);
	mod->module_init = NULL;
	mod->init_size = 0;
	mod->init_ro_size = 0;
	mod->init_text_size = 0;
	mutex_unlock(&module_mutex);
	return 0;
}
           

其中struct module的主要結構定義如下:

struct module
{
	/*
	//子產品的目前狀态
	MODULE_STATE_LIVE,		//正常運作狀态
	MODULE_STATE_COMING,	//正在裝在中
	MODULE_STATE_GOING,		//正在移除中
	*/
	enum module_state state;	

	struct list_head list;		//核心所有加載的子產品的雙連結清單,連結清單頭部是定義在kernel/module.c中的全局變量static LIST_HEAD(modules);

	char name[MODULE_NAME_LEN];	//子產品名,必須唯一,核心中解除安裝的時候就是指定的這個檔案名

	//給sysfs檔案系統提供資訊的字段
	struct module_kobject mkobj;
	struct module_attribute *modinfo_attrs;
	const char *version;
	const char *srcversion;
	struct kobject *holders_dir;

	//所有子產品均可使用的符号表
	//一個數組指針,管理核心的導出符号
	const struct kernel_symbol *syms;	
	//一個unsigned long數組,存儲導出符号的校驗和
	const unsigned long *crcs;			
	unsigned int num_syms;	//數組項個數

	//加載子產品時傳入的參數和個數
	struct kernel_param *kp;
	unsigned int num_kp;

	//僅供gpl相容子產品使用的符号表
	unsigned int num_gpl_syms;			
	const struct kernel_symbol *gpl_syms;
	const unsigned long *gpl_crcs;

	//目前可用,但将來隻提供給GPL子產品的符号。
	const struct kernel_symbol *gpl_future_syms;
	const unsigned long *gpl_future_crcs;
	unsigned int num_gpl_future_syms;

	//子產品的新的異常表
	unsigned int num_exentries;
	struct exception_table_entry *extable;

	int (*init)(void);			//子產品的init函數位址

	//如果module_init不為空,則在init函數傳回後會調用vfree釋放
	//這塊記憶體。init區域的記憶體隻在初始化(調用init的時候)有用。
	void *module_init;		
		
	//core區域存放非初始化的代碼和資料,在子產品解除安裝的時候才可釋放						
	void *module_core;			

	//init和core兩個區域的大小
	unsigned int init_size, core_size;	

	//每個區域中可執行代碼的大小
	unsigned int init_text_size, core_text_size;  

	//兩個區域中隻讀資料段的大小
	unsigned int init_ro_size, core_ro_size;	  
	
	//特定于體系結構的資訊
	struct mod_arch_specific arch;		

	unsigned int taints;  //如果子產品會污染核心,則設定此位


#ifdef CONFIG_KALLSYMS
	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	//多處理器相關的字段
	void __percpu *percpu;	//percpu資料
	unsigned int percpu_size;
#endif

#ifdef CONFIG_MODULE_UNLOAD
    //是否允許子產品強行移除,即使核心仍有引用該子產品的地方
	//依賴于目前子產品的子產品
	struct list_head source_list;
	//目前子產品依賴的子產品
	struct list_head target_list;	
	//等待目前子產品解除安裝完成的程序的task_struct
	struct task_struct *waiter;		
	//子產品解除安裝函數
	void (*exit)(void);			
	//子產品的引用計數,系統中每個cpu都對應該數組中的一個數組項	
	struct module_ref __percpu *refptr;
#endif
};

struct load_info {
	Elf_Ehdr *hdr;		//elf檔案頭指針
	unsigned long len;	//elf檔案大小
	Elf_Shdr *sechdrs;	//section header,節區頭部表指針
	/* 
	secstrings一般指向.shstrtab段,即節區頭部表的字元串表節區。secstrings是這個節區的起始位址(elf頭位址+檔案偏移),因為字元串表的格式是字元串數組,是以secstrings也可以認為是個char *的數組。
	strtab 是目前elf檔案中,SHT_SYMTAB屬性的節對應的字元串表所在節的位址。一個ko檔案中,類型為SHT_SYMTAB的節應該隻有一個
	*/
	char *secstrings, *strtab;
	unsigned long symoffs, stroffs;
	struct _ddebug *debug;
	unsigned int num_debug;
	struct {
		unsigned int sym, str, mod, vers, info, pcpu;
	} index;	
	//index就是索引的意思,這個結構體,記錄幾個關鍵節的索引
	//vers: __versions節的索引
	//info: .modinfo節的索引
	//sym:  唯一一個屬性為SHT_SYMTAB的節的索引
	//str: sym節對應的字元串表的索引
	//mod: .gnu.linkonce.this_module節的索引
	//pcpu: 在單處理器下為0,多處理器下為.data..percpu段的索引
	//不一定每個子產品都有percpu這個節的,不需要就沒有
};
           

###sys_init_module->load_module

static struct module *load_module(
			void __user *umod,	//檔案映像的使用者态指針
			unsigned long len,	//檔案映像長度
			const char __user *uargs)	//加載參數
{
	//info隻記錄檔案映像中的資訊
	struct load_info info = { NULL, };	
	struct module *mod;
	long err;

	//複制檔案映像到核心,做一些檔案格式,大小的基本檢查
	//最終設定info->hdr指向檔案映像首位址, info->len為
	//檔案映像長度。
	err = copy_and_check(&info, umod, len, uargs);
	if (err)
		return ERR_PTR(err);

	//計算elf中所有需要配置設定到核心中的節(分内init和core兩種),為其配置設定空間,并将節的内容複制到新核心空間,然後将檔案映像中的節表頭部表指向這新節的新空間,由于module也是新節之一,傳回新配置設定的節中module的位址。則個module_init和module_core區域,後面成為記憶體映像。
	//module為記憶體映像中的this_module節位址,&info還是指向檔案映像中的elf資訊(elf資訊是不會複制到核心的)。
	mod = layout_and_allocate(&info);
	if (IS_ERR(mod)) {
		err = PTR_ERR(mod);
		goto free_copy;
	}
	//以下都是初始化mod相關的
	//1. 初始化mod裡的連結清單一類的
	err = module_unload_init(mod);
	if (err)
		goto free_module;
	//2. 在elf檔案中查找各個節的位址,将其填充到mod中的相關字段
	//如__param,__ksymtab,__kcrctab等節,這時候找到的位址
	//都是記憶體映像中的位址了(前面修正過sh_addr)
	find_module_sections(mod, &info);
	//3. 版本檢查,主要是__kcrctab 表在不在,而不是真正比較crc
	err = check_module_license_and_versions(mod);
	if (err)
		goto free_unload;
	//4. 根據.modinfo段設定子產品資訊
	setup_modinfo(mod, &info);

	//修複所有的符号表,如果是子產品内部符号,則重定位一下位址
	//如果是子產品外部符号,則解決未決引用。
	err = simplify_symbols(mod, &info);
	if (err < 0)
		goto free_modinfo;
	
	//重定位
	err = apply_relocations(mod, &info);
	if (err < 0)
		goto free_modinfo;
	//重定位之後,處理子產品中的異常表,percpu變量,unwind相關處理
	err = post_relocation(mod, &info);
	if (err < 0)
		goto free_modinfo;

	flush_module_icache(mod);

	/* Now copy in args */
	//處理使用者态傳入的參數,實際上就是判斷下大小
	//是否過大,然後複制到核心态,指向args。
	mod->args = strndup_user(uargs, ~0UL >> 1);
	if (IS_ERR(mod->args)) {
		err = PTR_ERR(mod->args);
		goto free_arch_cleanup;
	}

	//狀态為正在裝在子產品
	mod->state = MODULE_STATE_COMING;
	mutex_lock(&module_mutex);
	
	//發現重名子產品
	if (find_module(mod->name)) {
		err = -EEXIST;
		goto unlock;
	}

	//驗證新子產品是否有重複的符号
	err = verify_export_symbols(mod);
	if (err < 0)
		goto ddebug;

	module_bug_finalize(info.hdr, info.sechdrs, mod);
	list_add_rcu(&mod->list, &modules);
	mutex_unlock(&module_mutex);

	
	//解析參數到mod->kp 和mod->num_kp
	err = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,-32768, 32767, NULL);
	if (err < 0)
		goto unlink;

	/* Link in to syfs. */
	//子產品資訊加入到sysfs中
	err = mod_sysfs_setup(mod, &info, mod->kp, mod->num_kp);
	if (err < 0)
		goto unlink;

	//檔案映像被釋放了,最終隻留下了記憶體映像
	free_copy(&info);

	/* Done! */
	trace_module_load(mod);
	return mod;

 unlink:
	mutex_lock(&module_mutex);
	/* Unlink carefully: kallsyms could be walking list. */
	list_del_rcu(&mod->list);
	module_bug_cleanup(mod);

 ddebug:
	dynamic_debug_remove(info.debug);
 unlock:
	mutex_unlock(&module_mutex);
	synchronize_sched();
	kfree(mod->args);
 free_arch_cleanup:
	module_arch_cleanup(mod);
 free_modinfo:
	free_modinfo(mod);
 free_unload:
	module_unload_free(mod);
 free_module:
	module_deallocate(mod, &info);
 free_copy:
	free_copy(&info);
	return ERR_PTR(err);
}
           

####sys_init_module->load_module->copy_and_check

/* Sets info->hdr and info->len. */
static int copy_and_check(
	struct load_info *info,  //要填充的info結構
	const void __user *umod, //使用者态檔案映像的位址
	unsigned long len,       //檔案映像的長度
	const char __user *uargs)//使用者态傳入的參數
{
	int err;
	Elf_Ehdr *hdr;
	//檢查檔案映像長度是否比一個标準elf頭大
	if (len < sizeof(*hdr))
		return -ENOEXEC;
	
	//将檔案映像複制到核心
	if ((hdr = vmalloc(len)) == NULL)
		return -ENOMEM;
	if (copy_from_user(hdr, umod, len) != 0) {
		err = -EFAULT;
		goto free_hdr;
	}
	
	//對檔案映像做初步檢查
	   
	if (
	    //是否為0x7f ELF開頭
		memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0
		//類型是否為可重定位檔案
	    || hdr->e_type != ET_REL
	    //檢查體系結構是否正确
	    || !elf_check_arch(hdr)
	    //節表頭部表表項大小是否與目前核心中代表節表頭部表的資料
	    //結構Elf_Shdr結構體大小相等。
	    || hdr->e_shentsize != sizeof(Elf_Shdr)) {
		err = -ENOEXEC;
		goto free_hdr;
	}
	
	//檢查檔案長度是否比elf中說明的節區頭部表的末端大,小則出錯
	if (len < hdr->e_shoff + hdr->e_shnum * 
	         sizeof(Elf_Shdr)) {
		err = -ENOEXEC;
		goto free_hdr;
	}

	//設定info的頭指針和長度指針,hdr指向複制到核心的檔案映像首位址
	info->hdr = hdr;
	info->len = len;
	return 0;

free_hdr:
	vfree(hdr);
	return err;
}
           

####sys_init_module->load_module->layout_and_allocate

//計算elf中所有需要配置設定到核心中的節(分内init和core兩種),為其配置設定空間,并将節的内容複制到新核心空間,然後将檔案映像中的節表頭部表指向這新節的新空間,由于module也是新節之一,傳回新配置設定的節中module的位址。
static struct module *layout_and_allocate(struct load_info *info)
{
	//這個mod先臨時指向檔案映像,最後會修正為記憶體映像
	struct module *mod;
	Elf_Shdr *pcpusec;	//Elf_Shdr為節區頭部表表項指針
	int err;

	//主要用來設定号info中的各個字段,傳回臨時的module指針
	//這個指針指向檔案映像的.gnu.linkonce.this_module節
	mod = setup_load_info(info);
	if (IS_ERR(mod))
		return mod;
	
	//檢查子產品版本資訊,是否符合加載要求
	err = check_modinfo(mod, info);
	if (err)
		return ERR_PTR(err);

	//體系結構相關的段處理函數,在arm下為空
	err = module_frob_arch_sections(info->hdr, info->sechdrs,info->secstrings, mod);
	if (err < 0)
		goto out;

	//如果有pcup單獨的變量,則處理,一般來說如果不是顯示指定
	//編譯的核心子產品都不需要pcpu變量
	pcpusec = &info->sechdrs[info->index.pcpu];
	if (pcpusec->sh_size) {
		err = percpu_modalloc(mod,pcpusec->sh_size, pcpusec->sh_addralign);
		if (err)
			goto out;
		pcpusec->sh_flags &= ~(unsigned long)SHF_ALLOC;
	}

	//分析子產品中有SHF_ALLOC屬性的段(有些段是主動加上/去掉SHF_ALLOC屬性的),區分是不是初始化段,計算這些段需要的空間(以記憶體格式對齊後的空間),最終記憶體映像中節大小記錄在檔案映像各個節的sh_entsize字段,記憶體映像大小相關資訊記錄在init_text_size,init_ro_size,init_size,core_size,core_text_size, core_ro_size 等字段
	layout_sections(mod, info);

	//core_size上加上所有核心符号的字元串和符号表的大小
	//init_size上加上整個符号表對應的字元串表的大小
	//最終的邏輯是,先把符号表臨時放在module_init段,将核心符号表複制到module_core裡面,然後釋放module_init。
	layout_symtab(mod, info);

	//配置設定module_core和module_init的記憶體,複制有SHF_ALLOC的
	//節表到新配置設定的空間,節表頭部表是不複制的,是以還要修正節表頭
	//部表中的sh_addr指針指向新的節的位置。
	err = move_module(mod, info);
	if (err)
		goto free_percpu;
		
	//mod結構所在的節肯定會被複制到記憶體,根據修正後的sh_addr,将Mod指向修正後的核心module結構
	mod=(void *)info->sechdrs[info->index.mod].sh_addr;
	kmemleak_load_module(mod, info);
	return mod;

free_percpu:
	percpu_modfree(mod);
out:
	return ERR_PTR(err);
}
           

####sys_init_module->load_module->layout_and_allocate->setup_load_info

//主要用來設定info中的各個字段,傳回臨時的module指針
static struct module *setup_load_info(struct load_info *info)
{
	unsigned int i;
	int err;
	struct module *mod;

    //設定info中的節區頭部表指針info->sechdrs
	//hdr是elf頭指針,e_shoff在elf頭部,記錄節區頭部表(section Header Table)檔案偏移的
	info->sechdrs = (void *)info->hdr + info->hdr->e_shoff;
	
	//設定info中的節區頭部表字元串标指針info->secstrings
	//e_shstrndx在elf頭部,記錄節區頭部表中,每個節區名字的字元串表的索引, 這個字元串标也是elf其中的一個節區,這個索引是節區頭部表中的下标。最後的sh_offset是節區頭部表的字元串表的起始檔案偏移
	info->secstrings = (void *)info->hdr + info->sechdrs[info->hdr->e_shstrndx].sh_offset;
	
	//去掉__versions, .modinfo節的SHF_ALLOC屬性(.exit)也有可能被改,并将其節索引号記錄到info結構中,将所有節的addr指向其檔案映像中節區的虛拟位址
	err = rewrite_section_headers(info);
	if (err)
		return ERR_PTR(err);

	//周遊所有節表
	for (i = 1; i < info->hdr->e_shnum; i++) {
		//找到第一個屬性為SHT_SYMTAB的節(靜态符号表),
		//和其對應的符号字元串标,記錄到info中
		if (info->sechdrs[i].sh_type == SHT_SYMTAB) {
			//設定索引項
			info->index.sym = i;
			//對于SHT_SYMTAB類型的節,其sh_link的值為這個節對應的字元串表的索引
			info->index.str = info->sechdrs[i].sh_link;
			//設定info中這個節的位址
			info->strtab = (char *)info->hdr +\
			   info->sechdrs[info->index.str].sh_offset;
			break;
		}
	}

	info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
	//如果沒找到.gnu.linkonce.this_module這個節,直接傳回出錯
	if (!info->index.mod) {
		return ERR_PTR(-ENOEXEC);
	}
	
	//目前子產品struct module * mod 指向mod節的位址,也就是
	//.gnu.linkonce.this_module節的首位址,這個是臨時的
	mod = (void *)info->sechdrs[info->index.mod].sh_addr;

	//如果符号表沒找到,非傳回出錯
	if (info->index.sym == 0) {
		return ERR_PTR(-ENOEXEC);
	}
	//這個應該是和SMP相關的,對于多處理器才有效
	info->index.pcpu = find_pcpusec(info);
	
	//!!!!!注意這裡有一處檢查!!!!
	if (!check_modstruct_version(info->sechdrs, info->index.vers, mod))
		return ERR_PTR(-ENOEXEC);
	return mod;
}
           

####sys_init_module->load_module->layout_and_allocate->setup_load_info->rewrite_section_headers

//去掉__versions, .modinfo節的SHF_ALLOC屬性(.exit)也有可能被改,并将其節索引号記錄到info結構中,将所有節的addr指向其檔案映像中節區的虛拟位址
static int rewrite_section_headers(struct load_info *info)
{
	unsigned int i;
	//标準中規定,節區頭部表的第一個項應該永遠是0,這裡再設定一遍 to make sure。
	info->sechdrs[0].sh_addr = 0;
	
	//周遊所有節區,e_shnum是節區的數目
	for (i = 1; i < info->hdr->e_shnum; i++) {
		//第i個節區的節區頭部表指針
		Elf_Shdr *shdr = &info->sechdrs[i];	
		//檢查所有非SHT_NOBITS節,如果這個節超過了elf的檔案範圍,則傳回錯誤 
		if (shdr->sh_type != SHT_NOBITS && info->len < shdr->sh_offset + shdr->sh_size) {
			return -ENOEXEC;
		}

		//修改每個節區的addr,指向檔案映像中這個節區的虛拟記憶體位址(為整個子產品的首位址 + 其檔案偏移)。可能是因為核心子產品是可重定位檔案的原因,其檔案映像中所有節的sh_addr都預設為0,
		shdr->sh_addr = (size_t)info->hdr + shdr->sh_offset;

#ifndef CONFIG_MODULE_UNLOAD
		//如果沒有定義UNLOAD宏,則子產品不可解除安裝,直接在其elf檔案的節表中修改.exit節的屬性,去掉這個節的SHF_ALLOC屬性。(info->secstrings + shdr->sh_name 是節表字元串表首位址,加上字元串的檔案偏移)
		if (strstarts(info->secstrings + shdr->sh_name, ".exit"))
			shdr->sh_flags &= ~(unsigned long)SHF_ALLOC;
#endif
	}
	
	//在info中記錄vers/info節的索引,但去掉這兩個節的SHF_ALLOC
	//屬性,這兩個節最終不進入記憶體映像。
	//find_sec(info,x)函數用來在檔案映像中找到節名為x,且屬性帶SHF_ALLOC的節的索引,找不到傳回0.
	info->index.vers = find_sec(info, "__versions");
	info->index.info = find_sec(info, ".modinfo");
	info->sechdrs[info->index.info].sh_flags &= ~(unsigned long)SHF_ALLOC;
	info->sechdrs[info->index.vers].sh_flags &= ~(unsigned long)SHF_ALLOC;
	return 0;
}
           

####sys_init_module->load_module->layout_and_allocate->check_modinfo

static int check_modinfo(struct module *mod, struct load_info *info)
{
	//在.modinfo節區查找vermagic = 開頭的字元串(相當于作為key查找),傳回的是vermagic=後面的字元串
	//如vermagic=3.4.0-g35ba5ff preempt mod_unload ARMv7 p2v8
	//傳回"3.4.0-g35ba5ff preempt mod_unload ARMv7 p2v8"
	const char *modmagic = get_modinfo(info, "vermagic");
	int err;
	
	if (!modmagic) {
		//沒找到版本資訊
		//如果配置了CONFIG_MODULE_FORCE_LOAD,則強制加載,标記taints,傳回true,否則傳回錯誤(-ENOEXEC)
		err = try_to_force_load(mod, "bad vermagic");
		if (err)
			return err;
	} else if (!same_magic(modmagic, vermagic, info->index.vers)) {
		//字元串比較,如果有vers,則加上vers比較
		//vermagic 是個全局變量,在最終導出的vmlinux中可以找到。
		return -ENOEXEC;
	}

	//标記核心是否被污染
	if (!get_modinfo(info, "intree"))
		add_taint_module(mod, TAINT_OOT_MODULE);

	if (get_modinfo(info, "staging")) {
		add_taint_module(mod, TAINT_CRAP);
		
	/* Set up license info based on the info section */
	set_license(mod, get_modinfo(info, "license"));

	return 0;
}
           

####sys_init_module->load_module->layout_and_allocate->layout_sections

static void layout_sections(struct module *mod, struct load_info *info)
{
	//這個數組中的所有項,都有SHF_ALLOC屬性,但隻有第一行這種是可執行的
	static unsigned long const masks[][2] = {
		{ SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL },	
		{ SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL },
		{ SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL },
		{ ARCH_SHF_SMALL | SHF_ALLOC, 0 }
	};
	unsigned int m, i;

	//e_shnum為節區頭部表中的表項數
	for (i = 0; i < info->hdr->e_shnum; i++)
		//将每個節區的元素長度設為0xffff???
		info->sechdrs[i].sh_entsize = ~0UL;

	//對masks數組中的每種節屬性組合,執行以下操作
	for (m = 0; m < ARRAY_SIZE(masks); ++m) {
		//周遊所有節區
		for (i = 0; i < info->hdr->e_shnum; ++i) {
			//擷取第i個節表的頭部表
			Elf_Shdr *s = &info->sechdrs[i];
			//這個節的名字
			const char *sname = info->secstrings + s->sh_name;
			//對于滿足mask的非.init開頭的節
			if (
			//如果節區與masks掩碼不比對
			(s->sh_flags & masks[m][0]) != masks[m][0]
			//或者節區flag不比對
			|| (s->sh_flags & masks[m][1])
			//或者節區元素長度不比對
			|| s->sh_entsize != ~0UL
			//或者是初始化段
			|| strstarts(sname, ".init"))	
				//則不處理
				continue; 
			//這裡剩下的主要是有SHF_ALLOC的非初始化段了,這樣的段最終會被放到module.module_core,也就是記憶體映像中去,這裡重用s->sh_entsize,來記錄目前段在記憶體映像中的偏移。core_size為最終記憶體映像的大小,get_offset的時候會加上目前這個節的大小。
			s->sh_entsize = get_offset(mod, &mod->core_size, s, i);
		}
			//對于各種類型的段的大小,記錄到mod都相應字段中
		switch (m) {
		case 0: /* executable */
			//對其
			mod->core_size = debug_align(mod->core_size);
			//如果段屬性為 SHF_EXECINSTR | SHF_ALLOC,則記錄到core_text_size的長度中
			mod->core_text_size = mod->core_size;
			break;
		case 1: /* RO: text and ro-data */
			mod->core_size = debug_align(mod->core_size);
			mod->core_ro_size = mod->core_size;
			break;
		case 3: /* whole core */
			mod->core_size = debug_align(mod->core_size);
			break;
		}
	}
	//對于隻在初始化時候用到的段,做相同的處理
	for (m = 0; m < ARRAY_SIZE(masks); ++m) {
		for (i = 0; i < info->hdr->e_shnum; ++i) {
			Elf_Shdr *s = &info->sechdrs[i];
			const char *sname = info->secstrings + s->sh_name;
			//滿足mask的.init開頭的節
			if ((s->sh_flags & masks[m][0]) != masks[m][0]
			    || (s->sh_flags & masks[m][1])
			    || s->sh_entsize != ~0UL
			    || !strstarts(sname, ".init"))
				continue;
			s->sh_entsize = (get_offset(mod, &mod->init_size, s, i)| INIT_OFFSET_MASK);
		}
		switch (m) {
		case 0: /* executable */
			mod->init_size = debug_align(mod->init_size);
			mod->init_text_size = mod->init_size;
			break;
		case 1: /* RO: text and ro-data */
			mod->init_size = debug_align(mod->init_size);
			mod->init_ro_size = mod->init_size;
			break;
		case 3: /* whole init */
			mod->init_size = debug_align(mod->init_size);
			break;
		}
	}
}
           

####sys_init_module->load_module->layout_and_allocate->layout_symtab

static void layout_symtab(struct module *mod, struct load_info *info)
{
	//符号表節區起始位址
	Elf_Shdr *symsect = info->sechdrs + info->index.sym;
	//字元串表節區起始位址
	Elf_Shdr *strsect = info->sechdrs + info->index.str;
	//臨時變量,指向目前符号
	const Elf_Sym *src;
	unsigned int i, nsrc, ndst, strtab_size;

	//給符号表加上SHF_ALLOC屬性
	symsect->sh_flags |= SHF_ALLOC;
	//sh_entsize = 符号表所在節區對齊後的大小
	symsect->sh_entsize = get_offset(mod, &mod->init_size, symsect,info->index.sym) | INIT_OFFSET_MASK;
	
	//指向第一個符号表項
	src = (void *)info->hdr + symsect->sh_offset;
	//符号表項數
	nsrc = symsect->sh_size / sizeof(*src);

	/* strtab always starts with a nul, so offset 0 is the empty string. */
	strtab_size = 1;	//字元串表所在節開始的字元串是個空串.

	//計算所有核心符号字元串的總大小
	for (ndst = i = 0; i < nsrc; i++) {
		if (i == 0 ||
			//如果是核心符号(核心符号指的是:非SHN_UNDEF,符号的st_shndx與一個節區綁定,且被綁定的節區有ALLOC屬性的符号)
		    is_core_symbol(src+i, info->sechdrs, info->hdr->e_shnum)) {
		    //加上符号對應字元串的長度,i=0的時候是個空串
			strtab_size += strlen(\
				&info->strtab[src[i].st_name])+1;
			ndst++;
		}
	}

	//在core_size的末尾增加這兩個表的空間,符号表和其字元串表
	info->symoffs = ALIGN(mod->core_size, symsect->sh_addralign ?: 1);
	info->stroffs = mod->core_size = info->symoffs + ndst * sizeof(Elf_Sym);
	mod->core_size += strtab_size;
	
	//給字元串表加上SHF_ALLOC屬性,并算到module_init中。
	strsect->sh_flags |= SHF_ALLOC;
	strsect->sh_entsize = get_offset(mod, &mod->init_size, strsect,info->index.str) | INIT_OFFSET_MASK;
}
           

####sys_init_module->load_module->layout_and_allocate->move_module

static int move_module(struct module *mod, struct load_info *info)
{
	int i;
	void *ptr;
	//為子產品配置設定core_size大小的記憶體
	ptr = module_alloc_update_bounds(mod->core_size);
	//标記到記憶體洩露檢測裡
	kmemleak_not_leak(ptr);
	if (!ptr)
		return -ENOMEM;
	//初始化
	memset(ptr, 0, mod->core_size);
	
	//設定module.module_core指針
	mod->module_core = ptr;

	//為init配置設定空間
	ptr = module_alloc_update_bounds(mod->init_size);
	
	//當洩漏時不掃描或報告對象
	kmemleak_ignore(ptr);
	if (!ptr && mod->init_size) {
		module_free(mod, mod->module_core);
		return -ENOMEM;
	}
	memset(ptr, 0, mod->init_size);
	//設定module.module_init
	mod->module_init = ptr;

	//複制所有有SHF_ALLOC屬性的節到新核心空間
	for (i = 0; i < info->hdr->e_shnum; i++) {
		void *dest;
		//第i個節區的頭部表
		Elf_Shdr *shdr = &info->sechdrs[i];
		//如果沒有SHF_ALLOC屬性,則continue
		if (!(shdr->sh_flags & SHF_ALLOC))
			continue;
		//如果這個節區屬于init的節區
		if (shdr->sh_entsize & INIT_OFFSET_MASK)
			//找到目前這個節應該複制到的位置
			dest = mod->module_init + (shdr->sh_entsize & ~INIT_OFFSET_MASK);
		else
			//如果是非init也一樣
			dest = mod->module_core + shdr->sh_entsize
			
		if (shdr->sh_type != SHT_NOBITS)
			//複制資料
			memcpy(dest, (void *)shdr->sh_addr, shdr->sh_size);

		//修改複制後的檔案映像中所有節的sh_addr指向核心新配置設定的位址,注:sh_addr是在節表頭部表中的,而複制到核心中的隻是有SHF_ALLOC的節表,節表頭部表是沒有複制到核心中的。
		shdr->sh_addr = (unsigned long)dest;
	}
	return 0;
}
           

###sys_init_module->load_module->simplify_symbols

//修複所有的符号表,如果是子產品内部符号,則重定位一下位址
//如果是子產品外部符号,則解決未覺引用。
static int simplify_symbols(struct module *mod, const struct load_info *info)
{
	//符号表節表的首位址(檔案映像中)
	Elf_Shdr *symsec = &info->sechdrs[info->index.sym];
	//符号表所在節的首位址(記憶體映像中)
	Elf_Sym *sym = (void *)symsec->sh_addr;
	unsigned long secbase;
	unsigned int i;
	int ret = 0;
	const struct kernel_symbol *ksym;

	//循環所有符号
	for (i = 1; i < symsec->sh_size / sizeof(Elf_Sym); i++) {
		//擷取符号名
		const char *name = info->strtab +sym[i].st_name;
		
		//switch st_shndx可以認為是個索引
		switch (sym[i].st_shndx) {
		case SHN_COMMON:
			//一般來說,未初始化的全局符号是這種類型,在ko檔案中
			//不應該出現這種類型的符号,出現則直接傳回錯誤了
			ret = -ENOEXEC;
			break;
		case SHN_ABS:
			//包含一個絕對值,不受重定位的影響,可能是節索引編号
			break;
		case SHN_UNDEF:
			//這是一個未定義符号,需要核心來找這個符号的定義
			//核心會查找自身+所有子產品,如果找到,傳回ksym,
			//指向這個符号。
			ksym = resolve_symbol_wait(mod, info, name);
			if (ksym && !IS_ERR(ksym)) {
				//符号表的這個st_value,指向這個符号的記憶體位址
				sym[i].st_value = ksym->value;
				break;
			}
			/* Ok if weak.  */
			if (!ksym && ELF_ST_BIND(sym[i].st_info) == STB_WEAK)
				break;
			ret = PTR_ERR(ksym) ?: -ENOENT;
			break;

		default:
			//其他的st_shndex,都認為是指向節區索引,則找到節區起始位址(記憶體映像中的)
			if (sym[i].st_shndx == info->index.pcpu)
				//pcpu要單獨處理的。
				secbase =(unsigned long)mod_percpu(mod);
			else
				secbase=info->sechdrs[sym[i].st_shndx]. sh_addr;
			//st_value是偏移,這裡加上節區起始位址(記憶體映像中的),為符号真正的位址。
			sym[i].st_value += secbase;
			break;
		}
	}
	return ret;
}
           

####sys_init_module->load_module->simplify_symbols->resolve_symbol_wait

//此函數主要調用resolve_symbol,這裡直接分析resolve_symbol
static const struct kernel_symbol *resolve_symbol(
						  struct module *mod,
						  const struct load_info *info,
						  const char *name,
						  char ownername[])
{
	struct module *owner;
	const struct kernel_symbol *sym;
	const unsigned long *crc;
	int err;

	mutex_lock(&module_mutex);
	//傳回符号對應的符号表,所屬子產品,crc
	sym = find_symbol(name, &owner, &crc,!(mod->taints & (1 << TAINT_PROPRIETARY_MODULE)), true);
	if (!sym)
		goto unlock;
	//檢測核心中目前符号的crc和新子產品中的記錄的crc是否相符
	if (!check_version(info->sechdrs, info->index.vers, name, mod, crc,owner)) {
		sym = ERR_PTR(-EINVAL);
		goto getname;
	}

	//增加子產品間的依賴關系
	err = ref_module(mod, owner);
	if (err) {
		sym = ERR_PTR(err);
		goto getname;
	}

getname:
	strncpy(ownername, module_name(owner), MODULE_NAME_LEN);
unlock:
	mutex_unlock(&module_mutex);
	return sym;
}
//其中最主要函數為find_symbol:
//查找核心符号,傳回描述該符号的核心結構體指針,如果核心符号存在于
//動态插入的子產品,且owner不會空,則owner傳回符号所在子產品的的struct module *
const struct kernel_symbol *find_symbol(
	const char *name,	//要查找的核心符号名 ,輸入	
	//是否傳回子產品的struct module,輸出	
	struct module **owner,	
	//傳回核心符号crc的位址(輸出參數),輸出
	const unsigned long **crc,	
	bool gplok,	//表示目前要搜尋的子產品是不是gplok的,輸入
	bool warn)	//表示是否輸出警告,輸入
{
	//臨時結構,為了不污染輸出參數,且讓子函數變得簡潔
	struct find_symbol_arg fsa;
	fsa.name = name;
	fsa.gplok = gplok;
	fsa.warn = warn;

	//此函數對核心所有的符号表進行搜尋,順序是:
	//核心自身的__ksymtab, __ksymtab_gpl, __ksymtab_gpl_future
	//每個子產品的__ksymtab, __ksymtab_gpl, __ksymtab_gpl_future
	//如果找到了,則根據目前搜尋子產品的性質(即gplok參數)判斷是否傳回true
	//如果目前傳入的gplok = true,表示目前子產品是gplok的,傳回true
	//如果傳入的gplok = false, 則符号屬于__ksymtab節區傳回true, __ksymtab_gpl_future也傳回true,提示warning,表示這個子產品以後會是gpl的, __ksymtab_gpl 傳回false了
	//如果要傳回true,在傳回之前會設定fsa.owner,表示從哪個子產品搜出來的
	//fsa.sym表示目前符号的kernel_symbol結構
	//fsa.crc傳回這個函數的crc
	if (each_symbol_section(find_symbol_in_section, &fsa)) {
		if (owner)
			*owner = fsa.owner;
		if (crc)
			*crc = fsa.crc;
		return fsa.sym;
	}

	return NULL;
}
           

繼續閱讀