Linux內核是如何管理內存的換入換出,以及是如何實現磁碟緩存的?

以前一直沒有仔細想過這個問題,糊糊塗塗地以為內存就是靠頁表來搞定這些事的。但是現在仔細一想,好像不對,因為頁表只保存著內存的邏輯地址到內存的物理地址的轉換關係,似乎和磁碟的塊地址是沒有關係的。想去自己研究Linux的內核代碼,無奈時間不足,水平也不夠,所以請達人解惑。

就是說,如果物理內存不足時,內核會換出一些內存頁到磁碟,同時在頁表上做某些標記。下次再訪問這些內存頁時,發生一個缺頁異常,內核再把磁碟上的內存頁換回來。內核是在哪裡記錄的內存頁是在哪裡的信息?還有就是,當內核讀入一個文件時,內核是在哪裡記錄內存頁和磁碟塊的對應關係,以便將來把臟數據flush回磁碟?具體來說,是在哪些數據結構里記錄這些信息的?

多謝多謝 m(_ _)m


1:linux是如何管理內存換入換出的?

內存swap的機制不在介紹,其實從問題來看,樓主是想知道,內存頁是怎麼與換出到磁碟上的內容一一對應的。答案是通過頁表。拿32位系統舉例子:

處理器通過頁表來把一個虛擬地址轉化為實際的物理內存地址。每個進程有屬於它自己的一組頁表;無論何時發生了進程切換,相應的會發生用戶空間的頁表切換。mm_struct中有一個pdg域,就指向該進程所使用的頁表集。對每一個虛擬頁在頁表中都有對應的一個頁表項(PTE),通常x86下的一頁是一個4位元組的如下記錄(pgd和pte的結構類似,12-31位是page base描述,0-11位是屬性描述,不過pgd中的page

base 指向的pte所在頁面的起始地址,而pte的page base指向的物理頁面的起始地址。假設下面是一條pte對頁的描述記錄):

注意第0位P位,P位告訴處理器該虛擬頁目前是否在物理內存,P位為1,表示該頁在內存中。P位為0頁表示不在內存,頁不在內存也分下面兩種情況:

A:從1位至31位為空:該頁不在進程的地址空間,需在請求調頁

B:1位至31位至少有一位被至位:說明該頁被換出到磁碟。

針對B情況作說明:如果有一位被至位,而PTE的關於該頁的條目被以如下方式解讀:

既,1-7被解讀成一個交換區的區號,而8-31位被解讀成頁槽索引(page-slot,在swap區中一個大小為4k的頁被稱為page

–slot,也許是為了區別與物理頁的page -frame),這樣該頁就指向了swap區的一個4k的空間。

我們再來看swap區,每個活動的swap區在內存中有一個swap區描述符,系統所有的交換區組成一個鏈表,交換區結構為swap_info_struct,該結構中有個重要欄位 :

struct block_device * bdev ,存放交換區的塊設備的描述符,就是你格式分成swap的設備/分區。最後來看磁碟上的swap區是什麼組織的:

每個交換區由一組page

slot組成(一般這些頁槽在磁碟上連續存放),就是說由一組4k大小的塊組成,每塊包含一個換出的頁,。交換區的第一個page slot(0號頁槽)用來永久存放交換區的管理信息,可用來存放換出頁的第一個頁槽為1號頁槽(頁槽索引為1)。所以上面描述換出頁的頁表條目時說「1位至31位至少有一位被至位」,因為0號頁槽不能存換出頁。

到這裡,不知道樓主對swap頁的標記有沒有一個大體的概念?

2: 內核讀入一個文件時,內核是在哪裡記錄內存頁和磁碟塊的對應關係,以便將來把臟數據flush回磁碟?具體來說,是在哪些數據結構里記錄這些信息的?

樓主先讀一下這個回答,(

Linux系統是怎樣將文件路徑轉換到具體物理存儲位置的? - 唐浩然的回答 - 知乎) 這個回答中暫時沒有考慮緩存,不過可以幫助對文件的打開和讀取有一個大體的印象。

先來看文件在磁碟和內存中的存放方式(ext3舉例):

磁碟:文件由inode以及inode指向的具體數據塊組成,來看看磁碟的Inode結構的描述(source/fs/ext4/ext3.h 下面為3.14內核的中的,source是指內核代碼根目錄,下同。內核版本不同,行號可能不同):

/*

256 * Structure of
an inode on the disk
257 */
258 struct ext3_inode {
259__le16 i_mode; /* File mode */
260__le16 i_uid; /* Low 16 bits of Owner Uid */
261__le32 i_size; /* Size in bytes */
262__le32 i_atime; /* Access time */
263__le32 i_ctime; /* Creation time */
264__le32 i_mtime; /* Modification time */
265__le32 i_dtime; /* Deletion Time */
266__le16 i_gid; /* Low 16 bits of Group Id */
267__le16 i_links_count; /* Links count */
268__le32 i_blocks; /* Blocks count */
269__le32 i_flags; /* File flags */
……
281__le32 i_block[EXT3_N_BLOCKS];/* Pointers to
blocks */
282__le32 i_generation; /* File version (for NFS) */
……
312 };

第281行有一個「__le32 i_block[EXT3_N_BLOCKS];

最前面的le 全稱為 little-endian,表示排序方式:低階位元組在高位位置與其對應的是be :big-endian。 如代碼注釋這個數據指向了具體存放文件數據的塊(這裡我們不講間接塊)。到此,估計已經了解了文件在磁碟上的存放的管理。

下面來說說文件在內存中的存放,文件在內存中存放由VSF層管理,VFS層在打開文件時在內存中依據磁碟的 ext3_inode而建立VFS的inode結構,VFS的inode與磁碟inode是一一對應的,下面摘出該結構的一些內容

(同前面的內核版本,文件在source/include/linux/fs.h):

*/
527 struct inode {
528 umode_t i_mode;
529 unsigned short i_opflags;
530 kuid_t i_uid;
531 kgid_t i_gid;
532 unsigned int i_flags;
……
560 dev_t i_rdev;
561 loff_t i_size;
562 struct timespec i_atime;
563 struct timespec i_mtime;
564 struct timespec i_ctime;
565 spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
566 unsigned short i_bytes;
567 unsigned int i_blkbits;
568 blkcnt_t i_blocks;
…………
593 struct file_lock *i_flock;
594 struct address_space i_data;
……

}

是不是發現和磁碟上的inode結構存在相似的地方,但又多了一些內容? 這裡與文件映射關係最為密切的是 594行的 struct address_spaces i_data ,該結構內容如下:

(同前面的內核版本,文件在source/include/linux/fs.h):

412 struct address_space {
413 struct inode *host; /* owner: inode, block_device */
414 struct radix_tree_root page_tree; /* radix tree of all pages */
415 spinlock_t tree_lock; /* and lock protecting it */
416 unsigned int i_mmap_writable;/* count VM_SHARED mappings */
417 struct rb_root i_mmap; /* tree of private and shared mappings */
418 struct list_head i_mmap_nonlinear;/*list VM_NONLINEAR mappings */
……
429 } __attribute__((aligned(sizeof(long))));

需要關注的是414行的 radix_tree_root 結構,描述如下(source/include/linux/radix-tree.h):

64 struct radix_tree_root {
65 unsigned int height;
66 gfp_t gfp_mask;
67 struct radix_tree_node__rcu *rnode;
68 };

Height描述樹的高/深度,*rnode指向了其子樹,根樹與子樹的關係

圖中也描述了子樹的結構的重要部分slots,slots指針數組指向了具體物理頁的頁描述符,如果基樹樹高為1,則slots可以指向[RADIX_TESS_MAP_SIZE]個頁面,如下面的結構描述:

(source/include/linux/radix-tree.h):

50 struct radix_tree_node {
51 unsigned int height; /* Height from the bottom */
52 unsigned int count;
53 union {
54 struct radix_tree_node *parent; /* Used when ascending tree */
55 struct rcu_headrcu_head; /* Used when freeing node */
56 };
57 void __rcu *slots[RADIX_TREE_MAP_SIZE];
58 unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
59 };

最後再來看看物理頁描述符的結構(source/include/linux/mm_types.h,內容和注釋太多,貼出來行號會亂掉,我只截了部分內容,且去掉了行號):

struct page {
……
union {
struct address_space *mapping;
void *s_mem; /* slab first object */
};
union {
pgoff_tindex; /* Our offset within mapping. */
void *freelist;
bool pfmemalloc;
};
….
}

其中兩個欄位 mapping 和index, 如果該頁被用於文件映射,則mapping用於指向文件inode的address_space結構,而index(頁索引)呢?index是與文件inode-&>address_space-&>radix_tree_root-&>radix_tree_node-&>slots[]相關的,我直接載圖好了:(且下面載圖中最後說的圖15-1就是前面已貼出的 根樹與子樹的關係圖):

總結起來就是:VFS從磁碟讀取inode,並在內存立對應的vfs的inode,vfs層inode中address_space中radix_tree_root中radix_tree_node中slots指向了內存中存放文件的物理頁描述符(找到頁描述符就能找到頁面)。

而頁描述符中的

mapping指向了 inode的address_space, 同時,頁描述符中index指定了頁在slots中的具體位置 。


能在知乎問這麼detail的問題,似乎題主不是想要大段大段代碼細節,所以我斗膽來簡答一下,歡迎各位點評:

1. 我們通常講Linux內存管理,但它管理的對象是誰?

我認為是從狹義上講,通常是指除了內核代碼佔用的,以及保留的內存區域以外的區域。合理管理這些內存區域,為驅動以及用戶程序分配空間,以及回收他們不用的內存。通常分配和回收的粒度是4k大小的page。

2. SWAP是如何工作的?

首先我們先思考下 page 中的數據是怎麼來的?一般來講,它是從文件系統中讀入進來的,比如應用程序的代碼頁。另外一種是來自於程序執行的中間數據,例如應用程序的棧。所以前者我們稱它們為文件頁,後者我們稱它們為匿名頁。當內存緊張時,我們就需要想辦法回收這兩種類型的內存,畢竟內核驅動代碼段,你沒法把他們強行沒收。

a, 對於文件頁的回收,一句話,該回寫的回寫,該拋棄的拋棄。有應用程序叫了,你把我代碼頁強行徵收,回頭我又要用了,怎麼辦?立馬回它一句,老子正忙著呢,等你要用時再BB。(當應用程序回頭要用被強行回收的文件頁時,內核會在應用無知覺的情況下,把文件數據(代碼也算是一種文件數據)載入進來。

b, 重點來了,對於匿名頁如何回收呢?它若被強行徵收的話,數據沒法還原,應用程序一定會crash。得想辦法把它們先保存到哪裡,要用到的時候再載入到內存中…實際上也是這麼做的,linux會把他們保存到SWAP分區中。

=============

問題1: 如果物理內存不足時,內核會換出一些內存頁到磁碟,同時在頁表上做某些標記。下次再訪問這些內存頁時,發生一個缺頁異常,內核再把磁碟上的內存頁換回來。內核是在哪裡記錄的內存頁是在哪裡的信息?

我的理解:

a, 對於文件頁,其實回收就是回寫後然後釋放,或者直接釋放。其實沒啥特別的標記。在MMU的頁表中,就是斷開這段虛擬地址與要釋放物理頁之間的映射。訪問到這段虛擬地址時,因為沒有對應的物理頁,所以進入缺頁異常程序,異常處理會檢測到這是個文件頁,對應著哪個文件中哪一段數據,然後再申請物理頁,再將申請到的物理頁映射到該虛擬地址上去。並載入這段文件數據到還區域中來。

b, 對於匿名頁,要在swap分區中申請一塊空地並將匿名頁中的數據寫入swap分區。然後釋放該物理頁,斷開該斷虛擬地址到物理頁的映射。當程序訪問到該虛擬地址,發現沒有對應的物理頁,進入缺頁異常程序,然後檢測到它是匿名頁,於是去查詢是否在swap分區中,是的話,就先申請物理地址,建立虛擬地址到該物理地址的映射。然後載入swap分區中的數據到該內存頁。

問題2: 還有就是,當內核讀入一個文件時,內核是在哪裡記錄內存頁和磁碟塊的對應關係,以便將來把臟數據flush回磁碟?具體來說,是在哪些數據結構里記錄這些信息的?

文件頁絕大部分跟進程相關,內核代碼和驅動代碼很少直接操作文件。進程上下文訪問到的都是虛擬內存。但這個也需要管理,用vm_area_struct將進程上下文用到的虛擬空間管理起來。若干個這樣的結構體構成了VMA鏈表,這個鏈表描述了一個不一定連續但絕不重疊的0-3G虛擬空間(32位系統,通常是這樣的。)若是一段虛擬空間對著某個文件,描述這段虛擬空間的vm_area_struct會記錄下這段虛擬地址對應該哪個文件,以及對應文件中哪一段數據。缺頁異常程序因為知道哪個虛擬地址缺頁,所以它只需要拿虛擬地址去查詢該進程的VMA鏈表,缺頁異常程序就知道需要載入哪個文件的哪一段數據,然後申請物理頁,讓這段虛擬地址映射到這段物理頁,然後載入相應的文件數據到這段空間來。

==========

賓館吃完早餐後,來寫點。完全憑藉4年前的代碼和記憶了…若有錯誤和遺漏,請輕拍…


說的挺清楚噠,面試給過噠。你去看一下ulk就可以了,就是介紹swap的章節。比在這裡問清楚多了


頁表可以直接記錄,當標記為缺頁時候,頁表記錄的內存地址是無效的,可以用來記錄swap中的地址。


推薦閱讀:

微軟在 Windows 10 裡面加入 Linux,這是他們認輸了嗎?
實現一個shell解釋器需要哪些編譯原理方面的知識?
為什麼WinXP的驅動無法用於Win7、Vista,但是Win7,Vista驅動可用於Win10?
計算機掉電的時候 CPU 真的會中斷嗎?操作系統會進行那些動作?
mac上有必要裝linux嗎?

TAG:操作系統 | 文件系統 | 內存管理 | Linux內核 | 操作系統內核 |