Linux驱动模块内存精简

Linux阅码场 2023-09-25 08:03

Linux 驱动模块可以独立的编译成 .ko 文件,虽然大小一般只有几 MB,但对总内存只有几十 MB 的小型 Linux 系统来说,常常也是一个非常值得优化的点。本文以一个实际例子,详细描述 .ko 内存精简优化的具体过程。

1. Strip 文件

因为 .ko 文件是一个标准的 ELF 文件,通常我们首先会想到使用 strip 命令来精简文件大小。strip .ko 有以下几种选项:

  1. strip --strip-all test.ko // strip 掉所有的调试段,ko 文件体积减少很多,ko 不能正常 insmod

  2. strip --strip-debug test.ko // strip 掉 debug 段,ko 文件体积减少不多,ko 可以正常 insmod

  3. strip --strip-unneeded test.ko // strip 掉 和动态重定位无关的段,ko 文件体积减少不多,ko 可以正常 insmod

.ko 文件具体的体积变化:

  1. 6978208 origin-test.ko* // no strip

  2. 1984856 strip-all-test.ko* // strip --strip-all

  3. 6884544 strip-debug-test.ko* // strip --strip-debug

  4. 6830704 strip-unneeded-test.ko* // strip --strip-unneeded

可以看到在保存 .ko 能正常使用的前提下, strip 命令对 .ko 文件并不能减少多大的体积。而且一通操作下来, .ko 文件中的关键数据 text/data/bss 段的体积没有任何变化:

  1. $ size *.ko

  2. text data bss dec hex filename

  3. 1697671 275791 28367 2001829 1e8ba5 origin-test.ko

  4. 1697671 275791 28367 2001829 1e8ba5 strip-all-test.ko

  5. 1697671 275791 28367 2001829 1e8ba5 strip-debug-test.ko

  6. 1697671 275791 28367 2001829 1e8ba5 strip-unneeded-test.ko

  • Question 1: strip 命令是否还有命令能实现更多的精简? strip 的本质是什么,具体 strip 掉了哪些东西?

我们通过读取 ELF 文件的 section 信息来比较 strip 前后的差异:

  1. $ readelf -S origin-test.ko

  2. There are 48 section headers, starting at offset 0x6a6ea0:

  3. Section Headers:

  4. [Nr] Name Type Address Offset

  5. Size EntSize Flags Link Info Align

  6. [ 0] NULL 0000000000000000 00000000

  7. 0000000000000000 0000000000000000 0 0 0

  8. [ 1] .note.gnu.build-i NOTE 0000000000000000 00000040

  9. 0000000000000024 0000000000000000 A 0 0 4

  10. [ 2] .note.Linux NOTE 0000000000000000 00000064

  11. 0000000000000018 0000000000000000 A 0 0 4

  12. [ 3] .text PROGBITS 0000000000000000 0000007c

  13. 00000000001393d6 0000000000000000 AX 0 0 2

  14. [ 4] .rela.text RELA 0000000000000000 003b9b90

  15. 00000000002b7550 0000000000000018 I 45 3 8

  16. [ 5] .text.unlikely PROGBITS 0000000000000000 00139452

  17. 0000000000000d74 0000000000000000 AX 0 0 2

  18. [ 6] .rela.text.unlike RELA 0000000000000000 006710e0

  19. 0000000000001950 0000000000000018 I 45 5 8

  20. [ 7] .init.text PROGBITS 0000000000000000 0013a1c6

  21. 000000000000016e 0000000000000000 AX 0 0 2

  22. [ 8] .rela.init.text RELA 0000000000000000 00672a30

  23. ...

  24. $ readelf -S strip-all-test.ko

  25. There are 27 section headers, starting at offset 0x1e4298:

  26. Section Headers:

  27. [Nr] Name Type Address Offset

  28. Size EntSize Flags Link Info Align

  29. [ 0] NULL 0000000000000000 00000000

  30. 0000000000000000 0000000000000000 0 0 0

  31. [ 1] .note.gnu.build-i NOTE 0000000000000000 00000040

  32. 0000000000000024 0000000000000000 A 0 0 4

  33. [ 2] .note.Linux NOTE 0000000000000000 00000064

  34. 0000000000000018 0000000000000000 A 0 0 4

  35. [ 3] .text PROGBITS 0000000000000000 0000007c

  36. 00000000001393d6 0000000000000000 AX 0 0 2

  37. [ 4] .text.unlikely PROGBITS 0000000000000000 00139452

  38. 0000000000000d74 0000000000000000 AX 0 0 2

  39. [ 5] .init.text PROGBITS 0000000000000000 0013a1c6

  40. 000000000000016e 0000000000000000 AX 0 0 2

  41. ...

从信息上看 strip 主要删除了 FlagsISections,而 FlagsASections 是不能被删除的。关于 SectionsFlags 的定义在 Readelf 命令的最后面有详细描述:

  1. Key to Flags:

  2. W (write), A (alloc), X (execute), M (merge), S (strings), I (info),

  3. L (link order), O (extra OS processing required), G (group), T (TLS),

  4. C (compressed), x (unknown), o (OS specific), E (exclude),

  5. p (processor specific)

另外还发现,对 .ko 文件来说 .rela. 开头的 Sections 是不能被删除的, insmod 时需要这些信息。例如 .rela.text 占用了很大的体积,但是不能直接粗暴的直接 strip 掉。

  • Question 2:对于 .ko 文件中 Flags 为 I 的 Sections 在模块 insmod 以后是否需要占据内存?

内核代码中对 .ko 文件 insmod 动态加载时的主流程:

  1. SYSCALL_DEFINE3(finit_module) / SYSCALL_DEFINE3(init_module)

  2. |→ load_module()

  3. |→ layout_and_allocate()

  4. | |→ setup_load_info() // info->index.mod = section ".gnu.linkonce.this_module"

  5. | |

  6. | |→ layout_sections() // 解析 ko ELF 文件,统计需要加载到内存中的 section

  7. | | // 累计长度到 mod->core_layout.size 和 mod->init_layout.size

  8. | |

  9. | |→ layout_symtab() // 解析 ko ELF 文件,统计需要加载到内存中的符号表

  10. | | // 累计长度到 mod->core_layout.size

  11. | |

  12. | |→ move_module() // 根据 mod->core_layout.size 和 mod->init_layout.size 的长度

  13. | // 使用 vmalloc 分配空间,并且拷贝对应的 section 到内存

  14. |

  15. |→ apply_relocations() // 对加载到内存的 section 做重定位处理

  16. |

  17. |→ do_init_module() // 执行驱动模块的 module_init() 函数,完成后释放 mod->init_layout.size 内存

分析具体的代码细节,发现只有带 ALLOC 属性(即 FlagsA)的 section 才会在模块加载时统计并拷贝进内存:

  1. static void layout_sections(struct module *mod, struct load_info *info)

  2. {

  3. /* (1) 只识别带 SHF_ALLOC 的 section */

  4. static unsigned long const masks[][2] = {

  5. /* NOTE: all executable code must be the first section

  6. * in this array; otherwise modify the text_size

  7. * finder in the two loops below */

  8. { SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL },

  9. { SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL },

  10. { SHF_RO_AFTER_INIT | SHF_ALLOC, ARCH_SHF_SMALL },

  11. { SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL },

  12. { ARCH_SHF_SMALL | SHF_ALLOC, 0 }

  13. };

  14. unsigned int m, i;

  15. for (i = 0; i < info->hdr->e_shnum; i++)

  16. info->sechdrs[i].sh_entsize = ~0UL;

  17. /* (2) 遍历 ko 文件的 section,根据上述标志来统计

  18. 把 ALLOC 类型的 section 统计进 mod->core_layout.size

  19. */

  20. pr_debug("Core section allocation order:\n");

  21. for (m = 0; m < ARRAY_SIZE(masks); ++m) {

  22. for (i = 0; i < info->hdr->e_shnum; ++i) {

  23. Elf_Shdr *s = &info->sechdrs[i];

  24. const char *sname = info->secstrings + s->sh_name;

  25. if ((s->sh_flags & masks[m][0]) != masks[m][0]

  26. || (s->sh_flags & masks[m][1])

  27. || s->sh_entsize != ~0UL

  28. || module_init_section(sname))

  29. continue;

  30. s->sh_entsize = get_offset(mod, &mod->core_layout.size, s, i);

  31. pr_debug("\t%s\n", sname);

  32. }

  33. }

  34. /* (3) 遍历 ko 文件的 section,根据上述标志来统计

  35. 把 ALLOC 类型的并且名字以 '.init' 开头的 section 统计进 mod->init_layout.size

  36. */

  37. pr_debug("Init section allocation order:\n");

  38. for (m = 0; m < ARRAY_SIZE(masks); ++m) {

  39. for (i = 0; i < info->hdr->e_shnum; ++i) {

  40. Elf_Shdr *s = &info->sechdrs[i];

  41. const char *sname = info->secstrings + s->sh_name;

  42. if ((s->sh_flags & masks[m][0]) != masks[m][0]

  43. || (s->sh_flags & masks[m][1])

  44. || s->sh_entsize != ~0UL

  45. || !module_init_section(sname))

  46. continue;

  47. s->sh_entsize = (get_offset(mod, &mod->init_layout.size, s, i)

  48. | INIT_OFFSET_MASK);

  49. pr_debug("\t%s\n", sname);

  50. }

  51. }

  52. }

FlagsI 的 section 只会在 apply_relocations() 重定位时提供信息,这部分 section 不会在内存中常驻。

结论:strip 操作 .ko 文件只会精简掉少量 I 的 section, .ko 文件少量减小,但是对动态加载后的内存占用毫无影响。

2. 运行时内存占用

但是生活还得继续,优化还得想办法。我们仔细分析关键数据 text/data/bss 段在模块加载过程中的内存占用。

加载前:

  1. $ size test.ko

  2. text data bss dec hex filename

  3. 1697671 275791 28367 2001829 1e8ba5 test.ko

模块 insmod 后的内存占用,因为是通过 vmalloc() 分配的,我们可以通过 vmallocinfo 查看内存占用情况:

  1. # cat /sys/module/test/coresize

  2. 4203425

  3. # cat /sys/module/test/initsize

  4. 0

  5. # cat /proc/vmallocinfo

  6. // core_layout.size 占用 4.2 M 内存

  7. 0x00000000fd4ec521-0x000000007ff17966 4210688 load_module+0x1b86/0x1c8e pages=1027 vmalloc vpages

  8. 0x000000007ff17966-0x000000004e29ad2e 16384 load_module+0x1b86/0x1c8e pages=3 vmalloc

可以看到,加载前 test.kotext/data/bss 段的总长为 2 M 左右,但是模块加载后总共占用了 4.2 M 内存。

  • Question 3:为什么模块加载后会有多出的内存占用?

我们在内核代码中加上调试信息,跟踪 mod->core_layout.size 的变化情况,终于找到了关键所在:

  1. SYSCALL_DEFINE3(finit_module) / SYSCALL_DEFINE3(init_module)

  2. |→ load_module()

  3. |→ layout_and_allocate()

  4. | |→ setup_load_info() // mod->core_layout.size = 0x0.

  5. | |

  6. | |→ layout_sections() // mod->core_layout.size = 0x1f8390

  7. | |

  8. | |→ layout_symtab() // mod->core_layout.size = 0x4023a1.

  9. | |

  10. | |→ move_module() // 根据 mod->core_layout.size 和 mod->init_layout.size 的长度

可以看到是在 layout_symtab() 函数中增大了多余的长度, layout_symtab() 函数在 CONFIG_KALLSYMS 使能的情况下才有效,存储的驱动模块的符号表。

一般情况下我们并不需要模块符号表,可以关闭内核的 CONFIG_KALLSYMS 选项来查看内存的占用情况:

  1. # cat /sys/module/test/coresize

  2. 2092876

  3. # cat /sys/module/test/initsize

  4. 0

  5. # cat /proc/vmallocinfo

  6. // core_layout.size 占用 2.0 M 内存

  7. 0x000000009e1c62e8-0x000000001024ef17 2097152 0xffffffff8006f3de pages=511 vmalloc

  8. 0x000000004070c817-0x00000000cc1b6736 28672 0xffffffff41534922 pages=6 vmalloc

多余的 2.2 M 内存被完美的精简下来。

但是这种方法也只能减少 .ko 的静态内存占用,驱动动态分配的内存只能分析代码逻辑去优化。

结论:关闭 CONFIG_KALLSYMS 选项可以精简 .ko 模块符号表的内存占用,精简收益还是不错的。


Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论
  • 铁氧体芯片是一种基于铁氧体磁性材料制成的芯片,在通信、传感器、储能等领域有着广泛的应用。铁氧体磁性材料能够通过外加磁场调控其导电性质和反射性质,因此在信号处理和传感器技术方面有着独特的优势。以下是对半导体划片机在铁氧体划切领域应用的详细阐述: 一、半导体划片机的工作原理与特点半导体划片机是一种使用刀片或通过激光等方式高精度切割被加工物的装置,是半导体后道封测中晶圆切割和WLP切割环节的关键设备。它结合了水气电、空气静压高速主轴、精密机械传动、传感器及自动化控制等先进技术,具有高精度、高
    博捷芯划片机 2024-12-12 09:16 72浏览
  • 首先在gitee上打个广告:ad5d2f3b647444a88b6f7f9555fd681f.mp4 · 丙丁先生/香河英茂工作室中国 - Gitee.com丙丁先生 (mr-bingding) - Gitee.com2024年对我来说是充满挑战和机遇的一年。在这一年里,我不仅进行了多个开发板的测评,还尝试了多种不同的项目和技术。今天,我想分享一下这一年的故事,希望能给大家带来一些启发和乐趣。 年初的时候,我开始对各种开发板进行测评。从STM32WBA55CG到瑞萨、平头哥和平海的开发板,我都
    丙丁先生 2024-12-11 20:14 61浏览
  • 全球知名半导体制造商ROHM Co., Ltd.(以下简称“罗姆”)宣布与Taiwan Semiconductor Manufacturing Company Limited(以下简称“台积公司”)就车载氮化镓功率器件的开发和量产事宜建立战略合作伙伴关系。通过该合作关系,双方将致力于将罗姆的氮化镓器件开发技术与台积公司业界先进的GaN-on-Silicon工艺技术优势结合起来,满足市场对高耐压和高频特性优异的功率元器件日益增长的需求。氮化镓功率器件目前主要被用于AC适配器和服务器电源等消费电子和
    电子资讯报 2024-12-10 17:09 95浏览
  • 天问Block和Mixly是两个不同的编程工具,分别在单片机开发和教育编程领域有各自的应用。以下是对它们的详细比较: 基本定义 天问Block:天问Block是一个基于区块链技术的数字身份验证和数据交换平台。它的目标是为用户提供一个安全、去中心化、可信任的数字身份验证和数据交换解决方案。 Mixly:Mixly是一款由北京师范大学教育学部创客教育实验室开发的图形化编程软件,旨在为初学者提供一个易于学习和使用的Arduino编程环境。 主要功能 天问Block:支持STC全系列8位单片机,32位
    丙丁先生 2024-12-11 13:15 60浏览
  • RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:​图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,且支持2D图形加速,轻松运行QT、LVGL等GUI,最快3S内开
    万象奥科 2024-12-11 15:42 81浏览
  • 在智能化技术快速发展当下,图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进步,对于图像数据的采集、处理和分析的需求日益增长,这不仅要求我们拥有高性能的相机硬件,还要求我们能够高效地集成和测试各种算法。我们探索了一种多源相机数据采集与算法集成测试方案,能够满足不同应用场景下对图像采集和算法测试的多样化需求,确保数据的准确性和算法的有效性。一、相机组成相机一般由镜头(Lens),图像传感器(Image
    康谋 2024-12-12 09:45 68浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-11 17:58 75浏览
  • 时源芯微——RE超标整机定位与解决详细流程一、 初步测量与问题确认使用专业的电磁辐射测量设备,对整机的辐射发射进行精确测量。确认是否存在RE超标问题,并记录超标频段和幅度。二、电缆检查与处理若存在信号电缆:步骤一:拔掉所有信号电缆,仅保留电源线,再次测量整机的辐射发射。若测量合格:判定问题出在信号电缆上,可能是电缆的共模电流导致。逐一连接信号电缆,每次连接后测量,定位具体哪根电缆或接口导致超标。对问题电缆进行处理,如加共模扼流圈、滤波器,或优化电缆布局和屏蔽。重新连接所有电缆,再次测量
    时源芯微 2024-12-11 17:11 104浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-10 16:13 113浏览
  • 我的一台很多年前人家不要了的九十年代SONY台式组合音响,接手时只有CD功能不行了,因为不需要,也就没修,只使用收音机、磁带机和外接信号功能就够了。最近五年在外地,就断电闲置,没使用了。今年9月回到家里,就一个劲儿地忙着收拾家当,忙了一个多月,太多事啦!修了电气,清理了闲置不用了的电器和电子,就是一个劲儿地扔扔扔!几十年的“工匠式”收留收藏,只能断舍离,拆解不过来的了。一天,忽然感觉室内有股臭味,用鼻子的嗅觉功能朝着臭味重的方向寻找,觉得应该就是这台组合音响?怎么会呢?这无机物的东西不会腐臭吧?
    自做自受 2024-12-10 16:34 163浏览
  • 近日,搭载紫光展锐W517芯片平台的INMO GO2由影目科技正式推出。作为全球首款专为商务场景设计的智能翻译眼镜,INMO GO2 以“快、准、稳”三大核心优势,突破传统翻译产品局限,为全球商务人士带来高效、自然、稳定的跨语言交流体验。 INMO GO2内置的W517芯片,是紫光展锐4G旗舰级智能穿戴平台,采用四核处理器,具有高性能、低功耗的优势,内置超微高集成技术,采用先进工艺,计算能力相比同档位竞品提升4倍,强大的性能提供更加多样化的应用场景。【视频见P盘链接】 依托“
    紫光展锐 2024-12-11 11:50 65浏览
  • 一、SAE J1939协议概述SAE J1939协议是由美国汽车工程师协会(SAE,Society of Automotive Engineers)定义的一种用于重型车辆和工业设备中的通信协议,主要应用于车辆和设备之间的实时数据交换。J1939基于CAN(Controller Area Network)总线技术,使用29bit的扩展标识符和扩展数据帧,CAN通信速率为250Kbps,用于车载电子控制单元(ECU)之间的通信和控制。小北同学在之前也对J1939协议做过扫盲科普【科普系列】SAE J
    北汇信息 2024-12-11 15:45 104浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦