自從誕生以來,Linux 就被不斷完善和普及,目前它已經成爲主流通用操做系統之一,使用得很是普遍,它與 Windows、UNIX 一塊兒佔據了操做系統領域幾乎全部的市場份額。特別是在高性能計算領域,Linux 已經成爲一個占主導地位的操做系統,在2005年6月全球TOP500 計算機中,有 301 臺部署的是 Linux 操做系統。所以,研究和使用 Linux 已經成爲開發者的不可迴避的問題了。linux
下面咱們介紹一下 Linux 內核中文件 Cache 管理的機制。本文以 2.6 系列內核爲基準,主要講述工做原理、數據結構和算法,不涉及具體代碼。算法
操做系統是計算機上最重要的系統軟件,它負責管理各類物理資源,並嚮應用程序提供各類抽象接口以便其使用這些物理資源。從應用程序的角度看,操做系統提供了一個統一的虛擬機,在該虛擬機中沒有各類機器的具體細節,只有進程、文件、地址空間以及進程間通訊等邏輯概念。這種抽象虛擬機使得應用程序的開發變得相對容易:開發者只需與虛擬機中的各類邏輯對象交互,而不須要了解各類機器的具體細節。此外,這些抽象的邏輯對象使得操做系統可以很容易隔離並保護各個應用程序。數據結構
對於存儲設備上的數據,操做系統嚮應用程序提供的邏輯概念就是"文件"。應用程序要存儲或訪問數據時,只需讀或者寫"文件"的一維地址空間便可,而這個地址空間與存儲設備上存儲塊之間的對應關係則由操做系統維護。異步
在 Linux 操做系統中,當應用程序須要讀取文件中的數據時,操做系統先分配一些內存,將數據從存儲設備讀入到這些內存中,而後再將數據分發給應用程序;當須要往文件中寫數據時,操做系統先分配內存接收用戶數據,而後再將數據從內存寫到磁盤上。文件 Cache 管理指的就是對這些由操做系統分配,並用來存儲文件數據的內存的管理。 Cache 管理的優劣經過兩個指標衡量:一是 Cache 命中率,Cache 命中時數據能夠直接從內存中獲取,再也不須要訪問低速外設,於是能夠顯著提升性能;二是有效 Cache 的比率,有效 Cache 是指真正會被訪問到的 Cache 項,若是有效 Cache 的比率偏低,則至關部分磁盤帶寬會被浪費到讀取無用 Cache 上,並且無用 Cache 會間接致使系統內存緊張,最後可能會嚴重影響性能。數據結構和算法
下面分別介紹文件 Cache 管理在 Linux 操做系統中的地位和做用、Linux 中文件 Cache相關的數據結構、Linux 中文件 Cache 的預讀和替換、Linux 中文件 Cache 相關 API 及其實現。函數
文件 Cache 是文件數據在內存中的副本,所以文件 Cache 管理與內存管理系統和文件系統都相關:一方面文件 Cache 做爲物理內存的一部分,須要參與物理內存的分配回收過程,另外一方面文件 Cache 中的數據來源於存儲設備上的文件,須要經過文件系統與存儲設備進行讀寫交互。從操做系統的角度考慮,文件 Cache 能夠看作是內存管理系統與文件系統之間的聯繫紐帶。所以,文件 Cache 管理是操做系統的一個重要組成部分,它的性能直接影響着文件系統和內存管理系統的性能。性能
圖1描述了 Linux 操做系統中文件 Cache 管理與內存管理以及文件系統的關係示意圖。從圖中能夠看到,在 Linux 中,具體文件系統,如 ext2/ext三、jfs、ntfs 等,負責在文件 Cache和存儲設備之間交換數據,位於具體文件系統之上的虛擬文件系統VFS負責在應用程序和文件 Cache 之間經過 read/write 等接口交換數據,而內存管理系統負責文件 Cache 的分配和回收,同時虛擬內存管理系統(VMM)則容許應用程序和文件 Cache 之間經過 memory map的方式交換數據。可見,在 Linux 系統中,文件 Cache 是內存管理系統、文件系統以及應用程序之間的一個聯繫樞紐。spa
在 Linux 的實現中,文件 Cache 分爲兩個層面,一是 Page Cache,另外一個 Buffer Cache,每個 Page Cache 包含若干 Buffer Cache。內存管理系統和 VFS 只與 Page Cache 交互,內存管理系統負責維護每項 Page Cache 的分配和回收,同時在使用 memory map 方式訪問時負責創建映射;VFS 負責 Page Cache 與用戶空間的數據交換。而具體文件系統則通常只與 Buffer Cache 交互,它們負責在外圍存儲設備和 Buffer Cache 之間交換數據。Page Cache、Buffer Cache、文件以及磁盤之間的關係如圖 2 所示,Page 結構和 buffer_head 數據結構的關係如圖 3 所示。在上述兩個圖中,假定了 Page 的大小是 4K,磁盤塊的大小是 1K。本文所講述的,主要是指對 Page Cache 的管理。操作系統
在 Linux 內核中,文件的每一個數據塊最多隻能對應一個 Page Cache 項,它經過兩個數據結構來管理這些 Cache 項,一個是 radix tree,另外一個是雙向鏈表。Radix tree 是一種搜索樹,Linux 內核利用這個數據結構來經過文件內偏移快速定位 Cache 項,圖 4 是 radix tree的一個示意圖,該 radix tree 的分叉爲4(22),樹高爲4,用來快速定位8位文件內偏移。Linux(2.6.7) 內核中的分叉爲 64(26),樹高爲 6(64位系統)或者 11(32位系統),用來快速定位 32 位或者 64 位偏移,radix tree 中的每個葉子節點指向文件內相應偏移所對應的Cache項。指針
另外一個數據結構是雙向鏈表,Linux內核爲每一片物理內存區域(zone)維護active_list和inactive_list兩個雙向鏈表,這兩個list主要用來實現物理內存的回收。這兩個鏈表上除了文件Cache以外,還包括其它匿名(Anonymous)內存,如進程堆棧等。
Linux內核中文件預讀算法的具體過程是這樣的:對於每一個文件的第一個讀請求,系統讀入所請求的頁面並讀入緊隨其後的少數幾個頁面(很多於一個頁面,一般是三個頁面),這時的預讀稱爲同步預讀。對於第二次讀請求,若是所讀頁面不在Cache中,即不在前次預讀的group中,則代表文件訪問不是順序訪問,系統繼續採用同步預讀;若是所讀頁面在Cache中,則代表前次預讀命中,操做系統把預讀group擴大一倍,並讓底層文件系統讀入group中剩下尚不在Cache中的文件數據塊,這時的預讀稱爲異步預讀。不管第二次讀請求是否命中,系統都要更新當前預讀group的大小。此外,系統中定義了一個window,它包括前一次預讀的group和本次預讀的group。任何接下來的讀請求都會處於兩種狀況之一:第一種狀況是所請求的頁面處於預讀window中,這時繼續進行異步預讀並更新相應的window和group;第二種狀況是所請求的頁面處於預讀window以外,這時系統就要進行同步預讀並重置相應的window和group。圖5是Linux內核預讀機制的一個示意圖,其中a是某次讀操做以前的狀況,b是讀操做所請求頁面不在window中的狀況,而c是讀操做所請求頁面在window中的狀況。
Linux內核中文件Cache替換的具體過程是這樣的:剛剛分配的Cache項鍊入到inactive_list頭部,並將其狀態設置爲active,當內存不夠須要回收Cache時,系統首先從尾部開始反向掃描active_list並將狀態不是referenced的項鍊入到inactive_list的頭部,而後系統反向掃描inactive_list,若是所掃描的項的處於合適的狀態就回收該項,直到回收了足夠數目的Cache項。Cache替換算法如圖6的算法描述僞碼所示。
Linux的Cache替換算法描述
Mark_Accessed(b) { if b.state==(UNACTIVE && UNREFERENCE) b.state = REFERENCE else if b.state == (UNACTIVE && REFERENCE) { b.state = (ACTIVE && UNREFERENCE) Add X to tail of active_list } else if b.state == (ACTIVE && UNREFERENCE) b.state = (ACTIVE && REFERENCE) } Reclaim() { if active_list not empty and scan_num<MAX_SCAN1 { X = head of active_list if (X.state & REFERENCE) == 0 Add X to tail of inactive_list else { X.state &= ~REFERENCE Move X to tail of active_list } scan_num++ } scan_num = 0 if inactive_list not emptry and scan_num < MAX_SCAN2 { X = head of inactive_list if (X.state & REFERENCE) == 0 return X else { X.state = ACTIVE | UNREFERENCE Move X to tail of active_list } scan_num++ } return NULL } Access(b){ if b is not in cache { if slot X free put b into X else { X=Reclaim() put b into X } Add X to tail of inactive_list } Mark_Accessed(X) }
Linux內核中與文件Cache操做相關的API有不少,按其使用方式能夠分紅兩類:一類是以拷貝方式操做的相關接口, 如read/write/sendfile等,其中sendfile在2.6系列的內核中已經再也不支持;另外一類是以地址映射方式操做的相關接口,如mmap等。
第一種類型的API在不一樣文件的Cache之間或者Cache與應用程序所提供的用戶空間buffer之間拷貝數據,其實現原理如圖7所示。
第二種類型的API將Cache項映射到用戶空間,使得應用程序能夠像使用內存指針同樣訪問文件,Memory map訪問Cache的方式在內核中是採用請求頁面機制實現的,其工做過程如圖8所示。
首先,應用程序調用mmap(圖中1),陷入到內核中後調用do_mmap_pgoff(圖中2)。該函數從應用程序的地址空間中分配一段區域做爲映射的內存地址,並使用一個VMA(vm_area_struct)結構表明該區域,以後就返回到應用程序(圖中3)。當應用程序訪問mmap所返回的地址指針時(圖中4),因爲虛實映射還沒有創建,會觸發缺頁中斷(圖中5)。以後系統會調用缺頁中斷處理函數(圖中6),在缺頁中斷處理函數中,內核經過相應區域的VMA結構判斷出該區域屬於文件映射,因而調用具體文件系統的接口讀入相應的Page Cache項(圖中七、八、9),並填寫相應的虛實映射表。通過這些步驟以後,應用程序就能夠正常訪問相應的內存區域了。
文件Cache管理是Linux操做系統的一個重要組成部分,同時也是研究領域一個很熱門的研究方向。目前,Linux內核在這個方面的工做集中在開發更有效的Cache替換算法上,如LIRS(其變種ClockPro)、ARC等。相關信息可見http://linux-mm.org/AdvancedPageReplacement。