程序的一生 請求調頁篇

2021-07-06 08:49:54 字數 4804 閱讀 9687

在講記憶體之前有幾個很重要的結構體簡單分析以下。

1、mm_struct結構體:程序記憶體描述符結構體,程序的task_struct的mm和active_mm結構體指向該程序的記憶體描述符結構體(普通程序的這兩個欄位是相等的,核心執行緒的mm欄位是null(執行緒沒有自己的線性位址空間),active__mm是指向上乙個執行的核心執行緒)。主要欄位有:線性區鍊錶的頭結點,線性區紅黑樹的根(線性區採用紅黑樹和),全域性頁表,線性區的個數,主計數器,次計數器,程式各個段的起始位址和最後位址等等。

2、vm_area_struct:描述線性區結構體。程序的線性位址空間是用乙個個的線性區組織起來(組織的方式有紅黑樹和雙向鍊錶,線性區定義中有next,prev和rb_node)。主要欄位有:vm的起始位址,vm的最後位址,擁有這個vm的程序mm_struct和vm的操作函式(全部是鉤子函式)。

linux記憶體管理採用四級頁表,pgd(頁全域性目錄),pud(頁上級目錄),pmd(頁中間目錄),pte(頁表)。

1、為什麼會有請頁

linux核心認為自己的所有任務都是很緊急的,前面分析排程函式我們已經了解了核心態所有的任務都比使用者態任務優先順序高(還記得嗎?全域性就緒佇列會優先掃瞄實時程序,然後是普通程序,最後是空閒程序)。程序的空間管理類似,核心認為自己的所有要求都是不可推遲的,核心要求的空間會直接分配,但是對於使用者態的任務會採用延遲處理的方式,剛剛產生的新程序並沒有自己的空間,而是去共享父程序的頁框,當子進**的需要乙個頁面的時候(exec或者對共享頁框執行寫操作等一些不能再延遲的時候),會產生乙個異常,啟動請頁機制。所以使用者態程序的記憶體分配基本上都會觸發請頁機制。

在do_fork函式中,新建立的子程序會呼叫函式copy_mm複製父程序的記憶體管理等部分,下面來簡單看一下函式copy_mm的部分實現**:

oldmm = current->mm;

if (clone_flags & clone_vm)

mm = dup_mm(tsk); //else process do this

tsk->mm = mm;

tsk->active_mm = mm;

tsk是新建立的程序。不難看出,新建立的子程序是完全複製了父程序的mm。

2、linux怎麼處理缺頁

如果子程序執行了修改頁內容(或者執行exec),這時候會引發乙個缺頁異常,呼叫缺頁異常處理程式do_page_fault。部分**如下:

if (!mm || in_atomic())

goto no_context;

if (address >= task_size)

goto vmalloc_fault;

retry:

down_read(&mm->mmap_sem);

vma = find_vma(mm, address);

if (!vma)

goto bad_area;

if (vma->vm_start <= address)

goto good_area;

...good_area:

... //出於安全,進行vma的許可權判斷

fault = handle_mm_fault(mm, vma, address, flags);

if ((fault & vm_fault_retry) && fatal_signal_pending(current))

return;

if (flags & fault_flag_allow_retry)

up_read(&mm->mmap_sem);

return ;

}

如果正在執行原子操作或者正在執行核心執行緒(核心執行緒的mm為null),函式不執行退出。如果虛擬位址超出了4gb(程式訪問了不可訪問的位址),跳到錯誤處理。獲取讀訊號量(缺頁異常的處理都是在讀訊號量中執行的),找出距離給定位址最近的乙個線性區(vma描述);如果沒有找到,轉到錯誤處理;該如果線性區的起始位址低於給定的位址,開始執行:判斷vma的許可權,呼叫函式handle_mm_fault(分析見後文),結果儲存在fault中。當處理缺頁失敗需要重來並且current(引發缺頁的程序)未決訊號集不空,轉去執行current的未決訊號;未決訊號集不空,重新處理缺頁。處理缺頁成功時候,開啟讀鎖,返回。

上述是do_page_fault的全部邏輯過程,很容易看出處理缺頁被封裝在了函式handle_mm_fault中。函式handle_mm_fault**如下(函式的三個引數分別是:引發缺頁程序的mm_struct,在do_page_fault函式中找到的vma,給定的位址address和flag):

int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,unsigned long address, unsigned int flags)

更新current程序的狀態為就緒態(缺頁異常處理的時候是等待態),進行一些標誌位判斷,呼叫函式__handle_mm_fault獲得物理頁框並返回。顯然,我們的重點是這個函式,函式**如下(引數分別是程序的mm_struct,在函式do_page_fault中找到的vma,指定位址和flags):

static

int __handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,unsigned

long address, unsigned

int flags)

獲取頁全域性目錄,申請新的頁上級目錄和頁中間目錄(不知道為什麼,但是這裡就是做了這些操作),得到address在頁表中的對應位址,執行handle_pte_fault函式(核心在這個函式裡具體分析引發缺頁的原因進而執行重新建立對映或者分配新頁框,很重要的函式)。函式**如下:

static

int handle_pte_fault(struct mm_struct *mm,struct vm_area_struct *vma, unsigned

long address,pte_t *pte, pmd_t *pmd, unsigned

int flags)

entry = pte_mkyoung(entry);

}

如果頁不在記憶體中並且從未被程序訪問過,如果線性區的操作函式不為空執行了***(好像是建立乙個新的對映,不懂為什麼要這麼做),如果為空則從未分配頁框,核心執行do_anonymous_page函式(分析見後文)分配乙個新頁框。讀頁面的髒位,如果髒位被置1(此時頁不在記憶體中),執行非線性檔案對映(不是本文的重點,不分析),如果髒位為0,頁面被置換出去了所以呼叫函式do_swap_page把該頁換進記憶體。在解釋後邊**之前來稍微提一下寫時複製機制。

3、寫時複製的實現

在copy_mm函式實現中我們了解了linux採用寫時複製機制,所以每乙個頁都需要有讀寫保護。當有程序試圖對具有寫保護的頁進行寫操作的時候,核心會分配乙個新的頁框然後複製共享頁框的內容。

我們接著來分析這個函式。啟動自旋鎖,如果程序要執行寫操作但是這頁有寫保護(pte_write讀取頁的write許可權)轉去執行do_wp_page(實現cow,好奇怪這個函式我找不到???為什麼找不到還把這個**寫上去了);如果沒有寫保護,置頁的dirty位。最後置頁的訪問標誌。

通過上邊的分析,我們發現handle_pte_fault函式的大部分功能是檢視頁的標誌位識別出來什麼原因引起了缺頁異常,然後轉到各自的處理函式。下面是對各個處理函式的分析。重新建立對映(略),把外存的也交換進記憶體(do_swap_page,在檔案那一部分分析)。do_numa_page(在記憶體管理那一部分會分析),所以在這裡我們主要分析乙個匿名分配函式。

do_anonymous_page函式(終於要到了分配頁框的時候了),**如下(函式的引數分別是,程序的mm_struct,找到的vma,指定位址,頁表,頁中間目錄和標誌位):

static

int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,unsigned

long address, pte_t *page_table, pmd_t *pmd,unsigned

int flags)

}page = alloc_zeroed_user_highpage_movable(vma, address);

...entry = mk_pte(page, vma->vm_page_prot);

if (vma->vm_flags & vm_write)

entry = pte_mkwrite(pte_mkdirty(entry));

page_table = pte_offset_map_lock(mm, pmd, address, &ptl);

page_add_new_anon_rmap(page, vma, address);

update_mmu_cache(vma, address, page_table);

如果程序只要求了讀頁,並不用分配頁框,直接跳去解鎖。分配新的頁面,建立乙個新的頁表項,如果線性區(在do_page_fault函式中找到的)允許寫操作,設定新頁的髒位和寫標誌,產生頁表項的線性位址。為新的匿名頁增加頁表對映,更新記憶體管理單元。

說兩點:

1、原始碼中錯誤處理要比真正實現功能的**多得多。

2、記憶體這部分還挺有意思的,東西也聽過的,本文只是寫了請頁,還有位址轉換、頁面分配和頁面**。

3、看函式之前如果已經明白了一些比較重要的結構,**看起來就容易多了。

乙個web請求的一生

之前看過一些這方面的資料,有幾個博主寫的很不錯,但是側重點不太一樣。我從自己的理解把內容總結一下,主要是方便自己記憶和理解。先假設請求的連線是 http localhost 8080 wcc index.jsp 請求從web到容器tomcat 如果是網域名稱訪問,那麼會有乙個尋找對應ip的過程 客戶...

一生的朋友

邵dd 和我的對話 雖然他人比我小,可做人做事的態度絕對值得我學習 望他一切皆好 我需要審視我人生做人做事的態度了 不能懶惰 不能消極 做事前要考慮下事情的目的 意義 方法 權衡下值不值得做 邵dd says 17 29 好好看書呀,自己的東西要全部明白,不然你的 就白做了 凌零0 says 17 ...

《奇特的一生》

這是一部寫真人真事的文獻性 講的是蘇聯昆蟲學家柳比歇夫獻身科學的故事。從 1916 年元旦開始,二十六歲的柳比歇夫便實行一種 時間統計法 他每天都要核算自己的時間,一天一小結,每月一大結,年終一總結。直到 1972 年他去世的那一天,56 年如一日,從不間斷。柳比歇夫在短促的一生中取得了豐碩的科學成...