天天看點

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

                                Linux記憶體管理架構圖

一、頁框管理

1.1. 頁框的定義和資料結構

  核心以頁框為基本機關管理實體記憶體,分頁單元中,頁指一組資料,而存放這組資料的實體記憶體就是頁框,當這組資料被釋放後,若有其他資料請求通路此記憶體,那麼頁框中的頁将會改變。

  核心必須記錄每個頁框目前的狀态。如,核心必須能區分哪些頁框包含的是屬于進展的也,哪些頁框包含的是核心代碼或者核心資料。同理,核心還必須能确認動态記憶體中頁框是否空閑。這種狀态資訊被儲存在一個描述符數組中,每個頁框對應數組中的一個元素,這種類型為sruct page描述符

  struct page是記憶體管理中第三個重要的資料結構,它代表系統記憶體的最小機關。(記憶體管理中重要的資料結構參考https://blog.csdn.net/melody157398/article/details/107241034)

  • 核心用struct page結構表示系統中的每個實體頁,該結構位于<linux/mm_types.h>中——下面給出了一個簡略版的定義,去除了兩個容易混淆我們讨論主題的聯合結構體:
    linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

    因為核心會為每一個實體頁幀建立一個struct page的結構體,是以要保證page結構體足夠的小,否則僅struct page就要占用大量的記憶體。

    出于節省記憶體的考慮,struct page中使用了大量的聯合體union。下面僅對常用的一些的字段做說明:

  • flags:描述page的狀态和其他資訊
  • _count域存放頁的引用計數——也就是這一頁被引用了多少次
  • virtual域是頁的虛拟位址。通常情況下,它就是頁在虛拟記憶體中的位址
  • 須要了解的一點是page結構與實體頁相關,而并非與虛拟頁相關。是以,該結構對頁的描述隻是短暫的。即使頁中所包含的資料繼續存在,由于交換等原因,它們也可能并不再和同一個page結構相關聯。核心僅僅用這個資料結構來描述目前時刻在相關的實體頁中存放的東西。 這種資料結構的目的在于描述實體記憶體本身,而不是描述包含在其中的資料
  • 核心用這一結構來管理系統中所有的頁,因為核心需要知道一個頁是否空閑(也就是頁有沒有被配置設定)。如果頁已經被配置設定,核心還需要知道誰擁有這個頁。擁有者可能是使用者空間程序、動态配置設定的核心資料、靜态核心代碼或頁高速緩存等
  • 系統中的每個實體頁都要配置設定一個這樣的結構體,開發者常常對此感到驚訝。他們會想 “這得浪費多少記憶體呀”!讓我們來算算對所有這些頁都這麼做,到底要消耗掉多少記憶體。就算struct page占40個位元組的記憶體吧,假定系統的實體頁為8KB大小,系統有4GB實體記憶體。那麼, 系統中共有頁面 524288個,而描述這麼多頁面的page結構體消耗的記憶體隻不過20MB:也許絕對值不小,但是相對系統4GB記憶體而言,僅是很小的一部分罷了。是以,要管理系統中這麼多實體頁面,這個代價并不算太大

https://www.cnblogs.com/zhaoyl/p/3695517.html

https://zhuanlan.zhihu.com/p/67059173

1.2.實體位址的空間分布

LInux下用夥伴系統管理實體記憶體頁

夥伴系統得益于其良好的算法,一定程度上可以避免外部碎片為何這麼說?先回顧下Linux下虛拟位址空間的分布。

x86的實體位址空間布局:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

針對不同的用途,Linux核心将所有的實體頁面劃分到3類記憶體管理區中,如圖,分别為ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。

ZONE_DMA 記憶體開始的16MB

ZONE_NORMAL 16MB~896MB

ZONE_HIGHMEM 896MB ~ 結束

  • ZONE_DMA+ZONE_NORMAL屬于直接映射區:虛拟位址=3G+實體位址 或 實體位址=虛拟位址-3G,從該區域配置設定記憶體不會觸發頁表操作來建立映射關系。
  • ZONE_HIGHMEM屬于動态映射區:128M虛拟位址空間可以動态映射到(X-896)M(其中X位實體記憶體大小)的實體記憶體,從該區域配置設定記憶體需要更新頁表來建立映射關系,vmalloc就是從該區域申請記憶體,是以配置設定速度較慢。
直接映射區的作用是為了保證能夠申請到實體位址上連續的記憶體區域,因為動态映射區,會産生記憶體碎片,導緻系統啟動一段時間後,想要成功申請到大量的連續的實體記憶體,非常困難,但是動态映射區帶來了很高的靈活性(比如動态建立映射,缺頁時才去加載實體頁)。

  Linux系統在初始化時,會根據實際的實體記憶體的大小,為每個實體頁面建立一個page對象,所有的page對象構成一個mem_map數組。

1.3 linux虛拟位址核心空間分布

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

  由于ZONE_NORMAL和核心線性空間存在直接映射關系,是以核心會将頻繁使用的資料如kernel代碼、GDT、IDT、PGD、mem_map數組等放在ZONE_NORMAL裡。而将使用者資料、頁表(PT)等不常用資料放在ZONE_ HIGHMEM裡,隻在要通路這些資料時才建立映射關系(kmap())。比如,當核心要通路I/O裝置存儲空間時,就使用ioremap()将位于實體位址高端的mmio區記憶體映射到核心空間的vmalloc area中,在使用完之後便斷開映射關系。

1.4 linux虛拟位址使用者空間分布

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

使用者程序的代碼區一般從虛拟位址空間的0x08048000開始,這是為了便于檢查空指針。代碼區之上便是資料區,未初始化資料區,堆區,棧區,以及參數、全局環境變量。

linux 應用程式加載位址
0x08048000 (32)
0x00400000 (64)
           

1.5 linux虛拟位址與實體位址映射的關系

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

 Linux将4G的線性位址空間分為2部分,0~3G為user space,3G~4G為kernel space。

由于開啟了分頁機制,核心想要通路實體位址空間的話,必須先建立映射關系,然後通過虛拟位址來通路。為了能夠通路所有的實體位址空間,就要将全部實體位址空間映射到1G的核心線性空間中,這顯然不可能。于是,核心将0~896M的實體位址空間一對一映射到自己的線性位址空間中,這樣它便可以随時通路ZONE_DMA和ZONE_NORMAL裡的實體頁面;此時核心剩下的128M線性位址空間不足以完全映射所有的ZONE_HIGHMEM,Linux采取了動态映射的方法,即按需的将ZONE_HIGHMEM裡的實體頁面映射到kernel space的最後128M線性位址空間裡,使用完之後釋放映射關系,以供其它實體頁面映射。雖然這樣存在效率的問題,但是核心畢竟可以正常的通路所有的實體位址空間了。
  • LInux核心加載在ZONE_NORMAL區域

核心空間中存放的是核心代碼和資料,而程序的使用者空間中存放的是使用者程式的代碼和資料。不管是核心空間還是使用者空間,它們都處于虛拟空間中。讀者會問,系 統啟動時,核心的代碼和資料不是被裝入到實體記憶體嗎?它們為什麼也處于虛拟記憶體中呢?這和編譯程式有關,後面我們通過具體讨論就會明白這一點。

雖 然核心空間占據了每個虛拟空間中的最高1GB位元組,但映射到實體記憶體卻總是從最低位址(0x00000000)開始。對核心空間來說,其位址映射是很簡單 的線性映射,0xC0000000就是實體位址與線性位址之間的位移量,在Linux代碼中就叫做PAGE_OFFSET。

Linux核心高端記憶體的由來

參考:https://www.zhihu.com/question/280526042/answer/1616700795

(為什麼需要高端記憶體)

對于核心,直接映射時虛拟位址0xc0000003對應的實體位址為0x00000003,0xc0000004對應的實體位址為0x00000004(可以看下面的linux虛拟位址與實體位址映射的關系)

虛拟位址與實體位址有如下的對應關系:

實體位址 = 虛拟位址 – 0xC0000000(3G)

  如果按照上面所說的采用直接映射的方式,将核心1G的位址空間全部直接映射,就會發現核心隻能通路1GB的實體記憶體,但是實際上我們的實體記憶體,往往是8G、16G,甚至更高,那麼其他空間核心将無法通路和管控。是以必須要有一種靈活的方式,既減少開銷,同時又讓核心能夠通路全部的實體記憶體,Linux高端記憶體十分必要。

Linux 規定“核心直接映射空間” 最多映射 896M 實體記憶體~

高端記憶體就是幫助我們通路除了直接映射的896MB實體記憶體之外的其他記憶體空間。

核心是如何借助128MB高端記憶體位址空間是如何實作通路可以所有實體記憶體呢?

  在《深入了解LINUX核心》中介紹了,核心可以采用三種不同的機制将頁框映射到高端記憶體,分别叫做:永久記憶體映射臨時記憶體映射非連續記憶體配置設定當核心想通路高于896MB實體位址記憶體時,從0xF8000000 ~ 0xFFFFFFFF位址空間範圍内找一段相應大小空閑的虛拟位址空間,借用一會。借用這段虛拟位址空間,建立映射到想通路的那段實體記憶體(即填充核心PTE頁面表),臨時用一會,用完後歸還。這樣别人也可以借用這段位址空間通路其他實體記憶體,實作了使用有限的位址空間,通路所有所有實體記憶體。

通俗地講,"high memory"要解決的是32位下虛拟位址空間不足帶來的問題(而顯然,對64位系統這個問題就不存在了),因為32的實體記憶體最大時4G,但是64位時512G,已夠用

2. 32位系統和64位系統的差別

32位和64位是指CPU處理器一次能處理的最大位數

  • 對于32位,最大尋址空間2 ^32 = 4G(32位可以通過PAE,實體位址擴充,把32位線性位址轉換為36位實體位址,PAE就是為了通路大于4GB的RAM,線性位址仍然是32位,而實體位址是36位。)
  • 對于64位,原來應該是2^64 = 16EiB,但是系統用不到那麼大的空間,是以系統使用48位作為尋址,即2 ^ 48 = 256T

    c

位址映射

64位位址采用4層位址映射,如下圖:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

pgd、pud、pmd、pte各占了9位,加上12位的頁内index,共用了48位。即可管理的位址空間為248=256T。而在32位位址模式時,該值僅為232=4G。

另外64位位址時支援的實體記憶體最大為64T,見e820.c中MAX_ARCH_PFN的定義:

#define MAX_ARCH_PFN MAXMEM>>PAGE_SHIFT

其中MAXMEM為2^46,PAGE_SHIFT為12。

https://www.cnblogs.com/wuchanming/p/4756911.html

https://blog.csdn.net/abc3240660/article/details/81484984

位址空間

32位與64位具體位址分布如下圖:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別
  • 64位位址時将0x0000,0000,0000,0000 – 0x0000,7fff,ffff,f000這128T位址用于使用者空間。
  • 而0xffff,8000,0000,0000以上為系統空間位址。
  • 另外0xffff,8800,0000,0000 – 0xffff,c7ff,ffff,ffff這64T直接和實體記憶體進行映射,0xffff,c900,0000,0000 – 0xffff,e8ff,ffff,ffff這32T用于vmalloc/ioremap的位址空間。
  • 而32位位址空間時,當實體記憶體大于896M時(Linux2.4核心是896M,3.x核心是884M,是個經驗值),由于位址空間的限制,核心隻會将0~896M的位址進行映射,而896M以上的空間用做一些固定映射和vmalloc/ioremap。而64位位址時是将所有實體記憶體都進行映射。
  • 在64位系統中,核心空間的映射變的簡單了,因為這時核心的虛拟位址空間已經足夠大了,即便它要通路所有的實體記憶體,直接映射就是,不再需要ZONE_HIGHMEM那種動态映射機制了。

注:雖然現在64位系統支援最大256T虛拟位址,64T實體位址,但是實際上現在市面上并沒有這麼大的RAM,現在通用的個人電腦大多都是4G/8G/16G或以上的記憶體條

常見問題:

1、使用者空間(程序)是否有高端記憶體概念?

使用者程序沒有高端記憶體概念。隻有在核心空間才存在高端記憶體。使用者程序最多隻可以通路3G實體記憶體,而核心程序可以通路所有實體記憶體。

2、64位核心中有高端記憶體嗎?

目前現實中,64位Linux核心不存在高端記憶體,因為64位核心可以支援超過512GB記憶體。若機器安裝的實體記憶體超過核心位址空間範圍,就會存在高端記憶體。

3、使用者程序能通路多少實體記憶體?核心代碼能通路多少實體記憶體?

32位系統使用者程序最大可以通路3GB,核心代碼可以通路所有實體記憶體。

64位系統使用者程序最大可以通路超過512GB,核心代碼可以通路所有實體記憶體。

4、高端記憶體和實體位址、邏輯位址、線性位址的關系?

高端記憶體隻和實體位址有關系,和線性位址、邏輯位址沒有直接關系。

5、為什麼不把所有的位址空間都配置設定給核心?

若把所有位址空間都給記憶體,那麼使用者程序怎麼使用記憶體?怎麼保證核心使用記憶體和使用者程序不起沖突?

二、夥伴算法

2.1為什麼會有夥伴算法

通常情況下,一個進階作業系統必須要給程序提供基本的、能夠在任意時刻申請和釋放任意大小記憶體的功能,就像malloc 函數那樣,然而,實作malloc 函數并不簡單,由于程序申請記憶體的大小是任意的,如果作業系統對malloc 函數的實作方法不對,将直接導緻一個不可避免的問題,那就是記憶體碎片。

記憶體碎片就是記憶體被分割成很小很小的一些塊,這些塊雖然是空閑的,但是卻小到無法使用。随着申請和釋放次數的增加,記憶體将變得越來越不連續。最後,整個記憶體将隻剩下碎片,即使有足夠的空閑頁框可以滿足請求,但要配置設定一個大塊的連續頁框就可能無法滿足,是以減少記憶體浪費的核心就是盡量避免産生記憶體碎片。

針對這樣的問題,有很多行之有效的解決方法,其中夥伴算法被證明是非常行之有效的一套記憶體管理方法,是以也被相當多的作業系統所采用。

2.1 算法作用

參考:https://blog.csdn.net/wenqian1991/article/details/27968779

  它要解決的問題是頻繁地請求和釋放不同大小的一組連續頁框,必然導緻在已配置設定頁框的塊内分散了許多小塊的空閑頁面,由此帶來的問題是,即使有足夠的空閑頁框可以滿足請求,但要配置設定一個大塊的連續頁框可能無法滿足請求。

Linux 便是采用這著名的夥伴系統算法來解決外部碎片的問題。把所有的空閑頁框分組為 11 塊連結清單,每一塊連結清單分别包含大小為1,2,4,8,16,32,64,128,256,512 和 1024 個連續的頁框。對1024 個頁框的最大請求對應着 4MB 大小的連續RAM ,是以,夥伴算法最多一次能夠配置設定4M的記憶體空間。每一塊的第一個頁框的實體位址是該塊大小的整數倍。例如,大小為 16個頁框的塊,其起始位址是 16 * 2^12 (2^12 = 4096,這是一個正常頁的大小)的倍數。

下面通過一個簡單的例子來說明該算法的工作原理:

假設要請求一個256(129~256)個頁框的塊。算法先在256個頁框的連結清單中檢查是否有一個空閑塊。如果沒有這樣的塊,算法會查找下一個更大的頁塊,也就是,在512個頁框的連結清單中找一個空閑塊。如果存在這樣的塊,核心就把512的頁框分成兩等分,一般用作滿足需求,另一半則插入到256個頁框的連結清單中。如果在512個頁框的塊連結清單中也沒找到空閑塊,就繼續找更大的塊——1024個頁框的塊。如果這樣的塊存在,核心就把1024個頁框塊的256個頁框用作請求,然後剩餘的768個頁框中拿512個插入到512個頁框的連結清單中,再把最後的256個插入到256個頁框的連結清單中。如果1024個頁框的連結清單還是空的,算法就放棄并發出錯誤信号。

    簡而言之,就是在配置設定記憶體時,首先從空閑的記憶體中搜尋比申請的記憶體大的最小的記憶體塊。如果這樣的記憶體塊存在,則将這塊記憶體标記為“已用”,同時将該記憶體配置設定給應用程式。如果這樣的記憶體不存在,則作業系統将尋找更大塊的空閑記憶體,然後将這塊記憶體平分成兩部分,一部分傳回給程式使用,另一部分作為空閑的記憶體塊等待下一次被配置設定。

下面通過一個例子,來深入地了解一下夥伴算法的真正内涵(下面這個例子并不嚴格表示Linux 核心中的實作,是闡述夥伴算法的實作思想):

假設系統中有 1MB 大小的記憶體需要動态管理,按照夥伴算法的要求:需要将這1M大小的記憶體進行劃分。這裡,我們将這1M的記憶體分為 64K、64K、128K、256K、和512K 共五個部分,如下圖 a 所示

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別
  • 1.此時,如果有一個程式A想要申請一塊45K大小的記憶體,則系統會将第一塊64K的記憶體塊配置設定給該程式(産生内部碎片為代價),如圖b所示;
  • 2.然後程式B向系統申請一塊68K大小的記憶體,系統會将128K記憶體配置設定給該程式,如圖c所示;
  • 3.接下來,程式C要申請一塊大小為35K的記憶體。系統将空閑的64K記憶體配置設定給該程式,如圖d所示;
  • 4.之後程式D需要一塊大小為90K的記憶體。當程式提出申請時,系統本該配置設定給程式D一塊128K大小的記憶體,但此時記憶體中已經沒有空閑的128K記憶體塊了,于是根據夥伴算法的原理,系統會将256K大小的記憶體塊平分,将其中一塊配置設定給程式D,另一塊作為空閑記憶體塊保留,等待以後使用,如圖e所示;
  • 5.緊接着,程式C釋放了它申請的64K記憶體。在記憶體釋放的同時,系統還負責檢查與之相鄰并且同樣大小的記憶體是否也空閑,由于此時程式A并沒有釋放它的記憶體,是以系統隻會将程式C的64K記憶體回收,如圖f所示;
  • 6.然後程式A也釋放掉由它申請的64K記憶體,系統随機發現與之相鄰且大小相同的一段記憶體塊恰好也處于空閑狀态。于是,将兩者合并成128K記憶體,如圖g所示;
  • 7.之後程式B釋放掉它的128k,系統也将這塊記憶體與相鄰的128K記憶體合并成256K的空閑記憶體,如圖h所示;
  • 8.最後程式D也釋放掉它的記憶體,經過三次合并後,系統得到了一塊1024K的完整記憶體,如圖i所示

有了前面的了解,我們通過Linux 核心源碼(mmzone.h)來看看夥伴算法是如何實作的:

夥伴算法管理結構

#define MAX_ORDER 11
 
struct zone {
  ……
	struct free_area	free_area[MAX_ORDER];
	……
} 
 
struct free_area {
	struct list_head	free_list[MIGRATE_TYPES];
	unsigned long		nr_free;//該組類别塊空閑的個數
};
           

前面說到夥伴算法把所有的空閑頁框分組為11塊連結清單,記憶體配置設定的最大長度便是2^10頁面。

上面兩個結構體向我們揭示了夥伴算法管理結構。zone結構中的free_area數組,大小為11,分别存放着這11個組,free_area結構體裡面又标注了該組别空閑記憶體塊的情況。

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

将所有空閑頁框分為11個組,然後同等大小的串成一個連結清單對應到free_area數組中。這樣能很好的管理這些不同大小頁面的塊。

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

三、記憶體區管理

     夥伴系統系統算法采用頁框作為基本記憶體區,這适合對于大塊記憶體的請求,但我們處理對小記憶體區的請求呢,比如說幾十或者幾百個位元組?

     顯然,如果為了存放很小的位元組而給他配置設定一個整頁框,這顯然是一種浪費。取而代之的正确方法是引入一種新的資料結構來描述同一頁框中如何配置設定小記憶體區。但這樣也引入了一個新的問題,即所謂的記憶體碎片,記憶體碎片的産生主要是由于請求記憶體的大小與配置設定給它的大小不比對而造成的。

https://blog.csdn.net/ibless/article/details/81367700

内部碎片和外部碎片

     外部碎片指的是還沒有被配置設定出去(不屬于任何程序)但由于太小而無法配置設定給申請記憶體空間的新程序的記憶體空閑區域。外部碎片是除了任何已配置設定區域或頁面外部的空閑存儲塊。這些存儲塊的總和可以滿足目前申請的長度要求,但是由于它們的位址不連續或其他原因,使得系統無法滿足目前申請。簡單示例如下圖:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

     如果某程序現在需要向作業系統申請位址連續的32K記憶體空間,注意是位址連續,實際上系統中目前共有10K+23K=33K空閑記憶體,但是這些空閑記憶體并不連續,是以不能滿足程序的請求。這就是所謂的外部碎片,造成外部碎片的原因主要是程序或者系統頻繁的申請和釋放不同大小的一組連續頁框。Linux作業系統中為了盡量避免外部碎片而采用了夥伴系統(Buddy System)算法。

     内部碎片就是已經被配置設定出去(能明确指出屬于哪個程序)卻不能被利用的記憶體空間;内部碎片是處于區域内部或頁面内部的存儲塊,占有這些區域或頁面的程序并不使用這個存儲塊,而在程序占有這塊存儲塊時,系統無法利用它。直到程序釋放它,或程序結束時,系統才有可能利用這個存儲塊。簡單示例如下圖:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

某程序向系統申請了3K記憶體空間,系統通過夥伴系統算法可能配置設定給程序4K(一個标準頁面)記憶體空間,導緻剩餘1K記憶體空間無法被系統利用,造成了浪費。這是由于程序請求記憶體大小與系統配置設定給它的記憶體大小不比對造成的。由于夥伴算法采用頁框(Page Frame)作為基本記憶體區,适合于大塊記憶體請求。在很多情況下,程序或者系統申請的記憶體都是4K(一個标準頁面)的,依然采用夥伴算法必然會造成系統記憶體的極大浪費。為滿足程序或者系統對小片記憶體的請求,對記憶體管理粒度更小的SLAB配置設定器就産生了。(注:Linux中的SLAB算法實際上是借鑒了Sun公司的Solaris作業系統中的SLAB模式)

3.1 SLAB配置設定器

參考:https://zhuanlan.zhihu.com/p/61457076

參考:https://www.jianshu.com/p/95d68389fbd1

3.1.1 為什麼Linux記憶體管理要用到slab配置設定器

我們知道Linux記憶體以頁為機關進行記憶體管理,buddy算法以2的n次方個頁面來進行記憶體配置設定管理,最小為2^ 0, 也就是一頁,最大為2^10,成就是4MB大小的連續記憶體空間。但是頁的粒度還是太大,Linux下是4KB大小,也就是4096個位元組,而kernel本身有很多資料結構時時刻刻都需要配置設定或者釋放,這些資料的大小又往往小于4KB大小,一般隻有幾個幾十個位元組這樣的大小。比方最常用到的task_struct結構體和mm_struct結構體,我們可以自己用下面程式測試一下它多大,可能不同機器上會有不同的結果。

#include<linux/slab.h>
#include<linux/module.h>
#include<linux/sched.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("");

int __init task_init(void)
{
    struct task_struct p;
    struct mm_struct m;
    printk("sizeof task_struct = %ld,sizeof mm_struct = %ld\n",sizeof(p),sizeof(m));
return 0;
}

void __exit task_exit(void)
{
return;
}

module_init(task_init);
module_exit(task_exit);
           
sizeof task_struct = 9152,sizeof mm_struct = 2064

task_struct稍微大一點将近2個頁面,mm_struct就隻有差不多半個頁面了。這樣一來如果所有的這些資料結構都按照頁來配置設定存儲和管理,那麼我相信kernel過不了多久自己就玩完了,記憶體碎片肯定一堆一堆。是以,引入slab配置設定器是為了彌補記憶體管理粒度太大的不足。

3.1.2 它能解決什麼問題?

slab配置設定需要解決的是記憶體的内部碎片問題

3.1.3 它的核心思想是什麼?

答案是使用對象的概念來管理記憶體

什麼叫對象?這裡的對象就是指具體相同的資料結構和大小的某個記憶體單元。比方上面所說的mm_struct結構體。我們知道,核心每建立一個程序時就需要給其配置設定mm_struct,這樣一來核心中需要維護這個結構體的數目是相當多的,如果全部使用buddy配置設定器來配置設定,那麼将會産生大量的碎片。而且,這種結構體在核心中配置設定和釋放的頻率是很高的,每配置設定一次又需要對它初始化,用過以後又需要釋放,對系統的性能影響也很大。

這種場景是非常多的,為了應對這種場景,slab為這樣的對象建立一個cache,即緩存。每個cache所占的記憶體區又被劃分多個slab,每個 slab是由一個或多個連續的頁框組成。每個頁框中包含若幹個對象,既有已經配置設定的對象,也包含空閑的對象。如下所示:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別
linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別
簡要分析下這個圖:kmem_cache是一個cache_chain的連結清單,描述了一個高速緩存,每個高速緩存包含了一個slabs的清單,這通常是一段連續的記憶體塊。存在3種slab:slabs_full(完全配置設定的slab),slabs_partial(部分配置設定的slab),slabs_empty(空slab,或者沒有對象被配置設定)。slab是slab配置設定器的最小機關,在實作上一個slab有一個貨多個連續的實體頁組成(通常隻有一頁)。單個slab可以在slab連結清單之間移動,例如如果一個半滿slab被配置設定了對象後變滿了,就要從slabs_partial中被删除,同時插入到slabs_full中去。

我們可以看出,核心對象的管理與使用者程序中的堆管理比較相似,核心問題均是:如何高效地管理記憶體空間,使得可以快速地進行對象的配置設定和回收并減少記憶體碎片。但是核心不能簡單地采用使用者程序的基于堆的記憶體配置設定算法,這是因為核心對其對象的使用具有以下特殊性:

  1. 核心使用的對象種類繁多,應該采用一種統一的高效管理方法。
  2. 核心對某些對象(如 task_struct)的使用是非常頻繁的,是以使用者程序堆管理常用的基于搜尋的配置設定算法比如First-Fit(在堆中搜尋到的第一個滿足請求的記憶體塊)和 Best-Fit(使用堆中滿足請求的最合适的記憶體塊)并不直接适用,而應該采用某種緩沖區的機制。
  3. 核心對象中相當一部分成員需要某些特殊的初始化(例如隊列頭部)而并非簡單地清成全 0。如果能充分重用已被釋放的對象使得下次配置設定時無需初始化,那麼可以提高核心的運作效率。
  4. 配置設定器對核心對象緩沖區的組織和管理必須充分考慮對硬體高速緩存的影響。
  5. 随着共享記憶體的多處理器系統的普及,多處理器同時配置設定某種類型對象的現象時常發生,是以配置設定器應該盡量避免處理器間同步的開銷,應采用某種 Lock-Free 的算法。

每個高速緩存通過kmem_cache結構來描述,這個結構中包含了對目前高速緩存各種屬性資訊的描述

3.1.4 slab配置設定器的API有哪些

下面看一下slab配置設定器的接口——看看slab緩存是如何建立、撤銷以及如何從緩存中配置設定一個對象的。

  • 一個新的kmem_cache通過kmem_cache_create()函數來建立:
struct kmem_cache *
kmem_cache_create( const char *name, size_t size, size_t align,
                   unsigned long flags, void (*ctor)(void*));
           
  • 撤銷一個kmem_cache則是通過kmem_cache_destroy()函數:
  • 從kmem_cache中配置設定一個對象:
  • 釋放一個對象的函數如下:

使用以上的API寫核心子產品,生成自己的slab高速緩存。

#include <linux/autoconf.h>
#include <linux/module.h>
#include <linux/slab.h>

MODULE_AUTHOR("wangzhangjun");
MODULE_DESCRIPTION("slab test module");

static struct kmem_cache  *test_cachep = NULL;
struct slab_test
{
    int val;
};
void fun_ctor(struct slab_test *object , struct kmem_cache  *cachep , unsigned long flags )
{
    printk(KERN_INFO "ctor fuction ...\n");
    object->val = 1;
}

static int __init slab_init(void)
{
    struct slab_test *object = NULL;//slab的一個對象
    printk(KERN_INFO "slab_init\n");
//注意:這個函數的第二個參數(對象的大小),是有上限和下限的
//4Byte<=object size <= 4M(即在4位元組到4M之間),如果不在這個範圍之内,會導緻核心崩潰
    test_cachep = kmem_cache_create("test_cachep",sizeof(struct slab_test)*3,0,SLAB_HWCACHE_ALIGN,fun_ctor);
    if(NULL == test_cachep) 
                return  -ENOMEM ;
    printk(KERN_INFO "Cache name is %s\n",kmem_cache_name(test_cachep));//擷取高速緩存的名稱
    printk(KERN_INFO "Cache object size  is %d\n",kmem_cache_size(test_cachep));//擷取高速緩存的大小
    object = kmem_cache_alloc(test_cachep,GFP_KERNEL);//從高速緩存中配置設定一個對象
    if(object)
    {
        printk(KERN_INFO "alloc one val = %d\n",object->val);
        kmem_cache_free( test_cachep, object );//歸還對象到高速緩存
        //這句話的意思是雖然對象歸還到了高速緩存中,但是高速緩存中的值沒有做修改
        //隻是修改了一些它的狀态。
        printk(KERN_INFO "alloc three val = %d\n",object->val);
            object = NULL;
        }else
            return -ENOMEM;
    return 0;
}

static void  __exit slab_clean(void)
{
    printk(KERN_INFO "slab_clean\n");
    if(test_cachep)
                kmem_cache_destroy(test_cachep);//調用這個函數時test_cachep所指向的緩存中所有的slab都要為空

}

module_init(slab_init);
module_exit(slab_clean);
MODULE_LICENSE("GPL");

           

我們結合結果來分析下這個核心子產品:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

這是dmesg的結果,可以發現我們自己建立的高速緩存的名字test_cachep,還有每個對象的大小。

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

還有構造函數修改了對象裡面的值,至于為什麼構造函數會出現這麼多次,可能是因為,這個函數被注冊了之後,系統的其他地方也會調用這個函數。在這裡可以分析源碼,當調用keme_cache_create()的時候是沒有調用對象的構造函數的,調用kmem_cache_create()并沒有配置設定slab,而是在建立對象的時候發現沒有空閑對象,在配置設定對象的時候,會調用構造函數初始化對象。

另外結合上面的代碼可以發現,alloc three val是在kmem_cache_free之後列印的,但是它的值依然可以被列印出來,這充分說明了,slab這種機制是在将某個對象使用完之後,就其緩存起來,它還是切切實實的存在于記憶體中。

再結合/proc/slabinfo的資訊看我們自己建立的slab高速緩存

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

可以發現名字為test_cachep的高速緩存,每個對象的大小(objsize)是16,和上面dmesg看到的值相同,objperslab(每個slab中的對象時202),pagesperslab(每個slab中包含的頁數),可以知道objsize * objperslab < pagesperslab。

在什麼情況下,會用到slab

1.驅動開發使用的kmalloc(核心需要經常配置設定記憶體,我們在核心中最常用的配置設定記憶體的方式就是kmalloc了)

3.1.5 slab配置設定器和buddy系統的關系?

slab系統與buddy系統所要解決的問題是互補的,一個解決外部碎片一個解決内部碎片,但事實上,slab在建立cache時同樣需要用到buddy來為之配置設定頁面,而在釋放cache時也需要buddy來回收這此頁面。也就是說,slab事實上是依賴buddy系統的。

3.1.6 slab中算法中提到了着色,請問着色問題,主要是解決什麼的?

  用來防止高速緩存沖突的

  着色即為緩存行添加偏移。

  相同類型的slab、對象很有可能被儲存到相同的CPU cache的緩存行中,經常使用的對象被放到CPU cache中,這當然使我們想要的,但如果兩個不同的對象每次都被放到相同的緩存行中,那交替的讀取這兩個對象,會導緻緩存行的内容不斷的被更新,也就無法展現緩存的好處了。

3.1.7 slab算法有什麼缺點,如果讓你來設計,你怎麼去改進它的缺點?

随着大規模多處理器系統和NUMA系統的廣泛應用,slab配置設定器逐漸暴露出自身嚴重的不足:

  • 較多複雜的隊列管理。在slab配置設定器中存在衆多的隊列,例如針對處理器的本地緩存隊列,slab中空閑隊列,每個slab處于一個特定狀态的隊列之中。是以,管理太費勁了。
  • slab管理資料和隊列的存儲開銷比較大。每個slab需要一個struct slab資料結構和一個管理者kmem_bufctl_t型的數組。當對象體積較小時,該數組将造成較大的開銷(比如對象大小為32位元組時,将浪費1/8空間)。為了使得對象在硬體告訴緩存中對齊和使用着色政策,還必須浪費額外的記憶體。同時,緩沖區針對節點和處理器的隊列也會浪費不少記憶體。測試表明在一個1000節點/處理器的大規模NUMA系統中,數GB記憶體被用來維護隊列和對象引用。
  • 緩沖區回收比較複雜。
  • 對NUMA的支援非常複雜。slab對NUMA的支援基于實體頁框配置設定器,無法細粒度的使用對象,是以不能保證處理器級的緩存來自同一節點(這個我暫時不太懂)。
  • 備援的partial隊列。slab配置設定器針對每個節點都有一個partial隊列,随着時間流逝,将有大量的partial slab産生,不利于記憶體的合理使用。
  • 性能調優比較困難。針對每個slab可以調整的參數比較複雜,而且配置設定處理器本地緩存時,不得不使用自旋鎖。
  • 調試功能比較難于使用。

為了解決以上slab配置設定器的不足,引入新的解決方案,slub配置設定器。slub配置設定器的特點是簡化設計理念,同時保留slab配置設定器的基本思想:每個緩沖區有多個slab組成,每個slab包含固定數目的對象。slub配置設定器簡化了kmem_cache,slab等相關的管理結構,擯棄了slab配置設定器中的衆多隊列概念,并針對多處理器、NUMA系統進行優化,進而提高了性能和可擴充性并降低了記憶體的浪費。并且,為了保證核心其他子產品能無縫遷移到slub配置設定器,API接口函數與slab保持一緻。

3.2 SLUB配置設定器

參考:https://blog.csdn.net/Vince_/article/details/79668199

  多年以來,Linux 核心使用一種稱為SLAB 的核心對象緩沖區配置設定器。但是,随着系統規模的不斷增大,SLAB 逐漸暴露出自身的諸多不足。SLUB 是 Linux 核心 2.6.22 版本中引入的一種新型配置設定器,它具有設計簡單、代碼精簡、額外記憶體占用率小、擴充性高,性能優秀、友善調試等特點。

  首先為什麼要說slub配置設定器,核心裡小記憶體配置設定一共有三種,SLAB/SLUB/SLOB,slub配置設定器是slab配置設定器的進化版,而slob是一種精簡的小記憶體配置設定算法,主要用于嵌入式系統。慢慢的slab配置設定器或許會被slub取代,是以對slub的了解是十分有必要的。

我們先說說slab配置設定器的弊端,我們知道slab配置設定器中每個node結點有三個連結清單,分别是空閑slab連結清單,部分空slab連結清單,已滿slab連結清單,這三個連結清單中維護着對應的slab緩沖區。我們也知道slab緩沖區的記憶體是從夥伴系統中申請過來的,我們設想一個情景,如果沒有記憶體回收機制的情況下,隻要申請的slab緩沖區就會存入這三個連結清單中,并不會傳回到夥伴系統裡,如果這個類型的SLAB迎來了一個配置設定高峰期,将會從夥伴系統中擷取很多頁面去生成許多slab緩沖區,之後這些slab緩沖區并不會自動傳回到夥伴系統中,而是會添加到node結點的這三個slab連結清單中去,這樣就會有很多slab緩沖區是很少用到的。

而slub配置設定器把node結點的這三個連結清單精簡為了一個連結清單,隻保留了部分空slab連結清單,而SLUB中對于每個CPU來說已經不使用空閑對象連結清單,而是直接使用單個slab,并且每個CPU都維護有自己的一個部分空連結清單。在slub配置設定器中,對于每個node結點,也沒有了所有CPU共享的空閑對象連結清單。我們用以下圖來表示以下slab配置設定器和slub配置設定器的差別(上圖為SLAB,下圖為SLUB):

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別
linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

SLUB配置設定器的原理:

https://blog.csdn.net/lukuen/article/details/6935068

四、非連續記憶體區管理

先來回顧一下虛拟記憶體位址空間和實體記憶體的映射關系:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

虛拟位址空間0~3G用于應用層

虛拟位址空間3~4G用于核心層

核心又将3~4G的虛拟位址空間,劃分為如下幾個部分:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別
  • 直接映射區:線性空間中從3G開始最大896M的區間,為直接記憶體映射區,該區域的線性位址和實體位址存線上性轉換關系:線性位址=3G+實體位址。
  • 動态記憶體映射區:該區域由核心函數vmalloc來配置設定,特點是:線性空間連續,但是對應的實體空間不一定連續。vmalloc配置設定的線性位址所對應的實體頁可能處于低端記憶體,也可能處于高端記憶體。
  • 永久記憶體映射區:該區域可通路高端記憶體。通路方法是使用alloc_page(_GFP_HIGHMEM)配置設定高端記憶體頁或者使用kmap函數将配置設定到的高端記憶體映射到該區域。
  • 固定映射區:該區域和4G的頂端隻有4k的隔離帶,其每個位址項都服務于特定的用途,如ACPI_BASE等。

上圖中的動态記憶體映射區就是非連續記憶體區,線性位址空間的起始位址由VMALLOC_START宏定義,而末尾位址由VMALLOC_END宏定義

4. 1 非連續記憶體區結構

參考:https://www.cnblogs.com/eustoma/archive/2012/04/28/2474608.html

Linux用vm_struct結構來表示vmalloc使用的線性位址

借用《深入了解linux核心》的圖:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

從上圖中我們可以看到每一個vmalloc_area用4KB隔開,這樣做是為了很容易就能捕捉到越界通路,因為中間是一個 “空洞”.

下面來分析一下vmalloc area的資料結構:

struct vm_struct {
     void          *addr;          //虛拟位址
     unsigned long     size;         //vm的大小
     unsigned long     flags;        //vm的标志
     struct page        **pages;      //vm所映射的page
     unsigned int       nr_pages;     //page個數
     unsigned long     phys_addr;    //對應的起始實體位址 
     struct vm_struct   *next;        //下一個vm.用來形成連結清單
}
           

對于配置設定非連續記憶體區和釋放記憶體區這裡不做深究,可以參考:

五、 kmalloc,vmalloc,malloc的差別

參考:https://www.cnblogs.com/hongzhunzhun/p/4533960.html(帶用例)

簡單的說:

  • 1.kmalloc和vmalloc是配置設定的是核心的記憶體,malloc配置設定的是使用者的記憶體
  • 2.kmalloc保證配置設定的記憶體在實體上是連續的,vmalloc保證的是在虛拟位址空間上的連續,malloc不保證任何東西(這點是自己猜測的,不一定正确)
  • 3.kmalloc能配置設定的大小有限,vmalloc和malloc能配置設定的大小相對較大
  • 4.記憶體隻有在要被DMA通路的時候才需要實體上連續
  • 5.vmalloc比kmalloc要慢

malloc是使用者空間的,後面再讨論,我們重點看kmalloc和vmalloc的差別:

在裝置驅動程式或者核心子產品中動态開辟記憶體,不是用malloc,而是kmalloc

,vmalloc,釋放記憶體用的是kfree,vfree,kmalloc函數傳回的是虛拟位址(線性位址).

kmalloc特殊之處在于它配置設定的記憶體是實體上連續的,這對于要進行DMA的裝置十分重要.

而用vmalloc配置設定的記憶體隻是線性位址連續,實體位址不一定連續,不能直接用于DMA。vmalloc函數的工作方式類似于kmalloc,隻不過前者配置設定的記憶體虛拟位址是連續的,而實體位址則無需連

續。通過vmalloc獲得的頁必須一個一個地進行映射,效率不高,

是以,隻在不得已(一般是為了獲得大塊記憶體)時使用。vmalloc函數傳回一個指針,指向邏輯上連續的一塊記憶體區,其大小至少為size。在發生錯誤時,函數傳回NULL。vmalloc可能睡眠,是以,不能從中斷上下文中進行調用,也不能從其它不允許阻塞的情況下調用。要釋放通過vmalloc所獲

得的記憶體,應使用vfree函數

vmalloc和kmalloc的配置設定記憶體的特點大概如下:

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

可以看到,kmalloc保證配置設定的記憶體在實體上是連續的,vmalloc保證的是在虛拟位址空間上的連續

差別大概可總結為:

1,vmalloc配置設定的一般為高端記憶體,隻有當記憶體不夠的時候才配置設定低端記憶體;kmallco從低端記憶體配置設定。

2,vmalloc配置設定的實體位址一般不連續,而kmalloc配置設定的位址連續,兩者配置設定的虛拟位址都是連續的;

3,vmalloc配置設定的一般為大塊記憶體,而kmaooc一般配置設定的為小塊記憶體,(一般不超過128k);

linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別
linux核心學習2:記憶體管理一、頁框管理常見問題:二、夥伴算法三、記憶體區管理四、非連續記憶體區管理五、 kmalloc,vmalloc,malloc的差別

有一篇文章整理linux的記憶體管理整理得比較好:https://www.jianshu.com/p/a563a5565705

思考問答

  1. 在系統啟動時,ARM Linux核心如何知道系統中有多大的記憶體空間?
  2. 在32bit Linux核心中,使用者空間和核心空間的比例通常是3:1,可以修改成2:2嗎?
  3. 實體記憶體頁面如何添加到夥伴系統中,是一頁一頁添加,還是以2的幾次幂來加入呢?
  4. 核心的一級頁表存放在什麼地方?二級頁表又存放在什麼地方?
  5. 使用者程序的一級頁表存放在什麼地方?二級頁表呢?
  6. 在ARM32系統中,頁表是如何映射的?在ARM64系統中,頁表又是如何映射的?
  7. 請簡述Linux核心在理想情況下頁面配置設定器(page allocator)是如何配置設定出連續實體頁面的。
  8. 在頁面配置設定器中,如何從配置設定掩碼(gfp_mask)中确定可以從哪些zone中配置設定記憶體?
  9. 頁面配置設定器是按照什麼方向來掃描zone的?
  10. 為使用者程序配置設定實體記憶體,配置設定掩碼應該選用GFP_KERNEL,還是GFP_HIGHUSER_MOVABLE呢?
  11. slab配置設定器是如何配置設定和釋放小塊記憶體的?
  12. slab配置設定器中有一個着色的概念(cache color),着色有什麼作用?
  13. slab配置設定其中的slab對象有沒有根據Per-CPU做一些優化?
  14. slab增長并導緻大量不用的空閑對象,該如何解決?
  15. 請問kmalloc、vmalloc和malloc之間有什麼差別以及實作上的差異?
  16. 使用使用者态的API函數malloc()配置設定記憶體時,會馬上為其配置設定實體記憶體嗎?
  17. 假設不考慮libc的因素,malloc配置設定100Byte,那麼實際上核心是為其配置設定100Byte嗎?
  18. 假設兩個使用者程序列印的malloc()配置設定的虛拟位址是一樣的,那麼在核心中這兩塊虛拟記憶體是否打架了呢?
  19. vm_normal_page()函數傳回的是什麼樣頁面的struct page資料結構?為什麼記憶體管理代碼中需要這個函數?
  20. 請簡述get_user_page()函數的作用和實作流程?
  21. 請簡述follow_page()函數的作用和實作流程?
  22. 請簡述私有映射和共享映射的差別。
  23. 為什麼第二次調用mmap時,Linux核心沒有捕捉到位址重疊并傳回失敗呢?
  24. struct page資料結構中的_count和_mapcount有什麼差別?
  25. 匿名頁面和page cache頁面有什麼差別?
  26. struct page資料結構中有一個鎖,請問trylock_page()和lock_page()有什麼差別?
  27. 在Linux 2.4.x核心中,如何從一個page找到所有映射該頁面的VMA?反響映射可以帶來哪些便利?
  28. 閱讀Linux 4.0核心RMAP機制的代碼,畫出父子程序之間VMA、AVC、anon_vma和page等資料結構之間的關系圖。
  29. 在Linux 2.6.34中,RMAP機制采用了新的實作,在Linux 2.6.33和之前的版本中稱為舊版本RMAP機制。那麼在舊版本RMAP機制中,如果父程序有1000個子程序,每個子程序都有一個VMA,這個VMA裡面有1000個匿名頁面,當所有的子程序的VMA同時發生寫複制時會是什麼情況呢?
  30. 當page加入lru連結清單中,被其他線程釋放了這個page,那麼lru連結清單如何知道這個page已經被釋放了。
  31. kswapd核心線程何時會被喚醒?
  32. LRU連結清單如何知道page的活動頻繁程度?
  33. kswapd按照什麼原則來換出頁面?
  34. kswapd按照什麼方向來掃描zone?
  35. kswapd以什麼标準來退出掃描LRU?
  36. 手持裝置例如Android系統,沒有swap分區或者swap檔案,kswapd會掃描匿名頁面LRU嗎?
  37. swappiness的含義是什麼?kswapd如何計算匿名頁面和page cache之間的掃描比重?
  38. 當系統充斥着大量隻通路一次的檔案通路(use-one streaming IO)時,kswapd如何來規避這種風暴?
  39. 在回收page cache時,對于dirty的page cache,kswapd會馬上回寫嗎?
  40. 核心有哪些頁面會被kswapd寫回交換分區?
  41. ARM32 Linux如何模拟這個Linux版本的L_PTE_YOUNG比特位呢?
  42. 如何了解Refault Distance算法?
  43. 請簡述匿名頁面的生命周期。在什麼情況下會産生匿名頁面?在什麼條件下會釋放匿名頁面?
  44. KSM是基于什麼原理來合并頁面的?
  45. 在KSM機制裡,合并過程中把page設定成寫保護的函數write_protect_page()有這樣一個判斷:。這個判斷的依據是什麼?
  46. 如果多個VMA的虛拟頁面同時映射了同一個匿名頁面,那麼此時page->index應該等于多少?
  47. 為什麼Dirty COW小程式可以修改一個隻讀檔案的内容?
  48. 在Dirty COW記憶體漏洞中,如果Diryt COW程式沒有madviseThread線程,即隻有procselfmemThread線程,能否修改foo檔案的内容呢?
  49. 假設在核心空間擷取了某個檔案對應的page cache頁面的struct page資料結構,而對應的VMA屬性是隻讀,那麼核心空間是否可以成功修改該檔案呢?
  50. 如果使用者程序使用隻讀屬性(PROT_READ)來mmap映射一個檔案到使用者空間,然後使用memcpy來寫這段記憶體空間,會是什麼樣的情況?
  51. 請畫出記憶體管理中常用的資料結構的關系圖,如mm_struct、vma、vaddr、page、pfn、pte、zone、paddr和pg_data等,并思考如下轉換關系。
  52. 請畫出在最糟糕的情況下配置設定若幹個連續實體頁面的流程圖。
  53. 在Android中新添加了LMK(Low Memory Killer),請描述LMK和OOM Killer之間的關系。
  54. 請描述一緻性DMA映射dma_alloc_coherent()函數在AEM中是如何管理cache一緻性的?
  55. 請描述流式DMA映射dma_map_single()函數在ARM中是如何管理cache一緻性的?
  56. 為什麼在Linux 4.8核心中要把基于zone的LRU連結清單機制遷移到基于Node呢?

Linux核心記憶體管理架構

https://www.cnblogs.com/wahaha02/p/9392088.html