在嵌入式系統(tǒng)中,內(nèi)存是比較緊缺的資源,特別是在消費類產(chǎn)品中, 為了節(jié)省成本,一般都會將硬件資源應用到極致。在開發(fā)過程中,就經(jīng)常會遇到,運行內(nèi)存(RAM)就還差一點,但就是不夠用的情況,比如:
需要在原系統(tǒng)上添加一個小算法
OTA只能將固件放到內(nèi)存上時
需要動態(tài)分配比較大的空間
需要放置一些比較大的臨時文件
如果你原來設(shè)備內(nèi)存已經(jīng)使用到90%甚至更高,要實現(xiàn)上面功能,大概率會影響系統(tǒng)的整體性能,甚至會出現(xiàn)系統(tǒng)異常。那該怎么辦?
在不考慮硬件增加RAM大小的情況下,軟件上還有沒有其它的方式,可以擠出一點點空間呢?答案是可以的,但是需要綜合評估帶來的問題和風險。
下面以君正T31ZC為實例,來介紹一下它的內(nèi)存使用情況,以及如何擠出更加多的內(nèi)存空間。
一、內(nèi)存使用情況分析
T31ZC?是一款基于MIPS架構(gòu)的主處理器,上面集成了512Mbit的DDR2內(nèi)存,使用的是linux操作系統(tǒng)。
1、物理內(nèi)存分布
512Mbit的物理內(nèi)存,也就是64MByte,在實際使用的時候,它被劃分為了兩塊,rmem?和?mem
rmem?用于多媒體,比如音視頻編碼、裁剪、OSD等功能
mem?是用于linux系統(tǒng)內(nèi)存
它通過tag部分的cmdline來設(shè)置:
[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系統(tǒng),24M分配給了多媒體。
通過?dmesg?命令可以看到更多內(nèi)存的使用情況:
rd_start=0x80600000 rd_size=0xd35c00
這個是用來存放根文件系統(tǒng)的起始位置,以及這個空間的大小。
Memory: 20680k/40960k
40960K?是linux系統(tǒng)可使用的總大小,也就是上面設(shè)置的40MB,20680k?是系統(tǒng)實際可以使用的內(nèi)存。
剩下的?40960K - 20680k = 20280K?內(nèi)存到哪里去了?
3868k kernel code, 20280k reserved, 1052k data, 196k init, 0k highmem
一部分是給內(nèi)核使用,包括內(nèi)核代碼段、數(shù)據(jù)段、以及?init?段
剩下的20280k - 3868K -1052K - 196K = 15164K
這剩下的1516K?是預留給ramfs?使用的。
2、mem使用情況
linux?系統(tǒng)啟動后,不運行其它的應用程序,我們查看的內(nèi)存使用情況如下:
cat /proc/meminfo
可用的實際物理內(nèi)存大小還剩余12872 kB
分配給linux系統(tǒng)使用的內(nèi)存有40M,但是應用程序都還沒開始運行,內(nèi)存就只剩下12872K,內(nèi)存都到哪里去了!
總結(jié)歸納如下:
總物理內(nèi)存64M,24M分配給了多媒體,40M分配給了linux系統(tǒng)
?
總大小 | 多媒體內(nèi)存 | linux系統(tǒng) |
---|---|---|
64M | 24M | 40M |
?
linux系統(tǒng)內(nèi)存中,可使用的內(nèi)存為20680k,預留的內(nèi)存為20280k
?
linux系統(tǒng) | available | reserved |
---|---|---|
40960k | 20680k | 20280k |
?
linux系統(tǒng)中可使用的內(nèi)存,內(nèi)核模塊加載,緩沖緩存等系統(tǒng)服務占用7768K,剩余的為應用可使用的內(nèi)存
available | MemFree | modules/others |
---|---|---|
20680k | 12912k | 7768K |
linux系統(tǒng)中預留的內(nèi)存,主要是預留給kernel和根文件系統(tǒng),其中kernel中主要有代碼段,數(shù)據(jù)段,init段。剩下的主要是根文件系統(tǒng)占用的空間。
reserved | kernel code | kernel data | kernel init | ramfs | others |
---|---|---|---|---|---|
20280k | 3868K | 1052K | 196K | 13527K | 1637K |
?
二、優(yōu)化方向
從上面的分析來看,可以優(yōu)化的方向有:
減少模塊加載,系統(tǒng)服務
kernel?優(yōu)化
rootfs?優(yōu)化
1、減少模塊加載,系統(tǒng)服務
這個可優(yōu)化的空間有限,這里不做詳細討論,可以通過命令查看實際模塊加載情況:cat /proc/modules
2、kernel 優(yōu)化
將內(nèi)核的調(diào)試信息去除掉,可以在menuconfig?中取消?Load all symbols for debugging/ksymoops?選項,這里可以省出少量的空間。
3、rootfs 優(yōu)化
從上面的分析,我們看到為ramfs預留的空間為13527K,這是個非常大的空間。從啟動cmdline中我們看到Flash的分區(qū)信息如下:
256K(boot),352K(tag),5M(kernel),6M(rootfs),2560K(recovery),1440K(system),512K(config),16M@0(all)
(1)flash為rootfs預留的空間為6M,而內(nèi)存為rootfs預留的是13527K,為何相差如此之大?
主要的原因是我們rootfs在燒錄到Flash的時候是壓縮過的,在加載rootfs的時候,首先是將rootfs從Flash中讀取出來,再解壓到內(nèi)存指定的地址去,然后再將解壓后的rootfs掛載成ramfs文件系統(tǒng)加載起來。
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?表示強制執(zhí)行,如果rootfs_camera.cpio.lzo已經(jīng)存在則覆蓋它
mark_rootfs_pc?是用來更新rootfs_camera.cpio.lzo的實際文件大小,實際是將壓縮后的文件大小寫入到rootfs_camera.cpio.lzo的最開始位置(4字節(jié))。
(2)為什么要使用ramfs文件系統(tǒng)?
ramfs?適合用于臨時存儲、臨時文件、內(nèi)核模塊載入等臨時性應用,其中數(shù)據(jù)不需要長期保存的場景,它是linux內(nèi)核中的虛擬文件系統(tǒng),不需要指定特定的掛載選項。
優(yōu)點有:
快速讀寫操作
零延遲
輕量級
易于創(chuàng)建和銷毀
缺點有:
不具備持久性
內(nèi)存限制
不適合大型文件
需要足夠的內(nèi)存
君正T31ZC是屬于低功耗SOC,實時性,快速啟動要求比較高。一般應用場景是有另外的MCU來控制它上下電,有事件觸發(fā)的時候,SOC上電處理事件,事件處理完成后,SOC下電,只留MCU工作,以達到省功耗的目的,所以使用ramfs?也是合理的選擇。
官方手冊上的建議是這樣的:
rootfs中只存放對快起有要求的必要程序和庫文件,如果對快起沒有要求的程序或庫文件建議放到 system 分區(qū),以達到快速啟動和節(jié)省內(nèi)存的目的。
三、另類解決方案
1、問題分析
根據(jù)上面的分析,如果系統(tǒng)使用的是ramfs,優(yōu)化空間最大的就是根文件系統(tǒng),減小根文件系統(tǒng)的大小可以直接節(jié)省內(nèi)存。
上面官方給的建議中需要面對另外一個問題,就是需要在應用程序啟動之后,再去system分區(qū)使用dl_open加載所需要的動態(tài)庫。如果你程序主要使用的是靜態(tài)庫,那這種方式就達不到想要的效果。
查看rootfs?文件系統(tǒng)里面的內(nèi)容,占用空間大的,一個是stone目錄,另外一個是lib目錄。
./stone?目錄下放置的是需要運行的main程序
./lib?目錄下放置的主要是一些動態(tài)庫,ko驅(qū)動文件,以及一個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
2、解決方案
main?執(zhí)行程序在啟動的時候就執(zhí)行
./etc/init.d/rcS:/stone/main &
wifi?驅(qū)動也是在啟動的時候被加載
insmod /lib/modules/cywdhd.ko firmware_path=/lib/firmware/fw_bcm43438a1.bin nvram_path=/lib/firmware/nvram.txt iface_name=wlan0
是否可以這樣:
在啟動的時候,main 執(zhí)行文件,cywdhd.ko驅(qū)動,驅(qū)動固件fw_bcm43438a1.bin被加載完之后,就把它們刪除,以達到釋放內(nèi)存的目的?
因為這些文件都是放置在內(nèi)存上,刪除它們并不會影響Flash中的文件,下次上電可以重新從Flash中讀取出來,刪除它們也確實是可以釋放一部分內(nèi)存。
以我上面的例子,刪除?main?文件就可以釋放6.2M的空間,確實也是可以達到釋放內(nèi)存的目的。
但是,這樣操作是否有風險?
四、刪除ELF文件是否有影響
我們知道,程序運行是被段頁式加載到內(nèi)存的,那要怎么知道在刪除main程序執(zhí)行文件的時候,main程序里面的內(nèi)容已經(jīng)被全部加載到內(nèi)存中去了呢?
同樣,刪除ko文件和bin固件數(shù)據(jù)文件的時候同樣會遇到相同的疑問。
1、刪除 cywdhd.ko 驅(qū)動
通過?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被加載到內(nèi)存的0xc0215000?這個地址,Live?表示已經(jīng)加載到內(nèi)核并且在在運行。671712?表示它的大小。
為什么我們前面看cywdhd.ko 大小是1003K ,加載到內(nèi)核中去卻只剩下67171 ?
我們使用?file?命令查看cywdhd.ko文件信息,發(fā)現(xiàn)它是ELF文件格式,并且是not stripped,也就是它里面還包含一些調(diào)試信息和調(diào)試符號表等內(nèi)容
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?包含了內(nèi)核符號表信息,其中包括已加載模塊的地址范圍。如果模塊的地址范圍包括整個模塊,那么它通常已經(jīng)完全加載到內(nèi)存中。
2、刪除fw_bcm43438a1.bin固件
fw_bcm43438a1.bin?是作為cywdhd.ko?驅(qū)動的一個固件被加載到內(nèi)核上的,至于它是什么時候使用,或者說是否一次全部加載到內(nèi)存上了,應該取決于cywdhd.ko驅(qū)動
因此我個人認為刪除fw_bcm43438a1.bin是會有比較大的風險的
3、刪除main程序
在Linux系統(tǒng)中, 執(zhí)行文件,動態(tài)庫,驅(qū)動文件都是屬于ELF文件格式。ELF文件里面分很多段,常見的有代碼段,數(shù)據(jù)段,BSS?段。
可以使用命令?readelf -S your_program?來具體查看:
我們看到代碼段的大小是0x46a6b0,在很多操作系統(tǒng)中,為了節(jié)省內(nèi)存空間,都是使用動態(tài)加載的方式,也就是段頁式加載方式。
根據(jù)局部性原理,一般程序在執(zhí)行的時候,都是將當前執(zhí)行位置附近的數(shù)據(jù)加載到內(nèi)存上運行。
但是對于ramfs文件系統(tǒng),因為它已經(jīng)是被全部加載到內(nèi)存上了,那在它上面的main文件,在執(zhí)行的時候,它是一次性加載(全局加載)還是按需加載(段頁式加載)呢?
如果它是在運行的時候一次性加載到內(nèi)存上,那么,在它運行之后,把main文件刪除是沒有風險的,否則會引入系統(tǒng)風險。
對于執(zhí)行文件在ramfs文件系統(tǒng)上的加載,暫時沒有找到更詳細的介紹資料,熟悉這一領(lǐng)域的同學可以給點建議。
五、手動釋放內(nèi)存
可以使用命令手動釋放內(nèi)存:
echo 1 > /proc/sys/vm/drop_caches
關(guān)于drop_caches?的介紹,可以通過?man proc?的介紹來了解,這里不做過多說明
六、總結(jié)
綜合上面介紹的內(nèi)容,在嵌入式Linux系統(tǒng)內(nèi)存不足的情況下,可以使用下面幾種方式進行優(yōu)化:
將kernel的調(diào)試信息符號表去除,減少內(nèi)核鏡像文件大小
將文件系統(tǒng)上的ELF文件(包括執(zhí)行文件、動態(tài)庫和ko驅(qū)動文件)strip處理,去除多余的信息
將動態(tài)庫放置到其它分區(qū)上,程序運行后再通過dl_open來加載
執(zhí)行文件或是驅(qū)動文件,在使用過后把它們刪除(可能存在風險)
手動釋放內(nèi)存
具體使用哪種方式,可以根據(jù)實際使用情況進行評估和選擇。
編輯:黃飛
?
評論
查看更多