雖然直覺上會使用 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 程式會有不同的結構和控制的手法, 讀不同專案時, 需要理解一下該專案使用的方式。也許多讀幾次後, 會發覺大家的手法也差不多吧。