<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>

          讀取文件時(shí),程序經(jīng)歷了什么?

          2021-01-25 16:25

          承接上文《一文徹底理解高性能高并發(fā)中的線程與線程池》,這是高性能、高并發(fā)系列的第二篇文章,在這里我們來(lái)到了I/O這一話題。


          你有沒(méi)有想過(guò)當(dāng)我們執(zhí)行I/O操作時(shí)計(jì)算機(jī)底層都發(fā)生了些什么?

          在回答這個(gè)問(wèn)題之前,我們先來(lái)看下為什么對(duì)于計(jì)算機(jī)來(lái)說(shuō)I/O是極其重要的。


          不能執(zhí)行I/O的計(jì)算機(jī)是什么?

          相信對(duì)于程序員來(lái)說(shuō)I/O操作是最為熟悉不過(guò)的了:

          當(dāng)我們使用C語(yǔ)言中的printf、C++中的"<<",Python中的print,Java中的System.out.println等時(shí),這是I/O;當(dāng)我們使用各種語(yǔ)言讀寫(xiě)文件時(shí),這也是I/O;當(dāng)我們通過(guò)TCP/IP進(jìn)行網(wǎng)絡(luò)通信時(shí),這同樣是I/O;當(dāng)我們使用鼠標(biāo)龍飛鳳舞時(shí),當(dāng)我們扛起鍵盤(pán)在評(píng)論區(qū)里指點(diǎn)江山亦或是埋頭苦干努力制造bug時(shí)、當(dāng)我們能看到屏幕上的漂亮的圖形界面時(shí)等等,這一切都是I/O。


          想一想,如果沒(méi)有I/O計(jì)算機(jī)該是一種多么枯燥的設(shè)備,不能看電影、不能玩游戲,也不能上網(wǎng),這樣的計(jì)算機(jī)最多就是一個(gè)大號(hào)的計(jì)算器。


          既然I/O這么重要,那么到底什么才是I/O呢?


          什么是I/O

          I/O就是簡(jiǎn)單的數(shù)據(jù)Copy,僅此而已。


          這一點(diǎn)很重要,為了加深大家的印象,來(lái),Everybody,F(xiàn)ollow me,那邊樹(shù)上的朋友,還有那邊墻上的朋友們,舉起你們的雙手,跟我唱,蒼茫的天涯是。。。Sorry,I/O僅僅就是數(shù)據(jù)copy、I/O僅僅就是數(shù)據(jù)copy。


          讓我們先把演唱會(huì)的事情放在一邊,既然是copy數(shù)據(jù),又是從哪里copy到哪里呢?


          如果數(shù)據(jù)是從外部設(shè)備copy到內(nèi)存中,這就是Input。

          如果數(shù)據(jù)是從內(nèi)存copy到外部設(shè)備,這就是Output。

          內(nèi)存與外部設(shè)備之間不嫌麻煩的來(lái)回copy數(shù)據(jù)就是Input and Output,簡(jiǎn)稱I/O(Input/Output),僅此而已。



          I/O與CPU

          現(xiàn)在我們知道了什么是I/O,接下來(lái)就是重點(diǎn)部分了,大家注意,坐穩(wěn)了。


          我們知道現(xiàn)在的CPU其主頻都是數(shù)GHz起步,這是什么意思呢?簡(jiǎn)單說(shuō)就是CPU執(zhí)行機(jī)器指令的速度是納秒級(jí)別的,而通常的I/O比如磁盤(pán)操作,一次磁盤(pán)seek大概在毫秒級(jí)別,因此如果我們把CPU的速度比作戰(zhàn)斗機(jī)的話,那么I/O操作的速度就是肯德雞



          也就是說(shuō)當(dāng)我們的程序跑起來(lái)時(shí)(CPU執(zhí)行機(jī)器指令),其速度是要遠(yuǎn)遠(yuǎn)快于I/O速度的,那么接下來(lái)的問(wèn)題就是二者速度相差這么大,那么我們?cè)撊绾卧O(shè)計(jì)、該如何更加合理的高效利用系統(tǒng)資源呢?


          既然有速度差異,而且進(jìn)程在執(zhí)行完I/O操作前不能繼續(xù)向前推進(jìn),那么顯然只有一個(gè)辦法,那就是等待,wait


          同樣是等待,有聰明的等待,也有傻傻的等待,簡(jiǎn)稱傻等,那么是選擇聰明的等待呢還是選擇傻等呢?


          假設(shè)你是一個(gè)急性子(CPU),需要等待一個(gè)重要的文件,不巧的是這個(gè)文件只能快遞過(guò)來(lái)(I/O),那么這時(shí)你是選擇什么事情都不干了,深情的注視著門(mén)口就像盼望著你的哈尼一樣專心等待這個(gè)快遞呢?還是暫時(shí)先不要管快遞了,玩?zhèn)€游戲看個(gè)電影刷會(huì)兒短視頻等快遞來(lái)了再說(shuō)呢?


          很顯然,更好的方法就是先去干其它事情,快遞來(lái)了再說(shuō)。


          因此這里的關(guān)鍵點(diǎn)就是快遞沒(méi)到前手頭上的事情可以先暫停,切換到其它任務(wù),等快遞過(guò)來(lái)了再切換回來(lái)


          理解了這一點(diǎn)你就能明白執(zhí)行I/O操作時(shí)底層都發(fā)生了什么。


          接下來(lái)讓我們以讀取磁盤(pán)文件為例來(lái)講解這一過(guò)程。


          執(zhí)行I/O時(shí)底層都發(fā)生了什么

          在上一篇《一文徹底理解高并發(fā)高性能中的線程與線程池》中,我們引入了進(jìn)程和線程的概念,在支持線程的操作系統(tǒng)中,實(shí)際上被調(diào)度的是線程而不是進(jìn)程,為了更加清晰的理解I/O過(guò)程,我們暫時(shí)假設(shè)操作系統(tǒng)只有進(jìn)程這樣的概念,先不去考慮線程,這并不會(huì)影響我們的討論。


          現(xiàn)在內(nèi)存中有兩個(gè)進(jìn)程,進(jìn)程A和進(jìn)程B,當(dāng)前進(jìn)程A正在運(yùn)行,如圖所示:



          進(jìn)程A中有一段讀取文件的代碼,不管在什么語(yǔ)言中通常我們定義一個(gè)用來(lái)裝數(shù)據(jù)的buff,然后調(diào)用read之類(lèi)的函數(shù),像這樣:

          read(buff);

          這就是一種典型的I/O操作,當(dāng)CPU執(zhí)行到這段代碼的時(shí)候會(huì)向磁盤(pán)發(fā)送讀取請(qǐng)求,注意與CPU執(zhí)行指令的速度相比,I/O操作操作是非常慢的,因此操作系統(tǒng)是不可能把寶貴的CPU計(jì)算資源浪費(fèi)在無(wú)謂的等待上的,這時(shí)重點(diǎn)來(lái)了,注意接下來(lái)是重點(diǎn)哦。


          由于外部設(shè)備執(zhí)行I/O操作是相當(dāng)慢的,因此在I/O操作完成之前進(jìn)程是無(wú)法繼續(xù)向前推進(jìn)的,這就是所謂的阻塞,即通常所說(shuō)的block。操作系統(tǒng)檢測(cè)到進(jìn)程向I/O設(shè)備發(fā)起請(qǐng)求后就暫停進(jìn)程的運(yùn)行,怎么暫停運(yùn)行呢?很簡(jiǎn)單,只需要記錄下當(dāng)前進(jìn)程的運(yùn)行狀態(tài)并把CPU的PC寄存器指向其它進(jìn)程的指令就可以了。


          進(jìn)程有暫停就會(huì)有繼續(xù)執(zhí)行,因此操作系統(tǒng)必須保存被暫停的進(jìn)程以備后續(xù)繼續(xù)執(zhí)行,顯然我們可以用隊(duì)列來(lái)保存被暫停執(zhí)行的進(jìn)程,如圖所示,進(jìn)程A被暫停執(zhí)行并被放到阻塞隊(duì)列中(注意,不同的操作系統(tǒng)會(huì)有不同的實(shí)現(xiàn),可能每個(gè)I/O設(shè)備都有一個(gè)對(duì)應(yīng)的阻塞隊(duì)列,但這種實(shí)現(xiàn)細(xì)節(jié)上的差異不影響我們的討論)。



          這時(shí)操作系統(tǒng)已經(jīng)向磁盤(pán)發(fā)送了I/O請(qǐng)求,因此磁盤(pán)driver開(kāi)始將磁盤(pán)中的數(shù)據(jù)copy到進(jìn)程A的buff中,雖然這時(shí)進(jìn)程A已經(jīng)被暫停執(zhí)行了,但這并不妨礙磁盤(pán)向內(nèi)存中copy數(shù)據(jù)。注意,現(xiàn)代磁盤(pán)向內(nèi)存copy數(shù)據(jù)時(shí)無(wú)需借助CPU的幫助,這就是所謂的DMA(Direct Memory Access),這個(gè)過(guò)程如圖所示:



          讓磁盤(pán)先copy著數(shù)據(jù),我們接著聊。


          實(shí)際上操作系統(tǒng)中除了有阻塞隊(duì)列之外也有就緒隊(duì)列,所謂就緒隊(duì)列是指隊(duì)列里的進(jìn)程準(zhǔn)備就緒可以被CPU執(zhí)行了,你可能會(huì)問(wèn)為什么不直接執(zhí)行非要有個(gè)就緒隊(duì)列呢?答案很簡(jiǎn)單,那就是僧多粥少,在即使只有1個(gè)核的機(jī)器上也可以創(chuàng)建出成千上萬(wàn)個(gè)進(jìn)程,CPU不可能同時(shí)執(zhí)行這么多的進(jìn)程,因此必然存在這樣的進(jìn)程,即使其一切準(zhǔn)備就緒也不能被分配到計(jì)算資源,這樣的進(jìn)程就被放到了就緒隊(duì)列。


          現(xiàn)在進(jìn)程B就位于就緒隊(duì)列,萬(wàn)事俱備只欠CPU,如圖所示:



          當(dāng)進(jìn)程A被暫停執(zhí)行后CPU是不可以閑下來(lái)的,因?yàn)榫途w隊(duì)列中還有嗷嗷待哺的進(jìn)程B,這時(shí)操作系統(tǒng)開(kāi)始在就緒隊(duì)列中找下一個(gè)可以執(zhí)行的進(jìn)程,也就是這里的進(jìn)程B。


          此時(shí)操作系統(tǒng)將進(jìn)程B從就緒隊(duì)列中取出,找出進(jìn)程B被暫停時(shí)執(zhí)行到的機(jī)器指令的位置,然后將CPU的PC寄存器指向該位置,這樣進(jìn)程B就開(kāi)始運(yùn)行啦,如圖所示:



          注意,注意,接下來(lái)的這段是重點(diǎn)中的重點(diǎn)。


          注意觀察上圖,此時(shí)進(jìn)程B在被CPU執(zhí)行,磁盤(pán)在向進(jìn)程A的內(nèi)存空間中copy數(shù)據(jù),看出來(lái)了嗎,大家都在忙,誰(shuí)都沒(méi)有閑著,數(shù)據(jù)copy和指令執(zhí)行在同時(shí)進(jìn)行,在操作系統(tǒng)的調(diào)度下,CPU、磁盤(pán)都得到了充分的利用,這就是程序員的智慧所在。


          現(xiàn)在你應(yīng)該理解為什么操作系統(tǒng)這么重要了吧。


          此后磁盤(pán)終于將全部數(shù)據(jù)都copy到了進(jìn)程A的內(nèi)存中,這時(shí)磁盤(pán)通知操作系統(tǒng)任務(wù)完成啦,你可能會(huì)問(wèn)怎么通知呢?這就是中斷。


          操作系統(tǒng)接收到磁盤(pán)中斷后發(fā)現(xiàn)數(shù)據(jù)copy完畢,進(jìn)程A重新獲得繼續(xù)運(yùn)行的資格,這時(shí)操作系統(tǒng)小心翼翼的把進(jìn)程A從阻塞隊(duì)列放到了就緒隊(duì)列當(dāng)中,如圖所示:



          注意,從前面關(guān)于就緒狀態(tài)的討論中我們知道,操作系統(tǒng)是不會(huì)直接運(yùn)行進(jìn)程A的,進(jìn)程A必須被放到就緒隊(duì)列中等待,這樣對(duì)大家都公平。


          此后進(jìn)程B繼續(xù)執(zhí)行,進(jìn)程A繼續(xù)等待,進(jìn)程B執(zhí)行了一會(huì)兒后操作系統(tǒng)認(rèn)為進(jìn)程B執(zhí)行的時(shí)間夠長(zhǎng)了,因此把進(jìn)程B放到就緒隊(duì)列,把進(jìn)程A取出并繼續(xù)執(zhí)行。


          注意操作系統(tǒng)把進(jìn)程B放到的是就緒隊(duì)列,因此進(jìn)程B被暫停運(yùn)行僅僅是因?yàn)闀r(shí)間片到了而不是因?yàn)榘l(fā)起I/O請(qǐng)求被阻塞,如圖所示:



          進(jìn)程A繼續(xù)執(zhí)行,此時(shí)buff中已經(jīng)裝滿了想要的數(shù)據(jù),進(jìn)程A就這樣愉快的運(yùn)行下去了,就好像從來(lái)沒(méi)有被暫停過(guò)一樣,進(jìn)程對(duì)于自己被暫停一事一無(wú)所知,這就是操作系統(tǒng)的魔法


          現(xiàn)在你應(yīng)該明白了I/O是一個(gè)怎樣的過(guò)程了吧。


          這種進(jìn)程執(zhí)行I/O操作被阻塞暫停執(zhí)行的方式被稱為阻塞式I/O,blocking I/O,這也是最常見(jiàn)最容易理解的I/O方式,有阻塞式I/O就有非阻塞式I/O,在這里我們暫時(shí)先不考慮這種方式。


          在本節(jié)開(kāi)頭我們說(shuō)過(guò)暫時(shí)只考慮進(jìn)程而不考慮線程,現(xiàn)在我們放寬這個(gè)條件,實(shí)際上也非常簡(jiǎn)單,只需要把前圖中調(diào)度的進(jìn)程改為線程就可以了,這里的討論對(duì)于線程一樣成立。


          零拷貝,Zero-copy

          最后需要注意的一點(diǎn)就是上面的講解中我們直接把磁盤(pán)數(shù)據(jù)copy到了進(jìn)程空間中,但實(shí)際上一般情況下I/O數(shù)據(jù)是要首先copy到操作系統(tǒng)內(nèi)部,然后操作系統(tǒng)再copy到進(jìn)程空間中。因此我們可以看到這里其實(shí)還有一層經(jīng)過(guò)操作系統(tǒng)的copy,對(duì)于性能要求很高的場(chǎng)景其實(shí)也是可以繞過(guò)操作系統(tǒng)直接進(jìn)行數(shù)據(jù)copy的,這也是本文描述的場(chǎng)景,這種繞過(guò)操作系統(tǒng)直接進(jìn)行數(shù)據(jù)copy的技術(shù)被稱為Zero-copy,也就零拷貝,高并發(fā)、高性能場(chǎng)景下常用的一種技術(shù),原理上很簡(jiǎn)單吧。


          總結(jié)

          本文講解的是程序員常用的I/O,一般來(lái)說(shuō)作為程序員我們無(wú)需關(guān)心,但是理解I/O背后的底層原理對(duì)于設(shè)計(jì)高性能、高并發(fā)系統(tǒng)是極為有益的,希望這篇能對(duì)大家加深對(duì)I/O的認(rèn)識(shí)有所幫助。

          瀏覽 26
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  精品人妻网站 | 成人免费性生活视频 | 色天堂影院| 日韩无码免费看 | 看黄在线网站 |