標籤:

內核頁表和linux的夥伴系統是不是有衝突?

linux的進程地址空間裡面3G到4G的區間是內核空間,其中偏移0到892M的地方是內核的線性映射區。假如我的板子內存只有128M它是如何映射的,如果它把這128m的物理內存都映射了,那麼進程要malloc的時候,夥伴系統改怎麼給這個進程分配物理地址呢?如果內核的線性映射又重新分配給了malloc的地址,這樣就衝突了呀? @wheeler


哈哈,我做面試官時,遇到對Linux kernel 內存子系統理解較深的同學,我基本都會問這個問題,看看他們的思考能力。我想說的是:

  1. 映射跟分配是兩回事,他們是不相關的。
  2. 但是如果能做到 分配後才映射,釋放後就解映射,從運行安全的角度來說是最好的,可惜這個很難做到。

題主的問題是集中在第二點上。

先看第一個問題:映射跟分配是兩回事,他們是不相關的

整個內核,由buddy管理演算法來管理所有物理頁,只有經過它分配出去的物理頁,OS和應用程序才能使用。

映射是指建好頁表,即某塊虛擬地址空間物理地址空間的映射,有了這個映射,處理器就可以訪問這塊虛擬地址空間。

所以,分配是管哪些物理地址可使用,而映射是管哪些虛擬地址可訪問

那麼問題來了:如果某個物理頁沒有被分配出來,但已有某個虛擬地址映射到這個物理頁上,怎麼辦?會不會出問題呢?這就是內核lowmem給大家造成的疑惑。

再看第二個問題:但是如果能做到 分配後才映射,釋放後就解映射,從運行安全的角度來說是最好的,可惜這個很難做到

我們先看高端內存(即highmem),請大家要注意概念,高端內存是指物理地址空間超過896M的內存地址,跟虛擬空間上的(3G+896M ~ 4G]沒有任何關係。

高端內存是滿足分配後才映射,釋放後就解映射 這個原則的,這個空間主要給用戶態進程(比如用戶態代碼段,數據段,棧,堆,匿名映射)或者用戶態業務相關功能使用(訪問文件產生的pagecache)。

但是lowmem卻不是這樣的。總結起來有兩個原因:

1) 先有雞還有先有蛋

虛擬地址到物理地址的映射關係,要寫到頁表裡面,如果這個頁表所在的址物理沒有映射到虛擬地址,處理器是通過虛擬地址來修改頁表的,但在MMU模式下,處理器只能使用虛擬地址來訪問任何物理內存。為了解決這個矛盾,需要在進入保護模式(X86架構)/MMU模式(ARM架構)之前,先人為規划出一個固定映射,當然這個映射越簡單越好,就是我們常說的線性映射。

2)固定映射應該是多大

從道理上講,kernel必須的東西:比如代碼段,數據段,page結構數組,缺頁函數所涉及的訪問地址空間,都要在這個預先規划出來的固定映射空間內,才能保證kernel功能的正常運行作。後續內核根據代碼邏輯要求,動態申請內存時,建好頁表就可以了。但是內核作為整個系統的管理者,它的性能必須高,不能成為系統的瓶頸;用戶在運行過程中,內核態必須創建很多管理對象,才能將用戶態業務管理好。比如task_struct, mm, vma,file, inode等關鍵數據結構。內核如果在創建這些對象時,從buddy裡面分配內存(當然中間是通過slub來分配的),然後才建立映關係,那麼性能必然會比較差。所以內核在運行過程中,動態創建的管理對象所在的內存空間,必須提前映射好。這個區域就是lowmem zone。

kernel運行過程中需要動態分配的管理對象,都是從lowmem zone裡面分配的,這個zone的性能比highmem zone優,因為頁表已提前建好的。

內核1G的虛擬空間里,使用896M空間做為lowmem空間,即固定映射空間(線性映射區)。內核使用1G的7/8空間大小作為線性區,剩下的1/8作為臨時映射區,用於訪問高端內存。

最後,回到題主的問題:如果整系統只有128M內存,情況會怎麼樣?

如果系統只有128M內存,那麼系統沒有highmem zone,128M都屬於lowmem,kernel初始化時,會將這128M內存映射到[3G, 3G + 128M)虛擬空間上。

如果惡意寫個KO,可以直接訪問這128M內存空間的,因為頁表已經建立了,從處理器層面來講是可訪問的。

用戶態調用malloc做內存申請時,最終會通過系統調用mmap向kernel申請內存,kernel最終(缺頁過程完成後)會為進程分配虛擬內存和物理內存。而這個物理內存就是這128M內存中,還沒有分配的物理內存,但這塊物理內存早已被映射到[3G, 3G + 128M)空間上了。但如果內核工作正常,由於這塊物理內存沒有被分配給內核態使用,那內核態就沒有指針 指向 訪物理內存對應的內核態虛擬地址空間,即內核態是不會訪這塊物理內存的。

在128M物理內存場景下,用戶態進程之間頁表是獨立的,不可能相互踩,同時用戶態是無法踩內核態內存的。由於只有lowmem區,理論上內核態能踩用戶態內存,但經上一段分析,概率較低,只有編程錯誤時才會發生。

對於64位 kernel,沒有highmem了,只有lowmem,所有物理內存都映射到lowmem了。與32位系統物理內存小於896M場景是一模一樣的。


LINUX每個進程的地址空間是虛擬的,也就是說,我應用程序的可用的地址空間在32位系統上是0x00000000到0xffffffff一共4gb而不是實際128mb內存地址範圍,因此Linux進程內存的劃分實際上是基於0-4gb虛擬地址的。如果一個程序大於128mb而內存不夠用時,操作系統會通過缺頁機制將不在內存中的頁面通過內存管理單元(mmu)將虛擬地址轉換成物理地址並將其從外存載入進來,並且把內存中不需要的頁面給置換出去(由操作系統的頁面置換演算法決定)。因此就能做到進程的能使用的地址比實際內存大這一種「假象」了。

能做到這一點的原因是因為一般的高性能cpu上有一個內存管理單元(mmu),這個硬體模塊保存了操作系統給它設置的頁表和頁目錄表(TLB)並且記錄頁面使用情況,而頁表的作用就是進行進程虛擬地址到內存物理地址的轉換。MMU會把把進程給出的虛擬地址在TLB(裡面保存了頁表)中進行查找,如果找到頁面在內存中,則直接將頁面從內存(或高速緩存)中取出。如果找到頁面不在內存中,則發生缺頁,將外存中的頁面置換到內存中。在老的CPU里,可能會因為TLB不夠大而只能起到頁表緩存作用。現代計算機由於內存足夠大,操作系統一般會更多的在內存中cache頁面,而不是指望缺頁機制進行置換。

夥伴系統是一種內存頁面管理方法,為的是減小動態分配內存所造成的內存碎片化,提高內存利用率。因為操作系統管理物理內存本質上是以頁面的形式進行管理的,當內存中有足夠空閑多的頁面時(內存有空間),它就用夥伴系統管理這些空閑的頁面,當應用程序進行malloc,free時,實質上是要通過系統調用來申請/釋放內存頁面的,這時候操作系統就通過夥伴演算法來進行頁面的分配和釋放。你如果非要malloc一個非常大的空間,導致內存不夠了,那就只好觸發缺頁機制將暫時用不到的頁面置換出去咯。還有,Linux在管理更大塊的內存的時候實際上是用slab的方法進行管理的,這個就不多說了


首先要分清楚,映射和分配內存的區別。 以ARMv7舉例吧,

如果是128M的內存,都映射到了內核的邏輯地址空間,表明內核是可以通過頁表去訪問這些內存的。但是並不代表內核擁有全部這些內存。(內核可以去讀寫任一地址,但在邏輯上就是越界訪問了)內核的處理需要內存時,也需要申請的。直接或者間接申請和使用了page。

用戶空間在malloc時,libc的實現中會調用brk之類的系統調用,先擴大該進程的虛擬地址範圍(進程是有自己的頁表的,這個應該都知道吧)。當真正訪問到這片虛地址時,會觸發缺頁,進而在內核中申請page,然後映射到這個虛擬地址空間。進程退出的時候又會回收這些資源。

所以內核和用戶進程都是通過page申請來獲取內存的,進程申請的內存,確實會導致同一個物理page存在兩個頁表對其映射。這也是copy_from_usr能夠簡單實現的原理吧。

個人理解,供參考。


這是ARM32才有的高端和低端內存,這是為了解決虛擬地址不夠的問題

你的問題跟ARM64下 4G內存都有線性映射一樣,內核只是映射了地址,並沒有使用,用戶態申請到虛擬地址後 在寫時發生缺頁異常,才分配到真實物理地址,並建立用戶態的映射表


不懂就別裝,你知不知道android的內核也是linux?ios的內核和android的內核也是同源於unix?他們是不是也不好意思說自己是兩個操作系統?


推薦閱讀:

諸如 __u32 __u16 __u8 這類定義主要適用於什麼情況?
學習操作系統的知識,看哪本書好?
C語言里,main 函數中 return x和 exit(x) 到底有什麼區別 ?
為何linux作為伺服器端十年不重啟都不卡而安卓用半年就十分卡?
2017年6月19-20日在北京舉行的 LinuxCon 會議有哪些看點?

TAG:Linux內核 |