liwen01 2023.10.01
在嵌入式系統中,記憶體是比較緊缺的資源,特別是在消費類產品中, 為了節省成本,一般都會將硬體資源應用到極致。在開發過程中,就經常會遇到,執行記憶體(RAM
)就還差一點,但就是不夠用的情況,比如:
OTA
只能將韌體放到記憶體上時如果你原來裝置記憶體已經使用到90%
甚至更高,要實現上面功能,大概率會影響系統的整體效能,甚至會出現系統異常。那該怎麼辦?
在不考慮硬體增加RAM
大小的情況下,軟體上還有沒有其它的方式,可以擠出一點點空間呢?答案是可以的,但是需要綜合評估帶來的問題和風險。
這裡以君正T31ZC
為範例,來介紹它的記憶體使用情況,以及如何擠出更加多的記憶體空間
T31ZC
是一款基於MIPS
架構的主處理器,上面整合了512Mbit
的DDR2
記憶體,使用的是linux
作業系統。
512Mbit
的實體記憶體,也就是64MByte
,在實際使用的時候,它被劃分為了兩塊,rmem
和 mem
rmem
用於多媒體,比如音視訊編碼、裁剪、OSD等功能mem
是用於linux
系統記憶體它通過tag
部分的cmdline
來設定
[root@Zeratul:~]# cat /proc/cmdline
console=ttyS0,115200n8 mem=40M@0x0 rmem=24M@0x2800000 root=/dev/ram0 rw rdinit=/linuxrc mtdparts=jz_sfc:256K(boot),352K(tag),5M(kernel),6M(rootfs),2560K(recovery),1440K(system),512K(config),16M@0(all) lpj=6955008 quiet senv;[HW];init_vw=1920;init_vh=1080;nrvbs=2;mode=0;eenv; lzo_size=5907415 rd_start=0x80600000 rd_size=0xd35c00
[root@Zeratul:~]#
這裡我們看到40M
分配給了linux
系統,24M
分配給了多媒體。
通過 dmesg
命令可以看到更多記憶體的使用情況
rd_start=0x80600000 rd_size=0xd35c00
這個是用來存放根檔案系統的起始位置,以及這個空間的大小
Memory: 20680k/40960k
40960K
是linux
系統可使用的總大小,也就是上面設定的40MB,20680k
是系統實際可以使用的記憶體
剩下的 40960K - 20680k = 20280K
記憶體到哪裡去了?
3868k kernel code, 20280k reserved, 1052k data, 196k init, 0k highmem
一部分是給核心使用,包括核心程式碼段、資料段、以及 init
段
剩下的20280k - 3868K -1052K - 196K = 15164K
這剩下的1516K
是預留給ramfs
使用的
linux
系統啟動後,不執行其它的應用程式,我們檢視的記憶體使用情況如下:
cat /proc/meminfo
可用的實際實體記憶體大小還剩餘12872 kB
分配給linux
系統使用的記憶體有40M
,但是應用程式都還沒開始執行,記憶體就只剩下12872K
,記憶體都到哪裡去了!
總結歸納如下:
64M
,24M
分配給了多媒體,40M
分配給了linux
系統總大小 | 多媒體記憶體 | linux系統 |
---|---|---|
64M | 24M | 40M |
linux
系統記憶體中,可使用的記憶體為20680k
,預留的記憶體為20280k
linux系統 | available | reserved |
---|---|---|
40960k | 20680k | 20280k |
linux
系統中可使用的記憶體,核心模組載入,緩衝快取等系統服務佔用7768K
,剩餘的為應用可使用的記憶體available | MemFree | modules/others |
---|---|---|
20680k | 12912k | 7768K |
linux
系統中預留的記憶體,主要是預留給kernel
和根檔案系統,其中kernel
中主要有程式碼段,資料段,init
段。剩下的主要是根檔案系統佔用的空間。reserved | kernel code | kernel data | kernel init | ramfs | others |
---|---|---|---|---|---|
20280k | 3868K | 1052K | 196K | 13527K | 1637K |
從上面的分析來看,可以優化的方向有:
kernel
優化rootfs
優化這個可優化的空間有限,這裡不做詳細討論,可以通過命令檢視實際模組載入情況:cat /proc/modules
將核心的偵錯資訊去除掉,可以在menuconfig
中取消 Load all symbols for debugging/ksymoops
選項,這裡可以省出少量的空間
從上面的分析,我們看到為ramfs
預留的空間為13527K
,這是個非常大的空間。從啟動cmdline
中我們看到Flash
的分割區資訊如下:
256K(boot),352K(tag),5M(kernel),6M(rootfs),2560K(recovery),1440K(system),512K(config),16M@0(all)
1. flash為rootfs預留的空間為6M,而記憶體為rootfs預留的是13527K,為何相差如此之大?
主要的原因是我們rootfs
在燒錄到Flash
的時候是壓縮過的,在載入rootfs
的時候,首先是將rootfs
從Flash
中讀取出來,再解壓到記憶體指定的地址去,然後再將解壓後的rootfs
掛載成ramfs
檔案系統載入起來。
rootfs
的打包命令如下:
cd ./_rootfs_camera
find . | cpio -H newc -o > ../rootfs_camera.cpio
cd ..
lzop -9 -f rootfs_camera.cpio -o rootfs_camera.cpio.lzo
./mark_rootfs_pc rootfs_camera.cpio.lzo
_rootfs_camera
目錄下的所有檔案歸檔到上一目錄的rootfs_camera.cpio文
件中lzop
命令使用最高等級(9)的壓縮方式把rootfs_camera.cpio
壓縮到rootfs_camera.cpio.lzo
檔案,-f
表示強制執行,如果rootfs_camera.cpio.lzo
已經存在則覆蓋它mark_rootfs_pc
是用來更新rootfs_camera.cpio.lzo
的實際檔案大小,實際是將壓縮後的檔案大小寫入到rootfs_camera.cpio.lzo
的最開始位置(4位元組)。2. 為什麼要使用ramfs檔案系統?
ramfs
適合用於臨時儲存、臨時檔案、核心模組載入等臨時性應用,其中資料不需要長期儲存的場景,它是linux
核心中的虛擬檔案系統,不需要指定特定的掛載選項。
優點有:
缺點有:
君正T31ZC
是屬於低功耗SOC
,實時性,快速啟動要求比較高。一般應用場景是有另外的MCU
來控制它上下電,有事件觸發的時候,SOC
上電處理事件,事件處理完成後,SOC
下電,只留MCU
工作,以達到省功耗的目的,所以使用ramfs
也是合理的選擇。
官方手冊上的建議是這樣的:
rootfs中只存放對快起有要求的必要程式和庫檔案,如果對快起沒有要求的程式或庫檔案建議放到 system 分割區,以達到快速啟動和節省記憶體的目的。
根據上面的分析,如果系統使用的是ramfs
,優化空間最大的就是根檔案系統,減小根檔案系統的大小可以直接節省記憶體。
上面官方給的建議中需要面對另外一個問題,就是需要在應用程式啟動之後,再去system
分割區使用dl_open
載入所需要的動態庫。如果你程式主要使用的是靜態庫,那這種方式就達不到想要的效果。
檢視rootfs
檔案系統裡面的內容,佔用空間大的,一個是stone
目錄,另外一個是lib
目錄。
./stone
目錄下放置的是需要執行的main
程式./lib
目錄下放置的主要是一些動態庫,ko
驅動檔案,以及一個wifi
使用的 bin
檔案-rwxrwxr-x 1 biao biao 6.2M Oct 15 18:14 main
-rwxrwxrwx 1 biao biao 1003K Aug 21 10:04 cywdhd.ko
-rwxrwxrwx 1 biao biao 404K Aug 21 10:04 fw_bcm43438a1.bin
main
執行程式在啟動的時候就執行
./etc/init.d/rcS:/stone/main &
wifi
驅動也是在啟動的時候被載入
insmod /lib/modules/cywdhd.ko firmware_path=/lib/firmware/fw_bcm43438a1.bin nvram_path=/lib/firmware/nvram.txt iface_name=wlan0
是否可以這樣:
在啟動的時候,main 執行檔案,cywdhd.ko驅動,驅動韌體fw_bcm43438a1.bin被載入完之後,就把它們刪除,以達到釋放記憶體的目的?
因為這些檔案都是放置在記憶體上,刪除它們並不會影響Flash
中的檔案,下次上電可以重新從Flash
中讀取出來,刪除它們也確實是可以釋放一部分記憶體。
以我上面的例子,刪除 main
檔案就可以釋放6.2M
的空間,確實也是可以達到釋放記憶體的目的。
但是,這樣操作是否有風險?
我們知道,程式執行是被段頁式載入到記憶體的,那要怎麼知道在刪除main
程式執行檔案的時候,main
程式裡面的內容已經被全部載入到記憶體中去了呢?
同樣,刪除ko
檔案和bin
韌體資料檔案的時候同樣會遇到相同的疑問。
通過 cat /proc/modules
命令可以檢視模組的載入情況
[root@Zeratul:bin]# cat /proc/modules | grep cywdhd
cywdhd 671712 0 - Live 0xc0215000
jzmmc 17113 1 cywdhd, Live 0xc010f000
mmc_core 90139 3 mmc_block,cywdhd,jzmmc, Live 0xc00e5000
我們看到 cywdhd.ko
被載入到記憶體的0xc0215000
這個地址,Live
表示已經載入到核心並且在在執行。671712
表示它的大小。
為什麼我們前面看cywdhd.ko 大小是1003K ,載入到核心中去卻只剩下67171 ?
我們使用 file
命令檢視cywdhd.ko
檔案資訊,發現它是ELF
檔案格式,並且是not stripped
,也就是它裡面還包含一些偵錯資訊和偵錯符號表等內容
biao@ubuntu: file cywdhd.ko
cywdhd.ko: ELF 32-bit LSB relocatable, MIPS, MIPS32 version 1 (SYSV), BuildID[sha1]=a00e1928dd77ac04bb813b22cd485156f3392741, not stripped
strip
處理之後,檔案大小變成了657788
biao@ubuntu: mips-linux-uclibc-gnu-strip cywdhd.ko
biao@ubuntu:ll
-rwxrwxrwx 1 biao biao 657788 Oct 15 19:42 cywdhd.ko
綜合上面資訊,我們可以大致判斷刪除cywdhd.ko檔案是沒有風險的。
還有一種確認方式是:通過檢視 /proc/kallsyms
檔案資訊
/proc/kallsyms
包含了核心符號表資訊,其中包括已載入模組的地址範圍。如果模組的地址範圍包括整個模組,那麼它通常已經完全載入到記憶體中。
fw_bcm43438a1.bin
是作為cywdhd.ko
驅動的一個韌體被載入到核心上的,至於它是什麼時候使用,或者說是否一次全部載入到記憶體上了,應該取決於cywdhd.ko
驅動
因此我個人認為刪除fw_bcm43438a1.bin
是會有比較大的風險的
在Linux
系統中, 執行檔案,動態庫,驅動檔案都是屬於ELF
檔案格式。ELF
檔案裡面分很多段,常見的有程式碼段,資料段,BSS
段。
可以使用命令 readelf -S your_program
來具體檢視
我們看到程式碼段的大小是0x46a6b0
,在很多作業系統中,為了節省記憶體空間,都是使用動態載入的方式,也就是段頁式載入方式。
根據區域性性原理,一般程式在執行的時候,都是將當前執行位置附近的資料載入到記憶體上執行。
但是對於ramfs
檔案系統,因為它已經是被全部載入到記憶體上了,那在它上面的main
檔案,在執行的時候,它是一次性載入(全域性載入)還是按需載入(段頁式載入)呢?
如果它是在執行的時候一次性載入到記憶體上,那麼,在它執行之後,把main
檔案刪除是沒有風險的,否則會引入系統風險。
對於執行檔案在ramfs
檔案系統上的載入,暫時沒有找到更詳細的介紹資料,熟悉這一領域的同學可以給點建議。
可以使用命令手動釋放記憶體:
echo 1 > /proc/sys/vm/drop_caches
關於drop_caches
的介紹,可以通過 man proc
的介紹來了解,這裡不做過多說明
綜合上面介紹的內容,在嵌入式Linux
系統記憶體不足的情況下,可以使用下面幾種方式進行優化
kernel
的偵錯資訊符號表去除,減少核心映象檔案大小ELF
檔案(包括執行檔案、動態庫和ko
驅動檔案)strip
處理,去除多餘的資訊dl_open
來載入具體使用哪種方式,可以根據實際使用情況進行評估和選擇。
以上如有分析錯誤的地方,歡迎批評指正。