Linux 驅(qū)動(dòng)模塊可以獨(dú)立的編譯成 .ko 文件,雖然大小一般只有幾 MB,但對(duì)總內(nèi)存只有幾十 MB 的小型 Linux 系統(tǒng)來(lái)說(shuō),常常也是一個(gè)非常值得優(yōu)化的點(diǎn)。本文以一個(gè)實(shí)際例子,詳細(xì)描述 .ko?內(nèi)存精簡(jiǎn)優(yōu)化的具體過(guò)程。
1. Strip 文件
因?yàn)?.ko 文件是一個(gè)標(biāo)準(zhǔn)的 ELF 文件,通常我們首先會(huì)想到使用 strip 命令來(lái)精簡(jiǎn)文件大小。strip .ko 有以下幾種選項(xiàng):
strip --strip-all test.ko // strip 掉所有的調(diào)試段,ko 文件體積減少很多,ko 不能正常 insmod
strip --strip-debug test.ko // strip 掉 debug 段,ko 文件體積減少不多,ko 可以正常 insmod
strip --strip-unneeded test.ko // strip 掉 和動(dòng)態(tài)重定位無(wú)關(guān)的段,ko 文件體積減少不多,ko 可以正常 insmod
.ko 文件具體的體積變化:
6978208 origin-test.ko* // no strip
1984856 strip-all-test.ko* // strip --strip-all
6884544 strip-debug-test.ko* // strip --strip-debug
6830704 strip-unneeded-test.ko* // strip --strip-unneeded
可以看到在保存 .ko 能正常使用的前提下, strip 命令對(duì) .ko 文件并不能減少多大的體積。而且一通操作下來(lái), .ko 文件中的關(guān)鍵數(shù)據(jù) text/data/bss 段的體積沒(méi)有任何變化:
$ size *.ko
text data bss dec hex filename
1697671 275791 28367 2001829 1e8ba5 origin-test.ko
1697671 275791 28367 2001829 1e8ba5 strip-all-test.ko
1697671 275791 28367 2001829 1e8ba5 strip-debug-test.ko
1697671 275791 28367 2001829 1e8ba5 strip-unneeded-test.ko
Question 1:?strip?命令是否還有命令能實(shí)現(xiàn)更多的精簡(jiǎn)??strip?的本質(zhì)是什么,具體?strip?掉了哪些東西?
我們通過(guò)讀取 ELF 文件的 section 信息來(lái)比較 strip 前后的差異:
$ readelf -S origin-test.ko
There are 48 section headers, starting at offset 0x6a6ea0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.build-i NOTE 0000000000000000 00000040
0000000000000024 0000000000000000 A 0 0 4
[ 2] .note.Linux NOTE 0000000000000000 00000064
0000000000000018 0000000000000000 A 0 0 4
[ 3] .text PROGBITS 0000000000000000 0000007c
00000000001393d6 0000000000000000 AX 0 0 2
[ 4] .rela.text RELA 0000000000000000 003b9b90
00000000002b7550 0000000000000018 I 45 3 8
[ 5] .text.unlikely PROGBITS 0000000000000000 00139452
0000000000000d74 0000000000000000 AX 0 0 2
[ 6] .rela.text.unlike RELA 0000000000000000 006710e0
0000000000001950 0000000000000018 I 45 5 8
[ 7] .init.text PROGBITS 0000000000000000 0013a1c6
000000000000016e 0000000000000000 AX 0 0 2
[ 8] .rela.init.text RELA 0000000000000000 00672a30
...
$ readelf -S strip-all-test.ko
There are 27 section headers, starting at offset 0x1e4298:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.build-i NOTE 0000000000000000 00000040
0000000000000024 0000000000000000 A 0 0 4
[ 2] .note.Linux NOTE 0000000000000000 00000064
0000000000000018 0000000000000000 A 0 0 4
[ 3] .text PROGBITS 0000000000000000 0000007c
00000000001393d6 0000000000000000 AX 0 0 2
[ 4] .text.unlikely PROGBITS 0000000000000000 00139452
0000000000000d74 0000000000000000 AX 0 0 2
[ 5] .init.text PROGBITS 0000000000000000 0013a1c6
000000000000016e 0000000000000000 AX 0 0 2
...
從信息上看 strip 主要?jiǎng)h除了 Flags 為 I 的 Sections,而 Flags 帶 A 的 Sections 是不能被刪除的。關(guān)于 SectionsFlags 的定義在 Readelf 命令的最后面有詳細(xì)描述:
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
另外還發(fā)現(xiàn),對(duì) .ko 文件來(lái)說(shuō) .rela. 開(kāi)頭的 Sections 是不能被刪除的, insmod 時(shí)需要這些信息。例如 .rela.text 占用了很大的體積,但是不能直接粗暴的直接 strip 掉。
Question 2:對(duì)于?.ko?文件中?Flags?為?I?的?Sections?在模塊 insmod 以后是否需要占據(jù)內(nèi)存?
內(nèi)核代碼中對(duì) .ko 文件 insmod 動(dòng)態(tài)加載時(shí)的主流程:
SYSCALL_DEFINE3(finit_module) / SYSCALL_DEFINE3(init_module)
|→ load_module()
|→ layout_and_allocate()
| |→ setup_load_info() // info->index.mod = section ".gnu.linkonce.this_module"
| |
| |→ layout_sections() // 解析 ko ELF 文件,統(tǒng)計(jì)需要加載到內(nèi)存中的 section
| | // 累計(jì)長(zhǎng)度到 mod->core_layout.size 和 mod->init_layout.size
| |
| |→ layout_symtab() // 解析 ko ELF 文件,統(tǒng)計(jì)需要加載到內(nèi)存中的符號(hào)表
| | // 累計(jì)長(zhǎng)度到 mod->core_layout.size
| |
| |→ move_module() // 根據(jù) mod->core_layout.size 和 mod->init_layout.size 的長(zhǎng)度
| // 使用 vmalloc 分配空間,并且拷貝對(duì)應(yīng)的 section 到內(nèi)存
|
|→ apply_relocations() // 對(duì)加載到內(nèi)存的 section 做重定位處理
|
|→ do_init_module() // 執(zhí)行驅(qū)動(dòng)模塊的 module_init() 函數(shù),完成后釋放 mod->init_layout.size 內(nèi)存
分析具體的代碼細(xì)節(jié),發(fā)現(xiàn)只有帶 ALLOC 屬性(即 Flags 帶 A)的 section 才會(huì)在模塊加載時(shí)統(tǒng)計(jì)并拷貝進(jìn)內(nèi)存:
?
static void layout_sections(struct module *mod, struct load_info *info)
{
/* (1) 只識(shí)別帶 SHF_ALLOC 的 section */
static unsigned long const masks[][2] = {
/* NOTE: all executable code must be the first section
* in this array; otherwise modify the text_size
* finder in the two loops below */
{ SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL },
{ SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL },
{ SHF_RO_AFTER_INIT | SHF_ALLOC, ARCH_SHF_SMALL },
{ SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL },
{ ARCH_SHF_SMALL | SHF_ALLOC, 0 }
};
unsigned int m, i;
for (i = 0; i < info->hdr->e_shnum; i++)
info->sechdrs[i].sh_entsize = ~0UL;
/* (2) 遍歷 ko 文件的 section,根據(jù)上述標(biāo)志來(lái)統(tǒng)計(jì)
把 ALLOC 類型的 section 統(tǒng)計(jì)進(jìn) mod->core_layout.size
*/
pr_debug("Core section allocation order: ");
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;
if ((s->sh_flags & masks[m][0]) != masks[m][0]
|| (s->sh_flags & masks[m][1])
|| s->sh_entsize != ~0UL
|| module_init_section(sname))
continue;
s->sh_entsize = get_offset(mod, &mod->core_layout.size, s, i);
pr_debug(" %s ", sname);
}
}
/* (3) 遍歷 ko 文件的 section,根據(jù)上述標(biāo)志來(lái)統(tǒng)計(jì)
把 ALLOC 類型的并且名字以 '.init' 開(kāi)頭的 section 統(tǒng)計(jì)進(jìn) mod->init_layout.size
*/
pr_debug("Init section allocation order: ");
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;
if ((s->sh_flags & masks[m][0]) != masks[m][0]
|| (s->sh_flags & masks[m][1])
|| s->sh_entsize != ~0UL
|| !module_init_section(sname))
continue;
s->sh_entsize = (get_offset(mod, &mod->init_layout.size, s, i)
| INIT_OFFSET_MASK);
pr_debug(" %s ", sname);
}
}
}
Flags 帶 I 的 section 只會(huì)在 apply_relocations() 重定位時(shí)提供信息,這部分 section 不會(huì)在內(nèi)存中常駐。
結(jié)論:strip 操作 .ko 文件只會(huì)精簡(jiǎn)掉少量 I 的 section, .ko 文件少量減小,但是對(duì)動(dòng)態(tài)加載后的內(nèi)存占用毫無(wú)影響。
2. 運(yùn)行時(shí)內(nèi)存占用
但是生活還得繼續(xù),優(yōu)化還得想辦法。我們仔細(xì)分析關(guān)鍵數(shù)據(jù) text/data/bss 段在模塊加載過(guò)程中的內(nèi)存占用。
加載前:
$ size test.ko
text data bss dec hex filename
1697671 275791 28367 2001829 1e8ba5 test.ko
模塊 insmod 后的內(nèi)存占用,因?yàn)槭峭ㄟ^(guò) vmalloc() 分配的,我們可以通過(guò) vmallocinfo 查看內(nèi)存占用情況:
# cat /sys/module/test/coresize
4203425
# cat /sys/module/test/initsize
0
# cat /proc/vmallocinfo
// core_layout.size 占用 4.2 M 內(nèi)存
0x00000000fd4ec521-0x000000007ff17966 4210688 load_module+0x1b86/0x1c8e pages=1027 vmalloc vpages
0x000000007ff17966-0x000000004e29ad2e 16384 load_module+0x1b86/0x1c8e pages=3 vmalloc
可以看到,加載前 test.ko 的 text/data/bss 段的總長(zhǎng)為 2 M 左右,但是模塊加載后總共占用了 4.2 M 內(nèi)存。
Question 3:為什么模塊加載后會(huì)有多出的內(nèi)存占用?
我們?cè)趦?nèi)核代碼中加上調(diào)試信息,跟蹤 mod->core_layout.size 的變化情況,終于找到了關(guān)鍵所在:
SYSCALL_DEFINE3(finit_module) / SYSCALL_DEFINE3(init_module)
|→ load_module()
|→ layout_and_allocate()
| |→ setup_load_info() // mod->core_layout.size = 0x0.
| |
| |→ layout_sections() // mod->core_layout.size = 0x1f8390
| |
| |→ layout_symtab() // mod->core_layout.size = 0x4023a1.
| |
| |→ move_module() // 根據(jù) mod->core_layout.size 和 mod->init_layout.size 的長(zhǎng)度
可以看到是在 layout_symtab() 函數(shù)中增大了多余的長(zhǎng)度, layout_symtab() 函數(shù)在 CONFIG_KALLSYMS 使能的情況下才有效,存儲(chǔ)的驅(qū)動(dòng)模塊的符號(hào)表。
一般情況下我們并不需要模塊符號(hào)表,可以關(guān)閉內(nèi)核的 CONFIG_KALLSYMS 選項(xiàng)來(lái)查看內(nèi)存的占用情況:
# cat /sys/module/test/coresize
2092876
# cat /sys/module/test/initsize
0
# cat /proc/vmallocinfo
// core_layout.size 占用 2.0 M 內(nèi)存
0x000000009e1c62e8-0x000000001024ef17 2097152 0xffffffff8006f3de pages=511 vmalloc
0x000000004070c817-0x00000000cc1b6736 28672 0xffffffff41534922 pages=6 vmalloc
多余的 2.2 M 內(nèi)存被完美的精簡(jiǎn)下來(lái)。
但是這種方法也只能減少 .ko 的靜態(tài)內(nèi)存占用,驅(qū)動(dòng)動(dòng)態(tài)分配的內(nèi)存只能分析代碼邏輯去優(yōu)化。
結(jié)論:關(guān)閉 CONFIG_KALLSYMS 選項(xiàng)可以精簡(jiǎn) .ko 模塊符號(hào)表的內(nèi)存占用,精簡(jiǎn)收益還是不錯(cuò)的。
編輯:黃飛
?
評(píng)論
查看更多