深入理解linux IO

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

  1. 系統(tǒng)調(diào)用層鸦概,應(yīng)用程序使用系統(tǒng)調(diào)用指定讀寫哪個(gè)文件,文件偏移是多少
  2. 文件系統(tǒng)層堕伪,寫文件時(shí)將用戶態(tài)中的buffer拷貝到內(nèi)核態(tài)下,并由cache緩存該部分?jǐn)?shù)據(jù)
  3. 塊層栗菜,管理塊設(shè)備I/O隊(duì)列欠雌,對(duì)I/O請(qǐng)求進(jìn)行合并、排序
  4. 設(shè)備層疙筹,通過(guò)DMA與內(nèi)存直接交互富俄,將數(shù)據(jù)寫到磁盤

下圖清晰地說(shuō)明了Linux I/O層次結(jié)構(gòu):

image.png

寫文件過(guò)程

寫文件的過(guò)程包含了讀的過(guò)程,文件先從磁盤載入內(nèi)存而咆,存到cache中霍比,磁盤內(nèi)容與物理內(nèi)存頁(yè)間建立起映射關(guān)系。用于寫文件的write函數(shù)的聲明如下:

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

其中fd對(duì)應(yīng)進(jìn)程的file結(jié)構(gòu)暴备, buf指向?qū)懭氲臄?shù)據(jù)悠瞬。內(nèi)核從cache中找出與被寫文件相應(yīng)的物理頁(yè),write決定寫內(nèi)存的第幾個(gè)頁(yè)面涯捻,例如"echo 1 > a.out"(底層調(diào)用write)寫入的是a.out文件的第0個(gè)位置浅妆,write將寫相應(yīng)內(nèi)存的第一頁(yè)。

write函數(shù)修改內(nèi)存內(nèi)容之后障癌,相應(yīng)的內(nèi)存頁(yè)凌外、inode被標(biāo)記為dirty,此時(shí)write函數(shù)返回涛浙。注意至此尚未往磁盤寫數(shù)據(jù)康辑,只是cache中的內(nèi)容被修改摄欲。

那什么時(shí)候內(nèi)存中的內(nèi)容會(huì)刷到磁盤中呢?
把臟數(shù)據(jù)刷到磁盤的工作由內(nèi)核線程flush完成疮薇,flush搜尋內(nèi)存中的臟數(shù)據(jù)蒿涎,按設(shè)定將臟數(shù)據(jù)寫到磁盤,我們可以通過(guò)sysctl命令查看惦辛、設(shè)定flush刷臟數(shù)據(jù)的策略:

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

以上數(shù)值單位為1/100秒,“dirty_writeback_centisecs = 500”指示flush每隔5秒執(zhí)行一次仓手,“dirty_expire_centisecs = 3000” 指示內(nèi)存中駐留30秒以上的臟數(shù)據(jù)將由flush在下一次執(zhí)行時(shí)寫入磁盤胖齐,“dirty_background_ratio = 10”指示若臟頁(yè)占總物理內(nèi)存10%以上,則觸發(fā)flush把臟數(shù)據(jù)寫回磁盤嗽冒。

flush找出了需要寫回磁盤的臟數(shù)據(jù)呀伙,那存儲(chǔ)臟數(shù)據(jù)的物理頁(yè)又與磁盤的哪些扇區(qū)對(duì)應(yīng)呢?

物理頁(yè)與扇區(qū)的對(duì)應(yīng)關(guān)系由文件系統(tǒng)定義添坊,文件系統(tǒng)定義了一個(gè)內(nèi)存頁(yè)(4KB)與多少個(gè)塊對(duì)應(yīng)剿另,對(duì)應(yīng)關(guān)系在格式化磁盤時(shí)設(shè)定,運(yùn)行時(shí)由buffer_head保存對(duì)應(yīng)關(guān)系:

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

文件系統(tǒng)層告知塊I/O層寫哪個(gè)設(shè)備贬蛙,具體哪個(gè)塊雨女,執(zhí)行以下命令后,我們可以在/var/log/messages中看到文件系統(tǒng)層下發(fā)到塊層的讀寫請(qǐng)求:

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記錄文件系統(tǒng)層下發(fā)的I/O請(qǐng)求阳准,bio中主要保存了需要往磁盤刷數(shù)據(jù)的物理頁(yè)信息氛堕,以及對(duì)應(yīng)磁盤上的扇區(qū)信息。

塊I/O層為每一個(gè)磁盤設(shè)備維護(hù)了一條I/O請(qǐng)求隊(duì)列野蝇,請(qǐng)求隊(duì)列在內(nèi)核中由struct request_queue表示讼稚。每一個(gè)讀或?qū)懻?qǐng)求都需經(jīng)過(guò)submit_bio函數(shù)處理,submit_bio將讀寫請(qǐng)求放入相應(yīng)I/O請(qǐng)求隊(duì)列中绕沈。該層起到最主要的作用就是對(duì)I/O請(qǐng)求進(jìn)行合并和排序锐想,這樣減少了實(shí)際的磁盤讀寫次數(shù)和尋道時(shí)間,達(dá)到優(yōu)化磁盤讀寫性能的目的乍狐。

使用crash解析vmcore文件赠摇,執(zhí)行"dev -d"命令,可以看到塊設(shè)備請(qǐng)求隊(duì)列的相關(guān)信息:

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

執(zhí)行"struct request_queue 0xffff88011a6a6948"澜躺,可對(duì)以上sda設(shè)備相應(yīng)的request_queue請(qǐng)求隊(duì)列結(jié)構(gòu)進(jìn)行解析蝉稳。

執(zhí)行以下命令,可以查看sda設(shè)備的請(qǐng)求隊(duì)列大芯虮伞:

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

如何對(duì)I/O請(qǐng)求進(jìn)行合并耘戚、排序,那就是I/O調(diào)度算法完成的工作操漠,Linux支持多種I/O調(diào)度算法收津,通過(guò)以下命令可以查看:

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

塊I/O層的另一個(gè)作用就是對(duì)I/O讀寫情況進(jìn)行統(tǒng)計(jì)饿这,執(zhí)行iostat命令,看到的就是該層提供的統(tǒng)計(jì)信息:

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 %util
     sda     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分別指示了每秒寫請(qǐng)求长捧、讀請(qǐng)求的合并次數(shù)。

task_io_account_read函數(shù)用于統(tǒng)計(jì)各個(gè)進(jìn)程發(fā)起的讀請(qǐng)求量吻贿, 由該函數(shù)得到的是進(jìn)程讀請(qǐng)求量的準(zhǔn)確值串结。而對(duì)于寫請(qǐng)求,由于數(shù)據(jù)寫入cache后write調(diào)用就返回舅列,因而在內(nèi)核的層面無(wú)法統(tǒng)計(jì)到一個(gè)進(jìn)程發(fā)起的準(zhǔn)確寫請(qǐng)求量肌割,讀時(shí)進(jìn)程會(huì)等buff可用,而寫則寫入cache后返回帐要,讀是同步的把敞,寫卻不一定同步,這是讀寫實(shí)現(xiàn)上的最大區(qū)別榨惠。

再往下就是設(shè)備層奋早,設(shè)備從隊(duì)列中取出I/O請(qǐng)求,scsi的scsi_request_fn函數(shù)就是完成取請(qǐng)求并處理的任務(wù)赠橙。scsi層最終將處理請(qǐng)求轉(zhuǎn)化為指令耽装,指令下發(fā)后進(jìn)行DMA(direct memory access)映射,將內(nèi)存的部分cache映射到DMA简烤,這樣設(shè)備繞過(guò)cpu直接操作主存剂邮。

設(shè)備層完成內(nèi)存數(shù)據(jù)到磁盤拷貝后,該消息將一層層上報(bào)横侦,最后內(nèi)核去除原臟頁(yè)的dirty位標(biāo)志挥萌。

以上為寫磁盤的大致實(shí)現(xiàn)過(guò)程,對(duì)于讀磁盤枉侧,內(nèi)核首先在緩存中查找對(duì)應(yīng)內(nèi)容引瀑,若命中則不會(huì)進(jìn)行磁盤操作。若進(jìn)程讀取一個(gè)字節(jié)的數(shù)據(jù)榨馁,內(nèi)核不會(huì)僅僅返回一個(gè)字節(jié)憨栽,其以頁(yè)面為單位(4KB),最少返回一個(gè)頁(yè)面的數(shù)據(jù)翼虫。另外屑柔,內(nèi)核會(huì)預(yù)讀磁盤數(shù)據(jù),執(zhí)行以下命令可以看到能夠預(yù)讀的最大數(shù)據(jù)量(以KB為單位):

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

下面我們通過(guò)一段systemtap代碼珍剑,了解內(nèi)核的預(yù)讀機(jī)制:

//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)
  }
}

以上代碼指示當(dāng)dd命令讀寫inode號(hào)為5234的文件掸宛、經(jīng)過(guò)內(nèi)核函數(shù)submit_bio時(shí),輸出inode號(hào)招拙、操作方式(讀或?qū)?唧瘾、文件所在設(shè)備名措译、讀寫大小、扇區(qū)號(hào)信息饰序。執(zhí)行以下代碼安裝探測(cè)模塊:

stap test.stp &

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

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

以上命令故意將bs設(shè)為1领虹,即每次讀取一個(gè)字節(jié),以此觀察內(nèi)核預(yù)讀機(jī)制求豫。執(zhí)行該命令的過(guò)程中塌衰,我們?cè)诮K端中可以看到以下輸出:

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

由以上輸出可知,預(yù)讀從16384字節(jié)(16KB)逐漸增大蝠嘉,最后變?yōu)?24288字節(jié)(512KB)猾蒂,可見(jiàn)內(nèi)核會(huì)根據(jù)讀的情況動(dòng)態(tài)地調(diào)整預(yù)讀的數(shù)據(jù)量。

由于讀是晨、寫磁盤均要經(jīng)過(guò)submit_bio函數(shù)處理,submit_bio之后讀舔箭、寫的底層實(shí)現(xiàn)大致相同罩缴。

直接I/O

當(dāng)我們以O(shè)_DIRECT標(biāo)志調(diào)用open函數(shù)打開(kāi)文件時(shí),后續(xù)針對(duì)該文件的read层扶、write操作都將以直接I/O(direct I/O)的方式完成箫章;對(duì)于裸設(shè)備,I/O方式也為直接I/O镜会。

直接I/O跳過(guò)了文件系統(tǒng)這一層檬寂,但塊層仍發(fā)揮作用,其將內(nèi)存頁(yè)與磁盤扇區(qū)對(duì)應(yīng)上戳表,這時(shí)不再是建立cache到DMA映射桶至,而是進(jìn)程的buffer映射到DMA。進(jìn)行直接I/O時(shí)要求讀寫一個(gè)扇區(qū)(512bytes)的整數(shù)倍匾旭,否則對(duì)于非整數(shù)倍的部分镣屹,將以帶cache的方式進(jìn)行讀寫。

使用直接I/O价涝,寫磁盤少了用戶態(tài)到內(nèi)核態(tài)的拷貝過(guò)程女蜈,這提升了寫磁盤的效率,也是直接I/O的作用所在色瘩。而對(duì)于讀操作伪窖,第一次直接I/O將比帶cache的方式快,但因帶cache方式后續(xù)再讀時(shí)將從cache中讀居兆,因而后續(xù)的讀將比直接I/O快覆山。有些數(shù)據(jù)庫(kù)使用直接I/O,同時(shí)實(shí)現(xiàn)了自己的cache方式史辙。

異步I/O

Linux下有兩種異步I/O(asynchronous I/O)方式汹买,一種是aio_read/aio_write庫(kù)函數(shù)調(diào)用佩伤,其實(shí)現(xiàn)方式為純用戶態(tài)的實(shí)現(xiàn),依靠多線程晦毙,主線程將I/O下發(fā)到專門處理I/O的線程生巡,以此達(dá)到主線程異步的目的。

另一種是io_submit见妒,該函數(shù)是內(nèi)核提供的系統(tǒng)調(diào)用孤荣,使用io_submit也需要指定文件的打開(kāi)方式為O_DIRECT,并且讀寫需按扇區(qū)對(duì)齊须揣。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盐股,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子耻卡,更是在濱河造成了極大的恐慌疯汁,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卵酪,死亡現(xiàn)場(chǎng)離奇詭異幌蚊,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)溃卡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門溢豆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人瘸羡,你說(shuō)我怎么就攤上這事漩仙。” “怎么了犹赖?”我有些...
    開(kāi)封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵队他,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我峻村,道長(zhǎng)漱挎,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任雀哨,我火速辦了婚禮磕谅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雾棺。我一直安慰自己膊夹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布捌浩。 她就那樣靜靜地躺著放刨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尸饺。 梳的紋絲不亂的頭發(fā)上进统,一...
    開(kāi)封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天助币,我揣著相機(jī)與錄音,去河邊找鬼螟碎。 笑死眉菱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掉分。 我是一名探鬼主播俭缓,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼酥郭!你這毒婦竟也來(lái)了华坦?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤不从,失蹤者是張志新(化名)和其女友劉穎惜姐,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體椿息,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡载弄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撵颊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惫叛,死狀恐怖倡勇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嘉涌,我是刑警寧澤妻熊,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站仑最,受9級(jí)特大地震影響扔役,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜警医,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一亿胸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧预皇,春花似錦侈玄、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鲁豪,卻和暖如春潘悼,著一層夾襖步出監(jiān)牢的瞬間律秃,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工治唤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棒动,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓肝劲,卻偏偏與公主長(zhǎng)得像迁客,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辞槐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容