雖然直覺上會使用 top 或 free 觀看結果, 但是 top 或 free 顯示的資訊包含 kernel 使用的 cache 量, 不是「所有 process 使用的量」。因此, 我習慣看 htop 顯示的值。
經 Viller Hsiao 提醒, 才知道原來 htop 顯示的記憶體用量和 free 第二行的數值一樣, 都是讀 /proc/meminfo, 然後得出實際使用量 = MemTotal - MemFree - Buffers - Cached。若需要寫 script 記錄系統的 memory peak, 可用 free 的輸出, 畢竟 htop 沒有 batch mode 可用。
以下是一個 free 顯示的例子:
$ free total used free shared buffers cached Mem: 11418888 3247916 8170972 0 1274988 693072 -/+ buffers/cache: 1279856 10139032 Swap: 0 0 0
我之前一直誤會第二行的意思, 原來它的意思是「考慮第一行顯示的 buffers 和 cached 後, 重新計算 used 和 free 的值」。
Viller Hsiao 翻了一下原始碼證實這件事。我自己也試著練習翻閱原始碼, 增加翻原始碼查資料的能力。
以下是查詢過程的一些記錄。
free
找 free 原始碼的過程比較麻煩一點:
$ apt-file search /usr/bin/free | grep "free$" procps: /usr/bin/free $ aptitude show procps ... Description: /proc file system utilities This package provides command line and full screen utilities for browsing procfs, a "pseudo" file system dynamically generated by the kernel to provide information about the status of entries in its process table (such as whether the process is running, stopped, or a "zombie"). It contains free, kill, pkill, pgrep, pmap, ps, pwdx, skill, slabtop, snice, sysctl, tload, top, uptime, vmstat, w, and watch. Homepage: http://procps.sf.net/ $ apt-get source procps
但原始碼滿好讀的:
free.c:
63 meminfo(); ... 93 unsigned KLONG buffers_plus_cached = kb_main_buffers + kb_main_cached; 94 printf( 95 "-/+ buffers/cache: %10Lu %10Lu\\n", 96 S(kb_main_used - buffers_plus_cached), 97 S(kb_main_free + buffers_plus_cached) 98 );
利用上面的關鍵變數和已知關鍵資訊存在 /proc/meminfo 裡 (man proc 然後搜 meminfo), 進一步找到 proc/sysinfo.c, 從 memoinfo() 的實作證實 free 是讀 /proc/meminfo 的特定欄位, 計算出實際使用量。
htop
htop 本身套件名就是 htop, 但程式碼比較複雜一些。
先假設 htop 也是讀 /proc/meminfo, 以 meminfo 作為關鍵字下手, 可以找到關鍵的程式碼:
ProcessList.h
20 #define PROCDIR "/proc" ... 28 #define PROCMEMINFOFILE PROCDIR "/meminfo"
ProcessList.c
750 void ProcessList_scan(ProcessList* this) { 751 unsigned long long int usertime, nicetime, systemtime, systemalltime, idlealltime, idletime, totaltime, virtalltime; 752 unsigned long long int swapFree = 0; 753 754 FILE* file = fopen(PROCMEMINFOFILE, "r"); 755 assert(file != NULL); 756 int cpus = this->cpuCount; 757 { 758 char buffer[128]; 759 while (fgets(buffer, 128, file)) { 760 761 switch (buffer[0]) { 762 case 'M': 763 if (String_startsWith(buffer, "MemTotal:")) 764 sscanf(buffer, "MemTotal: %llu kB", &this->totalMem); 765 else if (String_startsWith(buffer, "MemFree:")) 766 sscanf(buffer, "MemFree: %llu kB", &this->freeMem); 767 else if (String_startsWith(buffer, "MemShared:")) 768 sscanf(buffer, "MemShared: %llu kB", &this->sharedMem); 769 break; 770 case 'B': 771 if (String_startsWith(buffer, "Buffers:")) 772 sscanf(buffer, "Buffers: %llu kB", &this->buffersMem); 773 break; 774 case 'C': 775 if (String_startsWith(buffer, "Cached:")) 776 sscanf(buffer, "Cached: %llu kB", &this->cachedMem); 777 break; 778 case 'S': 779 if (String_startsWith(buffer, "SwapTotal:")) 780 sscanf(buffer, "SwapTotal: %llu kB", &this->totalSwap); 781 if (String_startsWith(buffer, "SwapFree:")) 782 sscanf(buffer, "SwapFree: %llu kB", &swapFree); 783 break; 784 } 785 } 786 } 787 788 this->usedMem = this->totalMem - this->freeMem; 789 this->usedSwap = this->totalSwap - swapFree; 790 fclose(file);
另一方面, 以下是唯一用到 memory 欄位的程式碼:
MemoryMeter.c
28 static void MemoryMeter_setValues(Meter* this, char* buffer, int size) { 29 long int usedMem = this->pl->usedMem; 30 long int buffersMem = this->pl->buffersMem; 31 long int cachedMem = this->pl->cachedMem; 32 usedMem -= buffersMem + cachedMem; 33 this->total = this->pl->totalMem; 34 this->values[0] = usedMem; 35 this->values[1] = buffersMem; 36 this->values[2] = cachedMem; 37 snprintf(buffer, size, "%ld/%ldMB", (long int) usedMem / 1024, (long int) this->total / 1024); 38 }
Meter.h
21 typedef struct Meter_ Meter; ... 53 struct Meter_ { ... 62 ProcessList* pl; ... 65 };
證實兩者計算方式相同。
結語
有疑問的時候, 還是翻原始碼最穩, 多做幾次之後會更順手。
另外藉由這次的經驗, 發覺寫純 C 程式時, 作者自己會有一套封裝資料結構和控制的方法。不像 C++ 已內建物件觀念, 不同的 C 程式會有不同的結構和控制的手法, 讀不同專案時, 需要理解一下該專案使用的方式。也許多讀幾次後, 會發覺大家的手法也差不多吧。
沒有留言:
張貼留言