国产亚洲精品AA片在线观看,丰满熟妇HD,亚洲成色www成人网站妖精,丁香五月天婷婷五月天男人天堂

博客專欄

EEPW首頁 > 博客 > 解析 Linux 內(nèi)核可裝載模塊的版本檢查機制

解析 Linux 內(nèi)核可裝載模塊的版本檢查機制

發(fā)布人:電子禪石 時間:2020-06-24 來源:工程師 發(fā)布文章
內(nèi)核可裝載模塊概述

Linux 在發(fā)展過程中(即自 Linux 1.2 之后)引進了模塊這一重要特性,該特性提供內(nèi)核可在運行時進行擴展??裳b載模塊(Loadable Kernel Module,即 LKM)也被稱為模塊,就是可在內(nèi)核運行時加載到內(nèi)核的一組目標代碼(并非一個完整的可執(zhí)行程序)。這就意味著在重構(gòu)和使用可裝載模塊時并不需要重新編譯內(nèi)核。模塊依據(jù)代碼編寫與編譯時的位置可分:內(nèi)部模塊和外部模塊,即 in-tree module 和 out-of-tree module,在內(nèi)核樹外部編寫并構(gòu)建的模塊就是外部模塊。如果只是認為可裝載模塊就是外部模塊或者認為在模塊與內(nèi)核通訊時模塊是位于內(nèi)核的外部的,那么這在 Linux 下均是錯誤的。當模塊被裝載到內(nèi)核后,可裝載模塊已是內(nèi)核的一部分。另外,我們使用的 Linux 發(fā)行版在系統(tǒng)啟動過程 initrd 中已使用了必要的模塊,除非我們只討論基礎內(nèi)核(base kernel)。本文主要是對 Linux 2.6 的外部模塊進行討論的。

可裝載模塊在 Linux 2.6 與 2.4 之間存在巨大差異,其最大區(qū)別就是模塊裝載過程變化(如 圖 1所示,在 Linux 2.6 中可裝載模塊在內(nèi)核中完成連接)。其他一些變化大致如下:

  • 模塊的后綴及裝載工具;

對于使用模塊的授權(quán)用戶而言,模塊最直觀的改變應是模塊后綴由原先的 .o 文件(即 object)變成了 .ko 文件(即 kernel object)。同時,在 Linux 2.6 中,模塊使用了新的裝卸載工具集 module-init-tools(工具 insmod 和 rmmod 被重新設計)。模塊的構(gòu)建過程改變巨大,在 Linux 2.6 中代碼先被編譯成 .o 文件,再從 .o 文件生成 .ko 文件,構(gòu)建過程會生成如 .mod.c、.mod.o 等文件。

  • 模塊信息的附加過程;

在 Linux 2.6 中,模塊的信息在構(gòu)建時完成了附加;這與 Linux 2.4 不同,先前模塊信息的附加是在模塊裝載到內(nèi)核時進行的(在 Linux 2.4 時,這一過程由工具 insmod 完成)。

  • 模塊的標記選項。

在 Linux 2.6 中,針對管理模塊的選項做了一些調(diào)整,如取消了 can_unload 標記(用于標記模塊的使用狀態(tài)),添加了 CONFIG_MODULE_UNLOAD 標記(用于標記禁止模塊卸載)等。還修改了一些接口函數(shù),如模塊的引用計數(shù)。

圖 1. 模塊在內(nèi)核中完成連接

圖 1. 模塊在內(nèi)核中完成連接

發(fā)展到 Linux 2.6,內(nèi)核中越來越多的功能被模塊化。這是由于可裝載模塊相對內(nèi)核有著易維護,易調(diào)試的特點??裳b載模塊還為內(nèi)核節(jié)省了內(nèi)存空間,因為模塊一般是在真正需要時才被加載。根據(jù)模塊作用,可裝載模塊還可分三大類型:設備驅(qū)動、文件系統(tǒng)和系統(tǒng)調(diào)用。另須指出的是,雖然可裝載模塊是從用戶空間加載到內(nèi)核空間的,但是并非用戶空間的程序。

模塊的版本檢查

Linux 的迅速發(fā)展致使相鄰版本的內(nèi)核之間亦存在較大的差異,即在版本補丁號(Patch Level,即內(nèi)核版本號的第四位數(shù))相鄰的內(nèi)核之間。為此 Linux 的開發(fā)者為了保證內(nèi)核的穩(wěn)定,Linux 在加載模塊到內(nèi)核時對模塊采用了版本校驗機制。當被期望加載模塊的系統(tǒng)環(huán)境與模塊的構(gòu)建環(huán)境相左時,通常會出現(xiàn)如清單 1 所示的裝載模塊失敗。

清單 1. 裝載模塊失敗

1
2
3
4
5
# insmod ./hello/hello.ko
insmod: error inserting './hello/hello.ko': -1 Invalid module format
 
# dmesg | grep hello
[ 9206.599843] hello: disagrees about version of symbol module_layout

清單 1 中,模塊 hello.ko 構(gòu)建時的環(huán)境與當前系統(tǒng)不一致,導致工具 insmod 在嘗試裝載模塊 hello.ko 到內(nèi)核時失敗。hello.ko 是一個僅使用了函數(shù) printk 的普通模塊(您可在示例源碼中找到文件 hello/hello.c)。我們通過命令 dmesg(或者您也可以查看系統(tǒng)日志文件如 /var/log/messages 等,如果您啟用了這些系統(tǒng)日志的話)獲取模塊裝載失敗的具體原因,模塊 hello.ko 裝載失敗是由于模塊中 module_layout 的導出符號的版本信息與當前內(nèi)核中的不符。函數(shù) module_layout 被定義在內(nèi)核模塊版本選項 MODVERSIONS(即內(nèi)核可裝載模塊的版本校驗選項)之后。清單 2所示為 module_layout 在內(nèi)核文件 kernel/module.c 中的定義。

清單 2. 函數(shù) module_layout

1
2
3
4
5
6
7
8
9
10
11
/* kernel/module.c */
#ifdef CONFIG_MODVERSIONS
void module_layout(struct module *mod,
      struct modversion_info *ver,
      struct kernel_param *kp,
      struct kernel_symbol *ks,
      struct tracepoint * const *tp)
{
}
EXPORT_SYMBOL(module_layout);
#endif

清單 3. 結(jié)構(gòu)體 modversion_info

1
2
3
4
5
6
/* include/linux/module.h */
struct modversion_info
{
    unsigned long crc;
    char name[MODULE_NAME_LEN];
};

正如您所想,函數(shù) module_layout 的第二個參數(shù) ver 存儲了模塊的版本校驗信息。結(jié)構(gòu)體 modversion_info 中保存了用于模塊校驗的 CRC(Cyclic Redundancy Check,即循環(huán)冗余碼校驗)值(見 清單 3)。Linux 對可裝載模塊采取了兩層驗證:模塊的 CRC 值校驗和 vermagic 的檢查。其中模塊 CRC 值校驗針對模塊(內(nèi)核)導出符號,是一種簡單的 ABI(即 Application Binary Interface)一致性檢查,清單 1中模塊 hello.ko 加載失敗的根本原因就是沒有通過 CRC 值校驗(即 module_layout 的 CRC 值與當前內(nèi)核中的不符);而模塊 vermagic(即 Version Magic String)則保存了模塊編譯時的內(nèi)核版本以及 SMP 等配置信息(見 清單 4,模塊 hello.ko 的 vermagic 信息),當模塊 vermagic 與主機信息不相符時亦將終止模塊的加載。

清單 4. 模塊的 vermagic 信息

1
2
3
4
5
6
7
8
9
# uname – r
2.6.38-10-generic
 
# modinfo ./hello/hello.ko
filename:       ./hello/hello.ko
license:        Dual BSD/GPL
srcversion:     31FE72DA6A560C890FF9B3F
depends:       
vermagic:       2.6.38-9-generic SMP mod_unload modversions

通常,內(nèi)核與模塊的導出符號的 CRC 值被保存在文件 Module.symvers 中,該文件需在開啟內(nèi)核配置選項 CONFIG_MODVERSIONS 之后并完全編譯內(nèi)核獲得(或者您也可在編譯外部模塊后獲得該文件,保存的是模塊的導出符號的 CRC 信息),其信息的保存格式如清單 5 所示。

清單 5. 導出符號的 CRC 值

1
2
0x1de386dd  module_layout  vmlinux  EXPORT_SYMBOL
<CRC>        <Symbol>  <module>

Linux 內(nèi)核在進行模塊裝載時先完成模塊的 CRC 值校驗,再核對 vermagic 中的字符信息,圖 2展示了內(nèi)核中與模塊版本校驗相關(guān)的函數(shù)的調(diào)用過程(分別在函數(shù) setup_load_info 和 check_modinfo 中完成校驗)。Linux 使用 GCC 中的聲明函數(shù)屬性 __attribute__ 完成對模塊的版本信息附加。構(gòu)建的模塊存在幾個 section,如 .modinfo、.gnu.linkonce.this_module 和 __versions 等,這些 ELF 小節(jié)(即 section)保存了模塊校驗所需的信息(關(guān)于這些 section 信息的附加過程,您可查看模塊構(gòu)建時生成的文件 <module>.mod.c 及工具 modpost,見 清單 8清單 15)。

圖 2. 模塊的兩層版本校驗過程

圖 2. 模塊的兩層版本校驗過程

為了更好的理解可裝載模塊,我們查看內(nèi)核頭文件 include/linux/module.h,它不僅定義了上述中的 struct modversion_info(見 清單 3)還定義了 struct module 等結(jié)構(gòu)體。模塊 CRC 值校驗查看的是就是模塊 __versions 小節(jié)的內(nèi)容,即是附加的 struct modversion_info 信息。模塊的 CRC 校驗過程在函數(shù) setup_load_info 中完成。Linux 使用 .gnu.linkonce.this_module 小節(jié)來解決模塊對 struct module 信息的附加。文件 kernel/module.c 中的函數(shù) check_modinfo 完成了主機與模塊的 vermagic 值的對比(見 清單 6)。清單 6 中函數(shù) get_modinfo 用于獲取內(nèi)核中的 vermagic 信息,模塊 vermagic 信息則被保存在了 ELF 的 .modinfo 小節(jié)中。

清單 6. 函數(shù) check_modinfo

1
2
3
4
5
6
7
8
9
10
11
12
13
/* kernel/module.c */
static int check_modinfo(struct module *mod, struct load_info *info)
{
 const char *modmagic = get_modinfo(info, "vermagic");
 
 ...
 } else if (!same_magic(modmagic, vermagic, info->index.vers)) {
   ...
 }
 ...
 
 return 0;
}

須指出的是模塊的 vermagic 信息來自內(nèi)核頭文件 include/linux/vermagic.h 中的宏 VERMAGIC_STRING,其中宏 UTS_RELEASE 保存了內(nèi)核版本信息(見 清單 7)。與其關(guān)聯(lián)的頭文件 include/generated/utsrelease.h 需經(jīng)內(nèi)核預編譯生成,即通過命令 make 或 make modules_prepare 等。

清單 7. 宏 VERMAGIC_STRING

1
2
3
4
5
6
7
8
9
/* kernel/module.c */
static const char vermagic[] = VERMAGIC_STRING;
 
/* include/linux/vermagic.h */
#define VERMAGIC_STRING                         \
    UTS_RELEASE " "                         \
    MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT             \
    MODULE_VERMAGIC_MODULE_UNLOAD MODULE_VERMAGIC_MODVERSIONS   \
    MODULE_ARCH_VERMAGICLINE

模塊的裝載與卸載

上述中,我們在裝載模塊時使用了工具 insmod。在 Linux 2.6 中,工具 insmod 被重新設計并作為工具集 module-init-tools 中的一個程序,其通過系統(tǒng)調(diào)用 sys_init_module(您可查看頭文件 include/asm-generic/unistd.h)銜接了模塊的版本檢查,模塊的裝載等功能(如 圖 3所示)。module-init-tools 是為 2.6 內(nèi)核設計的運行在 Linux 用戶空間的模塊裝卸載工具集,其包含的程序 rmmod 用于卸載當前內(nèi)核中的模塊。

圖 3. 模塊的裝卸載

圖 3. 模塊的裝卸載

表 1. 工具集 module-init-tools 中的部分程序

值得一提的是在 module-init-tools 中可用于模塊裝卸載的程序 modprobe。程序 modprobe 的內(nèi)部函數(shù)調(diào)用過程正如您所想與 insmod 類似,只是其裝載過程會查找一些模塊裝載的配置文件,且 modprobe 在裝載模塊時可解決模塊間的依賴性,即若有必要,程序 modprobe 會在裝載一個模塊時自動加載該模塊依賴的其他模塊。

其他一些細節(jié)

從用戶空間裝載模塊到內(nèi)核時,Linux 還對用戶權(quán)限進行了檢查。模塊的裝載須是獲得 CAP_SYS_MODULE 權(quán)限的超級用戶,這正是模塊裝載時最先檢查的內(nèi)容(見 圖 2)。在 Linux 2.6 中,模塊在構(gòu)建時生成了一些臨時文件,如 .o 文件、.mod.o 文件等。了解這些文件的生成有助于我們更好的理解 Linux 2.6 的內(nèi)核模塊構(gòu)建過程以及版本信息的檢查等內(nèi)容。文件 .o 是模塊代碼(即 .c 文件)經(jīng)編譯后獲得的目標文件,文件 .mod.o 則對應文件 .mod.c。文件 <module>.mod.c 是對 <modulue>.c 的擴展,清單 8展示了文件 kobject-example.mod.c 的內(nèi)容 ( 即模塊 kobject-example.ko 的 .mod.c 文件 ),您可見到與模塊版本檢查相關(guān)三個小節(jié)。

清單 8. 文件 kobject-example.mod.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# cat ./kobject/kobject-example.mod.c
 ...
 
 MODULE_INFO(vermagic, VERMAGIC_STRING);
 
 struct module __this_module
 __attribute__((section(".gnu.linkonce.this_module"))) = {
 ...
 };
 
 static const struct modversion_info ____versions[]
 __used
 __attribute__((section("__versions"))) = {
 ...
 };
 
 static const char __module_depends[]
 __used
 __attribute__((section(".modinfo"))) =
"depends=";
 
 MODULE_INFO(srcversion, "B06F9B8B7AB52AEED247B9F");

清單 8 中顯示了模塊 kobject-example.ko 中的三個 section 以及宏 MODULE_INFO,最后一行 srcversion 則需開啟內(nèi)核配置選項 MODULE_SRCVERSION_ALL。經(jīng)上述,我們知道這三個 section 正是模塊版本檢查的附加信息。我們通過工具 objdump 查看 .modinfo 小節(jié)(見 清單 9, 即模塊的 vermagic 信息)。<module>.ko 的附加信息合并自文件 <module>.o 與文件 <module>.mod.o。內(nèi)核工具 modpost 完成了一這步驟,且該工具是 Linux 2.6 內(nèi)核模塊構(gòu)建時所必須的。

清單 9. 使用工具 objdump 查看 .modinfo 小節(jié)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# objdump --section=.modinfo -s hello/hello.o
 
hello/hello.o:     file format elf64-x86-64
 
Contents of section .modinfo:
0000 6c696365 6e73653d 4475616c 20425344  license=Dual BSD
0010 2f47504c 00                          /GPL.
 
# modinfo hello/hello.o
filename:       hello/hello.o
license:        Dual BSD/GPL
 
# objdump --section=.modinfo -s hello/hello.mod.o
...
# objdump --section=.modinfo -s hello/hello.ko
...

經(jīng)上述,我們可知內(nèi)核樹的頂層 Makefile 文件包含了內(nèi)核版本的信息,且該信息經(jīng)編譯后被添加到模塊的(頭文件 include/generated/utsrelease.h 保存的內(nèi)核版本信息來自頂層 Makefile)。表 1中,工具 lsmod 打開文件 /proc/modules 查詢當前內(nèi)核中已裝載的模塊(見清單 10),文件 /proc/modules 還被 rmmod 在卸載模塊時使用。另外,若您在裝載模塊 hello.ko 后沒能在終端下看到相應的字符串輸出,則需檢查文件 /proc/sys/kernel/printk,并重設下消息級別。

清單 10. 工具 lsmod 的使用

1
2
3
4
5
6
7
8
9
10
# insmod ./kobject/kobject-example.ko
 
# ls /sys/kernel/kobject_example/
bar  baz  foo
 
# lsmod | grep kobject
kobject_example        12857  0
 
# cat /proc/modules | grep kobject
kobject_example 12857 0 - Live 0xffffffffa0523000

Linux 2.6 構(gòu)建模塊時工具 modpost 被 scripts/Makefile.modpost 調(diào)用,生成 <module>.mod.c 及文件 Module.symvers(見 清單 15)。在開啟內(nèi)核選項 CONFIG_MODVERSIONS 之后,文件 Makefile.Build 會調(diào)用工具 genksyms(現(xiàn)位于內(nèi)核樹 scripts/genksyms 目錄下,在 Linux 2.4 時是模塊工具集 Modutils 的一部分)生成 CRC 信息(見 清單 11)。其中代碼 call cmd_gensymtypes 就是對工具 genksyms 的調(diào)用。另外一個較為明晰的方式是,使用工具 objdump 或 readelf 查看相關(guān)的 ELF 小節(jié),并使用 make – n 查看模塊構(gòu)建過程。

清單 11. 文件 Makefile.Build 的部分內(nèi)容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ifndef CONFIG_MODVERSIONS
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
else
cmd_cc_o_c = $(CC) $(c_flags) -c -o $(@D)/.tmp_$(@F) $<
cmd_modversions =              \
 if $(OBJDUMP) -h $(@D)/.tmp_$(@F) | grep -q __ksymtab; then  \
   $(call cmd_gensymtypes, $(KBUILD_SYMTYPES))    \
       > $(@D)/.tmp_$(@F:.o=.ver);      \
                 \
   $(LD) $(LDFLAGS) -r -o $@ $(@D)/.tmp_$(@F)     \
     -T $(@D)/.tmp_$(@F:.o=.ver);      \
   rm -f $(@D)/.tmp_$(@F) $(@D)/.tmp_$(@F:.o=.ver);  \
 else                \
   mv -f $(@D)/.tmp_$(@F) $@;        \
 fi;
endif

模塊的構(gòu)建與測試

為內(nèi)核構(gòu)建外部模塊前,我們須準備一顆內(nèi)核源碼樹(kernel source tree)。內(nèi)核源碼樹就是一套包含系統(tǒng)配置及內(nèi)核頭文件的內(nèi)核目錄樹。須指出的是 Linux 2.6 的內(nèi)核源碼樹與 2.4 的不同,先前的內(nèi)核只需一套內(nèi)核頭文件就可以了,但在 2.6 的內(nèi)核源碼樹中還需存在一些目標文件及工具,如 scripts/mod/modpost 等。清單 12 所示是從內(nèi)核源碼進行內(nèi)核模塊預編譯以此生成內(nèi)核樹,當然您也可使用 Linux 發(fā)行版的內(nèi)核源碼樹(系統(tǒng)內(nèi)核樹一般存放在 /lib/modules/<kernel version>/build,如果存在的話)。

清單 12. 預編譯內(nèi)核模塊

1
2
3
# make menuconfig
# make modules_prepare
#

當然,我們最先須根據(jù)主機的硬件信息產(chǎn)生內(nèi)核配置文件 .config。您可使用命令 make menuconfig 或 make config 等來配置與模塊相關(guān)的選項(清單 13清單 14相互對應,顯示了模塊相關(guān)的內(nèi)核配置選項)。設置選項 CONFIG_MODULES=y 以及 CONFIG_MODVERSIONS=y 使內(nèi)核支持模塊的版本檢查。另須注意的是,模塊預編譯并不生成 Module.symvers 文件,即使您開啟了 CONFIG_MODVERSIONS 選項。因此最好的方式是完全編譯 Linux 內(nèi)核。

清單 13. 使用 make menuconfig 配置內(nèi)核模塊選項

1
2
3
4
5
6
7
# make menuconfig
--- Enable loadable module support
   [ ]   Forced module loading
   [*]   Module unloading
   [ ]     Forced module unloading
   [*]   Module versioning support
   [*]   Source checksum for all modules

清單 14. 模塊相關(guān)的內(nèi)核配置選項

1
2
3
4
5
6
CONFIG_MODULES
CONFIG_MODULE_FORCE_LOAD
CONFIG_MODULE_UNLOAD
CONFIG_MODULE_FORCE_UNLOAD
CONFIG_MODVERSIONS
CONFIG_MODULE_SRCVERSION_ALL

內(nèi)核 2.6 時,我們常為模塊的構(gòu)建編寫一個 Makefile 文件,但仍可使用類似內(nèi)核 2.4 下的模塊構(gòu)建命令。清單 15 展示了外部模塊構(gòu)建的 make 命令,其中 $KDIR 是內(nèi)核樹的絕對路徑,$MDIR 是期望構(gòu)建的模塊的絕對路徑(若是內(nèi)部模塊則可使用 make CONFIG_EXT2_FS=m …)。

清單 15. 構(gòu)建外部模塊

1
2
3
4
5
6
7
8
9
10
# make -C $KDIR M=$MDIR [target]
 
# make -C /lib/modules/2.6.38-10-generic/build M=$PWD/hello  modules
make: Entering directory `/usr/src/linux-headers-2.6.38-10-generic'
CC [M] /home/harris/work/samples/hello/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/harris/work/samples/hello/hello.mod.o
LD [M] /home/harris/work/samples/hello/hello.ko
make: Leaving directory `/usr/src/linux-headers-2.6.38-10-generic'

結(jié)束語

雖然本文已盡量集中描述可裝載模塊的版本檢查機制,但是仍然涉及了非常多的內(nèi)容。您需花一些時間來了解這些看似與模塊不相關(guān)的內(nèi)容,如 /proc、/sys 文件系統(tǒng)、ELF(即 Executable and Linkable Format)格式等,以此更好的全面理解內(nèi)核可裝載模塊以及模塊版本版本檢查機制。另外,由于文章討論的是 Linux 2.6 的外部模塊,沒有清晰的講述內(nèi)核的 Kbuild 系統(tǒng)及 Makefile 文件,但這對于模塊亦是重要的內(nèi)容。

https://www.ibm.com/developerworks/cn/linux/l-cn-kernelmodules/

*博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點,如有侵權(quán)請聯(lián)系工作人員刪除。



關(guān)鍵詞:

相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉