<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入淺出Linux的 I/O 原理

          共 4077字,需瀏覽 9分鐘

           ·

          2021-12-09 22:04

          傳統(tǒng)的 System Call I/O

          在 Linux 系統(tǒng)中,傳統(tǒng)的訪問方式是通過 write() 和 read() 兩個系統(tǒng)調(diào)用實現(xiàn)的,通過 read() 函數(shù)讀取文件到到緩存區(qū)中,然后通過 write() 方法把緩存中的數(shù)據(jù)輸出到網(wǎng)絡(luò)端口。

          read(file_fd,?tmp_buf,?len);
          write(socket_fd,?tmp_buf,?len);

          下圖分別對應(yīng)傳統(tǒng) I/O 操作的數(shù)據(jù)讀寫流程,整個過程涉及 2 次 CPU 拷貝、2 次 DMA 拷貝,總共 4 次拷貝,以及 4 次上下文切換。

          • CPU 拷貝:由 CPU 直接處理數(shù)據(jù)的傳送,數(shù)據(jù)拷貝時會一直占用 CPU 的資源。

          • DMA 拷貝:由 CPU 向DMA磁盤控制器下達(dá)指令,讓 DMA 控制器來處理數(shù)據(jù)的傳送,數(shù)據(jù)傳送完畢再把信息反饋給 CPU,從而減輕了 CPU 資源的占有率。

          • 上下文切換:當(dāng)用戶程序向內(nèi)核發(fā)起系統(tǒng)調(diào)用時,CPU 將用戶進(jìn)程從用戶態(tài)切換到內(nèi)核態(tài);當(dāng)系統(tǒng)調(diào)用返回時,CPU 將用戶進(jìn)程從內(nèi)核態(tài)切換回用戶態(tài)。

          讀操作

          當(dāng)應(yīng)用程序執(zhí)行 read 系統(tǒng)調(diào)用讀取一塊數(shù)據(jù)的時候,如果這塊數(shù)據(jù)已經(jīng)存在于用戶進(jìn)程的頁內(nèi)存中,就直接從內(nèi)存中讀取數(shù)據(jù)。

          如果數(shù)據(jù)不存在,則先將數(shù)據(jù)從磁盤加載數(shù)據(jù)到內(nèi)核空間的讀緩存(Read Buffer)中,再從讀緩存拷貝到用戶進(jìn)程的頁內(nèi)存中。

          read(file_fd,?tmp_buf,?len);

          基于傳統(tǒng)的 I/O 讀取方式,read 系統(tǒng)調(diào)用會觸發(fā) 2 次上下文切換,1 次 DMA 拷貝和 1 次 CPU 拷貝。

          發(fā)起數(shù)據(jù)讀取的流程如下:

          • 用戶進(jìn)程通過 read() 函數(shù)向 Kernel 發(fā)起 System Call,上下文從 user space 切換為 kernel space。

          • CPU 利用 DMA 控制器將數(shù)據(jù)從主存或硬盤拷貝到 kernel space 的讀緩沖區(qū)(Read Buffer)。

          • CPU 將讀緩沖區(qū)(Read Buffer)中的數(shù)據(jù)拷貝到 user space 的用戶緩沖區(qū)(User Buffer)。

          • 上下文從 kernel space 切換回用戶態(tài)(User Space),read 調(diào)用執(zhí)行返回。

          寫操作

          當(dāng)應(yīng)用程序準(zhǔn)備好數(shù)據(jù),執(zhí)行 write 系統(tǒng)調(diào)用發(fā)送網(wǎng)絡(luò)數(shù)據(jù)時,先將數(shù)據(jù)從用戶空間的頁緩存拷貝到內(nèi)核空間的網(wǎng)絡(luò)緩沖區(qū)(Socket Buffer)中,然后再將寫緩存中的數(shù)據(jù)拷貝到網(wǎng)卡設(shè)備完成數(shù)據(jù)發(fā)送。

          write(socket_fd,?tmp_buf,?len);

          基于傳統(tǒng)的 I/O 寫入方式,write() 系統(tǒng)調(diào)用會觸發(fā) 2 次上下文切換,1 次 CPU 拷貝和 1 次 DMA 拷貝。

          用戶程序發(fā)送網(wǎng)絡(luò)數(shù)據(jù)的流程如下:

          • 用戶進(jìn)程通過 write() 函數(shù)向 kernel 發(fā)起 System Call,上下文從 user space 切換為 kernel space。

          • CPU 將用戶緩沖區(qū)(User Buffer)中的數(shù)據(jù)拷貝到 kernel space 的網(wǎng)絡(luò)緩沖區(qū)(Socket Buffer)。

          • CPU 利用 DMA 控制器將數(shù)據(jù)從網(wǎng)絡(luò)緩沖區(qū)(Socket Buffer)拷貝到 NIC 進(jìn)行數(shù)據(jù)傳輸。

          • 上下文從 kernel space 切換回 user space,write 系統(tǒng)調(diào)用執(zhí)行返回。

          網(wǎng)絡(luò) I/O

          磁盤 I/O

          高性能優(yōu)化的 I/O

          • 零拷貝技術(shù)。

          • 多路復(fù)用技術(shù)。

          • 頁緩存(PageCache)技術(shù)。

          其中,頁緩存(PageCache)是操作系統(tǒng)對文件的緩存,用來減少對磁盤的 I/O 操作,以頁為單位的,內(nèi)容就是磁盤上的物理塊,頁緩存能幫助程序?qū)ξ募M(jìn)行順序讀寫的速度幾乎接近于內(nèi)存的讀寫速度,主要原因就是由于 OS 使用 PageCache 機(jī)制對讀寫訪問操作進(jìn)行了性能優(yōu)化。

          頁緩存讀取策略:當(dāng)進(jìn)程發(fā)起一個讀操作 (比如,進(jìn)程發(fā)起一個 read() 系統(tǒng)調(diào)用),它首先會檢查需要的數(shù)據(jù)是否在頁緩存中:

          • 如果在,則放棄訪問磁盤,而直接從頁緩存中讀取。

          • 如果不在,則內(nèi)核調(diào)度塊 I/O 操作從磁盤去讀取數(shù)據(jù),并讀入緊隨其后的少數(shù)幾個頁面(不少于一個頁面,通常是三個頁面),然后將數(shù)據(jù)放入頁緩存中。

          頁緩存寫策略:當(dāng)進(jìn)程發(fā)起 write 系統(tǒng)調(diào)用寫數(shù)據(jù)到文件中,先寫到頁緩存,然后方法返回。此時數(shù)據(jù)還沒有真正的保存到文件中去,Linux 僅僅將頁緩存中的這一頁數(shù)據(jù)標(biāo)記為 “臟”,并且被加入到臟頁鏈表中。

          然后,由 flusher 回寫線程周期性將臟頁鏈表中的頁寫到磁盤,讓磁盤中的數(shù)據(jù)和內(nèi)存中保持一致,最后清理“臟”標(biāo)識。在以下三種情況下,臟頁會被寫回磁盤:

          • 空閑內(nèi)存低于一個特定閾值。

          • 臟頁在內(nèi)存中駐留超過一個特定的閾值時。

          • 當(dāng)用戶進(jìn)程調(diào)用 sync() 和 fsync() 系統(tǒng)調(diào)用時。

          存儲設(shè)備的 I/O 棧

          由圖可見,從系統(tǒng)調(diào)用的接口再往下,Linux 下的 IO 棧致大致有三個層次:

          • 文件系統(tǒng)層,以 write 為例,內(nèi)核拷貝了 write 參數(shù)指定的用戶態(tài)數(shù)據(jù)到文件系統(tǒng) Cache 中,并適時向下層同步。

          • 塊層,管理塊設(shè)備的 IO 隊列,對 IO 請求進(jìn)行合并、排序(還記得操作系統(tǒng)課程學(xué)習(xí)過的 IO 調(diào)度算法嗎?)。

          • 設(shè)備層,通過 DMA 與內(nèi)存直接交互,完成數(shù)據(jù)和具體設(shè)備之間的交互。

          結(jié)合這個圖,想想 Linux 系統(tǒng)編程里用到的?Buffered IO、mmap、Direct IO,這些機(jī)制怎么和?Linux I/O?棧聯(lián)系起來呢?上面的圖有點(diǎn)復(fù)雜,我畫一幅簡圖,把這些機(jī)制所在的位置添加進(jìn)去:

          這下一目了然了吧?傳統(tǒng)的 Buffered IO?使用 read 讀取文件的過程什么樣的?假設(shè)要去讀一個冷文件(Cache 中不存在),open 打開文件內(nèi)核后建立了一系列的數(shù)據(jù)結(jié)構(gòu),接下來調(diào)用 read,到達(dá)文件系統(tǒng)這一層,發(fā)現(xiàn) Page Cache 中不存在該位置的磁盤映射,然后創(chuàng)建相應(yīng)的?Page Cache?并和相關(guān)的扇區(qū)關(guān)聯(lián)。然后請求繼續(xù)到達(dá)塊設(shè)備層,在 IO 隊列里排隊,接受一系列的調(diào)度后到達(dá)設(shè)備驅(qū)動層,此時一般使用?DMA?方式讀取相應(yīng)的磁盤扇區(qū)到 Cache 中,然后 read 拷貝數(shù)據(jù)到用戶提供的用戶態(tài) buffer?中去(read 的參數(shù)指出的)。

          整個過程有幾次拷貝?從磁盤到 Page Cache 算第一次的話,從 Page Cache 到用戶態(tài) buffer 就是第二次了。而 mmap 做了什么?mmap 直接把 Page Cache 映射到了用戶態(tài)的地址空間里了,所以 mmap 的方式讀文件是沒有第二次拷貝過程的。

          那 Direct IO 做了什么?這個機(jī)制更狠,直接讓用戶態(tài)和塊 IO 層對接,直接放棄 Page Cache,從磁盤直接和用戶態(tài)拷貝數(shù)據(jù)。好處是什么?寫操作直接映射進(jìn)程的buffer到磁盤扇區(qū),以 DMA 的方式傳輸數(shù)據(jù),減少了原本需要到 Page Cache 層的一次拷貝,提升了寫的效率。對于讀而言,第一次肯定也是快于傳統(tǒng)的方式的,但是之后的讀就不如傳統(tǒng)方式了(當(dāng)然也可以在用戶態(tài)自己做 Cache,有些商用數(shù)據(jù)庫就是這么做的)。

          除了傳統(tǒng)的 Buffered IO 可以比較自由的用偏移+長度的方式讀寫文件之外,mmap 和 Direct IO 均有數(shù)據(jù)按頁對齊的要求,Direct IO 還限制讀寫必須是底層存儲設(shè)備塊大小的整數(shù)倍(甚至 Linux 2.4 還要求是文件系統(tǒng)邏輯塊的整數(shù)倍)。所以接口越來越底層,換來表面上的效率提升的背后,需要在應(yīng)用程序這一層做更多的事情。所以想用好這些高級特性,除了深刻理解其背后的機(jī)制之外,也要在系統(tǒng)設(shè)計上下一番功夫。

          I/O Buffering

          如圖,當(dāng)程序調(diào)用各類文件操作函數(shù)后,用戶數(shù)據(jù)(User Data)到達(dá)磁盤(Disk)的流程如圖所示。

          圖中描述了 Linux 下文件操作函數(shù)的層級關(guān)系和內(nèi)存緩存層的存在位置。中間的黑色實線是用戶態(tài)和內(nèi)核態(tài)的分界線。

          從上往下分析這張圖:

          1.首先是 C 語言 stdio 庫定義的相關(guān)文件操作函數(shù),這些都是用戶態(tài)實現(xiàn)的跨平臺封裝函數(shù)。stdio 中實現(xiàn)的文件操作函數(shù)有自己的 stdio buffer,這是在用戶態(tài)實現(xiàn)的緩存。此處使用緩存的原因很簡單 — 系統(tǒng)調(diào)用總是昂貴的。如果用戶代碼以較小的 size 不斷的讀或?qū)懳募脑?,stdio 庫將多次的讀或者寫操作通過 buffer 進(jìn)行聚合是可以提高程序運(yùn)行效率的。stdio 庫同時也支持 fflush 函數(shù)來主動的刷新 buffer,主動的調(diào)用底層的系統(tǒng)調(diào)用立即更新 buffer 里的數(shù)據(jù)。特別地,setbuf 函數(shù)可以對 stdio 庫的用戶態(tài) buffer 進(jìn)行設(shè)置,甚至取消 buffer 的使用。

          2.系統(tǒng)調(diào)用的 read/write 和真實的磁盤讀寫之間也存在一層 buffer,這里用術(shù)語 Kernel buffer cache 來指代這一層緩存。在 Linux 下,文件的緩存習(xí)慣性的稱之為 Page Cache,而更低一級的設(shè)備的緩存稱之為 Buffer Cache。這兩個概念很容易混淆,這里簡單的介紹下概念上的區(qū)別:Page Cache 用于緩存文件的內(nèi)容,和文件系統(tǒng)比較相關(guān)。文件的內(nèi)容需要映射到實際的物理磁盤,這種映射關(guān)系由文件系統(tǒng)來完成;Buffer Cache 用于緩存存儲設(shè)備塊(比如磁盤扇區(qū))的數(shù)據(jù),而不關(guān)心是否有文件系統(tǒng)的存在(文件系統(tǒng)的元數(shù)據(jù)緩存在 Buffer Cache 中)。

          來源:https://is-cloud.blog.csdn.net/article/details/105897963
          作者:范桂颶

          Linux學(xué)習(xí)指南

          有收獲,點(diǎn)個在看?

          瀏覽 79
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  婷婷影音先锋 | 台湾中文字幕娱乐网 | 成人激情在线视频 | 黄色免费看看 | 91av在线观看完整版 |