簡體   English   中英

linux kernel /proc/$PID/maps 中的臟頁記帳

[英]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

如您所見,粗體顏色的參數發生了變化。

問題

  1. kernel 到底是怎么知道我寫了這個頁面的?
  2. 這個匿名字段是什么以及它為什么會改變?

任何其他詳細解釋所有工作的頁面/博客/手冊都會有所幫助。

我猜:

也許 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 文件),因此兩種可能的情況如下:

  1. 當發生 memory讀取時,會發生頁面錯誤,並將頁面內容從文件讀取到 memory。 新分配的頁面現在被標記為只讀,即使它被映射為 RW(kernel 仍然知道這個 VMA 是 RW)。

  2. 當發生 memory寫入時,有兩種情況:( A )頁面已經存在於 memory 中,因為之前的讀取(並標記為 RO),或者( B )頁面根本不在 ZCD69B4957F0190CD818D7BFED3 中,因為第一個 memory 訪問它。 在這兩種情況下,都會發生頁面錯誤,kernel 檢查是否允許寫入(是的),並發生寫入時復制

    由於頁面是文件支持的,但不是共享的(即不使用MAP_SHARED映射),因此不需要將數據寫回文件,因此 kernel 只需分配一個新的匿名頁面,然后將內容從前一個頁面復制過來頁(案例A )或在應用寫入之前將頁面從文件讀取到 memory (案例B )。 這就是為什么您會看到Anonymous go 從 0kB 到 4kB。

    此外,由於舊頁面在寫時復制之前只有一個用戶,因此可以將其釋放,這就是您看到Rss保持不變的原因。 最后,在寫入之前頁面是干凈的(不臟),而在寫入之后它變得臟(不干凈),所以這就是為什么您會看到Private_CleanPrivate_Dirty更改值的原因。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM