关键词:readelf、bloat-o-meter、graph-size、totalram_pages、reserved、meminfo、PSS、procrank、maps等等。html
根据项目的需求,进行ROM/RAM的低成本裁剪。linux
在进行优化以前,(1)首要任务是对待优化的方案进行量化,从ROM来看有uboot、kernel、rootfs;从RAM来看,有静态RAM和运行时产生的动态RAM。git
(2)而后就是根据量化结果,寻找浪费点进行优化;不须要的直接删除,过量配置的适当下降。github
(3)再而后就是要验证裁剪结果,须要一个稳定的最大化的场景。网络
固然这尚未结束,随着项目的推动,会是一个(1)->(2)->(3)->(1)往复的过程。dom
固然过程当中须要对关键步骤进行敏捷处理,量化工做脚本化,固定格式输出可读性强报告;验证自动化,动态获取运行中数据等等。ide
经过编写脚本分析ROM使用、经过build-in.o分析模块、bloat-o-meter等工具能够从不一样角度分析存储容量。函数
ROM的量化按照从大到小的步骤,首先分析整个方案的ROM,包括uboot、kernel、rootfs,总大小就是整个方案大小。工具
uboot和kernel能够经过readelf或者size查看细节;rootfs须要细分每一个文件大小,针对bin文件根据须要删除,lib文件能够经过依赖关系判断是否被依赖。post
下面按照从文件,到段,再到符号进行分析。
经过遍历整个文件系统,将每一个文件路径和大小列出。
经过readelf来分析应用程序以及各库之间的依赖关系,进而分析出孤立无用的库,以及不被使用到的可执行文件。
经过images_analyze.ipynb分析rootfs.cpio、uImage、uboot等。
输出结果包括kernel、u-boot、rootfs尺寸详细信息,以及相关库的依赖关系lib_depend.txt。
对elf文件能够经过size,快速获取其text/data/bss段大小,以及总大小。
size vmlinux text data bss dec hex filename 4680576 3596750 247304 8524630 821356 vmlinux
若是须要更多细节,须要经过readelf -S来获取不一样section信息。
观察Linux内核的编译过程,能够知道内核中重要的子模块都会生成一个built-in.o的中间文件。
利用这个特定,经过'find -name built-in.o | xargs size'能够看出不一样模块的大小。
find -name built-in.o | xargs size text data bss dec hex filename 53946 68 4 54018 d302 ./fs/ext2/built-in.o 8020 31 8 8059 1f7b ./fs/sysfs/built-in.o 363316 916 260 364492 58fcc ./fs/nls/built-in.o 81972 336 108 82416 141f0 ./fs/proc/built-in.o 1731 24 12 1767 6e7 ./fs/notify/dnotify/built-in.o 4882 151 24 5057 13c1 ./fs/notify/inotify/built-in.o 20254 239 144 20637 509d ./fs/notify/built-in.o 5895 4 12 5911 1717 ./fs/notify/fanotify/built-in.o 18891 74 4104 23069 5a1d ./fs/kernfs/built-in.o ...
若是要对结果排序,能够经过'find -name built-in.o | xargs size | sort -n -r -k 4'。这里参数4是对第4列排序。
find -name built-in.o | xargs size | sort -n -r -k 42072769 88609 13276 2174654 212ebe ./drivers/built-in.o 1785972 56766 15740 1858478 1c5bae ./fs/built-in.o 1602716 0 0 1602716 18749c ./usr/built-in.o 763745 74364 169664 1007773 f609d ./kernel/built-in.o 547445 16417 2580 566442 8a4aa ./drivers/usb/built-in.o 423991 34416 412 458819 70043 ./fs/ext4/built-in.o 340686 35659 25524 401869 621cd ./mm/built-in.o 363316 916 260 364492 58fcc ./fs/nls/built-in.o
可是要注意,这里的built-in.o存在包含关系。
当真正须要优化的时候,仍是须要查看每个symbol占用的空间。
整体来说,text段占用ROM和RAM;data段占用ROM和RAM;bss段只占用RAM。
能够经过nm --size -r vmlinux | head -20,按size降序排列显示头20个symbol。
第一列是16进制的大小;第二列是符号类型;第三列是符号名称。
t/T表示text段,b/B表示bss段,d/D表示data段,r/R表示只读data段。
nm --size -r vmlinux | head -10 00020000 b __log_buf 00007290 r option_ids 00006250 T hidinput_connect 00004246 t ntfs_file_write_iter 00004200 D irq_desc 00004000 b page_address_maps 00003af2 T __blockdev_direct_IO 000039c0 R v4l2_dv_timings_presets 00002f52 T ntfs_mft_record_alloc 00002cd6 t ext4_fill_super
通过排序容易找出哪些变量或者函数异常。
内核scripts目录中的bloat-o-meter工具,提供了发现两次编译间符号尺寸差别的功能。
这个工具不但能够用于对比vmlinux,其余elf文件一样适用。其本质上是经过nm工具获取符号信息。
符号对比无非就是add、remove、change几种状况。
适用方法很简单./scripts/bloat-o-meter vmlinux.old vmlinux
./scripts/bloat-o-meter vmlinux.old vmlinux add/remove: 11/6598 grow/shrink: 687/15668 up/down: 18226/-1662970 (-1644744) function old new delta ext4_ext_handle_unwritten_extents - 1936 +1936 isolate_lru_pages.isra - 566 +566 get_implied_cluster_alloc.isra - 254 +254 ... s 4120 - -4120 hid_usage_table 4680 - -4680 ntfs_file_write_iter 16966 11788 -5178 __video_register_device 9638 4444 -5194 iter 8328 - -8328 hidinput_connect 25168 15600 -9568 Total: Before=5511746, After=3867002, chg -29.84%
结果也很容易理解,add/remove表示新增和删减的符号个数;grow/shrink表示尺寸增大和缩减符号个数;up/down表示新增尺寸和删减尺寸;最后是一个汇总大小。
最后一行Total显示先后两个尺寸对比,以及变化率。
参考文档:《USING THE BLOAT-O-METER LINUX EMBEDDED SYSTEMS》
buildroot中提供了统计rootfs尺寸的编译命令,make graph-size。详情见《buildroot编译结果尺寸分析》。
在Linux DTS中配置的RAM大小,和在内核中看到的MEMTotal即totalram_pages大小并不匹配。
不在totalram_pages统计范围的内存包括:内核代码、页表描述符等等在内核启动过程当中计算在Reverved中。
因此基本上认为:物理内存=totalram_pages+Reserved。
内核显示建立两个sysfs节点,显示总内存和reserved内存。在memblock_init_debugfs()中建立:
static int __init memblock_init_debugfs(void) { struct dentry *root = debugfs_create_dir("memblock", NULL); if (!root) return -ENXIO; debugfs_create_file("memory", S_IRUGO, root, &memblock.memory, &memblock_debug_fops); debugfs_create_file("reserved", S_IRUGO, root, &memblock.reserved, &memblock_debug_fops); return 0; } static int memblock_debug_show(struct seq_file *m, void *private) { struct memblock_type *type = m->private; struct memblock_region *reg; int i; for (i = 0; i < type->cnt; i++) { reg = &type->regions[i]; seq_printf(m, "%4d: ", i); if (sizeof(phys_addr_t) == 4) seq_printf(m, "0x%08lx..0x%08lx\n", (unsigned long)reg->base, (unsigned long)(reg->base + reg->size - 1)); else seq_printf(m, "0x%016llx..0x%016llx\n", (unsigned long long)reg->base, (unsigned long long)(reg->base + reg->size - 1)); } return 0; }
物理内存地址范围,显示memblock.memory内容。
cat /sys/kernel/debug/memblock/memory 0: 0x00000000..0x0fffffff
查看reserved内存,显示memblock.reserved内容。
cat /sys/kernel/debug/memblock/reserved 0: 0x00000000..0x008239ff 1: 0x03dad000..0x03de8fff 2: 0x03de9500..0x03de953b 3: 0x03de9540..0x03de95b7 4: 0x03de95c0..0x03de95c3 5: 0x03de95e0..0x03de95e3 6: 0x03de9600..0x03de9603 7: 0x03de9620..0x03de965b 8: 0x03de9660..0x03de969b 9: 0x03de96a0..0x03de96db 10: 0x03de96e0..0x03dfffaf 11: 0x03dfffc0..0x03dfffc3 12: 0x03dfffe0..0x0fffffff
若是想要详细了解上述memblock建立的过程,能够在dts的bootargs中添加"memblock=debug"打印更多信息。
关于reserved内存跟详细能够参考《Linux内存都去哪了:(1)分析memblock在启动过程当中对内存的影响》。
由/proc/meminfo可知,MemTotal的大小,那么物理内存-MemTotal=Reserved内存。
/proc/meminfo的核心函数是meminfo_proc_show()。
经过/prof/meminfo查看动态内存使用状况:
cat /proc/meminfo | grep "MemTotal:\|MemFree:\|Slab:\|VmallocUsed:\|PageTables:\|KernelStack:\|HardwareCorrupted:\|Bounce:\|Active:\|Inactive:\|Unevictable:\|HugePages_Total:\|CmaTotal:\|CmaFree:" | awk '{print $1 $2; if($1 == "MemTotal:") {} else if($1 == "CmaFree:") {total-=$2} else total+=$2}; END {print "Sum:" total}'
经过循环能够看出相关内存的动态变化。
while true; do cat /proc/meminfo; sleep 2; done
参考资料:《/proc/meminfo》、《/PROC/MEMINFO之谜》。
PSS相对于VSS、RSS更加准确,P是Portion的意思,若是库被多个应用依赖,则只取相应比例;独占库,则取100%。
grep Pss /proc/[0-9]*/smaps | awk '{total+=$2}; END {print total}'
procrank是Android下的工具,在buildroot中Target packages->System tools->procrank_linux。
procrank显示系统每一个进程的内存统计信息,包括Vss、Rss、Pss、Uss,还能够显示进程所占用的cached pages、non-cached pages等信息,以及RAM的统计信息。
/proc/<PID>/maps记录了进程地址空间的内存分布状况,详细分析见《/proc/xxx/maps简要记录》。
/proc/vmallocinfo
若是在buildroot编译时选择了'Build options‘->build packages with debug symbols’,为了节省空间须要在配置中打开‘strip target binaries’。
这样binaries和libraries在打包到target目录的时候就会被strip命令裁减掉调试信息。
在General setup->Compiler optimization level中选择‘Optimize for size’会下降生成uImage尺寸。
一个3.2M的内核,能够优化掉200K。
这里是利用了编译优化选项-Ox。-Os是优化尺寸,内核默认选项是-O2。
ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE KBUILD_CFLAGS += -Os $(call cc-disable-warning,maybe-uninitialized,) else ifdef CONFIG_PROFILE_ALL_BRANCHES KBUILD_CFLAGS += -O2 $(call cc-disable-warning,maybe-uninitialized,) else KBUILD_CFLAGS += -O2 endif endif
在buildroot的配置‘Build options->gcc optimization level’选择不一样的优化等级,默认是‘optimization level 2’。
若是须要优化尺寸,能够配置‘optimization for size’。rootfs.cpio的大小从4.7M下降到3.5M。
这个配置的在package/Makefile.in中,也一样是对应不一样的-Ox优化。
ifeq ($(BR2_OPTIMIZE_0),y) TARGET_OPTIMIZATION = -O0 endif ... ifeq ($(BR2_OPTIMIZE_S),y) TARGET_OPTIMIZATION = -Os endif
经过readelf -d能够肯定elf文件依赖于哪些库文件,进而能够得出库依赖关系图。
若是rootfs.cpio中的库文件,不在被依赖列表中。那么他就是孤立的,也就是说能够被删除。
这些操做能够在buildroot中经过BR2_ROOTFS_POST_BUILD_SCRIPT来指定脚本,在post_build.sh中删除不须要的文件。
多余配置文件删除(语言、时区)能够根据须要只保留一小部分。
文件系统:内核中每每默认使用了多种文件系统,可是在实际应用中有些文件系统根本不会用到。这些文件系统能够删除。文件系统多语言支持File systems->Native language support。
硬件驱动:Linux为了保持兼容,打开了不少驱动。同一个驱动还包括不少子系统,好比USB。这些驱动能够根据须要裁剪。
网络:若是一个嵌入式设备没有使用到网络,那么网络协议以及网络设备驱动不少内容能够直接移除。
输入输出设备:一些嵌入式设备,可能没有按键、鼠标。显示设备,这些功能均可以删除。
调试和优化信息:在量产发布的版本中,调试辅助功能能够关闭。
busybox提供了简单高效的替代工具,在login的时候须要libnss*.so。
经过打开CONFIG_USE_BB_PWD_GRP,也一样可使用。libnss*.so的相关库文件就能够删除。
参考文档:《删除libnss*库后,busybox login遭遇login incorrect》
经过将dtb文件内置到uImage中,也能够达到下降尺寸的关系。
在start_kernel()以前,经过early_init_dt_scan()函数解析dtb文件。
early_init_dt_scan()的参数是dtb的入口地址,经过vmlinux.lds中指定__dtb_start和__dtb_end。
__dtb_start = .; *(.dtb.init.rodata) __dtb_end = .;
vmlinux.lds.h中定义以下:
/* init and exit section handling */ #define INIT_DATA \ KEEP(*(SORT(___kentry+*))) \ ... KERNEL_DTB() \ ... EARLYCON_TABLE() #define KERNEL_DTB() \ STRUCT_ALIGN(); \ VMLINUX_SYMBOL(__dtb_start) = .; \ *(.dtb.init.rodata) \ VMLINUX_SYMBOL(__dtb_end) = .;
在编译的时候,将dtb文件编入vmlinux中。
builtindtb-y := $(patsubst "%",%,$(CONFIG_CSKY_BUILTIN_DTB_NAME)) dtb-y += $(builtindtb-y).dtb obj-y += $(builtindtb-y).dtb.o .SECONDARY: $(obj)/$(builtindtb-y).dtb.S
生成的.S文件
#include <asm-generic/vmlinux.lds.h> .section .dtb.init.rodata,"a" .balign STRUCT_ALIGNMENT .global __dtb_xxx_begin __dtb_xxx_begin: .incbin "arch/csky/boot/dts/xxx.dtb" __dtb_xxx_end: .global __dtb_xxx_end .balign STRUCT_ALIGNMENT
若是将dtb内置到uImage中,那么还须要配合修改。
经过修改CONFIG_EXTRA_ENV_SETTINGS的变量,
/* Initial environment variables */ #define CONFIG_EXTRA_ENV_SETTINGS \ "kernel_img=uImage\0" \ ..."sd_loadimg=fatload mmc ${sddev}:${sdpart} ${linux_load_addr_phys} ${kernel_img}\0" \ "sd_loadfdt=fatload mmc ${sddev}:${sdpart} ${dtb_load_addr_phys} ${fdt_file}\0" \ "fdt_file_exists=fatls mmc ${sddev}:${sdpart} ${fdt_file}\0" \ "sdboot=echo Booting from SD ...; " \ "if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \ "if run fdt_file_exists; then " \ "if run sd_loadfdt; then " \ "bootm ${linux_load_addr_virt} - ${dtb_load_addr_virt}; \n" \ "fi;" \ "else " \ "echo Use builtin DTB; " \ "bootm ${linux_load_addr_virt}; \n" \ "fi; " \ "else " \ "echo wait for boot; " \ "fi;\0" \ #define CONFIG_BOOTCOMMAND \ "mmc rescan; if mmc dev ${sddev}; then " \ "if run sd_loadimg; then " \ "run sdboot; " \ "fi; " \ "fi; " \ "else echo No available boot device ...; fi"
参考文档:《Kernel Size Tuning Guide》