日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

kernel笔记——块I/O

發布時間:2025/4/9 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 kernel笔记——块I/O 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Linux下,I/O處理的層次可分為4層:

?  1. 系統調用層,應用程序使用系統調用指定讀寫哪個文件,文件偏移是多少

  ?2. 文件系統層,寫文件時將用戶態中的buffer拷貝到內核態下,并由cache緩存該部分數據

? ? ? ? 3. 塊層,管理塊設備I/O隊列,對I/O請求進行合并、排序

? ? ? ? 4. 設備層,通過DMA與內存直接交互,將數據寫到磁盤

?

下圖清晰地說明了Linux I/O層次結構:

?

寫文件過程

寫文件的過程包含了讀的過程,文件先從磁盤載入內存,存到cache中,磁盤內容與物理內存頁間建立起映射關系。用于寫文件的write函數的聲明如下:

ssize_t write(int fd, const void *buf, size_t count);

其中fd對應進程的file結構, buf指向寫入的數據。內核從cache中找出與被寫文件相應的物理頁,write決定寫內存的第幾個頁面,例如"echo 1 > a.out"(底層調用write)寫入的是a.out文件的第0個位置,write將寫相應內存的第一頁。

?

write函數修改內存內容之后,相應的內存頁、inode被標記為dirty,此時write函數返回。注意至此尚未往磁盤寫數據,只是cache中的內容被修改。

?

那什么時候內存中的內容會刷到磁盤中呢?

把臟數據刷到磁盤的工作由內核線程flush完成,flush搜尋內存中的臟數據,按設定將臟數據寫到磁盤,我們可以通過sysctl命令查看、設定flush刷臟數據的策略:

linux # sysctl -a | grep centi vm.dirty_writeback_centisecs = 500 vm.dirty_expire_centisecs = 3000 linux # sysctl -a | grep background_ratio vm.dirty_background_ratio = 10?

以上數值單位為1/100秒,“dirty_writeback_centisecs = 500”指示flush每隔5秒執行一次,“dirty_expire_centisecs = 3000” 指示內存中駐留30秒以上的臟數據將由flush在下一次執行時寫入磁盤,“dirty_background_ratio = 10”指示若臟頁占總物理內存10%以上,則觸發flush把臟數據寫回磁盤。

?

flush找出了需要寫回磁盤的臟數據,那存儲臟數據的物理頁又與磁盤的哪些扇區對應呢?

物理頁與扇區的對應關系由文件系統定義,文件系統定義了一個內存頁(4KB)與多少個塊對應,對應關系在格式化磁盤時設定,運行時由buffer_head保存對應關系:

linux # cat /proc/slabinfo | grep buffer_head buffer_head 12253 12284 104 37 1 : tunables 120 60 8 : slabdata 332 332 0

?

文件系統層告知塊I/O層寫哪個設備,具體哪個塊,執行以下命令后,我們可以在/var/log/messages中看到文件系統層下發到塊層的讀寫請求:

linux # echo 1 > /proc/sys/vm/block_dump linux # tail -n 3 /var/log/messages Aug 7 00:50:31 linux-q62c kernel: [ 7523.602144] bash(5466): READ block 1095792 on sda1 Aug 7 00:50:31 linux-q62c kernel: [ 7523.622857] bash(5466): dirtied inode 27874 (tail) on sda1 Aug 7 00:50:31 linux-q62c kernel: [ 7523.623213] tail(5466): READ block 1095824 on sda1

?

塊I/O層使用struct bio記錄文件系統層下發的I/O請求,bio中主要保存了需要往磁盤刷數據的物理頁信息,以及對應磁盤上的扇區信息。

?

塊I/O層為每一個磁盤設備維護了一條I/O請求隊列,請求隊列在內核中由struct request_queue表示。每一個讀或寫請求都需經過submit_bio函數處理,submit_bio將讀寫請求放入相應I/O請求隊列中。該層起到最主要的作用就是對I/O請求進行合并和排序,這樣減少了實際的磁盤讀寫次數和尋道時間,達到優化磁盤讀寫性能的目的。

?

使用crash解析vmcore文件,執行"dev -d"命令,可以看到塊設備請求隊列的相關信息:

crash > dev -d MAJOR GENDISK NAME REQUEST QUEUE TOTAL ASYNC SYNC DRV8 0xffff880119e85800 sda 0xffff88011a6a6948 10 0 0 108 0xffff880119474800 sdb 0xffff8801195632d0 0 0 0 0

執行"struct request_queue 0xffff88011a6a6948",可對以上sda設備相應的request_queue請求隊列結構進行解析。

執行以下命令,可以查看sda設備的請求隊列大小:

linux # cat /sys/block/sda/queue/nr_requests 128

?

如何對I/O請求進行合并、排序,那就是I/O調度算法完成的工作,Linux支持多種I/O調度算法,通過以下命令可以查看:

linux # cat /sys/block/sda/queue/scheduler noop anticipatory deadline [cfq]

?

塊I/O層的另一個作用就是對I/O讀寫情況進行統計,執行iostat命令,看到的就是該層提供的統計信息:

linux # iostat -x -k -d 1 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %utilsda 0.00 9915.00 1.00 90.00 4.00 34360.00 755.25 11.79 120.57 6.33 57.60

?

其中rrqm/s、wrqm/s分別指示了每秒寫請求、讀請求的合并次數。

?

task_io_account_read函數用于統計各個進程發起的讀請求量, 由該函數得到的是進程讀請求量的準確值。而對于寫請求,由于數據寫入cache后write調用就返回,因而在內核的層面無法統計到一個進程發起的準確寫請求量,讀時進程會等buff可用,而寫則寫入cache后返回,讀是同步的,寫卻不一定同步,這是讀寫實現上的最大區別。

?

再往下就是設備層,設備從隊列中取出I/O請求,scsi的scsi_request_fn函數就是完成取請求并處理的任務。scsi層最終將處理請求轉化為指令,指令下發后進行DMA(direct memory access)映射,將內存的部分cache映射到DMA,這樣設備繞過cpu直接操作主存。

?

設備層完成內存數據到磁盤拷貝后,該消息將一層層上報,最后內核去除原臟頁的dirty位標志。

?

以上為寫磁盤的大致實現過程,對于讀磁盤,內核首先在緩存中查找對應內容,若命中則不會進行磁盤操作。若進程讀取一個字節的數據,內核不會僅僅返回一個字節,其以頁面為單位(4KB),最少返回一個頁面的數據。另外,內核會預讀磁盤數據,執行以下命令可以看到能夠預讀的最大數據量(以KB為單位):

linux # cat /sys/block/sda/queue/read_ahead_kb 512

?

下面我們通過一段systemtap代碼,了解內核的預讀機制:

//test.stp probe kernel.function("submit_bio") {if(execname() == "dd" && __bio_ino($bio) == 5234){printf("inode %d %s on %s %d bytes start %d\n",__bio_ino($bio),bio_rw_str($bio),__bio_devname($bio),$bio->bi_size,$bio->bi_sector)} }

?

以上代碼指示當dd命令讀寫inode號為5234的文件、經過內核函數submit_bio時,輸出inode號、操作方式(讀或寫)、文件所在設備名、讀寫大小、扇區號信息。執行以下代碼安裝探測模塊:

stap test.stp &

?

之后我們使用dd命令讀取inode號為5234的文件(可通過stat命令取得文件inode號):

dd if=airport.txt of=/dev/null bs=1 count=10000000

?

以上命令故意將bs設為1,即每次讀取一個字節,以此觀察內核預讀機制。執行該命令的過程中,我們在終端中可以看到以下輸出:

inode 5234 R on sda2 16384 bytes start 70474248 inode 5234 R on sda2 32768 bytes start 70474280 inode 5234 R on sda2 32768 bytes start 70474352 inode 5234 R on sda2 131072 bytes start 70474416 inode 5234 R on sda2 262144 bytes start 70474672 inode 5234 R on sda2 524288 bytes start 70475184

?

由以上輸出可知,預讀從16384字節(16KB)逐漸增大,最后變為524288字節(512KB),可見內核會根據讀的情況動態地調整預讀的數據量。?

?

由于讀、寫磁盤均要經過submit_bio函數處理,submit_bio之后讀、寫的底層實現大致相同。

?

直接I/O

當我們以O_DIRECT標志調用open函數打開文件時,后續針對該文件的read、write操作都將以直接I/O(direct I/O)的方式完成;對于裸設備,I/O方式也為直接I/O。

?

直接I/O跳過了文件系統這一層,但塊層仍發揮作用,其將內存頁與磁盤扇區對應上,這時不再是建立cache到DMA映射,而是進程的buffer映射到DMA。進行直接I/O時要求讀寫一個扇區(512bytes)的整數倍,否則對于非整數倍的部分,將以帶cache的方式進行讀寫。

?

使用直接I/O,寫磁盤少了用戶態到內核態的拷貝過程,這提升了寫磁盤的效率,也是直接I/O的作用所在。而對于讀操作,第一次直接I/O將比帶cache的方式快,但因帶cache方式后續再讀時將從cache中讀,因而后續的讀將比直接I/O快。有些數據庫使用直接I/O,同時實現了自己的cache方式。

?

異步I/O

Linux下有兩種異步I/O(asynchronous I/O)方式,一種是aio_read/aio_write庫函數調用,其實現方式為純用戶態的實現,依靠多線程,主線程將I/O下發到專門處理I/O的線程,以此達到主線程異步的目的。

?

另一種是io_submit,該函數是內核提供的系統調用,使用io_submit也需要指定文件的打開方式為O_DIRECT,并且讀寫需按扇區對齊。

?

Reference: Chapter 14 - The Block I/O Layer, Linux kernel development.3rd.Edition

轉載于:https://www.cnblogs.com/felixzh/p/9039519.html

總結

以上是生活随笔為你收集整理的kernel笔记——块I/O的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。