1. 程式人生 > >《嵌入式Linux記憶體使用與效能優化》筆記

《嵌入式Linux記憶體使用與效能優化》筆記

這本書有兩個關切點:系統記憶體(使用者層)和效能優化。

這本書和Brendan Gregg的《Systems Performance》相比,無論是技術層次還是更高的理論都有較大差距。但是這不影響,快速花點時間簡單過一遍。

然後在對《Systems Performance》進行詳細的學習。

由於Ubuntu測試驗證更合適,所以在Ubuntu(16.04)+Kernel(4.10.0)環境下做了下面的實驗。

全書共9章:1~4章著重於記憶體的使用,儘量降低程序的記憶體使用量,定位和發現記憶體洩露;5~9章著重於如何讓系統性能優化,提高執行速度。

使用者空間的記憶體使用量是由程序使用量累積和系統使用之和,所以優化系統記憶體使用,就是逐個攻克每個程序的使用量和優化系統記憶體使用。。

俗話說“知己知彼,百戰不殆”,要優化一個程序的使用量,首先得使用工具去評估記憶體使用量(第1章 記憶體的測量);

然後就來看看程序那些部分耗費記憶體,並針對性進行優化(第2章 程序記憶體優化);

最後從系統層面尋找方法進行優化(第3章 系統記憶體優化)。

記憶體的使用一個致命點就是記憶體洩露,如何發現記憶體洩露,並且將記憶體洩露定位是重點(第4章 記憶體洩露)

第1章 記憶體的測量

作者在開頭的一段話說明了本書採用的方法論:

關於系統記憶體使用,將按照(1)明確目標->(2)尋找評估方法(3)瞭解當前狀況->對系統記憶體進行優化->重新測量,評估改善狀況的過程,來闡述系統的記憶體使用與優化。

(1)明確目標,針對系統記憶體優化,有兩個:

  A.每個守護程序使用的記憶體儘可能少

  B.長時間執行後,守護程序記憶體仍然保持較低使用量,沒有內存洩露

(2)尋找評估方法,第1章關注點。

(3)對系統記憶體進行優化,第2章針對程序進行優化,第3章針對系統層面進行記憶體優化,第4章關注記憶體洩露。

系統記憶體測量

free用以獲得當前系統記憶體使用情況。

在一嵌入式裝置獲取如下:

busybox free
             total         used         free       shared      buffers
Mem:         23940        15584         8356            0            0 (23940=15584+8356)


-/+ buffers:              15584         8356
Swap:            0            0            0

和PC使用的free對比:

             total       used       free     shared    buffers     cached
Mem:      14190636   10494128    3696508     587948    1906824    5608888
-/+ buffers/cache:    2978416   11212220
Swap:      7999484      68844    7930640

可見這兩個命令存在差異,busybox沒有cached。這和實際不符,實際可用記憶體=free+buffers+cached。

buffers是用來給Linux系統中塊裝置做緩衝區,cached用來緩衝開啟的檔案。下面是通過cat /proc/meminfo獲取,可知實際可用記憶體=8352+0+3508=11860。已經使用記憶體為=23940-11860=12080。可見兩者存在差異,busybox的free不太準確;/proc/meminfo的資料更準確。

MemTotal:          23940 kB
MemFree:            8352 kB
Buffers:               0 kB
Cached:             3508 kB

程序記憶體測量

在程序的proc中與記憶體有關的節點有statm、maps、memmap。

cat /proc/xxx/statm

1086 168 148 1 0 83 0

這些引數以頁(4K)為單位,分別是:

1086 Size,任務虛擬地址空間的大小。

168 Resident,應用程式正在使用的實體記憶體的大小。

148 Shared,共享頁數。

1 Trs,程式所擁有的可執行虛擬記憶體的大小。

0 Lrs,被映像到任務的虛擬記憶體空間的的庫的大小。

83 Drs,程式資料段和使用者態的棧的大小。

0 dt,髒頁數量(已經修改的物理頁面)。

Size、Trs、Lrs、Drs對應虛擬記憶體,Resident、Shared、dt對應實體記憶體。

cat /proc/xxx/maps

00400000-00401000 r-xp 00000000 08:05 18561374                           /home/lubaoquan/temp/hello
00600000-00601000 r--p 00000000 08:05 18561374                           /home/lubaoquan/temp/hello
00601000-00602000 rw-p 00001000 08:05 18561374                           /home/lubaoquan/temp/hello
00673000-00694000 rw-p 00000000 00:00 0                                  [heap]
7f038c1a1000-7f038c35f000 r-xp 00000000 08:01 3682126                    /lib/x86_64-linux-gnu/libc-2.19.so
7f038c35f000-7f038c55e000 ---p 001be000 08:01 3682126                    /lib/x86_64-linux-gnu/libc-2.19.so
7f038c55e000-7f038c562000 r--p 001bd000 08:01 3682126                    /lib/x86_64-linux-gnu/libc-2.19.so
7f038c562000-7f038c564000 rw-p 001c1000 08:01 3682126                    /lib/x86_64-linux-gnu/libc-2.19.so
7f038c564000-7f038c569000 rw-p 00000000 00:00 0
7f038c569000-7f038c58c000 r-xp 00000000 08:01 3682489                    /lib/x86_64-linux-gnu/ld-2.19.so
7f038c762000-7f038c765000 rw-p 00000000 00:00 0
7f038c788000-7f038c78b000 rw-p 00000000 00:00 0
7f038c78b000-7f038c78c000 r--p 00022000 08:01 3682489                    /lib/x86_64-linux-gnu/ld-2.19.so
7f038c78c000-7f038c78d000 rw-p 00023000 08:01 3682489                    /lib/x86_64-linux-gnu/ld-2.19.so
7f038c78d000-7f038c78e000 rw-p 00000000 00:00 0
7ffefe189000-7ffefe1aa000 rw-p 00000000 00:00 0                          [stack]
7ffefe1c4000-7ffefe1c6000 r--p 00000000 00:00 0                          [vvar]
7ffefe1c6000-7ffefe1c8000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

第一列,代表該記憶體段的虛擬地址。

第二列,r-xp,代表該段記憶體的許可權,r=讀,w=寫,x=執行,s=共享,p=私有。

第三列,代表在程序地址裡的偏移量。

第四列,對映檔案的的主從裝置號。

第五列,映像檔案的節點號。

第六列,映像檔案的路徑。

image

kswapd

Linux存在一個守護程序kswapd,他是Linux記憶體回收機制,會定期監察系統中空閒呢村的數量,一旦發現空閒記憶體數量小於一個閾值的時候,就會將若干頁面換出。

但是在嵌入式Linux系統中,卻沒有交換分割槽。沒有交換分割槽的原因是:

1.一旦使用了交換分割槽,系統系能將下降的很快,不可接受。

2.Flash的寫次數是有限的,如果在Flash上面建立交換分割槽,必然導致對Flash的頻繁讀寫,影響Flash壽命。

那沒有交換分割槽,Linux是如何做記憶體回收的呢?

對於那些沒有被改寫的頁面,這塊記憶體不需要寫到交換分割槽上,可以直接回收。

對於已經改寫了的頁面,只能保留在系統中,,沒有交換分割槽,不能寫到Flash上。

在Linux實體記憶體中,每個頁面有一個dirty標誌,如果被改寫了,稱之為dirty page。所有非dirty page都可以被回收。

第2章 程序記憶體優化

當存在很多守護程序,又要去降低守護程序記憶體佔用量,如何去推動:

1.所有守護程序記憶體只能比上一個版本變少。

2.Dirty Page排前10的守護程序,努力去優化,dirty page減少20%。

可以從三個方面去優化:

1.執行檔案所佔用的記憶體

2.動態庫對記憶體的影響

3.執行緒對記憶體的影響

2.1 執行檔案

一個程式包括程式碼段、資料段、堆段和棧段。一個程序執行時,所佔用的記憶體,可以分為如下幾部分:

棧區(stack):由編譯器自動分配釋放,存放函式的引數、區域性變數等

堆區(heap):一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可有作業系統來回收

全域性變數、靜態變數:初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和靜態變數在另一塊區域,程式結束後由系統釋放

文字常量:常量、字串就是放在這裡的,程式結束後有系統釋放

程式程式碼:存放函式體的二進位制程式碼

下面結合一個例項分析:

#include <stdlib.h>
#include <stdio.h>

int n=10;
const int n1=20;
int m;

int main()
{
  int s=7;
  static int s1=30;
  char *p=(char *)malloc(20);
  pid_t pid=getpid();

  printf("pid:%d\n", pid);
  printf("global variable address=%p\n", &n);
  printf("const global address=%p\n", &n1);
  printf("global uninitialization variable address=%p\n", &m);;
  printf("static variable address=%p\n", &s1);
  printf("stack variable address=%p\n", &s);
  printf("heap variable address=%p\n", &p);
  pause();
}

執行程式結果:

pid:18768
global variable address=0x601058
const global address=0x400768
global uninitialization variable address=0x601064
static variable address=0x60105c
stack variable address=0x7ffe1ff7d0e0
heap variable address=0x7ffe1ff7d0e8

檢視cat /proc/17868/maps

00400000-00401000 r-xp 00000000 08:05 18561376                           /home/lubaoquan/temp/example
  (只讀全域性變數n1位於程序的程式碼段)
00600000-00601000 r--p 00000000 08:05 18561376                           /home/lubaoquan/temp/example
00601000-00602000 rw-p 00001000 08:05 18561376                           /home/lubaoquan/temp/example
  (全域性初始變數n、全域性未初始變數m、區域性靜態變數s1,都位於程序的資料段)
00771000-00792000 rw-p 00000000 00:00 0                                  [heap]
7f7fb86a2000-7f7fb8860000 r-xp 00000000 08:01 3682126                    /lib/x86_64-linux-gnu/libc-2.19.so
7f7fb8860000-7f7fb8a5f000 ---p 001be000 08:01 3682126                    /lib/x86_64-linux-gnu/libc-2.19.so
7f7fb8a5f000-7f7fb8a63000 r--p 001bd000 08:01 3682126                    /lib/x86_64-linux-gnu/libc-2.19.so
7f7fb8a63000-7f7fb8a65000 rw-p 001c1000 08:01 3682126                    /lib/x86_64-linux-gnu/libc-2.19.so
7f7fb8a65000-7f7fb8a6a000 rw-p 00000000 00:00 0
7f7fb8a6a000-7f7fb8a8d000 r-xp 00000000 08:01 3682489                    /lib/x86_64-linux-gnu/ld-2.19.so
7f7fb8c63000-7f7fb8c66000 rw-p 00000000 00:00 0
7f7fb8c89000-7f7fb8c8c000 rw-p 00000000 00:00 0
7f7fb8c8c000-7f7fb8c8d000 r--p 00022000 08:01 3682489                    /lib/x86_64-linux-gnu/ld-2.19.so
7f7fb8c8d000-7f7fb8c8e000 rw-p 00023000 08:01 3682489                    /lib/x86_64-linux-gnu/ld-2.19.so
7f7fb8c8e000-7f7fb8c8f000 rw-p 00000000 00:00 0
7ffe1ff5f000-7ffe1ff80000 rw-p 00000000 00:00 0                          [stack]
  (區域性變數s、malloc分配記憶體指標p都位於棧段)
7ffe1ffbb000-7ffe1ffbd000 r--p 00000000 00:00 0                          [vvar]
7ffe1ffbd000-7ffe1ffbf000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

第3章 系統記憶體優化

3.1 守護程序的記憶體使用

守護程序由於上期執行,對系統記憶體使用影響很大:

1.由於一直存貨,所以其佔用的記憶體不會被釋放。

2.即使什麼都不做,由於引用動態庫,也會佔用大量的實體記憶體。

3.由於生存週期很長,哪怕一點記憶體洩露,累積下來也會很大,導致記憶體耗盡。

那麼如何降低風險呢?

1.設計守護程序時,區分常駐部分和非常駐部分。儘量降低守護程序的邏輯,降低記憶體佔用,降低記憶體洩露機率。或者將幾個守護程序內容合為一個。

2.有些程序只是需要儘早啟動,而不需要變成守護程序。可以考慮加快啟動速度,從而使服務達到按需啟動的需求。優化方式有優化載入動態庫、使用Prelink方法、採用一些程序排程方法等。

3.2 tmpfs分割槽

Linux中為了加快檔案讀寫,基於記憶體建立了一個檔案系統,成為ramdisk或者tmpfs,檔案訪問都是基於實體記憶體的。

使用df -k /tmp可以檢視分割槽所佔空間大小:

Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda1       77689292 9869612  63850172  14% /

在對這個分割槽進行讀寫時,要時刻注意,他是佔用實體記憶體的。不需要的檔案要及時刪除。

3.3 Cache和Buffer

系統空閒記憶體=MemFree+Buffers+Cached。

Cache也稱快取,是把從Flash中讀取的資料儲存起來,若再次讀取就不需要去讀Flash了,直接從快取中讀取,從而提高讀取檔案速度。Cache快取的資料會根據讀取頻率進行組織,並最頻繁讀取的內容放在最容易找到的位置,把不再讀的內容不短往後排,直至從中刪除。
在程式執行過程中,發現某些指令不在記憶體中,便會產生page fault,將程式碼載入到實體記憶體。程式退出後,程式碼段記憶體不會立即丟棄,二是作為Cache快取。

Buffer也稱快取,是根據Flash讀寫設計的,把零散的寫操作集中進行,減少Flash寫的次數,從而提高系統性能。

Cache和BUffer區別簡單的說都是RAM中的資料,Buffer是即將寫入磁碟的,而Cache是從磁碟中讀取的

使用free -m按M來顯示Cache和Buffer大小:

             total       used       free     shared    buffers     cached
Mem:         13858       1204      12653        206         10        397
-/+ buffers/cache:        796      13061
Swap:         7811          0       7811

降低Cache和Buffer的方法:

sync
  該命令將未寫的系統緩衝區寫到磁碟中。包含已修改的 i-node、已延遲的塊 I/O 和讀寫對映檔案。

/proc/sys/vm/drop_caches
  a)清理pagecache(頁面快取)
  # echo 1 > /proc/sys/vm/drop_caches     或者 # sysctl -w vm.drop_caches=1
  b)清理dentries(目錄快取)和inodes
  # echo 2 > /proc/sys/vm/drop_caches     或者 # sysctl -w vm.drop_caches=2
 
c)清理pagecache、dentries和inodes
  # echo 3 > /proc/sys/vm/drop_caches     或者 # sysctl -w vm.drop_caches=3
  上面三種方式都是臨時釋放快取的方法,要想永久釋放快取,需要在/etc/sysctl.conf檔案中配置:vm.drop_caches=1/2/3,然後sysctl -p生效即可!

vfs_cache_pressure
vfs_cache_pressure=100    這個是預設值,核心會嘗試重新宣告dentries和inodes,並採用一種相對於頁面快取和交換快取比較”合理”的比例。
減少vfs_cache_pressure的值,會導致核心傾向於保留dentry和inode快取。
增加vfs_cache_pressure的值,(即超過100時),則會導致核心傾向於重新宣告dentries和inodes
總之,vfs_cache_pressure的值:
小於100的值不會導致快取的大量減少
超過100的值則會告訴核心你希望以高優先順序來清理快取。

3.4 記憶體回收

kswapd有兩個閾值:pages_high和pages_low,當空閒記憶體數量低於pages_low時,kswapd程序就會掃描記憶體並且每次釋放出32個free pages,知道free page數量達到pages_high。

kswapd回收記憶體的原則?

1.如果物理頁面不是dirty page,就將該物理頁面回收。

  • 程式碼段,只讀不能被改寫,所佔記憶體都不是dirty page。
  • 資料段,可讀寫,所佔記憶體可能是dirty page,也可能不是。
  • 堆段,沒有對應的對映檔案,內容都是通過修改程式改寫的,所佔實體記憶體都是dirty page。
  • 棧段和堆段一樣,所佔實體記憶體都是dirty page。
  • 共享記憶體,所佔實體記憶體都是dirty page。

就是說,這條規則主要面向程序的程式碼段和未修改的資料段

2.如果物理頁面已經修改並且可以備份迴文件系統,就呼叫pdflush將記憶體中的內容和檔案系統進行同步。pdflush寫回磁碟,主要針對Buffers。

3.如果物理頁面已經修改但是沒有任何磁碟的備份,就將其寫入swap分割槽。

kswapd再回首過程中還存在兩個重要方法:LMR(Low on Memory Reclaiming)和OMK(Out of Memory Killer)。

由於kswapd不能提供足夠空閒記憶體是,LMR將會起作用,每次釋放1024個垃圾頁知道記憶體分配成功。

當LMR不能快速釋放記憶體的時候,OMK就開始起作用,OMK會採用一個選擇演算法來決定殺死某些程序。傳送SIGKILL,就會立即釋放記憶體。

3.5 /proc/sys/vm優化

此資料夾下面有很多介面控制記憶體操作行為,在進行系統級記憶體優化的時候需要仔細研究,適當調整。

block_dump
  表示是否開啟Block Debug模式,用於記錄所有的讀寫及Dirty Block寫回操作。0,表示禁用Block Debug模式;1,表示開啟Block Debug模式。

dirty_background_ratio
  表示髒資料達到系統整體記憶體的百分比,此時觸發pdflush程序把髒資料寫回磁碟。

dirty_expires_centisecs
  表示髒資料在記憶體中駐留時間超過該值,pdflush程序在下一次將把這些資料寫回磁碟。預設值3000,單位是1/100s。

dirty_ratio
  表示如果程序產生的髒資料達到系統整體記憶體的百分比,此時程序自行吧髒資料寫回磁碟。

dirty_writeback_centisecs
  表示pdflush程序週期性間隔多久把髒資料協會磁碟,單位是1/100s。

vfs_cache_pressure
  表示核心回收用於directory和inode cache記憶體的傾向;預設值100表示核心將根據pagecache和swapcache,把directory和inode cache報紙在一個合理的百分比;降低該值低於100,將導致核心傾向於保留directory和inode cache;高於100,將導致核心傾向於回收directory和inode cache。

min_free_kbytes
  表示強制Linux VM最低保留多少空閒記憶體(KB)。

nr_pdflush_threads
  表示當前正在進行的pdflush程序數量,在I/O負載高的情況下,核心會自動增加更多的pdflush。

overcommit_memory
  指定了核心針對記憶體分配的策略,可以是0、1、2.
  0 表示核心將檢查是否有足夠的可用記憶體供應用程序使用。如果足夠,記憶體申請允許;反之,記憶體申請失敗。
  1 表示核心允許分配所有實體記憶體,而不管當前記憶體狀態如何。
  2 表示核心允許分配查過所有實體記憶體和交換空間總和的記憶體。

overcommit_ratio
  如果overcommit_memory=2,可以過在記憶體的百分比。

page-cluster
  表示在寫一次到swap區時寫入的頁面數量,0表示1頁,3表示8頁。

swapiness
  表示系統進行交換行為的成都,數值(0~100)越高,越可能發生磁碟交換。

legacy_va_layout
  表示是否使用最新的32位共享記憶體mmap()系統呼叫。

nr_hugepages
  表示系統保留的hugetlg頁數。

第4章 記憶體洩露

4.1 如何確定是否有記憶體洩露

解決記憶體洩露一個好方法就是:不要讓你的程序成為一個守護程序,完成工作後立刻退出,Linux會自動回收該程序所佔有的記憶體。
測試記憶體洩露的兩種方法:

1.模仿使用者長時間使用裝置,檢視記憶體使用情況,對於那些記憶體大量增長的程序,可以初步懷疑其有記憶體洩露。

2.針對某個具體測試用例,檢查是否有記憶體洩露。

在發現程序有漏洞之後,看看如何在程式碼中檢查記憶體洩露。

4.2 mtrace

glibc針對記憶體洩露給出一個鉤子函式mtrace:

1.加入標頭檔案<mcheck.h>

2.在需要記憶體洩露檢查的程式碼開始呼叫void mtrace(),在需要記憶體洩露檢查程式碼結尾呼叫void muntrace()。如果不呼叫muntrace,程式自然結束後也會顯示記憶體洩露

3.用debug模式編譯檢查程式碼(-g或-ggdb)

4.在執行程式前,先設定環境變數MALLOC_TRACE為一個檔名,這一檔案將存有記憶體分配資訊

5.執行程式,記憶體分配的log將輸出到MALLOC_TRACE所執行的檔案中。

程式碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <mcheck.h>

int main(void)
{
  mtrace();

  char *p=malloc(10);
  return 0;
}

編譯,設定環境變數,執行,檢視log:

gcc -o mem-leakage -g mem-leakage.c

export MALLOC_TRACE=/home/lubaoquan/temp/malloc.og

./mem-leakage

= Start
@ ./mem-leakage:[0x400594] + 0x100d460 0xa (0xa表示洩露的記憶體大小,和malloc(10)對應)

加入mtrace會導致程式執行緩慢:

1.日誌需要寫到Flash上(可以將MALLOC_TRACE顯示到stdout上。)

2.mtrace函式內,試圖根據呼叫malloc程式碼指標,解析出對應的函式

效能優化是一個艱苦、持續、枯燥、反覆的過程,涉及到的內容非常多,編譯器優化、硬體體系結構、軟體的各種技巧等等。

另外在嵌入式電池供電系統上,效能的優化也要考慮到功耗的使能。PnP的兩個P(Power and Performance)是不可分割的部分。

第5章 效能優化的流程

5.1 效能評價

首先“快”與“慢”需要一個客觀的指標,同時明確定義測試階段的起訖點。

同時優化也要考慮到可移植性以及普適性,不要因為優化過度導致其他問題的出現。

5.2 效能優化的流程

1. 測量,獲得資料,知道和目標效能指標的差距。

2. 分析待優化的程式,查詢效能瓶頸。

3. 修改程式。

4. 重新測試,驗證優化結果。

5. 達到效能要求,停止優化。不達目標,繼續分析。

5.3 效能評測 

介紹兩種方法:可視操作(攝像頭)和日誌。

話說攝像頭錄影評測,還是很奇葩的,適用範圍很窄。但是貌似還是有一定市場。

5.4 效能分析

導致效能低下的三種主要原因:

(1) 程式運算量很大,消耗過多CPU指令。

(2) 程式需要大量I/O,讀寫檔案、記憶體操作等,CPU更多處於I/O等待。

(3) 程式之間相互等待,結果CPU利用率很低。

簡單來說即是CPU利用率高、I/O等待時間長、死鎖情況。

下面重點放在第一種情況,提供三種方法。

1. 系統相關:/proc/stat、/proc/loadavg

cat /proc/stat結果如下:

cpu  12311503 48889 7259266 561072284 575332 0 72910 0 0 0-----分別是user、nice、system、idle、iowait、irq、softirq、steal、guest、guest_nice
  user:從系統啟動開始累計到當前時刻,使用者態CPU時間,不包含nice值為負的程序。
  nice:
從系統啟動開始累計到當前時刻,nice值為負的程序所佔用的CPU時間。
  system:從系統啟動開始累計到當前時刻,核心所佔用的CPU時間。
  idle:從系統啟動開始累計到當前時刻,除硬碟IO等待時間以外其他等待時間。
  iowait:從系統啟動開始累計到當前時刻,硬碟IO等待時間。
  irq:從系統啟動開始累計到當前時刻,硬中斷時間。
  softirq:從系統啟動開始累計到當前時刻,軟中斷時間。
  steal:從系統啟動開始累計到當前時刻,involuntary wait
  guest:running as a normal guest
  guest_nice:running as a niced guest
cpu0 3046879 11947 1729621 211387242 95062 0 1035 0 0 0 cpu1 3132086 8784 1788117 116767388 60010 0 535 0 0 0 cpu2 3240058 12964 1826822 116269699 353944 0 31989 0 0 0 cpu3 2892479 15192 1914705 116647954 66316 0 39349 0 0 0 intr 481552135 16 183 0 0 0 0 0 0 175524 37 0 0 2488 0 0 0 249 23 0 0 0 0 0 301 0 0 3499749 21 1470158 156 33589268 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-------------------Counts of interrupts services since boot time.Fist column is the total of all interrupts services, each subsequent if total for particular interrupt.
ctxt 2345712926-------------------------------------------------Toal number of context switches performed since bootup accross all CPUs. btime 1510217813------------------------------------------------Give the time at which the system booted, in seconds since the Unix epoch. processes 556059------------------------------------------------Number of processes and threads created, include(but not limited to) those created by fork() or clone() system calls. procs_running 2-------------------------------------------------Current number of runnable threads procs_blocked 1-------------------------------------------------Current number of threads blocked, waiting for IO to complete. softirq 415893440 117 134668573 4001105 57050104 3510728 18 1313611 104047789 0 111301395---總softirq和各種型別softirq產生的中斷數:RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

由cpu的各種時間可以推匯出:

CPU時間=user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice

CPU利用率=1-idle/(user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice)

CPU使用者態利用率=(user+nice)/(user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice)

CPU核心利用率=system/(user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice)

IO利用率=iowait/(user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice)

cat /proc/loadavg結果如下:

0.46 0.25 0.16 2/658 13300

1、5、15分鐘平均負載;

2/658:在取樣時刻,執行佇列任務數目和系統中活躍任務數目。

13300:最大pid值,包括執行緒。

2. 程序相關:/proc/xxx/stat

24021 (atop) S 1 24020 24020 0 -1 4194560 6179 53 0 0 164 196 0 0 0 -20 1 0 209898810 19374080 1630 18446744073709551615 1 1 0 0 0 0 0 0 27137 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0

3. top

top是最常用來監控系統範圍內程序活動的工具,提供執行在系統上的與CPU關係最密切的程序列表,以及很多統計值。

第6章 程序啟動速度

程序啟動可以分為兩部分:

(1) 程序啟動,載入動態庫,直到main函式值錢。這是還沒有執行到程式設計師編寫的程式碼,其效能優化有其特殊方法。

(2) main函式之後,直到對使用者的操作有所響應。涉及到自身編寫程式碼的優化,在7、8章介紹。

6.1 檢視程序的啟動過程

hello原始碼如下:

#include <stdio.h>
#include <stdlib.h>

int main()
{
  printf("Hello world!\n");
  return 0;
}

編譯:

gcc -o hello -O2 hello.c

strace用於檢視系統執行過程中系統呼叫,同時得知程序在載入動態庫時的大概過程,-tt可以列印微妙級別時間戳。

strace -tt ./hello如下:

20:15:10.185596 execve("./hello", ["./hello"], [/* 82 vars */]) = 0
20:15:10.186087 brk(NULL)               = 0x19ad000
20:15:10.186206 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
20:15:10.186358 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24710ea000
20:15:10.186462 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
20:15:10.186572 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
20:15:10.186696 fstat(3, {st_mode=S_IFREG|0644, st_size=121947, ...}) = 0
20:15:10.186782 mmap(NULL, 121947, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f24710cc000
20:15:10.186857 close(3)                = 0
20:15:10.186975 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
20:15:10.187074 open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
20:15:10.187153 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832----------------libc.so.6檔案控制代碼3,大小832。
20:15:10.187270 fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
20:15:10.187358 mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2470afd000
20:15:10.187435 mprotect(0x7f2470cbd000, 2097152, PROT_NONE) = 0
20:15:10.187558 mmap(0x7f2470ebd000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f2470ebd000---引數依次是:addr、length、prot、flags、fd、offset。
20:15:10.187662 mmap(0x7f2470ec3000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2470ec3000
20:15:10.187749 close(3)                = 0
20:15:10.187887 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24710cb000
20:15:10.187992 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24710ca000
20:15:10.188072 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f24710c9000
20:15:10.188191 arch_prctl(ARCH_SET_FS, 0x7f24710ca700) = 0--------------------------------set architecture-specific thread state, the parameters are code and addr。
20:15:10.188334 mprotect(0x7f2470ebd000, 16384, PROT_READ) = 0
20:15:10.188419 mprotect(0x600000, 4096, PROT_READ) = 0
20:15:10.188541 mprotect(0x7f24710ec000, 4096, PROT_READ) = 0
20:15:10.188633 munmap(0x7f24710cc000, 121947) = 0
20:15:10.188785 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
20:15:10.188965 brk(NULL)               = 0x19ad000
20:15:10.189158 brk(0x19ce000)          = 0x19ce000
20:15:10.189243 write(1, "Hello world!\n", 13Hello world!-----------------------------------往控制代碼1寫13個字元Hello world!\n。
) = 13
20:15:10.189299 exit_group(0)           = ?
20:15:10.189387 +++ exited with 0 +++

 通過設定LD_DEBUG環境變數,可以打印出在程序載入過程中都做了那些事情:

LD_DEBUG=all ./hello如下。看似簡單的一個Hello world!,其系統已經做了很多準備工作。

     13755:    
     13755:    file=libc.so.6 [0];  needed by ./hello [0]----------(1) 搜尋其所依賴的動態庫。
     13755:    find library=libc.so.6 [0]; searching
     13755:     search cache=/etc/ld.so.cache
     13755:      trying file=/lib/x86_64-linux-gnu/libc.so.6
     13755:    
     13755:    file=libc.so.6 [0];  generating link map
     13755:      dynamic: 0x00007fbac5cedba0  base: 0x00007fbac592a000   size: 0x00000000003c99a0
     13755:        entry: 0x00007fbac594a950  phdr: 0x00007fbac592a040  phnum:                 10
     13755:    
     13755:    checking for version `GLIBC_2.2.5