Linux源碼(0.11)學習02---內核寫時復制 verify_area write_verify


前言

  在進程創建之初,父子進程的數據段和代碼段共享並且設置為只讀,直到他們之一要將代碼和數據段進行修改時才會進行復制即寫時復制。但是,這種判斷條件只能用於用戶態,因為8086cpu, 在執行特權0代碼時不會理會用戶空間中頁面是否為有保護,用戶空間中數據頁面保護標志不起任何作用的。這樣將違背了進程的獨立性。
用戶態的寫時復制:
          在對頁面進程修改時會受到用戶空間頁面標志的影響。在用戶態上的寫時復制是由硬件支持的,寫時當你把頁表項是的屬性設為只讀的話,如果對頁表所指向的這段內存空間執行了寫操作(具本說就是寫數據的指令,比如說 mov ) ,CPU 就會自動發現,然后進行一個陷阱中,去執行你事先設定好的處理程序,在這個處理程序中你自己把數據拷一份給寫數據的進程,給這個進程分配真正的物理空間, 然后再改頁表,讓內存可寫,這時候重新執行這條寫指令就行了~~~~~~~~這里純粹是硬件的機制問題,只是軟件利用了這種機制而已
內核態的寫時復制:
        為了保證進程的獨立性,在內核態時,需要執行寫前檢測。[(verify_area(void &*addr, int size)) ],由於在實行寫前驗證是通過調用write_verify() 實現的,而對於該函數是以頁為單位的,所以在verify_area需要得到addr所在的頁面的首地址。

verify_area

 

void verify_area(void * addr,int size)
{
    unsigned long start;

    start = (unsigned long) addr;
// 由於start調整,size的大小也會變大
    size += start & 0xfff;
// start調整為所在頁的起始地址
    start &= 0xfffff000;
// 這里的size是邏輯地址,線性地址需要加上段的起始地址,ldt[2]表示數據和堆棧段
    start += get_base(current->ldt[2]);
// 下面循環驗證頁,如果不可寫,則復制頁面
    while (size>0) {
        size -= 4096;
        write_verify(start);
        start += 4096;
    }
}

 

verify_area驗證內存的起始位置和范圍的調整示意圖。主要是因為內存是以頁為單位操作的

write_verify
void write_verify(unsigned long address)
{
    unsigned long page;
// 判斷頁目錄項是否存在,這里為啥移動20,而不是22?下面解釋
    if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
        return;
    page &= 0xfffff000;
// 頁表地址
    page += ((address>>10) & 0xffc);
// 頁面不可寫,寫時復制
    if ((3 & *(unsigned long *) page) == 1)  /* non-writeable, present */
        un_wp_page((unsigned long *) page);
    return;
}
 
        
  • 分頁機制
page = *((unsigned long *) ((address>>20) & 0xffc 要看懂這句,需要回顧一下分頁機制

看上圖,正常來說右移22位正好是目錄號,移動20位是什么鬼?下面要搞清楚兩個方面:
  • 目錄號是從1開始的,不能從0開始
  • 目錄號和指向目錄的指針之間有個聯系,實際上,看下圖目錄表從0x0000開始,可以一個指針占4個字節,目錄號*4剛好等於指針地址,也是目錄號左移動2位,這個等於 (address>>20) & 0xffc

 

  •  頁表項

 

 P--位0是存在(Present)標志,用於指明表項對地址轉換是否有效。P=1表示有效;P=0表示無效。在頁轉換過程中,如果說涉及的頁目錄或頁表的表項無效,則會導致一個異常。如果P=0,那么除表示表項無效外,其余位可供程序自由使用,如圖4-18b所示。例如,操作系統可以使用這些位來保存已存儲在磁盤上的頁面的序號。

R/W--位1是讀/寫(Read/Write)標志。如果等於1,表示頁面可以被讀、寫或執行。如果為0,表示頁面只讀或可執行。當處理器運行在超級用戶特權級(級別0、1或2)時,則R/W位不起作用。頁目錄項中的R/W位對其所映射的所有頁面起作用。
U/S--位2是用戶/超級用戶(User/Supervisor)標志。如果為1,那么運行在任何特權級上的程序都可以訪問該頁面。如果為0,那么頁面只能被運行在超級用戶特權級(0、1或2)上的程序訪問。頁目錄項中的U/S位對其所映射的所有頁面起作用。
A--位5是已訪問(Accessed)標志。當處理器訪問頁表項映射的頁面時,頁表表項的這個標志就會被置為1。當處理器訪問頁目錄表項映射的任何頁面時,頁目錄表項的這個標志就會被置為1。處理器只負責設置該標志,操作系統可通過定期地復位該標志來統計頁面的使用情況。
D--位6是頁面已被修改(Dirty)標志。當處理器對一個頁面執行寫操作時,就會設置對應頁表表項的D標志。處理器並不會修改頁目錄項中的D標志。
AVL--該字段保留專供程序使用。處理器不會修改這幾位,以后的升級處理器也不會。

 

所以(3 & *(unsigned long *) page) == 1這里是判斷R/W標志位是否為0,為0則表示只讀,這個是父進程的數據段,需要復制

 

參考:

  1. 0.11 內核 在內核態上如何實現‘寫時復制’ verify_area write_verify
  2. 《linux 內核完全剖析》上帝為什么是右移20,而不是22! dir = (unsigned long *) ((from>>20) & 0xffc)
  3. Linux 內核完全注釋


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM