[英]Dirty page accounting in linux kernel /proc/$PID/maps
TLDR; kernel 究竟是如何在 /proc/$PID/maps 中進行臟頁統計的。
考慮 C 中的以下程序語句
static char page1[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE)))
現在未初始化的變量在開始時為零。 我的理解是,在程序開始時,kernel 將未初始化的變量映射到零頁,並進行頁的寫時復制惰性分配。 很好,這樣 kernel 可以在頁面錯誤發生時解釋未初始化部分的臟頁。
現在考慮語句
static char page1[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE))) = {'c'}
在這里,加載器將在程序初始化時加載 page1 的值,並將頁面標記為 RW。 因此,程序所做的任何寫入都必須對 kernel 不可見,因為不會觸發頁面錯誤。
這是我為實驗編寫的程序。
#define PAGE_SIZE (4*1024)
static char page1[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE))) = {'c'};
int main()
{
char c; int i; int *d;
scanf("%c", &c); // --------- tag 1
for(i = 0; i < PAGE_SIZE; i++)
{
page1[i] = c; // --------- tag 2
}
d = malloc(sizeof(int));
while(1);
return 0;
}
現在,在標簽 1 之前和標簽 2 之后(代碼中的注釋),包含page1
的部分的/proc/$PID/smaps
的 output 粘貼在下表中
地圖 | TAG-1 之前 | TAG-2 之后 |
---|---|---|
尺寸: | 8 KB | 8 KB |
內核頁面大小: | 4 KB | 4 KB |
MMU頁面大小: | 4 KB | 4 KB |
回復: | 8 KB | 8 KB |
附: | 8 KB | 8 KB |
Shared_Clean: | 0 KB | 0 KB |
Shared_Dirty: | 0 KB | 0 KB |
私人清潔: | 4 KB | 0 KB |
Private_Dirty : | 4 KB | 8 KB |
參考: | 8 KB | 8 KB |
匿名: | 4 KB | 8 KB |
如您所見,粗體顏色的參數發生了變化。
問題
任何其他詳細解釋所有工作的頁面/博客/手冊都會有所幫助。
我猜:
也許 kernel 將頁面標記為 RO,即使它是 RW 以便觸發頁面錯誤並且它可以進行計算。 或者也許有一些其他進程不斷地遍歷進程的頁表,但這太昂貴了。
現在考慮語句
static char page1[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE))) = {'c'}
在這里,加載器將在程序初始化時加載 page1 的值,並將頁面標記為 RW。
您似乎相信加載程序會為該語句寫入 memory,但事實並非如此。
在這種情況下發生的不是mmap
RW + 字節'c'
寫入。 該字節已經在編譯時嵌入到您的可執行文件中,因此唯一發生的就是mmap
RW,僅此而已。 像這樣的東西:
mmap(0, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd_of_your_elf, offset_of_data_section);
或者,最有可能的是mmap(...entire file...)
后跟一系列mprotect()
,對 ELF 的不同部分具有正確的權限。
實際上,執行此操作的甚至不是加載程序,而是 kernel 本身將可執行文件映射到 memory 在這種情況下,假設您以./exe
啟動程序。 加載程序僅在以/path/to/loader./exe
調用時自行映射程序。 另請參閱我的另一個答案,其中我有更詳細的解釋。
kernel 到底是怎么知道我寫了這個頁面的?
您可能已經知道,當您的程序最初映射到 memory (包括包含page1
的頁面)時,即使該頁面的映射是 RW,也沒有真正需要 kernel 實際分配 ZCD69B4957F06CDDE298D7 到 a3BF6198發生讀或寫。 這種技術稱為需求分頁。 最初(在映射之后)該頁面甚至不存在於進程的頁表中:它僅作為任務的 memory map 中的許多vm_area_struct
條目之一存在。
當發生頁面錯誤(由讀取或寫入引起)時,kernel 然后根據映射的性質決定要做什么。 在這種情況下,映射是文件支持的(整個page1
數組的實際初始值在編譯時寫入您的 ELF 文件),因此兩種可能的情況如下:
當發生 memory讀取時,會發生頁面錯誤,並將頁面內容從文件讀取到 memory。 新分配的頁面現在被標記為只讀,即使它被映射為 RW(kernel 仍然知道這個 VMA 是 RW)。
當發生 memory寫入時,有兩種情況:( A )頁面已經存在於 memory 中,因為之前的讀取(並標記為 RO),或者( B )頁面根本不在 ZCD69B4957F0190CD818D7BFED3 中,因為第一個 memory 訪問它。 在這兩種情況下,都會發生頁面錯誤,kernel 檢查是否允許寫入(是的),並發生寫入時復制。
由於頁面是文件支持的,但不是共享的(即不使用MAP_SHARED
映射),因此不需要將數據寫回文件,因此 kernel 只需分配一個新的匿名頁面,然后將內容從前一個頁面復制過來頁(案例A )或在應用寫入之前將頁面從文件讀取到 memory (案例B )。 這就是為什么您會看到Anonymous
go 從 0kB 到 4kB。
此外,由於舊頁面在寫時復制之前只有一個用戶,因此可以將其釋放,這就是您看到Rss
保持不變的原因。 最后,在寫入之前頁面是干凈的(不臟),而在寫入之后它變得臟(不干凈),所以這就是為什么您會看到Private_Clean
和Private_Dirty
更改值的原因。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.