Linux方案級ROM/RAM優化記錄

關鍵詞: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

1.  量化存儲空間

經過編寫腳本分析ROM使用、經過build-in.o分析模塊、bloat-o-meter等工具能夠從不一樣角度分析存儲容量。函數

ROM的量化按照從大到小的步驟,首先分析整個方案的ROM,包括uboot、kernel、rootfs,總大小就是整個方案大小。工具

uboot和kernel能夠經過readelf或者size查看細節;rootfs須要細分每一個文件大小,針對bin文件根據須要刪除,lib文件能夠經過依賴關係判斷是否被依賴。post

下面按照從文件,到段,再到符號進行分析。

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\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在啓動過程當中對內存的影響》。

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\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》 

相關文章
相關標籤/搜索