天天看點

Linux核心建構系統之七

通過前面的分析,我們已經知道,在 Linux 中,區分有兩種子產品:内部子產品和外部子產品。我們這裡說的對目标 modules 的處理指的就是要編譯出那些内部子產品,對外部子產品的處理我們将在後面叙述。我們還知道,不管是内部子產品,還是外部子產品,其編譯都要分兩個階段進行。階段一生成組成子產品的對應 .o 檔案和 .mod 檔案,階段二要用 scripts/mod/modpost 來生成 .mod.c 檔案,并将其編譯成 .mod.o 對象檔案,最後将 .mod.o 連同前面的 .o 一起連結成 .ko 子產品檔案。另外我們還知道,在生成vmlinux的過程中,會在核心頂層目錄中生成一個 Modules.symvers,裡面存放基本核心導出的、供子產品使用的符号以及CRC校驗和。通過前面的讨論所得到的這些知識,或許對你來說還不十厘清楚,沒關系,我們再行深入繼續對内部子產品目标 modules 的讨論,它将讓你有個較為清楚的認識。

好,先在頂層 Makefile 中(架構中的E1部分)找到處理 modules 目标的規則:

all: modules
 
#       Build modules
#
#       A module can be listed more than once in obj-m resulting in
#       duplicate lines in modules.order files.  Those are removed
#       using awk while concatenating to the final file.
 
PHONY += modules
modules: $(vmlinux-dirs) $(if $(KBUILD_BUILTIN),vmlinux)
        $(Q)$(AWK) '!x[$$0]++' $(vmlinux-dirs:%=$(objtree)/%/modules.order) > $(objtree)/modules.order
        @$(kecho) '  Building modules, stage 2.';
        $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
        $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.fwinst obj=firmware __fw_modbuild      

上面顯示 modules 目标依賴于 $(vmlinux-dirs)。這種依賴就意味着内部子產品處理的第一階段就已經在處理 vmlinux-dirs 的過程中完成了。前面對 vmlinux-dirs 的讨論過程也說了如何編譯出構成子產品的那些 .o 對象檔案,以及如何生成 .mod 檔案。

很顯然,既然内部子產品的第一階段已經完成,那處理 modules 目标規則的指令部分就是來完成内部子產品的第二階段了。

指令部分中的第一行用一個awk調用來将各子目錄中 modules.order 檔案内容歸集到頂層目錄的 modules.order 檔案中。該檔案列出了建構系統建構内部子產品的次序。如果你深入學習,你會知道package module-init-tools 中包含有一個工具:depmod。該工具解析出各個核心子產品的依賴關系,它會将依賴關系儲存在檔案 modules.dep 中。所謂子產品之間的依賴,舉個例子比方說子產品A的代碼中用到了子產品B所導出來的函數,那麼我們就說子產品A是依賴于子產品B的。很顯然,既然子產品之間有依賴,那子產品之間的加載次序就得規定好。如我們的例子中,必須先加載子產品B,後加載子產品A,否則就會出錯。老版本的 depmod 隻是單純的依賴内部子產品之間的依賴來決定内部子產品的加載順序。但是先版本的 depmod 還會考慮核心建構系統建構各核心子產品的順序。關于更多 modules.orders 的使用資訊,你可以參考核心開發郵件清單中的内容,在這裡可以看到:http://lkml.org/lkml/2007/12/7/96。另外你也可以檢視 module-init-tools 包git的修訂記錄:

​​http://git.kernel.org/?p=utils/kernel/module-init-tools/module-init-tools.git&a=search&h=HEAD&st=commit&s=modules.order​​

上面指令部分中最關鍵的就是接下來那一行:$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost。由于該指令沒有指定make的目标,是以它會建構 Makefile.modpost 中的預設目标 _modpost。而在同一個檔案中檢視一下 _modpost 的相關規則:

PHONY := _modpost
_modpost: __modpost
......
# Stop after building .o files if NOFINAL is set. Makes compile tests quicker
_modpost: $(if $(KBUILD_MODPOST_NOFINAL), $(modules:.ko:.o),$(modules))      

上面代碼表明,_modpost 依賴于 __modpost。 同時,如果有定義過KBUILD_MODPOST_NOFINAL,那麼它還依賴于那些和子產品名稱對應的 .o 檔案。打個比方,如果有兩個對象檔案 part1.o 和 part2.o組成一個子產品 MyModule.ko,那麼它就依賴于 MyModule.o 對象檔案。另外如果沒有定義過,那它還依賴于所有的内部子產品。是以變量 KBUILD_MODPOST_NOFINAL 的定義就意味着我們隻是生成 MyModule.o,而不要再繼續從 MyModule.o 出發生成 MyModule.ko 子產品。變量 modules 被這樣定義:

# Step 1), find all modules listed in $(MODVERDIR)/
__modules := $(sort $(shell grep -h '\.ko' /dev/null $(wildcard $(MODVERDIR)/*.mod)))
modules   := $(patsubst %.o,%.ko, $(wildcard $(__modules:.ko=.o)))      

這個定義用 grep 搜尋目錄 $(MODVERDIR)/ 中的所有 *.mod 檔案,找出其中包含子產品檔案名稱字尾 .ko 的那些行。效果上也就是等價于找出所有的内部子產品名稱,組成清單賦給 modules。還記得麼?前面提到過,目錄$(MODVERDIR)就是 .../.tmp_version/,其中存有子產品處理第一階段中生成的所有 .mod 檔案。

我們回來看一下 __modpost 目标的處理,找出代碼如下:

PHONY += __modpost
__modpost: $(modules:.ko=.o) FORCE
        $(call cmd,modpost) $(wildcard vmlinux) $(filter-out FORCE,$^)
        $(Q)echo 'cmd_$@ := (call cmd,modpost) $(wildcard vmlinux) $(filter-out FORCE,$^)' > $(@D)/.$(@F).cmd      

仔細看該規則的指令部分,它調用了 cmd_modpost,我們來看看它的定義:

# Step 2), invoke modpost
#  Includes step 3,4
modpost = scripts/mod/modpost                    \
 $(if $(CONFIG_MODVERSIONS),-m)                  \
 $(if $(CONFIG_MODULE_SRCVERSION_ALL),-a,)       \
 $(if $(KBUILD_EXTMOD),-i,-o) $(kernelsymfile)   \
 $(if $(KBUILD_EXTMOD),-I $(modulesymfile))      \
 $(if $(KBUILD_EXTRA_SYMBOLS), $(patsubst %, -e %,$(KBUILD_EXTRA_SYMBOLS))) \
 $(if $(KBUILD_EXTMOD),-o $(modulesymfile))      \
 $(if $(CONFIG_DEBUG_SECTION_MISMATCH),,-S)      \
 $(if $(CONFIG_MARKERS),-K $(kernelmarkersfile)) \
 $(if $(CONFIG_MARKERS),-M $(markersfile))       \
 $(if $(KBUILD_EXTMOD)$(KBUILD_MODPOST_WARN),-w) \
 $(if $(cross_build),-c)
 
quiet_cmd_modpost = MODPOST $(words $(filter-out vmlinux FORCE, $^)) modules
      cmd_modpost = $(modpost) -s      

似曾相識對吧?沒錯,我們在前面讨論 vmlinux.o 的處理的時候,就已經碰到工具程式 .../scripts/mod/modpost 的使用了。隻不過,那時候使用它的是變量 cmd_kernel-mod,而非cmd_modpost。當時,建構系統用來完成兩個動作:mis-match section的檢查和生成基本核心導出符号檔案 Module.symvers,其中包含基本核心所導出的所有符号及CRC校驗。那此處調用 .../scripts/mod/modpost 來做何用途呢?我們且先來看 modpost 工具程式的調用方式。

由于我們的配置(s3c2410_defcofig)中,并沒有設定 CONFIG_MODVERSIONS,CONFIG_MODULE_SRCVERSION_ALL,CONFIG_DEBUG_SECTION_MISMATCH 以及 CONFIG_MARKERS。同時我們也沒設定KBUILD_EXTRA_SYMBOLS,而目前我們是在處理内部子產品的第二階段,是以上面處理 __modpost 規則中的指令實際上就是:

scripts/mod/modpost -o /home/yihect/linux-2.6.31/Module.symvers -S -c -s vmlinux MyModule.o YouModule.o HisModule.o ....

其中,指令後半部分包括省略号所表示的,是與各内部子產品名稱對應的 .o 檔案。這個指令在這裡主要也是要完成兩項工作:

解析出 vmlinux以及各對應的 .o 檔案内的符号,并重新将它們連同各自的CRC校驗寫入到頂層目錄中的檔案 Modules.symvers 内。是以最後該檔案内不僅包含基本核心的符号及CRC校驗,還包括各内部子產品所導出的符号及CRC校驗,在結果上是前面處理 vmlinux.o 時所生成的 Modules.symvers 的超集;

針對各個内部子產品,生成對應的 *.mod.c 檔案。生成 *.mod.c 檔案的代碼在 modpost.c 檔案的main函數中:

int main(int argc, char **argv)
{
        struct module *mod;
        struct buffer buf = { };
        //......
        for (mod = modules; mod; mod = mod->next) {
                char fname[strlen(mod->name) + 10];
 
                if (mod->skip)
                        continue;
 
                buf.pos = 0;
 
                add_header(&buf, mod);
                add_staging_flag(&buf, mod->name);
                err |= add_versions(&buf, mod);
                add_depends(&buf, mod, modules);
                add_moddevtable(&buf, mod);
                add_srcversion(&buf, mod);
 
                sprintf(fname, "%s.mod.c", mod->name);
                write_if_changed(&buf, fname);
        }
        //.......
        return err;
}      

具體的生成代碼就分布在不同的 add_* 函數當中,由于超出本問主題範圍,我們在這裡不對它們詳加闡述。你可自行檢視代碼,并參與我們在 mail list 中的讨論。我們這裡看下它生成的 *.mod.c 檔案的内容,我們以目錄 .../net/wireless/ 下的子產品 cfg80211.ko 為例(由于 s3c2410_defconfig 的預設配置将變量 CONFIG_CFG80211 設定為M,是以根據該目錄下 Makefile 的内容,建構系統會生成子產品 cfg80211.ko)。為了完整的說明 *.mod.c 檔案的内容,我們特意修改了 .../.config 配置檔案,将 CONFIG_MODVERSIONS 及 CONFIG_MODULE_SRCVERSION_ALL 兩變量設定為y。也就是打開了核心的 Module versioning 功能。我們列出檔案 cfg80211.mod.c 的内容(有删減):

​​​​

該檔案大部分的代碼是定義一些變量,并将其放在三個不同的 elf section 内(後面建構系統會編譯這個 .mod.c 形成對象檔案,連結進 .ko):

定義struct module結構變量 __this_module,并将其放在 .gnu.linkonce.this_module section 中。在将子產品加載進運作着的核心時,核心負責将這個對象加到内部的modules list中。modules list 是核心維護所有已加載子產品的一個雙向連結清單(更多請看:http://lkml.org/lkml/2002/12/27/16)。

定義 struct modversion_info 結構數組 ____versions,并将其放到 __versions sectiong 中。該數組中存放的都是該子產品中使用到,但沒被定義的符号,也就是所謂的 unresolved symbol,它們或在基本核心中定義,或在其他子產品中定義,核心使用它們來做 Module versioning。注意其中的 module_layout 符号,這是一個 dummy symbol。核心使用它來跟蹤不同核心版本關于子產品處理的相關資料結構的變化。當一個子產品在A版本的核心中編譯後,又在另外一個B版本的核心中加載,如果兩個核心中處理modules的那些資料結構體定義發生變化了,那核心就拒絕繼續做其他 Module versioning 工作,也就是拒絕加載子產品。

符号 module_layout 在檔案 .../kernel/module.c 中被定義成一個函數:

#ifdef CONFIG_MODVERSIONS
/* Generate the signature for all relevant module structures here.
 * If these change, we don't want to try to parse the module. */
void module_layout(struct module *mod,
                   struct modversion_info *ver,
                   struct kernel_param *kp,
                   struct kernel_symbol *ks,
                   struct marker *marker,
                   struct tracepoint *tp)
{
}
EXPORT_SYMBOL(module_layout);
#endif      

該函數函數體為空,但卻有很多的參數類型。為什麼?就是因為核心要用它來跟蹤 module/modversion_info/kernel_param/kernel_symbol/marker/tracepoint 等結構體定義變化。那如何跟蹤這種變化呢?核心會和處理其他的符号一樣,用這個函數原型做一次CRC校驗, 産生校驗和。将其放如 *.mod.c 的__versions section中,待在子產品加載時,拿其與儲存在正運作的核心中的CRC進行比較,如果不同,就拒絕進一步加載子產品。加載子產品時核心對此項的檢查代碼在 .../kernel/module.c,如下:

static inline int check_modstruct_version(Elf_Shdr *sechdrs,
                                          unsigned int versindex,
                                          struct module *mod)
{
        const unsigned long *crc;
 
        if (!find_symbol(MODULE_SYMBOL_PREFIX "module_layout", NULL,
                         &crc, true, false))
                BUG();
        return check_version(sechdrs, versindex, "module_layout", mod, crc);
}      

函數 check_version 就做真正的檢查工作,檢查不通過,核心會報出著名的錯誤資訊:disagrees about version of symbol module_layout。

最後,.mod.c 中會将很多資訊塞進 .modinfo section 中,包括:vermagic字元串,子產品依賴資訊,srcversion資訊等等(還有其他很多資訊)。我們以vermagic來舉例分析。

宏 MODULE_INFO 定義在 .../include/linux/module.h 中:

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

而 __MODULE_INFO 則定義在 .../include/linux/moduleparam.h 中:

#define ___module_cat(a,b) __mod_ ## a ## b
#define __module_cat(a,b) ___module_cat(a,b)
#define __MODULE_INFO(tag, name, info)                                    \
static const char __module_cat(name,__LINE__)[]                           \
  __used                                                                  \
  __attribute__((section(".modinfo"),unused)) = __stringify(tag) "=" info      

其中 __stringify 又是定義在 .../include/linux/stringify.h 檔案中的宏:

#define __stringify_1(x...)     #x
#define __stringify(x...)       __stringify_1(x)      

是以,綜上所述,當你在一個c程式檔案的第21行寫下這樣一條語句後:MODULE_INFO(pppp, "qqq"); 那麼經過C預處理,就會展開成:

static const char __mod_pppp21[] __used __attribute__((section(".modinfo"),unused)) = "pppp" "=" "qqq";      

實際上,就是定義了一個名為 __mod_pppp21 的字元數組,将其初始化成字元串 "pppp=qqq" 形式後放入 .modinfo section 中。再來看宏 VERMAGIC_STRING 的定義:它定義在檔案.../include/linux/vermagic.h 中:

/* Simply sanity version stamp for modules. */
#ifdef CONFIG_SMP
#define MODULE_VERMAGIC_SMP "SMP "
#else
#define MODULE_VERMAGIC_SMP ""
#endif
#ifdef CONFIG_PREEMPT
#define MODULE_VERMAGIC_PREEMPT "preempt "
#else
#define MODULE_VERMAGIC_PREEMPT ""
#endif
#ifdef CONFIG_MODULE_UNLOAD
#define MODULE_VERMAGIC_MODULE_UNLOAD "mod_unload "
#else
#define MODULE_VERMAGIC_MODULE_UNLOAD ""
#endif
#ifdef CONFIG_MODVERSIONS
#define MODULE_VERMAGIC_MODVERSIONS "modversions "
#else
#define MODULE_VERMAGIC_MODVERSIONS ""
#endif
#ifndef MODULE_ARCH_VERMAGIC
#define MODULE_ARCH_VERMAGIC ""
#endif
 
#define VERMAGIC_STRING                                                 \
        UTS_RELEASE " "                                                 \
        MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT                     \
        MODULE_VERMAGIC_MODULE_UNLOAD MODULE_VERMAGIC_MODVERSIONS       \
        MODULE_ARCH_VERMAGIC      

可見,機器是否為SMP,核心是否配置為搶占式,是否不允許子產品解除安裝以及Module versioning功能是否開啟等等,都影響着 VERMAGIC_STRING 的取值。當加載子產品到核心中時,核心也會檢查 vermagic 是否有變化。如果不一樣,核心照樣不允許該子產品的加載。檢查代碼在 .../kernel/module.c 中:

modmagic = get_modinfo(sechdrs, infoindex, "vermagic");
        /* This is allowed: modprobe --force will invalidate it. */
        if (!modmagic) {
                err = try_to_force_load(mod, "bad vermagic");
                if (err)
                        goto free_hdr;
        } else if (!same_magic(modmagic, vermagic, versindex)) {
                printk(KERN_ERR "%s: version magic '%s' should be '%s'\n",
                       mod->name, modmagic, vermagic);
                err = -ENOEXEC;
                goto free_hdr;
        }      

核心先取得存儲在子產品中的 magic 字元串(由 .mod.c 編譯連接配接到子產品中)放在 modmagic 中,再用 same_magic 函數去和核心中儲存好的 magic 字元串比較。比較不一緻時,核心就會報錯,進而拒絕該子產品的加載。

*.mod.c 檔案生成之後,如果 KBUILD_MODPOST_NOFINAL 定義過,那對 _modpost 的處理就算結束了,否則因為 _modpost 要依賴于 $(modules),建構系統還要負責建構出各個内部子產品(.ko)。從 .../scripts/Makefile.modpost 中找到處理 $(modules) 的相關規則:

$(modules): %.ko :%.o %.mod.o FORCE
        $(call if_changed,ld_ko_o)      

這是一條靜态比對規則,可以看出内部子產品檔案 *.ko 要依賴于同名的 *.o 和 同名的 *.mod.o 。同名*.o已經在第一階段處理 vmlinux-dirs 時準備妥當,但是同名*.mod.o還未生成,是以建構系統必須用下面的規則來生成它:

​​​​

由處理 $(modules) 的規則看出,生成 *.mod.o 後,建構系統使用變量 cmd_ld_ko_o 定義的指令來将同名*.o和同名*.mod.o連結成*.ko:

# Step 6), final link of the modules
quiet_cmd_ld_ko_o = LD [M]  $@
      cmd_ld_ko_o = $(LD) -r $(LDFLAGS) $(LDFLAGS_MODULE) -o $@         \
                          $(filter-out FORCE,$^)      

繼續閱讀