嵌入式 linux性能問題

這篇文章主要主要記錄下我對 linux 開發板的一些性能工具的使用和各問題定位的學習和應用, 分4大塊,基礎知識,內存泄露的定位,cpu 性能的定位, 還有 coredump 文件分析。 主要是內存泄露的定位。不過,我覺得在定位這些問題首先應該對相關的代碼有一定了解,因爲對代碼瞭解可以幫助我們更快的定位問題。

一. 基礎知識


1.1 Linux 內核的 OOM 機制

Linux內核內存管理使用 OOM killer(Out-Of-Memory killer)機制,在系統內存不足時,選擇性殺死一些進程以釋放內存,以使系統繼續運行。內核會通過特定的算法給每個進程計算一個分數來決定殺哪個進程,每個進程的 oom 分數可以 /proc/PID/oom_score 中找到。
防止重要的系統進程觸發(OOM)機制而被殺死:可以設置參數 /proc/PID/oom_adj 爲-17,可臨時關閉 linux 內核的 OOM 機制。

1.2 glibc 內存管理

  • 理解 glibc malloc

    glibc 內存管理機制:爲每個程序的每個線程維護一塊內存,稱爲 arena,隨着線程的增加會動態的增加 arena 的數量,arena 申請不回收,並且每次申請大塊的內存,自己管理,進程釋放並不會立馬還給系統而是自己管理,只有當堆頂的內存大於 M_TRIM_THRESHOLD 的值 glibc 纔會真正的還給系統。

  • 建議

  1. 對長期駐留的程序,並且會存在一瞬間猛增很多個線程的程序會出現內存爆增的現象,建議使用內存池的方式管理內存
  2. 建議後申請的內存儘量先釋放,利於堆頂內存的合併釋放,也會相應的會減小內存碎片
  3. glibc 的默認閾值是根據類似 pc 這樣的設備設置的,並不適用於小內存的開發板,我們需要根據自己內存的大小和進程的線程數設置合適的閾值。

1.3 linux 的 proc 目錄詳解

https://www.cnblogs.com/DswCnblog/p/5780389.html

1.4 基礎工具的使用

linux 常用的基礎工具有 nm,readlf,file,
gdb,gdbserver,strace,pstrace

  • gdbserver的使用
1. 在設備端運行 gdbserver,並 attach 到調試進程(ip是設備ip)
  
gdbserver IP:PORT --attach  ${pid} 


2. 在 pc 端運行 gdb 

target remote IP:PORT
file+測試進程
set sysroot+帶符號表的動態庫
3. 進行 gdb 調試
  • strace 的使用
查看進程或者線程當前的系統調用         

strace -p  ${pid}/${tid}

1.5 openwrt使用工具鏈編譯各工具的基本方法

1.指定具體開發板使用的編譯工具鏈

export PATH=$PATH:/home/hang/work/test/thub/openwrt/staging_dir/toolchain-aarch64-linux-gnu/bin
export CROSS_COMPILE = aarch64-linux-gnu-
export CC = ${CROSS_COMPILE}gcc
export CPP = ${CROSS_COMPILE}cpp
export CXX=${CROSS_COMPILE}g++
export LD = ${CROSS_COMPILE}ld
export AR = ${CROSS_COMPILE}ar

2. 下載編譯對應的工具

二. 內存


2.1 內存統計

  1. 查看 proc 的 status
cat /proc/${pid}/status
  • VSS - Virtual Set Size 虛擬耗用內存(包含共享庫佔用的內存),可以通過 /proc/${pid}/status 下的 VmSize 得到
  • RSS - Resident Set Size 實際使用物理內存(包含共享庫佔用的內存),可以通過 /proc/${pid}/status 下的 VmRss 得到,同時還可以得到RssAnon、RssFile、RssShmem。其中RssAnon 匿名內存代表當前進程通過 brk 或 mmap 向系統申請的物理內存(非文件映射),++在統計內存泄漏時比較有用。++
  • USS - Unique Set Size 進程獨自佔用的物理內存(不包含共享庫佔用的內存)
  1. 查看 proc 的 smaps
cat /proc/${pid}/smaps
  • PSS - Proportional Set Size 實際使用的物理內存(比例分配共享庫佔用的內存)

2.1 從系統申請和釋放內存

申請

  • mmap :申請虛擬內存地址空間,當實際讀寫時,由page_fault中斷將空閒的物理內存頁映射到虛擬內存地址。

  • brk : 邊界下移時申請內存。

釋放

  • munmap:釋放虛擬地址空間,同時釋放物理內存

  • brk: 邊界上移時釋放內存

  • madvise(…, MADV_DONTNEED):僅釋放物理內存,同時適用於mmap或brk申請的內存

2.3 內存問題偵測工具

【1】. gperftools

  • 優點:無需修改源碼,額外的內存開銷小,tcmalloc 性能優於 ptmalloc,可以用來優化進程內存
  • 缺點:需要設置環境變量 LD_PRELOAD 來啓用偵測,同時需要設置其他環境變量開啓使用不同功能,不可以同時使用 heap check 和 heap profile。
  • 原理:基本原理是 proflier 對運行着的程序定時採樣,最後根據每次記錄的堆棧頻度導出採樣信息。網上有人這樣解釋到:相當於用 gdb attach 一個正在運行的進程,然後每隔一段時間中斷程序打印堆棧,當然耗時最多的調用最頻繁的堆棧是最常被打印出來的。大致的實現就是註冊一個定時觸發的信號(SIGPROF)處理函數,在此函數中獲取當前堆棧信息,通過 hash 算法以此做 hash 表的 key,放入樣本統計 hash table中,如果hash table 中已經有同樣的堆棧 key 了,value 就加1,這樣當採樣結束,就把 hash table 的統計信息導出到文件供 pprof 程序解析,從而得到真正直觀的 profile 信息。
  • 官方文檔:https://gperftools.github.io/gperftools/

示例

heap check :

LD_PRELOAD=/tmp/libtcmalloc.so.4.5.3 env HEAPCHECK=as-is HEAP_CHECK_IGNORE_THREAD_LIVE=false   HEAP_CHECK_DUMP_DIRECTORY=/data/ /usr/bin/iotjs /usr/yoda/services/multimediad/index.js

heap profile :

1.
//注:Dump heap profiling information whenever the high-water memory usage mark increases by the specified number of bytes.

export HEAP_PROFILE_INUSE_INTERVAL=3145728

//注:Dump heap profiling information whenever the specified signal is sent to the process.

export HEAPPROFILESIGNAL=12

//注:Profile mmap, mremap and sbrk calls in addition to malloc, calloc, realloc, and new. NOTE: this causes the profiler to profile calls internal to tcmalloc, since tcmalloc and friends use mmap and sbrk internally for allocations. One partial solution is to filter these allocations out when running pprof, with something like pprof --ignore='DoAllocWithArena|SbrkSysAllocator::Alloc|MmapSysAllocator::Alloc.

export HEAP_PROFILE_MMAP=1

export HEAPPROFILE=/data/tcmalloc.log
LD_PRELOAD=/data/lib/libtcmalloc.so.4.5.3

2. 重啓程序

產生 dump 文件 ,在 pc 上使用 pprof 做 diff

pprof --text --lib_prefix=pc上帶符號表的庫   --base=tcmalloc.log.0001.heap ./bin/test(注:pc上對應的dump 進程) tcmalloc.log.0002.heap --stack

【2】. heaptrack

  • 優點:無需修改源碼,內存開銷小。原理類似 gperftools,但提供比 gperftools 更易用的腳本 heaptrack,無需手動設置環境變量。可以同時開啓 heap check和heap profile。有GUI環境展示偵測結果。
  • 缺點:編譯依賴 boost、elfutils、libunwind,GUI分析工具依賴 qt、kde framework,GUI 只在 linux 系統上安裝成功了,不能在 mac 上安裝

【3】. valgrind 的 memcheck

  • 優點:無需修改源碼。使用內置虛擬機運行被偵測程序,理論上可以偵測出所有內存問題,例如讀寫緩衝區越界等。除了默認的 memcheck 以外,還有 cachegrind、callgrind、helgrind、drd、massif、dhat、sgcheck、bbv等工具。
  • 缺點:CPU執行效率降低數十倍,內存消耗巨大,256M內存以下的設備基本無法運行。被偵測程序必須擁有符號表和調試符號,即不能被strip。
  • 原理:
    Memcheck 能夠檢測出內存問題,關鍵在於其建立了兩個全局表。
    Valid-Value 表:
    對於進程的整個地址空間中的每一個字節(byte),都有與之對應的 8 個 bits;對於 CPU 的每個寄存器,也有一個與之對應的 bit 向量。這些 bits 負責記錄該字節或者寄存器值是否具有有效的、已初始化的值。
    Valid-Address 表:
    對於進程整個空間中的每一個字節(byte),還有與之對應的 1 個 bit,負責記錄該地址是否能夠被讀寫。
    當要讀寫內存中的某個字節時,首先檢查這個字節對應的A bit。如果該A bit顯示該位置是無效位置,memcheck 則會報告讀寫錯誤;內核(core)類似於一個虛擬的CPU環境,這樣當內存中的某個字節被加載到真實的CPU時,該字節對應的V bit也會被加載到虛擬的 CPU 環境中。一旦寄存器中的值,被用來產生內存地址,或者該值能夠影響程序輸出,則 memcheck 會檢查對應的V bits,如果該值尚未初始化,則會報告使用未初始化內存錯誤。

示例

VALGRIND_LIB=/data//lib/valgrind/ valgrind --log-file=/data/val.log --error-limit=no --leak-check=full --show-leak-kinds=all /data/rplayer_demo https://rokidstorycdn.rokid.com/story/track/9677547/wKgDZ1Y4GyqTMGYYAC-S81q-PzI118.mp3

注意

  • 我們的設備的內存很小,很多帶符號表的文件無法直接推進去,可以推到 data 目錄下,然後路徑加載
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$pwd
  • 測試結論依賴程序正常結束

【4】. 其他工具

  • kchat 被偵測程序必須擁有符號表和調試符號,沒有實際使用過,大家有興趣可以自己嘗試
  • mtrace:ptmalloc 自帶,額外的內存開銷小,但需要在源碼中調用 mtrace() 和 muntrace(),同時需要設置環境變 量MALLOC_TRACE 用於輸出偵測結果

三. CPU高的定位方法


一個應用佔用 CPU 很高,除了確實是計算密集型應用之外,通常原因都是出現了死循環。

1.查看進程中的哪個線程cpu的佔用率高
  
  top -Hp ${pid} 
2. 如果對代碼很熟悉,可以直接使用 strace 查看線程當前的系統調用

注:tid 是通過 1 查看對高 cpu 的線程 id
strace -p  ${tid} 

否則就使用 adbserver attach 進程查看cpu高的線程的堆棧調用

四. CoreDump


4.1. gdb 調試 core 文件

1.gdb+ 程序+ core文件
2.file+程序
3. info share (查看動態庫)
4. 沒有動態庫的話加載動態庫
set solib-search-path + 庫的路徑
5.查看coredump 時的棧信息
bt 或者 where

4.2. 原理

https://blog.csdn.net/u014403008/article/details/54666438

4.3. 擴展

使用的是網頁版的 gdb( 有興趣可以看看網頁版gdb的實現 https://www.youtube.com/watch?v=OyWaAJD6hr8

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章