zoukankan      html  css  js  c++  java
  • Linux方案级ROM/RAM优化记录

    关键词:readelf、bloat-o-meter、graph-size、totalram_pages、reserved、meminfo、PSS、procrank、maps等等。

    根据项目的需求,进行ROM/RAM的低成本裁剪。

    在进行优化之前,(1)首要任务是对待优化的方案进行量化,从ROM来看有uboot、kernel、rootfs;从RAM来看,有静态RAM和运行时产生的动态RAM。

    (2)然后就是根据量化结果,寻找浪费点进行优化;不需要的直接删除,过量配置的适当降低。

    (3)再然后就是要验证裁剪结果,需要一个稳定的最大化的场景。

    当然这还没有结束,随着项目的推进,会是一个(1)->(2)->(3)->(1)往复的过程。

    当然过程中需要对关键步骤进行敏捷处理,量化工作脚本化,固定格式输出可读性强报告;验证自动化,动态获取运行中数据等等。

    1.  量化存储空间

    通过编写脚本分析ROM使用、通过build-in.o分析模块、bloat-o-meter等工具可以从不同角度分析存储容量。

    ROM的量化按照从大到小的步骤,首先分析整个方案的ROM,包括uboot、kernel、rootfs,总大小就是整个方案大小。

    uboot和kernel可以通过readelf或者size查看细节;rootfs需要细分每个文件大小,针对bin文件根据需要删除,lib文件可以通过依赖关系判断是否被依赖。

    下面按照从文件,到段,再到符号进行分析。

    1.1 jupyter-notebook获取完成uboot/kernel/rootfs大小

    通过遍历整个文件系统,将每个文件路径和大小列出。

    通过readelf来分析应用程序以及各库之间的依赖关系,进而分析出孤立无用的库,以及不被使用到的可执行文件。

    通过images_analyze.ipynb分析rootfs.cpio、uImage、uboot等。

    输出结果包括kernel、u-boot、rootfs尺寸详细信息,以及相关库的依赖关系lib_depend.txt

     

    1.2 readelf分析text/data/bss段

    对elf文件可以通过size,快速获取其text/data/bss段大小,以及总大小。

    size vmlinux 
       text       data        bss        dec        hex    filename
    4680576    3596750     247304    8524630     821356    vmlinux

    如果需要更多细节,需要通过readelf -S来获取不同section信息。

    1.3 kernel子系统分析

    观察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存在包含关系。

    1.4 symbol分析

    当真正需要优化的时候,还是需要查看每一个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

    经过排序容易找出哪些变量或者函数异常。

    1.5 bloat-o-meter

    内核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

    1.6 buildroot统计

    buildroot中提供了统计rootfs尺寸的编译命令,make graph-size。详情见《buildroot编译结果尺寸分析》。

    2. RAM量化

    在Linux DTS中配置的RAM大小,和在内核中看到的MEMTotal即totalram_pages大小并不匹配。

    不在totalram_pages统计范围的内存包括:内核代码、页表描述符等等在内核启动过程中计算在Reverved中。

    所以基本上认为:物理内存=totalram_pages+Reserved。

    2.1 内核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
    ",
                       (unsigned long)reg->base,
                       (unsigned long)(reg->base + reg->size - 1));
            else
                seq_printf(m, "0x%016llx..0x%016llx
    ",
                       (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在启动过程中对内存的影响》。

    2.2 meminfo

    由/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之谜》。

    2.3 应用内存分析

    2.3.1 查看进程smaps的PSS

    PSS相对于VSS、RSS更加准确,P是Portion的意思,如果库被多个应用依赖,则只取相应比例;独占库,则取100%。

    grep Pss /proc/[0-9]*/smaps | awk '{total+=$2}; END {print total}'

    2.3.2 procrank分析实际占用内存

    procrank是Android下的工具,在buildroot中Target packages->System tools->procrank_linux。

    procrank显示系统每个进程的内存统计信息,包括Vss、Rss、Pss、Uss,还可以显示进程所占用的cached pages、non-cached pages等信息,以及RAM的统计信息。

    2.3.3 进程maps分析

    /proc/<PID>/maps记录了进程地址空间的内存分布情况,详细分析见《/proc/xxx/maps简要记录》。

    2.4 动态申请内存

    /proc/vmallocinfo

    3. 优化

    3.1 buildroot编译后strip

    如果在buildroot编译时选择了'Build options‘->build packages with debug symbols’,为了节省空间需要在配置中打开‘strip target binaries’。

    这样binaries和libraries在打包到target目录的时候就会被strip命令裁减掉调试信息。

    3.2 kernel配置‘Optimize for size’

    在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

    3.3 buildroot配置‘optimize for size’

    在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

    3.4 孤立库删除

    通过readelf -d可以确定elf文件依赖于哪些库文件,进而可以得出库依赖关系图。

    如果rootfs.cpio中的库文件,不在被依赖列表中。那么他就是孤立的,也就是说可以被删除。

    这些操作可以在buildroot中通过BR2_ROOTFS_POST_BUILD_SCRIPT来指定脚本,在post_build.sh中删除不需要的文件。

    多余配置文件删除(语言、时区)可以根据需要只保留一小部分。

    3.5 冗余功能删除

    文件系统:内核中往往默认使用了多种文件系统,但是在实际应用中有些文件系统根本不会用到。这些文件系统可以删除。文件系统多语言支持File systems->Native language support。

    硬件驱动:Linux为了保持兼容,打开了很多驱动。同一个驱动还包括很多子系统,比如USB。这些驱动可以根据需要裁剪。

    网络:如果一个嵌入式设备没有使用到网络,那么网络协议以及网络设备驱动很多内容可以直接移除。

    输入输出设备:一些嵌入式设备,可能没有按键、鼠标。显示设备,这些功能都可以删除。

    调试和优化信息:在量产发布的版本中,调试辅助功能可以关闭。

    3.6 使用busybox的替代功能

    busybox提供了简单高效的替代工具,在login的时候需要libnss*.so。

    通过打开CONFIG_USE_BB_PWD_GRP,也同样可以使用。libnss*.so的相关库文件就可以删除。

    参考文档:《删除libnss*库后,busybox login遭遇login incorrect

    3.7 将dtb文件内置

    通过将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中,那么还需要配合修改。

    3.8 修改uboot加载dtb和builtin dtb两种方式

    通过修改CONFIG_EXTRA_ENV_SETTINGS的变量,

    /* Initial environment variables */
    #define CONFIG_EXTRA_ENV_SETTINGS   
        "kernel_img=uImage"    
    ..."sd_loadimg=fatload mmc ${sddev}:${sdpart} ${linux_load_addr_phys} ${kernel_img}" 
        "sd_loadfdt=fatload mmc ${sddev}:${sdpart} ${dtb_load_addr_phys} ${fdt_file}" 
        "fdt_file_exists=fatls mmc ${sddev}:${sdpart} ${fdt_file}" 
        "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}; 
    " 
                    "fi;" 
                "else " 
                    "echo Use builtin DTB; " 
                    "bootm ${linux_load_addr_virt}; 
    " 
                "fi; " 
            "else " 
                "echo wait for boot; " 
            "fi;" 
    
    #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》 

     
  • 相关阅读:
    表单
    框架
    表格
    列表
    标签
    封装类(包装类)
    常见类 --Object
    日志
    异常
    选择结构
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/10442099.html
Copyright © 2011-2022 走看看