計算 Linux 記憶體使用量

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

留言

這個網誌中的熱門文章

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

熟悉系統工具好處多多

virtualbox 使用 USB 裝置