今天還有時間,之前一直想抽時間來寫寫Linux核心原理相關的東西,關注點不在代碼,而在於內在的原理和機制,讓大家對Linux核心有個總體的感性認識,個人認為這很有必要,把看似複雜、深不可測的核心實現,用大家都能理解的方式,用講故事的方式,講給大家聽,如果有人聽後,有原來不過如此的感覺,那我的目的就達到了。
# 記憶體管理
從哪裡開始呢。還是從最基礎的記憶體管理開始吧。
記憶體管理是Linux核心中最基礎,也是相當重要的部分。理解相關原理,不管是對記憶體的理解,還是對大家寫使用者態代碼都很有協助。很多書上、很多文章都寫了相關內容,但個人總覺得內容太複雜,不是太容易理解,這裡想用我自己理解的簡單的方式來描述,希望能有所協助,按自己的思路,可能有點亂,見諒。 從記憶體配置開始
大家寫代碼時,應該都會分配記憶體,不同語言,層次不同,使用的介面不同,不管使用哪種方式,在Linux系統中,基本上都會調用到C庫的malloc介面,那就從malloc分配記憶體開始。
malloc就是用於分配一段記憶體,但這裡分配到的記憶體並非實體記憶體,而是虛擬記憶體,這裡沒有嚴格區分虛擬位址、線性地址之類的概念,只會給大家添負擔,也不深入講述實體記憶體和虛擬記憶體的概念,書上通常有大量的篇幅介紹,大家可以簡單這樣理解:
虛擬記憶體就是從進程的角度看,邏輯上的概念,並不實際存在;
實體記憶體就對應物理上記憶體條上的記憶體; 虛擬記憶體和實體記憶體有對應關係; 虛擬記憶體分配時,相應的實體記憶體還沒有分配;
既然虛擬記憶體並不實際存在,那麼分配來有何用處呢,如果要向其中寫資料,資料如何寫入呢。會寫到哪裡去呢。
當然,記憶體配置後就是拿來用的,如果不能用(比如寫資料),那就沒有意義了。前面說的虛擬記憶體和實體記憶體有對應關係,當分配虛擬記憶體時,相應的實體記憶體還沒有分配,這裡有幾個關鍵問題: 虛擬記憶體和實體記憶體的對應關係由誰來負責維護,如何對應。 實體記憶體何時分配。 虛擬記憶體到實體記憶體的映射
先來解答問題1。
由頁表來建立虛擬記憶體到實體記憶體之間的映射關係。 頁表是啥。
簡單看,頁表就是在記憶體中的一張表(不詳細介紹頁表的具體格式啥的,看書就可以了),可以簡單看做一張hash表,記錄的是虛擬位址和物理地址的對應關係,每個虛擬位址對應一個表項,通過這張表,就能將虛擬位址轉換為物理地址,也就能建立虛擬記憶體到實體記憶體的映射關係了。 誰用頁表。
頁表有了,那誰來用呢。不可能是應用程式自己用吧,我寫代碼時好像從來都沒見過頁表。
當然,使用者看到的只是虛擬位址(虛擬記憶體),其他的對使用者都是透明的~
CPU中有個硬體單元,叫MMU(記憶體管理單元),頁表就是給MMU硬體用的,MMU使用頁表進行虛擬位址到物理地址的映射。也就是說,地址映射是由硬體完成的,軟體(包括作業系統核心自身)都不管關心。
都說軟體不用關心了,那我們為嘛還需要講頁表。
軟體只是不用頁表而且,但頁表的建立和維護都是由軟體(作業系統核心)負責的,也就是說我們(軟體)建立虛擬記憶體和實體記憶體的映射關係,然後由硬體來自動進行地址映射(轉換),我們不需要關心具體的轉換過程。
前面說了,頁表是在記憶體中,而頁表是由軟體建立的,那MMU如果知道頁表到底在哪兒呢。
簡單說,需要我們(軟體)告訴它在哪兒,如何告訴。當然,寫寄存器。CPU上有特別的寄存器(CR3),向其中寫入頁表的地址,MMU就知道了,然後硬體自己使用即可,我們就不管了。 有多少張頁表。
看似一張頁表就能完成所有的地址映射了。
當然不行,如果是這樣,虛擬記憶體就沒有什麼必要存在了。
這裡又涉及新概念了:進程,這是作業系統中最基礎的概念,其實不“新”。
系統中,所有任務都是以進程方式啟動並執行,每個進程都有自己的獨立的虛擬位址空間,好像又說複雜了,簡單說,就是每個進程都有自己的虛擬記憶體,獨立代表,其他進程看不到自己的虛擬記憶體,那麼就意味著,每個進程都需要獨立的虛擬記憶體到實體記憶體的映射,就是說,每個進程都需要自己的頁表。
所以,系統中有多少進程,就有多少張頁表。 頁表建立
前面說了,頁表的建立和維護是由作業系統核心完成的,那何時建立頁表呢。
如前面所說,每個進程都需要自己的頁表,顯然,頁表需要在進程建立時建立。具體的建立過程和原理就不講了。 頁表的維護
如果維護頁表。
當然,進程自己維護,如果維護。
拿前面的例子說,malloc分配虛擬記憶體後,並沒有分配實體記憶體,也沒有建立映射關係,沒有修改頁表,那到底什麼時候,由誰來做映射。
答案是:“缺頁異常”。 缺頁異常
缺頁異常(page fault)又是另一個專業術語,表面上看好像不好理解。
簡單看,就是一個異常。什麼是異常。這又是硬體上的概念了,具體看看書
簡單說,就是硬體上的一種機制,當硬體檢測到某種“不對”時,主動觸發,然後會自動跳轉到例外處理常式處理。異常跟中斷類似,區別在於,中斷是非同步,由外設觸發;異常是同步的,由CPU自己觸發;好像又扯遠了。
缺頁異常就是一種特定的異常,觸發條件是:MMU檢測到頁表項(頁表中的項~)不存在。 何時觸發缺頁異常。
MMU何時會去檢測頁表項。
當CPU需要訪問某個虛擬位址時,比如向某個虛擬位址寫資料(memset),需要將虛擬位址轉換成物理地址,此時MMU就會自動去頁表中找相應的頁表項,如果發現此時相應的頁表項(也就是虛擬位址到物理地址的映射關係)還不存在,那麼就會自動從硬體層面觸發缺頁異常。
也就說,缺頁異常是硬體自己觸發的,條件是當需要訪問某個虛擬位址,而該虛擬位址還沒有相應的頁表項時。
典型的情境就是,當malloc之後,對相應的虛擬位址執行memset操作。 缺頁異常中幹嘛。
缺頁異常後,會跳轉到相應的例外處理常式處理,該例外處理常式是核心中提前註冊好的,其中要做的主要操作,就是:分配實體記憶體,然後修改相應進程的頁表,建立該實體記憶體和虛擬記憶體的映射關係(頁表項)。
當然,實際實現要複雜得多,這裡不詳細描述。 回頭看看
再來重頭捋一下記憶體配置過程: 使用者態程式使用malloc介面,分配虛擬位址。 使用者程式訪問該虛擬位址,比如memset。 硬體(MMU)需要將虛擬位址轉換為物理地址。 硬體讀取頁表。 硬體發現相應的頁表項不存在,硬體自動觸發缺頁異常。 硬體自動跳轉到page fault的處理常式(核心實現註冊好) 核心中的page fault處理常式執行,在其中分配實體記憶體,然後修改頁表(建立頁表項) 異常處理完畢,返回程式使用者態,繼續執行memset相應的操作。
至此,虛擬記憶體和實體記憶體都分配完成,並完成映射。
另一個角度看,如果malloc分配記憶體後,一直不使用,那就一直不會分配實體記憶體,這種記憶體配置策略叫延遲分配,有其相應的好處,自己理解一下~ 接下來。
本章,從記憶體配置的角度看了Linux核心中記憶體管理的關鍵原理,已經以盡量簡單的方式描述了,希望沒給大家帶來負擔~
Linux記憶體管理還涉及其他很多方面,如: 核心自身使用的記憶體(slab、vmalloc) 夥伴系統 進程地址空間管理
後面抽空慢慢講,但都希望能盡量簡單,希望大家一看就明白。
原文地址: http://happyseeker.github.io/kernel/2016/11/10/memory-management-in-kernel.html