linux內核中的address_space 結構解析

在閱讀Linux2.6的內核內存管理這一部分時,我看到page結構中的一個mapping成員,我感到很迷惑,這個成員的屬性太複雜了,我們來看看:

struct address_space *mapping;表示該頁所在地址空間描述結構指針,用於內容爲文件的頁幀

(1)       如果page->mapping等於0,說明該頁屬於交換告訴緩存swap cache

(2)       如果page->mapping不等於0,但第0位爲0,說明該頁爲匿名也,此時mapping指向一個struct anon_vma結構變量;

(3)       如果page->mapping不等於0,但第0位不爲0,則apping指向一個struct address_space地址空間結構變量;

這成員也太麻煩了吧,下面我先看看struct address_space是啥東東:

1、定義:

看linux內核很容易被struct address_space 這個結構迷惑,它是代表某個地址空間嗎?實際上不是的,它是用於管理文件(struct inode)映射到內存的頁面(struct page)的,其實就是每個file都有這麼一個結構,將文件系統中這個file對應的數據與這個file對應的內存綁定到一起;與之對應,address_space_operations 就是用來操作該文件映射到內存的頁面,比如把內存中的修改寫回文件、從文件中讀入數據到頁面緩衝等。file結構體和inode結構體中都有一個address_space結構體指針,實際上,file->f_mapping是從對應inode->i_mapping而來,inode->i_mapping->a_ops是由對應的文件系統類型在生成這個inode時賦予的。

也就是說address_space結構與文件的對應:一個具體的文件在打開後,內核會在內存中爲之建立一個struct inode結構(該inode結構也會在對應的file結構體中引用),其中的i_mapping域指向一個address_space結構。這樣,一個文件就對應一個address_space結構,一個 address_space與一個偏移量能夠確定一個page cache 或swap cache中的一個頁面。因此,當要尋址某個數據時,很容易根據給定的文件及數據在文件內的偏移量而找到相應的頁面。看下這張圖我們就很瞭解了:

 

下面先看下address_space結構定義:

struct address_space {

       struct inode   *host;  /* owner: inode, block_device擁有它的節點 */
       struct radix_tree_root    page_tree;/* radix tree of all pages包含全部頁面的radix樹 */
       rwlock_t        tree_lock;  /* and rwlock protecting it保護page_tree的自旋鎖  */
       unsigned int   i_mmap_writable;/* count VM_SHARED mappings共享映射數 VM_SHARED記數*/
       struct prio_tree_root      i_mmap;         /* tree of private and shared mappings 優先搜索樹的樹根*/
       struct list_head       i_mmap_nonlinear;/*list VM_NONLINEAR mappings 非線性映射的鏈表頭*/
       spinlock_t              i_mmap_lock; /* protect tree, count, list 保護i_mmap的自旋鎖*/
       unsigned int           truncate_count;      /* Cover race condition with truncate 將文件截斷的記數*/
       unsigned long         nrpages;  /* number of total pages 頁總數*/
       pgoff_t                  writeback_index;/* writeback starts here 回寫的起始偏移*/
       struct address_space_operations *a_ops;     /* methods  操作函數表*/
       unsigned long         flags;             /* error bits/gfp mask ,gfp_mask掩碼與錯誤標識 */
       struct backing_dev_info *backing_dev_info; /* device readahead, etc預讀信息 */
       spinlock_t              private_lock;   /* for use by the address_space  私有address_space鎖*/
       struct list_head       private_list;     /* ditto 私有address_space鏈表*/
       struct address_space     *assoc_mapping;    /* ditto 相關的緩衝*/
} __attribute__((aligned(sizeof(long))));

2、page cache和swap cache

瞭解了struct address_space這個東東,我們就知道“一個 address_space與一個偏移量能夠確定一個page cache 或swap cache中的一個頁面”,那麼page cache和swap cache又是什麼東東呢?

page cache是與文件映射對應的,而swap cache是與匿名頁對應的。如果一個內存頁面不是文件映射,則在換入換出的時候加入到swap cache,如果是文件映射,則不需要交換緩衝。 

這兩個的相同點就是它們都是address_space,都有相對應的文件操作:一個被訪問的文件的物理頁面都駐留在page cache或swap cache中,一個頁面的所有信息由struct page來描述。struct page中有一個域爲指針mapping ,它指向一個struct address_space類型結構。page cache或swap cache中的所有頁面就是根據address_space結構以及一個偏移量來區分的。而在這裏我可以負責任的告訴大家這個偏移量就是進程線性空間中某個線性地址對應的二級描述符(注意:我這裏說的是以SEP4020這個arm結構爲例的,它只有二級映射,另外這個二級描述符也是Linux版的,而不是硬件版的描述符)。

一般情況下用戶進程調用mmap()時,只是在進程空間內新增了一塊相應大小的緩衝區,並設置了相應的訪問標識,但並沒有建立進程空間到物理頁面的映射。因此,第一次訪問該空間時,會引發一個缺頁異常。對於共享內存映射情況,缺頁異常處理程序首先在swap cache中尋找目標頁(符合address_space以及偏移量的物理頁),如果找到,則直接返回地址;如果沒有找到,則判斷該頁是否在交換區 (swap area),如果在,則執行一個換入操作;如果上述兩種情況都不滿足,處理程序將分配新的物理頁面,並把它插入到page cache中。進程最終將更新進程頁表。注:對於映射普通文件情況(非共享映射),缺頁異常處理程序首先會在page cache中根據address_space以及數據偏移量尋找相應的頁面。如果沒有找到,則說明文件數據還沒有讀入內存,處理程序會從磁盤讀入相應的頁面,並返回相應地址,同時,進程頁表也會更新。

3、swap cache的補充知識;

當將頁面交換到交換文件中時,Linux總是避免頁面寫,除非必須這樣做。當頁面已經被交換出內存但是當有進程再次訪問時又要將它重新調入內存。只要頁面在內存中沒有被寫過,則交換文件中的拷貝是有效的。

Linux使用swap cache來跟蹤這些頁面。這個swap cache是一個頁表入口鏈表,每個對應於系統中的物理頁面。這是一個對應於交換出頁面的頁表入口並且描敘頁面放置在哪個交換文件中以及在交換文件中的位置。如果swap cache入口爲非0值,則表示在交換文件中的這一頁沒有被修改。如果此頁被修改(或者寫入)。 則其入口從swap cache中刪除。

當Linux需要將一個物理頁面交換到交換文件(文件類型之一)時,它將檢查swap cache,如果對應此頁面存在有效入口,則不必將這個頁面寫到交換文件中。這是因爲自從上次從交換文件中將其讀出來,內存中的這個頁面還沒有被修改。

swap cache中的入口是已換出頁面的頁表入口。它們雖被標記爲無效但是爲Linux提供了頁面在哪個交換文件中以及文件中的位置等信息。

保存在交換文件中的dirty頁 面可能被再次使用到,例如,當應用程序向包含在已交換出物理頁面上的虛擬內存區域寫入時。對不在物理內存中的虛擬內存頁面的訪問將引發頁面錯誤。由於處理 器不能將此虛擬地址轉換成物理地址,處理器將通知操作系統。由於已被交換出去,此時描敘此頁面的頁表入口被標記成無效。處理器不能處理這種虛擬地址到物理 地址的轉換,所以它將控制傳遞給操作系統,同時通知操作系統頁面錯誤的地址與原因。這些信息的格式以及處理器如何將控制傳遞給操作系統與具體硬件有關。

處理器相關頁面錯誤處理代碼將定位描敘包含出錯虛擬地址對應的虛擬內存區域的vm_area_struct數據結構。它通過在此進程的vm_area_struct中查找包含出錯虛擬地址的位置直到找到爲止。這些代碼與時間關係重大,進程的vm_area_struct數據結構特意安排成使查找操作時間更少。

執行完這些處理器相關操作並且找到出錯虛擬地址的有效內存區域後,頁面錯處理過程其餘部分和前面類似。

通用頁面錯處理代碼爲出錯虛擬地址尋找頁表入口。如果找到的頁表入口是一個已換出頁面,Linux必須將其交換進入物理內存。已換出頁面的頁表入口的格式與處理器類型有關,但是所有的處理器將這些頁面標記成無效並把定位此頁面的必要信息放入頁表入口中。Linux利用這些信息以便將頁面交換進物理入內存。

此時Linux知道出錯虛擬內存地址並且擁有一個包含頁面位置信息的頁表入口。vm_area_struct數據結構可能包含將此虛擬內存區域交換到物理內存中的子程序:swapin。如果對此虛擬內存區域存在swapin則Linux會使用它。這是已換出系統V共享內存頁面的處理過程-因爲已換出系統V共享頁面和普通的已換出頁面有少許不同。如果沒有swapin操作,這可能是Linux假定普通頁面無須特殊處理。

系統將分配物理頁面並將已換出頁面讀入。關於頁面在交換文件中位置信息從頁表入口中取出。如果引起頁面錯誤的訪問不是寫操作則頁面被保留在swap cache中並且它的頁表入口不再標記爲可寫。如果頁面隨後被寫入,則將產生另一個頁面錯誤,這時頁面被標記爲dirty,同時其入口從swap cache中刪除。如果頁面沒有被寫並且被要求重新換出,Linux可以免除這次寫,因爲頁面已經存在於交換文件中。

如果引起頁面從交換文件中讀出的操作是寫操作,這個頁面將被從swap cache中刪除並且其頁表入口被標記成dirty且可寫。

4、page cache的補充:

說到page cache我們很容易就與buffer cache混淆,

在這裏我需要說的是page cache是VFS的一部分,buffer cache是塊設備驅動的一部分,或者說page cache是面向用戶IO的cache,buffer cache是面向塊設備IO的cache,page cache按照文件的邏輯頁進行緩衝,buffer cache按照文件的物理塊進行緩衝。page cache與buffer cache並不相互獨立而是相互融合的,同一文件的cache頁即可存在於page cache中,又可存在於buffer cache中,它們在物理內存中只有一份拷貝。文件系統接口就處於page cache和buffer cache之間,它完成page cache的邏輯頁與buffer cache的物理塊之間的相互轉換,再交給統一的塊設備IO進行調度處理,文件的邏輯塊與物理塊的關係就表現爲page cache與buffer cache的關係。

Page cache實際上是針對文件系統的,是文件的緩存,在文件層面上的數據會緩存到page cache。文件的邏輯層需要映射到實際的物理磁盤,這種映射關係由文件系統來完成。當page cache的數據需要刷新時,page cache中的數據交給buffer cache,但是這種處理在2.6版本的內核之後就變的很簡單了,沒有真正意義上的cache操作。

Buffer cache是針對磁盤塊的緩存,也就是在沒有文件系統的情況下,直接對磁盤進行操作的數據會緩存到buffer cache中,例如,文件系統的元數據都會緩存到buffer cache中。

簡單說來,page cache用來緩存文件數據,buffer cache用來緩存磁盤數據。在有文件系統的情況下,對文件操作,那麼數據會緩存到page cache,如果直接採用dd等工具對磁盤進行讀寫,那麼數據會緩存到buffer cache。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章