如何在 Linux 上使用 pmap 命令

已發表: 2022-06-25
顯示 bash 提示符的 Linux 筆記本電腦
fatmawati achmad zaenuri/Shutterstock.com

找出一個 Linux 進程使用了多少 RAM 並不是一件簡單的事情——尤其是在需要考慮共享內存的時候。 值得慶幸的是, pmap命令可以幫助您理解這一切。

內存映射

在現代操作系統上,每個進程都存在於自己分配的內存區域或分配空間中。 分配區域的邊界不直接映射到物理硬件地址。 操作系統為每個進程創建一個虛擬內存空間,並充當將虛擬內存映射到物理內存的抽象層。

內核為每個進程維護一個轉換錶,並由 CPU 訪問。 當內核更改在特定 CPU 內核上運行的進程時,它會更新將進程和 CPU 內核聯繫在一起的轉換錶。

抽象的好處

這個計劃有好處。 對於用戶空間中的每個進程,內存的使用在某種程度上被封裝和沙盒化。 進程僅根據虛擬內存地址“看到”內存。 這意味著它只能使用操作系統提供的內存。 除非它可以訪問某些共享內存,否則它既不知道也無法訪問分配給其他進程的內存。

Linux 上的 Swappiness 是什麼? (以及如何改變它)
相關什麼是 Linux 上的 Swappiness? (以及如何改變它)

將基於硬件的物理內存抽象為虛擬內存地址,讓內核可以更改某些虛擬內存映射到的物理地址。 它可以通過更改虛擬內存區域指向的實際地址來將內存交換到磁盤。 它還可以推遲提供物理內存,直到實際需要。

只要讀取或寫入內存的請求在請求時得到服務,內核就可以自由地在它認為合適的時候調整映射表。

按需內存

映射表和“RAM on demand”的概念開啟了共享內存的可能性。 內核將嘗試避免多次將相同的東西加載到內存中。 例如,它將一個共享庫加載到內存中一次,並將其映射到需要使用它的不同進程。 每個進程都有自己唯一的共享庫地址,但它們都指向同一個實際位置。

如果內存的共享區域是可寫的,內核使用一種稱為寫時復制的方案。 如果一個進程寫入共享內存,而共享該內存的其他進程不應該看到更改,則在寫入請求時創建共享內存的副本。

初學者極客:如何創建和使用虛擬機
相關初學者極客:如何創建和使用虛擬機

2009 年 12 月發布的 Linux 內核 2.6.32 為 Linux 提供了一個名為“Kernel SamePage Merging”的功能。 這意味著 Linux 可以檢測不同地址空間中相同的數據區域。 想像一下在一台計算機上運行一系列虛擬機,並且這些虛擬機都運行相同的操作系統。 使用共享內存模型和寫時復制,主機上的開銷可以大大減少。

所有這些都使得 Linux 中的內存處理變得複雜且盡可能優化。 但是這種複雜性使得很難查看一個進程並知道它的內存使用情況到底是什麼。

pmap 實用程序

內核通過“/proc”系統信息偽文件系統中的兩個偽文件公開了它對 RAM 所做的很多事情。 每個進程有兩個文件,以每個進程的進程 ID 或PID命名:“/proc/maps”和“/proc//smaps”。

pmap工具從這些文件中讀取信息並在終端窗口中顯示結果。 很明顯,每當我們使用pmap時,我們都需要提供我們感興趣的進程的 PID。

查找進程 ID

有幾種方法可以找到進程的 PID。 這是我們將在示例中使用的一個簡單程序的源代碼。 它是用 C 語言編寫的。它所做的只是向終端窗口打印一條消息並等待用戶按下“Enter”鍵。

 #include <stdio.h>

int main(int argc, char *argv[])
{
  printf("How-To Geek 測試程序。");
  獲取(標準輸入);
} // main 結束

使用gcc編譯器將該程序編譯為名為pm的可執行文件:

 gcc -o 下午 pm.c 

編譯示例程序

因為程序會等待用戶點擊“Enter”,所以它會一直運行,只要我們願意。

 。/下午

運行示例程序

程序啟動,打印消息,並等待擊鍵。 我們現在可以搜索它的 PID。 ps命令列出正在運行的進程。 -e (顯示所有進程)選項使ps列出每個進程。 我們將通過grep管道輸出並過濾掉名稱中包含“pm”的條目。

 ps -e | grep 下午

使用 grep 查找進程 ID

這列出了名稱中任何位置帶有“pm”的所有條目。

我們可以使用pidof命令更具體。 我們在命令行中為pidof指定我們感興趣的進程的名稱,它會嘗試找到匹配項。 如果找到匹配項, pidof會打印匹配進程的 PID。

 pidof 下午

使用 pidof 查找進程 ID

當您知道進程名稱時, pidof方法更簡潔,但即使只知道部分進程名稱, ps方法也可以工作。

使用 pmap

隨著我們的測試程序運行,一旦我們確定了它的 PID,我們就可以像這樣使用 pmap:

 地圖 40919 

在示例程序上運行 pmap

為我們列出了進程的內存映射。

標準 pmap 輸出

這是命令的完整輸出:

 40919:./下午
000056059f06c000 4K r---- 下午
000056059f06d000 4K rx--下午
000056059f06e000 4K r---- 下午
000056059f06f000 4K r---- 下午
000056059f070000 4K rw--- 下午
000056059fc39000 132K rw--- [匿名]
00007f97a3edb000 8K rw--- [匿名]
00007f97a3edd000 160K r---- libc.so.6
00007f97a3f05000 1616K rx--libc.so.6
00007f97a4099000 352K r---- libc.so.6
00007f97a40f1000 4K ----- libc.so.6
00007f97a40f2000 16K r---- libc.so.6
00007f97a40f6000 8K rw--- libc.so.6
00007f97a40f8000 60K rw--- [匿名]
00007f97a4116000 4K r---- ld-linux-x86-64.so.2
00007f97a4117000 160K rx--ld-linux-x86-64.so.2
00007f97a413f000 40K r---- ld-linux-x86-64.so.2
00007f97a4149000 8K r---- ld-linux-x86-64.so.2
00007f97a414b000 8K rw--- ld-linux-x86-64.so.2
00007ffca0e7e000 132K rw--- [堆棧]
00007ffca0fe1000 16K r---- [匿名]
00007ffca0fe5000 8K rx-- [匿名]
ffffffffff600000 4K --x-- [匿名]
總計 2756K

第一行是進程名稱和它的 PID。 其他每一行顯示一個映射的內存地址,以及該地址的內存量,以千字節表示。 每行接下來的五個字符稱為虛擬內存權限。 有效權限是:

  • r :映射的內存可以被進程讀取。
  • w :映射的內存可以被進程寫入。
  • x :進程可以執行映射內存中包含的任何指令。
  • s :映射內存是共享的,對共享內存所做的更改對共享內存的所有進程都是可見的。
  • R :沒有為這個映射內存預留交換空間。

每行的最後信息是映射源的名稱。 這可以是進程名稱、庫名稱或系統名稱,例如堆棧或堆。

擴展顯示

-x (擴展)選項提供了兩個額外的列。

 pmap -x 40919 

將 -X 擴展選項與 pmap 一起使用

列有標題。 我們已經看到了“地址”、“千字節”、“模式”和“映射”列。 新列稱為“RSS”和“Dirty”。

pmap 擴展輸出

這是完整的輸出:

 40919:./下午
地址千字節 RSS 臟模式映射
000056059f06c000 4 4 0 r---- 下午
000056059f06d000 4 4 0 rx--下午
000056059f06e000 4 4 0 r---- 下午
000056059f06f000 4 4 4 r---- 下午
000056059f070000 4 4 4 rw--- 下午
000056059fc39000 132 4 4 rw--- [匿名]
00007f97a3edb000 8 4 4 rw--- [匿名]
00007f97a3edd000 160 160 0 r---- libc.so.6
00007f97a3f05000 1616 788 0 rx--libc.so.6
00007f97a4099000 352 64 0 r---- libc.so.6
00007f97a40f1000 4 0 0 ----- libc.so.6
00007f97a40f2000 16 16 16 r---- libc.so.6
00007f97a40f6000 8 8 8 rw--- libc.so.6
00007f97a40f8000 60 28 28 rw--- [匿名]
00007f97a4116000 4 4 0 r---- ld-linux-x86-64.so.2
00007f97a4117000 160 160 0 rx-- ld-linux-x86-64.so.2
00007f97a413f000 40 40 0 r---- ld-linux-x86-64.so.2
00007f97a4149000 8 8 8 r---- ld-linux-x86-64.so.2
00007f97a414b000 8 8 8 rw--- ld-linux-x86-64.so.2
00007ffca0e7e000 132 12 12 rw--- [堆棧]
00007ffca0fe1000 16 0 0 r---- [匿名]
00007ffca0fe5000 8 4 0 rx-- [匿名]
ffffffffff600000 4 0 0 --x-- [匿名]
---------------- -------- ------- -------- 
總 kB 2756 1328 96
  • RSS :這是駐留集的大小。 也就是說,當前在 RAM 中且未換出的內存量。
  • Dirty :自從進程和映射開始以來,“Dirty”內存已被更改。

向我展示一切

-X (甚至超過擴展)在輸出中添加了額外的列。 注意大寫的“X”。 另一個名為-XX的選項(甚至超過-X )向您展示了pmap可以從內核獲得的所有內容。 由於-X-XX的子集,我們將描述-XX的輸出。

 pmap -XX 40919 

在 pmap 中使用 -XX show me everything 選項

輸出在終端窗口中可怕地環繞,幾乎無法辨認。 這是完整的輸出:

 40919:./下午
         地址 Perm Offset 設備 Inode 大小 KernelPageSize MMUPageSize Rss Pss Shared_Clean Shared_Dirty Private_Clean Private_Dirty 引用匿名 LazyFree AnonHugePages ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible VmFlags Mapping
    56059f06c000 r--p 00000000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd pm
    56059f06d000 r-xp 00001000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me dw sd pm
    56059f06e000 r--p 00002000 08:03 393304 4 4 4 4 4 0 0 4 0 4 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd pm
    56059f06f000 r--p 00002000 08:03 393304 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw ac sd pm
    56059f070000 rw-p 00003000 08:03 393304 4 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me dw ac sd pm
    56059fc39000 rw-p 00000000 00:00 0 132 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd [堆]
    7f97a3edb000 rw-p 00000000 00:00 0 8 4 4 4 4 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd 
    7f97a3edd000 r--p 00000000 08:03 264328 160 4 4 160 4 160 0 0 0 160 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me sd libc.so.6
    7f97a3f05000 r-xp 00028000 08:03 264328 1616 4 4 788 32 788 0 0 0 788 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me sd libc.so.6
    7f97a4099000 r--p 001bc000 08:03 264328 352 4 4 64 1 64 0 0 0 64 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me sd libc.so.6
    7f97a40f1000 ---p 00214000 08:03 264328 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 mr mw me sd libc.so.6
    7f97a40f2000 r--p 00214000 08:03 264328 16 4 4 16 16 0 0 0 16 16 16 0 0 0 0 0 0 0 0 0 0 rd mr mw me ac sd libc.so.6
    7f97a40f6000 rw-p 00218000 08:03 264328 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd libc.so.6
    7f97a40f8000 rw-p 00000000 00:00 0 60 4 4 28 28 0 0 0 28 28 28 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me ac sd 
    7f97a4116000 r--p 00000000 08:03 264305 4 4 4 4 0 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd ld-linux-x86-64.so.2
    7f97a4117000 r-xp 00001000 08:03 264305 160 4 4 160 11 160 0 0 0 160 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me dw sd ld-linux-x86-64.so.2
    7f97a413f000 r--p 00029000 08:03 264305 40 4 4 40 1 40 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw sd ld-linux-x86-64.so.2
    7f97a4149000 r--p 00032000 08:03 264305 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd mr mw me dw ac sd ld-linux-x86-64.so.2
    7f97a414b000 rw-p 00034000 08:03 264305 8 4 4 8 8 0 0 0 8 8 8 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me dw ac sd ld-linux-x86-64.so.2
    7ffca0e7e000 rw-p 00000000 00:00 0 132 4 4 12 12 0 0 0 12 12 12 0 0 0 0 0 0 0 0 0 0 rd wr mr mw me gd ac [堆棧]
    7ffca0fe1000 r--p 00000000 00:00 0 16 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 rd mr pf io de dd sd [vvar]
    7ffca0fe5000 r-xp 00000000 00:00 0 8 4 4 4 0 4 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 rd ex mr mw me de sd [vdso]
ffffffffff600000 --xp 00000000 00:00 0 4 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ex [vsyscall]
                                             ==== ============================================== ========== ========================================= ===== ============================================== === ============== ================================== ========== 
                                             2756 92 92 1328 157 1220 0 12 96 1328 96 0 0 0 0 0 0 0 0 0 0 KB 

這裡有很多信息。 這是列的內容:

  • 地址:此映射的起始地址。 這使用虛擬內存尋址。
  • Perm :內存的權限。
  • Offset :如果內存是基於文件的,則此映射在文件內的偏移量。
  • Device :Linux 設備號,以主要和次要數字給出。 您可以通過運行lsblk命令查看計算機上的設備號。
  • Inode :與映射關聯的文件的 inode。 例如,在我們的示例中,這可能是保存有關 pm 程序信息的 inode。
  • Size :內存映射區域的大小。
  • KernelPageSize :內核使用的頁面大小。
  • MMUPageSize :內存管理單元使用的頁面大小。
  • Rss :這是駐留集的大小。 也就是說,當前在 RAM 中且未換出的內存量。
  • Pss :這是比例份額大小。 這是添加到的私有共享大小(共享大小除以共享數量。)
  • Shared_Clean :與其他進程共享的內存量,自映射創建以來更改。 請注意,即使內存是可共享的,如果它實際上沒有被共享,它仍然被認為是私有內存。
  • Shared_Dirty :與其他進程共享的內存量,自映射創建以來已更改。
  • Private_Clean :自映射創建以來與其他進程共享的私有內存量。
  • Private_Dirty :自映射創建以來已更改的私有內存量。
  • Referenced :當前標記為已引用或已訪問的內存量。
  • 匿名:沒有設備可換出的內存。 也就是說,它不是文件支持的。
  • LazyFree :被標記為MADV_FREE的頁面。 這些頁面已被標記為可以釋放和回收,即使它們可能有不成文的更改。 但是,如果在內存映射上設置MADV_FREE之後發生後續更改,則MADV_FREE標誌將被刪除,並且在寫入更改之前不會回收頁面。
  • AnonHugePages :這些是非文件支持的“巨大”內存頁面(大於 4 KB)。
  • ShmemPmdMapped :與大頁面關聯的共享內存。 它們也可能被完全駐留在內存中的文件系統使用。
  • FilePmdMapped :頁面中間目錄是內核可用的分頁方案之一。 這是 PMD 條目指向的文件支持頁面的數量。
  • Shared_Hugetlb :轉換後備表或 TLB 是內存緩存,用於優化訪問用戶空間內存位置所花費的時間。 這個數字是與共享大內存頁面相關聯的 TLB 中使用的 RAM 量。
  • Private_Hugetlb :這個數字是與私有大內存頁面相關聯的 TLB 中使用的 RAM 量。
  • 交換:正在使用的交換量。
  • SwapPss交換比例份額大小。 這是由添加到(共享大小除以共享數)的已交換專用內存頁組成的交換量。
  • Locked :可以鎖定內存映射以防止操作系統調出堆或堆外內存。
  • THPeligible :這是一個標誌,指示映射是否有資格分配透明大頁面。 1 表示正確,0 表示錯誤。 透明大頁面是一種內存管理系統,可減少在具有大量 RAM 的計算機上查找 TLB 頁面的開銷。
  • VmFlags :請參閱下面的標誌列表。
  • 映射:映射源的名稱。 這可以是進程名稱、庫名稱或系統名稱,例如堆棧或堆。

VmFlags - 虛擬內存標誌 - 將是以下列表的子集。

  • rd :可讀。
  • wr :可寫。
  • 例如:可執行。
  • sh :共享。
  • 先生:可以閱讀。
  • mw : 可以寫。
  • :可以執行。
  • 女士:可以分享。
  • gd :堆棧段向下增長。
  • pf :純頁框號範圍。 頁框號是物理內存頁的列表。
  • dw :禁用對映射文件的寫入。
  • lo :頁面被鎖定在內存中。
  • io : 內存映射 I/O 區域。
  • sr :提供順序讀取建議(由madvise()函數提供。)
  • rr :提供隨機閱讀建議。
  • dc :如果進程被分叉,則不要復制此內存區域。
  • de :不要在重新映射時擴展此內存區域。
  • ac :區域負責。
  • nr :不為該區域保留交換空間。
  • ht :區域使用巨大的 TLB 頁面。
  • sf :同步頁面錯誤。
  • ar :特定於架構的標誌。
  • wf :如果進程被分叉,則擦除此內存區域。
  • dd :不要在核心轉儲中包含此內存區域。
  • sd :軟臟標誌。
  • mm :混合地圖區域。
  • hg :巨大的頁面建議標誌。
  • nh :沒有大頁面建議標誌。
  • mg :可合併的建議標誌。
  • bt :ARM64 偏置溫度不穩定保護頁面。
  • mt :啟用 ARM64 內存標記擴展標記。
  • um : Userfaultfd 缺少跟踪。
  • uw : Userfaultfd 寫入保護跟踪。

內存管理很複雜

並且從數據表向後工作以了解實際發生的事情是困難的。 但至少pmap為您提供了完整的圖片,因此您有最好的機會弄清楚您需要知道什麼。

有趣的是,我們的示例程序編譯為 16 KB 的二進制可執行文件,但它使用(或共享)了大約 2756 KB 的內存,這幾乎完全歸功於運行時庫。

最後一個巧妙的技巧是,您可以同時使用pmappidof命令,將查找進程的 PID 並將其傳遞給pmap的操作組合成一個命令:

 pmap $(pidof pm)