第十六章--访问文件
生活随笔
收集整理的這篇文章主要介紹了
第十六章--访问文件
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
本章所涵蓋的主題即應用于磁盤文件系統的普通文件,也應用于塊設備文件;將這兩種文件系統都簡單地統稱為“文件”。
??????? 訪問文件的模式有多種。我們在本章考慮如下幾種情況:
??????? 規范模式:
????????規范模式下文件打開后,標志O_SYNC與O_DIRECT清0,而且它的內容是由系統調用read()和write()來存取。系統調用read()將阻塞調用進程,直到數據被拷貝進用戶態地址空間(內核允許返回的字節數少于要求的字節數)。但系統調用write()不同,它在數據被拷貝到頁高速緩存(延遲寫)后就馬上結束。
??????? 同步模式:
????????同步模式下文件打開后,標志O_SYNC置1或稍后由系統調用fcntl()對其置1。這個標志只影響寫操作(讀操作總是會阻塞),它將阻塞調用進程,直到數據被有效地寫入磁盤。
??????? 內存映射模式:
??????? 內存映射模式下文件打開后,應用程序發出系統調用mmap()將文件映射到內存中。因此,文件就成為RAM中的一個字節數組,應用程序就可以直接訪問數組元素,而不需要系統調用read()、write()或lseek()。
??????? 直接I/O模式:
??????? 直接I/O模式下文件打開后,標志O_DIRECT置1。任何讀寫操作都將數據在用戶態地址空間與磁盤間直接傳送而不通過頁高速緩存。
??????? 異步模式:
??????? 異步模式下,文件的訪問可以有兩種方法,即通過一組POSIX API或Linux特有的系統調用來實現。所謂異步模式就是數據傳輸請求并不阻塞調用進程,而是在后臺執行,同時應用程序繼續它的正常運行。
一、讀寫文件
??????? 讀文件是基于頁的,內核總是一次傳送幾個完整的數據頁。如果進程發出read()系統調用來讀取一些字節,而這些數據還不在RAM中,那么,內核就要分配一個新頁框,并使用文件的適當部分來填充這個頁,把該頁加入頁高速緩存,最后把所請求的字節拷貝到進程地址空間中。對于大部分文件系統來說,從文件中讀取一個數據頁就等同于在磁盤上查找所請求的數據存放在哪些塊上。事實上,大多數磁盤文件系統的read方法是由名為generic_file_read()的通用函數實現的。
??????? 對基于磁盤的文件來說,寫操作的處理相當復雜,因為文件大小可以改變,因此內核可能會分配磁盤上的一些物理塊。當然,這個過程到底如何實現要取決于文件系統的類型。不過,很多磁盤文件系統是通過通用函數generic_file_write()實現它們的write方法的。這樣的文件系統如Ext2、System V/Coherent/Xenix及Minix。另一方面,還有幾個文件系統(如日志文件系統和網絡文件系統)通過自定義的函數實現它們的write方法。
1.1、從文件中讀取數據
1.1.1、普通文件的readpage方法
1.1.2、塊設備文件的readpage方法
1.2、文件的預讀
??????? 預讀(read-ahead)是一種技術,這種技術在于在實際請求前讀普通文件或塊設備文件的幾個相鄰的數據頁。在大多數情況下,預讀能極大地提高磁盤的性能,因為預讀使磁盤控制器處理較少的命令,其中的每條命令都涉及一大組相鄰的扇區。此外,預讀還能提高系統的響應能力。順序讀取文件的進程通常不需要等待請求的數據,因為請求的數據已經在RAM中了。
??????? 但是,預讀對于隨機訪問的文件是沒有用的;在這種情況下,預讀實際上是有害的,因為它用無用的信息浪費了頁高速緩存的空間。因此,當內核確定出最近所進行的I/O訪問與前一次I/O訪問不是順序的時候就減少或停止預讀。
??????? 文件的預讀需要更復雜的算法,這是由于以下幾個原因:
??????? * 由于數據是逐頁進行讀取的,因此預讀算法不必考慮頁內偏移量,只要考慮所訪問的頁在文件內部的位置就可以了。
??????? * 只要進程持續地順序訪問一個文件,預讀就會逐漸增加。
??????? * 當前的訪問與上一次訪問不是順序的時(隨機訪問),預讀就會逐漸減少乃至禁止。
??????? * 當一個進程重復地訪問同一頁(即只使用文件的很小一部分)時,或者當幾乎所有的頁都已在頁高速緩存內時,預讀就必須停止。
??????? * 低級I/O設備驅動程序必須在合適的時候激活,這樣當將來進程需要時,頁已傳送完畢。
??????? 當訪問給定文件時,預讀算法使用兩個頁面集,各自對應文件的一個連續區域。這兩個頁面集分別叫做當前窗(current window)和預讀窗(ahead window)。
??????? 當前窗內的頁是進程請求的頁和內核預讀的頁,且位于頁高速緩存內(當前窗內的頁不必是最新的,因為I/O數據傳輸仍可能在運行中)。當前窗包含進程順序訪問的最后一頁,且可能有內核預讀但進程未申請的頁。
??????? 預讀窗內的頁緊接著當前窗內的頁,它們是內核正在預讀的頁。預讀窗內的頁都不是進程請求的,但內核假定進程會遲早請求。
??????? 當內核認為是順序訪問而且第一頁在當前窗內時,它就檢查是否建立了預讀窗。如果沒有,內核創建一個預讀窗并觸發相應頁的讀操作。理想情況下,進程繼續從當前窗請求頁,同時預讀窗的頁則正在傳送。當進程請求的頁在預讀窗,那么預讀窗就成為當前窗。
??????? 何時執行預讀算法?這有下列幾種情形:
??????? * 當內核用用戶態請求來讀文件數據的頁時。這一事件觸發page_cache_readahead()函數的調用。
??????? * 當內核為文件內存映射分配一頁時。
??????? * 當用戶態應用執行readahead()系統調用時,它會對某個文件描述符顯示觸發某預讀活動。
??????? * 當用戶態應用使用POSIX_FADV_NOREUSE或POSIX_FADV_WILLNEED命令執行posix_fadvise()系統調用時,它會通知內核,某個范圍的文件頁不久將要被訪問。
??????? * 當用戶態應用使用MADV_WILLNEED命令執行madvise()系統調用時,它會通知內核,某個文件內存映射區域中的給定范圍的文件頁不久將要被訪問。
1.2.1、page_cache_readahead()函數
????????page_cache_readahead()函數處理沒有被特殊系統調用顯示觸發的所有預讀操作。它填寫當前窗和預讀窗,根據預讀命中數更新當前窗和預讀窗的大小,也就是根據過去對文件訪問預讀策略的成功程度來調整。
1.2.2、handle_ra_miss()函數
1.3、寫入文件
??????? write()系統調用涉及把數據從調用進程的用戶態地址空間中移動到內核數據結構中,然后再移動到磁盤上。文件對象的write方法允許每種文件類型都定義一個專用的寫操作。在Linux2.6中,每個磁盤文件系統的write方法都是一個過程,該過程主要標識寫操作所涉及的磁盤塊,把數據從用戶態地址空間拷貝到頁高速緩存的某些頁中,然后把這些頁中的緩沖區標記成臟。
1.3.1、普通文件的prepare_write和commit_write方法
1.3.2、塊設備文件的prepare_write和commit_write方法
1.4、將臟頁寫到磁盤
??????? 系統調用write()的作用就是修改頁高速緩存內一些頁的內容,如果頁高速緩存內沒有所要的頁則分配并追加這些頁。某些情況下(例如文件帶O_SYNC標志打開),I/O數據傳輸立即啟動。但是通常I/O數據傳輸是延遲進行的。
二、內存映射
??????? 一個線性區可以和磁盤文件系統的普通文件的某一部分或者塊設備文件相關聯。這就意味著內核把對區線性中頁內某個字節的訪問轉換成對文件中相應字節的操作。這種技術稱為內存映射(memory mapping)。
??????? 有兩種類型的內存映射:
??????? 共享型:在線性區頁上的任何寫操作都會修改磁盤上的文件;而且,如果進程對共享映射中的一個頁進行寫,那么這種修改對于其他映射了這同一文件的所有進程來說都是可見的。
??????? 私有型:當進程創建的映射只是為讀文件,而不是寫文件時才會使用此種映射。出于這種目的,私有映射的效率要比共享映射的效率更高。但是對私有映射頁的任何寫操作都會使內核停止映射該文件中的頁。因此,寫操作既不會改變磁盤上的文件,對訪問相同文件的其他進程也不可見。但是私有內存映射中還沒有被進程改變的頁會因為其他進程進行的文件更新而更新。
??????? 作為一條通用規則,如果一個內存映射是共享的,相應的線性區就設置了VM_SHARED標志;如果一個內存映射是私有的,那么相應的線性區就清除了VM_SHARED標志。
2.1、內存映射的數據結構
??????? 內存映射可以用下列數據結構的組合來表示:
??????? * 與所映射的文件相關的索引節點對象。
??????? * 所映射文件的address_space對象。
??????? * 不同進程對一個文件進行不同映射所使用的文件對象。
??????? * 對文件進行每一不同映射所使用的vm_area_struct描述符。
??????? * 對文件進行映射的線性區所分配的每個頁框所對應的頁描述符。
??????? 共享內存映射的頁通常都包含在頁高速緩存中;私有內存映射的頁只要還沒有被修改,也都包含在頁高速緩存中。當進程試圖修改一個私有內存映射的頁時,內核就把該頁框進行復制,并在進程頁表中用復制的頁來替換原來的頁框,這是第八章中介紹的寫時復制機制的應用之一。雖然原來的頁框還仍然在頁高速緩存中,但不再屬于這個內存映射,這是由于被復制的頁框替換了原來的頁框。依次類推,這個復制的頁框不會被插入到頁高速緩存中,因為其中所包含的數據不再是磁盤上表示那個文件的有效數據。
??????? 事實上,一個新建立的內存映射就是一個不包含任何頁的線性區。當進程引用線性區中的一個地址時,缺頁異常發生,缺頁異常中斷處理程序檢查線性區的nopage方法是否被定義。如果沒有定義nopage,則說明線性區不映射磁盤上的文件;否則,進行映射,這個方法通過訪問塊設備處理讀取的頁。幾乎所有磁盤文件系統和塊設備文件都通過filemap_nopage()函數實現nopage方法。
2.2、創建內存映射
??????? 要創建一個新的內存映射,進程就要發出一個mmap()系統調用,并向該函數傳遞以下參數:
??????? * 文件描述符,標識要映射的文件。
??????? * 文件內的偏移量,指定要映射的文件部分的第一個字符。
??????? * 要映射的文件部分的長度。
??????? * 一組標志。進程必須顯示地設置MAP_SHARED標志或MAP_PRIVATE標志來指定所請求的內存映射的種類。
??????? * 一組權限,指定對線性區進行訪問的一種或者多種權限:讀訪問(PROT_READ)、寫訪問(PROT_WRITE)或執行訪問(PROT_EXEC)。
??????? * 一個可選的線性地址,內核把該地址作為新線性區應該從哪里開始的一個線索。如果指定了MAP_FIXED標志,且內核不能從指定的線性地址開始分配新線性區,那么這個系統調用失敗。
2.3、撤銷內存映射
??????? 當進程準備撤銷一個內存映射時,就調用munmap();該系統調用還可用于減少每種內存區的大小。給它傳遞的參數如下:
??????? * 要刪除的線性地址區間中第一個單元的地址。
??????? * 要刪除的線性地址區間的長度。
??????? 該系統調用的sys_munmap()服務例程實際上是調用do_munmap()函數。注意,不需要將待撤銷可寫共享內存映射中的頁刷新到磁盤。實際上,因為這些頁仍然在頁高速緩存內,因此繼續起磁盤高速緩存的作用。
2.4、內存映射的請求調頁
??????? 處于效率的原因,內存映射創建之后并沒有立即把頁框分配給它,而是盡可能向后推遲到不能再推遲----也就是說,當進程試圖對其中的一頁進行尋址時,就產生一個“缺頁”異常。
2.5、把內存映射的臟頁刷新到磁盤
??????? 進程可以使用msync()系統調用把屬于共享內存映射的臟頁刷新到磁盤。這個系統調用所接收的參數為:一個線性地址區間的起始地址、區間的長度以及具有下列含義的一組標志。
??????? MS_SYNC:要求這個系統調用掛起進程,直到I/O操作完成為止。在這種方式中,調用進程就可以假設當系統調用完成時,這個內存映射中的所有頁都已經被刷新到磁盤。
??????? MS_ASYNC(對MS_SYNC的補充):要求系統調用立即返回,而不用掛起調用進程。
??????? MS_INVALIDATE:要求系統調用使同一文件的其他內存映射無效(沒有真正實現,因為在Linux中無用)。
2.6、非線性內存映射
??????? 為了實現非線性映射,內核使用了另外一些數據結構。首先,線性區描述符的VM_NONLINEAR標志用于表示線性區存在一個非線性映射。給定文件的所有非線性映射線性區描述符都存放在一個雙向循環鏈表,該鏈表根植于address_space對象的i_mmap_nonlinear字段。
三、直接I/O傳送
??????? Linux提供了繞過頁高速緩存的簡單方法:直接I/O傳送。在每次I/O直接傳送中,內核對磁盤控制器進行編程,以便在自緩存的應用程序的用戶態地址空間中的頁與磁盤之間直接傳送數據。
??????? 我們知道,任何數據傳送都是異步進行的。當數據傳送正在進行時,內核可能切換當前進程,CPU可能返回到用戶態,產生數據傳送的進程的頁可能被交換出去,等等。這對于普通I/O數據傳送沒有什么影響,因為它們涉及磁盤高速緩存中的頁,磁盤高速緩存由內核擁有,不能被換出去,并且對內核態的所有進程都是可見的。
??????? 另一方面,直接I/O傳送應當在給定進程的用戶態地址空間的頁內移動數據。內核必須當心這些頁是由內核態的任一進程訪問的,當數據傳送正在進行時不能把它們交換出去。
四、異步I/O
??????? “異步”實際上就是:當用戶態進程調用庫函數讀寫文件時,一旦讀寫操作進入隊列函數就結束,甚至有可能真正的I/O數據傳輸還沒有開始。這樣調用進程可以在數據正在傳輸時繼續自己的運行。
4.1、Linux2.6中的異步I/O
4.1.1、異步I/O環境
??????? 基本上,一個異步I/O環境(簡稱AIO環境)就是一組數據結構,這個數據結構用于跟蹤進程請求的異步I/O操作的運行情況。每個AIO環境與一個kioctx對象關聯,它存放了與該環境有關的所有信息。一個應用可以創建多個AIO環境。一個給定進程的所有的kioctx描述符存放在一個單向鏈表中,該鏈表位于內存描述符的ioctx_list字段。
??????? AIO環是用戶態進程中地址空間的內存緩沖區,它也可以由內核態的所有進程訪問。kioctx對象中的ring_info.mmap_base和ring_info_mmap_size字段分別存放AIO環的用戶態起始地址和長度。ring_info.ring_pages字段則存放有一個數組指針,該數組存放所有含AIO環的頁框的描述符。
??????? AIO環實際上是一個環形緩沖區,內核用它來寫正運行的異步I/O操作的完成報告。AIO環的第一個字節有一個首部(struct aio_ring 數據結構),后面的所有字節是io_event數據結構,每個都表示一個已完成的異步I/O操作。因為AIO環的頁映射至進程的用戶態地址空間,應用可以直接檢查正運行的異步I/O操作的情況,從而避免使用相對較慢的系統調用。
4.1.2、提交異步I/O操作
??????? 訪問文件的模式有多種。我們在本章考慮如下幾種情況:
??????? 規范模式:
????????規范模式下文件打開后,標志O_SYNC與O_DIRECT清0,而且它的內容是由系統調用read()和write()來存取。系統調用read()將阻塞調用進程,直到數據被拷貝進用戶態地址空間(內核允許返回的字節數少于要求的字節數)。但系統調用write()不同,它在數據被拷貝到頁高速緩存(延遲寫)后就馬上結束。
??????? 同步模式:
????????同步模式下文件打開后,標志O_SYNC置1或稍后由系統調用fcntl()對其置1。這個標志只影響寫操作(讀操作總是會阻塞),它將阻塞調用進程,直到數據被有效地寫入磁盤。
??????? 內存映射模式:
??????? 內存映射模式下文件打開后,應用程序發出系統調用mmap()將文件映射到內存中。因此,文件就成為RAM中的一個字節數組,應用程序就可以直接訪問數組元素,而不需要系統調用read()、write()或lseek()。
??????? 直接I/O模式:
??????? 直接I/O模式下文件打開后,標志O_DIRECT置1。任何讀寫操作都將數據在用戶態地址空間與磁盤間直接傳送而不通過頁高速緩存。
??????? 異步模式:
??????? 異步模式下,文件的訪問可以有兩種方法,即通過一組POSIX API或Linux特有的系統調用來實現。所謂異步模式就是數據傳輸請求并不阻塞調用進程,而是在后臺執行,同時應用程序繼續它的正常運行。
一、讀寫文件
??????? 讀文件是基于頁的,內核總是一次傳送幾個完整的數據頁。如果進程發出read()系統調用來讀取一些字節,而這些數據還不在RAM中,那么,內核就要分配一個新頁框,并使用文件的適當部分來填充這個頁,把該頁加入頁高速緩存,最后把所請求的字節拷貝到進程地址空間中。對于大部分文件系統來說,從文件中讀取一個數據頁就等同于在磁盤上查找所請求的數據存放在哪些塊上。事實上,大多數磁盤文件系統的read方法是由名為generic_file_read()的通用函數實現的。
??????? 對基于磁盤的文件來說,寫操作的處理相當復雜,因為文件大小可以改變,因此內核可能會分配磁盤上的一些物理塊。當然,這個過程到底如何實現要取決于文件系統的類型。不過,很多磁盤文件系統是通過通用函數generic_file_write()實現它們的write方法的。這樣的文件系統如Ext2、System V/Coherent/Xenix及Minix。另一方面,還有幾個文件系統(如日志文件系統和網絡文件系統)通過自定義的函數實現它們的write方法。
1.1、從文件中讀取數據
1.1.1、普通文件的readpage方法
1.1.2、塊設備文件的readpage方法
1.2、文件的預讀
??????? 預讀(read-ahead)是一種技術,這種技術在于在實際請求前讀普通文件或塊設備文件的幾個相鄰的數據頁。在大多數情況下,預讀能極大地提高磁盤的性能,因為預讀使磁盤控制器處理較少的命令,其中的每條命令都涉及一大組相鄰的扇區。此外,預讀還能提高系統的響應能力。順序讀取文件的進程通常不需要等待請求的數據,因為請求的數據已經在RAM中了。
??????? 但是,預讀對于隨機訪問的文件是沒有用的;在這種情況下,預讀實際上是有害的,因為它用無用的信息浪費了頁高速緩存的空間。因此,當內核確定出最近所進行的I/O訪問與前一次I/O訪問不是順序的時候就減少或停止預讀。
??????? 文件的預讀需要更復雜的算法,這是由于以下幾個原因:
??????? * 由于數據是逐頁進行讀取的,因此預讀算法不必考慮頁內偏移量,只要考慮所訪問的頁在文件內部的位置就可以了。
??????? * 只要進程持續地順序訪問一個文件,預讀就會逐漸增加。
??????? * 當前的訪問與上一次訪問不是順序的時(隨機訪問),預讀就會逐漸減少乃至禁止。
??????? * 當一個進程重復地訪問同一頁(即只使用文件的很小一部分)時,或者當幾乎所有的頁都已在頁高速緩存內時,預讀就必須停止。
??????? * 低級I/O設備驅動程序必須在合適的時候激活,這樣當將來進程需要時,頁已傳送完畢。
??????? 當訪問給定文件時,預讀算法使用兩個頁面集,各自對應文件的一個連續區域。這兩個頁面集分別叫做當前窗(current window)和預讀窗(ahead window)。
??????? 當前窗內的頁是進程請求的頁和內核預讀的頁,且位于頁高速緩存內(當前窗內的頁不必是最新的,因為I/O數據傳輸仍可能在運行中)。當前窗包含進程順序訪問的最后一頁,且可能有內核預讀但進程未申請的頁。
??????? 預讀窗內的頁緊接著當前窗內的頁,它們是內核正在預讀的頁。預讀窗內的頁都不是進程請求的,但內核假定進程會遲早請求。
??????? 當內核認為是順序訪問而且第一頁在當前窗內時,它就檢查是否建立了預讀窗。如果沒有,內核創建一個預讀窗并觸發相應頁的讀操作。理想情況下,進程繼續從當前窗請求頁,同時預讀窗的頁則正在傳送。當進程請求的頁在預讀窗,那么預讀窗就成為當前窗。
??????? 何時執行預讀算法?這有下列幾種情形:
??????? * 當內核用用戶態請求來讀文件數據的頁時。這一事件觸發page_cache_readahead()函數的調用。
??????? * 當內核為文件內存映射分配一頁時。
??????? * 當用戶態應用執行readahead()系統調用時,它會對某個文件描述符顯示觸發某預讀活動。
??????? * 當用戶態應用使用POSIX_FADV_NOREUSE或POSIX_FADV_WILLNEED命令執行posix_fadvise()系統調用時,它會通知內核,某個范圍的文件頁不久將要被訪問。
??????? * 當用戶態應用使用MADV_WILLNEED命令執行madvise()系統調用時,它會通知內核,某個文件內存映射區域中的給定范圍的文件頁不久將要被訪問。
1.2.1、page_cache_readahead()函數
????????page_cache_readahead()函數處理沒有被特殊系統調用顯示觸發的所有預讀操作。它填寫當前窗和預讀窗,根據預讀命中數更新當前窗和預讀窗的大小,也就是根據過去對文件訪問預讀策略的成功程度來調整。
1.2.2、handle_ra_miss()函數
1.3、寫入文件
??????? write()系統調用涉及把數據從調用進程的用戶態地址空間中移動到內核數據結構中,然后再移動到磁盤上。文件對象的write方法允許每種文件類型都定義一個專用的寫操作。在Linux2.6中,每個磁盤文件系統的write方法都是一個過程,該過程主要標識寫操作所涉及的磁盤塊,把數據從用戶態地址空間拷貝到頁高速緩存的某些頁中,然后把這些頁中的緩沖區標記成臟。
1.3.1、普通文件的prepare_write和commit_write方法
1.3.2、塊設備文件的prepare_write和commit_write方法
1.4、將臟頁寫到磁盤
??????? 系統調用write()的作用就是修改頁高速緩存內一些頁的內容,如果頁高速緩存內沒有所要的頁則分配并追加這些頁。某些情況下(例如文件帶O_SYNC標志打開),I/O數據傳輸立即啟動。但是通常I/O數據傳輸是延遲進行的。
二、內存映射
??????? 一個線性區可以和磁盤文件系統的普通文件的某一部分或者塊設備文件相關聯。這就意味著內核把對區線性中頁內某個字節的訪問轉換成對文件中相應字節的操作。這種技術稱為內存映射(memory mapping)。
??????? 有兩種類型的內存映射:
??????? 共享型:在線性區頁上的任何寫操作都會修改磁盤上的文件;而且,如果進程對共享映射中的一個頁進行寫,那么這種修改對于其他映射了這同一文件的所有進程來說都是可見的。
??????? 私有型:當進程創建的映射只是為讀文件,而不是寫文件時才會使用此種映射。出于這種目的,私有映射的效率要比共享映射的效率更高。但是對私有映射頁的任何寫操作都會使內核停止映射該文件中的頁。因此,寫操作既不會改變磁盤上的文件,對訪問相同文件的其他進程也不可見。但是私有內存映射中還沒有被進程改變的頁會因為其他進程進行的文件更新而更新。
??????? 作為一條通用規則,如果一個內存映射是共享的,相應的線性區就設置了VM_SHARED標志;如果一個內存映射是私有的,那么相應的線性區就清除了VM_SHARED標志。
2.1、內存映射的數據結構
??????? 內存映射可以用下列數據結構的組合來表示:
??????? * 與所映射的文件相關的索引節點對象。
??????? * 所映射文件的address_space對象。
??????? * 不同進程對一個文件進行不同映射所使用的文件對象。
??????? * 對文件進行每一不同映射所使用的vm_area_struct描述符。
??????? * 對文件進行映射的線性區所分配的每個頁框所對應的頁描述符。
??????? 共享內存映射的頁通常都包含在頁高速緩存中;私有內存映射的頁只要還沒有被修改,也都包含在頁高速緩存中。當進程試圖修改一個私有內存映射的頁時,內核就把該頁框進行復制,并在進程頁表中用復制的頁來替換原來的頁框,這是第八章中介紹的寫時復制機制的應用之一。雖然原來的頁框還仍然在頁高速緩存中,但不再屬于這個內存映射,這是由于被復制的頁框替換了原來的頁框。依次類推,這個復制的頁框不會被插入到頁高速緩存中,因為其中所包含的數據不再是磁盤上表示那個文件的有效數據。
??????? 事實上,一個新建立的內存映射就是一個不包含任何頁的線性區。當進程引用線性區中的一個地址時,缺頁異常發生,缺頁異常中斷處理程序檢查線性區的nopage方法是否被定義。如果沒有定義nopage,則說明線性區不映射磁盤上的文件;否則,進行映射,這個方法通過訪問塊設備處理讀取的頁。幾乎所有磁盤文件系統和塊設備文件都通過filemap_nopage()函數實現nopage方法。
2.2、創建內存映射
??????? 要創建一個新的內存映射,進程就要發出一個mmap()系統調用,并向該函數傳遞以下參數:
??????? * 文件描述符,標識要映射的文件。
??????? * 文件內的偏移量,指定要映射的文件部分的第一個字符。
??????? * 要映射的文件部分的長度。
??????? * 一組標志。進程必須顯示地設置MAP_SHARED標志或MAP_PRIVATE標志來指定所請求的內存映射的種類。
??????? * 一組權限,指定對線性區進行訪問的一種或者多種權限:讀訪問(PROT_READ)、寫訪問(PROT_WRITE)或執行訪問(PROT_EXEC)。
??????? * 一個可選的線性地址,內核把該地址作為新線性區應該從哪里開始的一個線索。如果指定了MAP_FIXED標志,且內核不能從指定的線性地址開始分配新線性區,那么這個系統調用失敗。
2.3、撤銷內存映射
??????? 當進程準備撤銷一個內存映射時,就調用munmap();該系統調用還可用于減少每種內存區的大小。給它傳遞的參數如下:
??????? * 要刪除的線性地址區間中第一個單元的地址。
??????? * 要刪除的線性地址區間的長度。
??????? 該系統調用的sys_munmap()服務例程實際上是調用do_munmap()函數。注意,不需要將待撤銷可寫共享內存映射中的頁刷新到磁盤。實際上,因為這些頁仍然在頁高速緩存內,因此繼續起磁盤高速緩存的作用。
2.4、內存映射的請求調頁
??????? 處于效率的原因,內存映射創建之后并沒有立即把頁框分配給它,而是盡可能向后推遲到不能再推遲----也就是說,當進程試圖對其中的一頁進行尋址時,就產生一個“缺頁”異常。
2.5、把內存映射的臟頁刷新到磁盤
??????? 進程可以使用msync()系統調用把屬于共享內存映射的臟頁刷新到磁盤。這個系統調用所接收的參數為:一個線性地址區間的起始地址、區間的長度以及具有下列含義的一組標志。
??????? MS_SYNC:要求這個系統調用掛起進程,直到I/O操作完成為止。在這種方式中,調用進程就可以假設當系統調用完成時,這個內存映射中的所有頁都已經被刷新到磁盤。
??????? MS_ASYNC(對MS_SYNC的補充):要求系統調用立即返回,而不用掛起調用進程。
??????? MS_INVALIDATE:要求系統調用使同一文件的其他內存映射無效(沒有真正實現,因為在Linux中無用)。
2.6、非線性內存映射
??????? 為了實現非線性映射,內核使用了另外一些數據結構。首先,線性區描述符的VM_NONLINEAR標志用于表示線性區存在一個非線性映射。給定文件的所有非線性映射線性區描述符都存放在一個雙向循環鏈表,該鏈表根植于address_space對象的i_mmap_nonlinear字段。
三、直接I/O傳送
??????? Linux提供了繞過頁高速緩存的簡單方法:直接I/O傳送。在每次I/O直接傳送中,內核對磁盤控制器進行編程,以便在自緩存的應用程序的用戶態地址空間中的頁與磁盤之間直接傳送數據。
??????? 我們知道,任何數據傳送都是異步進行的。當數據傳送正在進行時,內核可能切換當前進程,CPU可能返回到用戶態,產生數據傳送的進程的頁可能被交換出去,等等。這對于普通I/O數據傳送沒有什么影響,因為它們涉及磁盤高速緩存中的頁,磁盤高速緩存由內核擁有,不能被換出去,并且對內核態的所有進程都是可見的。
??????? 另一方面,直接I/O傳送應當在給定進程的用戶態地址空間的頁內移動數據。內核必須當心這些頁是由內核態的任一進程訪問的,當數據傳送正在進行時不能把它們交換出去。
四、異步I/O
??????? “異步”實際上就是:當用戶態進程調用庫函數讀寫文件時,一旦讀寫操作進入隊列函數就結束,甚至有可能真正的I/O數據傳輸還沒有開始。這樣調用進程可以在數據正在傳輸時繼續自己的運行。
4.1、Linux2.6中的異步I/O
4.1.1、異步I/O環境
??????? 基本上,一個異步I/O環境(簡稱AIO環境)就是一組數據結構,這個數據結構用于跟蹤進程請求的異步I/O操作的運行情況。每個AIO環境與一個kioctx對象關聯,它存放了與該環境有關的所有信息。一個應用可以創建多個AIO環境。一個給定進程的所有的kioctx描述符存放在一個單向鏈表中,該鏈表位于內存描述符的ioctx_list字段。
??????? AIO環是用戶態進程中地址空間的內存緩沖區,它也可以由內核態的所有進程訪問。kioctx對象中的ring_info.mmap_base和ring_info_mmap_size字段分別存放AIO環的用戶態起始地址和長度。ring_info.ring_pages字段則存放有一個數組指針,該數組存放所有含AIO環的頁框的描述符。
??????? AIO環實際上是一個環形緩沖區,內核用它來寫正運行的異步I/O操作的完成報告。AIO環的第一個字節有一個首部(struct aio_ring 數據結構),后面的所有字節是io_event數據結構,每個都表示一個已完成的異步I/O操作。因為AIO環的頁映射至進程的用戶態地址空間,應用可以直接檢查正運行的異步I/O操作的情況,從而避免使用相對較慢的系統調用。
4.1.2、提交異步I/O操作
總結
以上是生活随笔為你收集整理的第十六章--访问文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个插件让你在 Redux 中写 pro
- 下一篇: 图像的遍历