<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] # 用戶空間以及內核空間概念 我們知道現在操作系統都是采用虛擬存儲器,那么對32位操作系統而言,它的尋址空間(虛擬存儲空間)為4G(2的32次方)。 操心系統的核心是內核,獨立于普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。 為了保證用戶進程不能直接操作內核,保證內核的安全,操心系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間。 針對linux操作系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱為內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為用戶空間。 每個進程可以通過系統調用進入內核,因此,Linux內核由系統內的所有進程共享。于是,從具體進程的角度來看,每個進程可以擁有4G字節的虛擬空間。 ![](https://box.kancloud.cn/5d2e43aa62635c1571e45f63080ec73a_554x287.png) 有了用戶空間和內核空間,整個linux內部結構可以分為三部分, 從最底層到最上層依次是:**硬件-->內核空間-->用戶空間** ![](https://box.kancloud.cn/9ec063ed44c0b4a693729ebcade19257_631x395.png) 需要注意的細節問題,從上圖可以看出內核的組成: 1. 內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據。 不管是內核空間還是用戶空間,它們都處于虛擬空間中。 2. Linux使用兩級保護機制:0級供內核使用,3級供用戶程序使用 # Linux 網絡 I/O模型 我們都知道,為了OS的安全性等的考慮,**進程是無法直接操作I/O設備的,其必須通過系統調用請求內核來協助完成I/O動作,而內核會為每個I/O設備維護一個buffer** ![](https://box.kancloud.cn/e2f183448b6202cb28f5e338d793cc58_483x106.png) **整個請求過程為: 用戶進程發起請求,內核接受到請求后,從I/O設備中獲取數據到buffer中,再將buffer中的數據copy到用戶進程的地址空間,該用戶進程獲取到數據后再響應客戶端** 在整個請求過程中,數據輸入至buffer需要時間,而從buffer復制數據至進程也需要時間。因此根據在這兩段時間內等待方式的不同,I/O動作可以分為以下**五種模式**: * 阻塞I/O (Blocking I/O) * 非阻塞I/O (Non-Blocking I/O) * I/O復用(I/O Multiplexing) * 信號驅動的I/O (Signal Driven I/O) * 異步I/O (Asynchrnous I/O) 記住這兩點很重要 1. 等待數據準備 (Waiting for the data to be ready) 2. 將數據從內核拷貝到進程中 (Copying the data from the kernel to the process) # 同步阻塞I/O (Blocking I/O) 同步阻塞 IO 模型是最常用的一個模型,也是最簡單的模型。在linux中,**默認情況下所有的socket都是blocking**。它符合人們最常見的思考邏輯。**阻塞就是進程 "被" 休息, CPU處理其它進程去了**。 在這個IO模型中,用戶空間的應用程序執行一個系統調用(recvform),這會導致應用程序阻塞,什么也不干,直到數據準備好,并且將數據從內核復制到用戶進程,最后進程再處理數據,**在等待數據到處理數據的兩個階段**,整個進程都被阻塞。不能處理別的網絡IO。**調用應用程序處于一種不再消費 CPU 而只是簡單等待響應的狀態**,因此從處理的角度來看,這是非常有效的。在調用recv()/recvfrom()函數時,發生在內核中等待數據和復制數據的過程,大致如下圖: ![](https://box.kancloud.cn/e0ec035f3bb0b0c493832c660763a69b_970x487.png) 當用戶進程調用了recv()/recvfrom()這個系統調用, kernel就開始了IO的**第一個階段:準備數據**(對于網絡IO來說,很多時候數據在一開始還沒有到達。比如,還沒有收到一個完整的UDP包。這個時候kernel就要等待足夠的數據到來)。這個過程需要等待,也就是說數據被拷貝到操作系統內核的緩沖區中是需要一個過程的。而在用戶進程這邊,整個進程會被阻塞(當然,是進程自己選擇的阻塞)。 **第二個階段:當kernel一直等到數據準備好了**,它就會將數據從kernel中拷貝到用戶內存,然后kernel返回結果,用戶進程才解除block的狀態,重新運行起來 所以,blocking IO的特點就是在IO執行的兩個階段都被block了。 # 同步非阻塞I/O (Non-Blocking I/O) ## 場景描述 我女友不甘心白白在這等,又想去逛商場,又擔心飯好了。所以我們逛一會,回來詢問服務員飯好了沒有,來來回回好多次,飯都還沒吃都快累死了啦。這就是非阻塞。需要不斷的詢問,是否準備好了。 ## 網絡模型 **同步非阻塞就是 “每隔一會兒瞄一眼進度條” 的輪詢(polling)方式**。在這種模型中,**設備是以非阻塞的形式打開的**。 這意味著 IO 操作不會立即完成,read 操作可能會返回一個錯誤代碼,說明這個命令不能立即滿足(EAGAIN 或 EWOULDBLOCK)。 在網絡IO時候,非阻塞IO也會進行recvform系統調用,檢查數據是否準備好,與阻塞IO不一樣,”非阻塞將大的整片時間的阻塞分成N多的小的阻塞, 所以進程不斷地有機會 ‘被’ CPU光顧”。 **也就是說非阻塞的recvform系統調用調用之后,進程并沒有被阻塞,內核馬上返回給進程,如果數據還沒準備好,此時會返回一個error。** 進程在返回之后,可以干點別的事情,然后再發起recvform系統調用。重復上面的過程,循環往復的進行recvform系統調用。**這個過程通常被稱之為輪詢**。輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程,進行數據處理。**需要注意,拷貝數據整個過程,進程仍然是屬于阻塞的狀態**。 在linux下,可以通過設置socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程如圖所示: ![](https://box.kancloud.cn/8b2ae743ae36f7b3ac17d67fd7f4cc57_962x491.png) 當用戶進程發出read操作時,如果kernel中的數據還沒有準備好,那么它并不會block用戶進程,而是立刻返回一個error。從用戶進程角度講,它發起一個read操作后,并不需要等待,而是馬上就得到了一個結果。用戶進程判斷結果是一個error時,它就知道數據還沒有準備好,于是它可以再次發送read操作。一旦kernel中的數據準備好了,并且又再次收到了用戶進程的system call,那么它馬上就將數據拷貝到了用戶內存,然后返回。 所以,nonblocking IO的特點是用戶進程需要不斷的主動詢問kernel數據好了沒有。 任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操作,而任務可能在兩次輪詢之間的任意時間完成。這會導致整體數據吞吐量的降低 # I/O復用(I/O Multiplexing) IO multiplexing這個詞可能有點陌生,但是如果我說select,epoll,大概就都能明白了。有些地方也稱這種IO方式為event driven IO。我們都知道,select/epoll的好處就在于單個process就可以同時處理多個網絡連接的IO。它的基本原理就是select/epoll這個function會不斷的輪詢所負責的所有socket,當某個socket有數據到達了,就通知用戶進程。它的流程如圖: ![](https://box.kancloud.cn/fa02917b2853f98fa1f39404d1701bc1_969x480.png) 當用戶進程調用了select,那么整個進程會被block,而同時,內核會“監視”所有select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操作,將數據從內核拷貝到用戶進程。 這個圖和blocking IO的圖其實并沒有太大的不同,事實上,還更差一些。因為這里需要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom)。但是,用select的優勢在于它可以同時處理多個connection。(多說一句。所以,如果處理的連接數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優勢**并不是對于單個連接能處理得更快,而是在于能處理更多的連接**。) 在IO multiplexing Model中,實際中,對于每一個socket,一般都設置成為non-blocking,但是,如上圖所示,整個用戶的process其實是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。 了解了前面三種IO模式,在用戶進程進行系統調用的時候,他們在等待數據到來的時候,處理的方式不一樣,直接等待,輪詢,select或poll輪詢,兩個階段過程: 第一個階段有的阻塞,有的不阻塞,有的可以阻塞又可以不阻塞。 第二個階段都是阻塞的。 從整個IO過程來看,他們都是順序執行的,因此可以歸為同步模型(synchronous)。都是進程主動等待且向內核檢查狀態。【此句很重要!!!】 ## 文件描述符fd Linux的內核將所有外部設備都可以看做一個文件來操作。那么我們對與外部設備的操作都可以看做對文件進行操作。我們對一個文件的讀寫,都通過調用內核提供的系統調用;內核給我們返回一個filede scriptor(fd,文件描述符)。而對一個socket的讀寫也會有相應的描述符,稱為socketfd(socket描述符)。描述符就是一個數字,指向內核中一個結構體(文件路徑,數據區,等一些屬性)。那么我們的應用程序對文件的讀寫就通過對描述符的讀寫完成。 ## select 基本原理:select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用后select函數會阻塞,直到有描述符就緒(有數據 可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設為null即可),函數返回。當select函數返回后,可以通過遍歷fdset,來找到就緒的描述符。 缺點: 1. select最大的缺陷就是單個進程所打開的FD是有一定限制的,它由FDSETSIZE設置,32位機默認是1024個,64位機默認是2048。 一般來說這個數目和系統內存關系很大,”具體數目可以cat /proc/sys/fs/file-max察看”。32位機默認是1024個。64位機默認是2048. 2. 對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低。 當套接字比較多的時候,每次select()都要通過遍歷FDSETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。”如果能給套接字注冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢”,這正是epoll與kqueue做的。 3. 需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時復制開銷大。 ## poll 基本原理:poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然后查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項并繼續遍歷,如果遍歷完所有fd后沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒后它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。 它沒有最大連接數的限制,原因是它是基于鏈表來存儲的,但是同樣有一個缺點:1、大量的fd的數組被整體復制于用戶態和內核地址空間之間,而不管這樣的復制是不是有意義。 2 、poll還有一個特點是“水平觸發”,如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd。 注意:從上面看,select和poll都需要在返回后,通過遍歷文件描述符來獲取已經就緒的socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態,因此隨著監視的描述符數量的增長,其效率也會線性下降。 ## epoll epoll是在2.6內核中提出的,是之前的select和poll的增強版本。相對于select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。 基本原理:epoll支持水平觸發和邊緣觸發,最大的特點在于邊緣觸發,它只告訴進程哪些fd剛剛變為就緒態,并且只會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epollctl注冊fd,一旦該fd就緒,內核就會采用類似callback的回調機制來激活該fd,epollwait便可以收到通知。 epoll的優點: 1. 沒有最大并發連接的限制,能打開的FD的上限遠大于1024(1G的內存上能監聽約10萬個端口)。 2. 效率提升,不是輪詢的方式,不會隨著FD數目的增加效率下降。 只有活躍可用的FD才會調用callback函數;即Epoll最大的優點就在于它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高于select和poll。 3. 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷。 **JDK1.5_update10版本使用epoll替代了傳統的select/poll,極大的提升了NIO通信的性能。** # 信號驅動的I/O (Signal Driven I/O) 由于signal driven IO在實際中并不常用,所以簡單提下 ![](https://box.kancloud.cn/e77c0271557183c2f11aecd37eb9330c_966x532.png) 很明顯可以看出用戶進程不是阻塞的。首先用戶進程建立SIGIO信號處理程序,并通過系統調用sigaction執行一個信號處理函數,這時用戶進程便可以做其他的事了,一旦數據準備好,系統便為該進程生成一個SIGIO信號,去通知它數據已經準備好了,于是用戶進程便調用recvfrom把數據從內核拷貝出來,并返回結果 # 異步I/O ## 場景描述 女友不想逛街,又餐廳太吵了,回家好好休息一下。于是我們叫外賣,打個電話點餐,然后我和女友可以在家好好休息一下,飯好了送貨員送到家里來。這就是典型的異步,只需要打個電話說一下,然后可以做自己的事情,飯好了就送來了 ## 網絡模型 一般來說,這些函數通過告訴內核啟動操作并在整個操作(包括內核的數據到緩沖區的副本)完成時通知我們。這個模型和前面的信號驅動I/O模型的主要區別是,在信號驅動的I/O中,內核告訴我們何時可以啟動I/O操作,**但是異步I/O時,內核告訴我們何時I/O操作完成**。 相對于同步IO,異步IO不是順序執行。用戶進程進行aio_read系統調用之后,無論內核數據是否準備好,都會直接返回給用戶進程,然后用戶態進程可以去做別的事情。等到socket數據準備好了,內核直接復制數據給進程,然后從內核向進程發送通知。IO兩個階段,進程都是非阻塞的 Linux提供了AIO庫函數實現異步,但是用的很少。目前有很多開源的異步IO庫,例如libevent、libev、libuv。異步過程如下圖所示 ![](https://box.kancloud.cn/cd1e2b6b6b735af0fd8be83d0920ebd6_955x528.png) 當用戶進程向內核發起某個操作后,會立刻得到返回,并把所有的任務都交給內核去完成(包括將數據從內核拷貝到用戶自己的緩沖區),內核完成之后,只需返回一個信號告訴用戶進程已經完成就可以了。 ## 流程描述 用戶進程發起aio_read操作之后,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之后,**首先它會立刻返回,所以不會對用戶進程產生任何block**。然后,kernel會等待數據準備完成,然后將數據拷貝到用戶內存,**當這一切都完成之后,kernel會給用戶進程發送一個signal或執行一個基于線程的回調函數來完成這次 IO 處理過程**,告訴它read操作完成了。 在 Linux 中,通知的方式是 “信號”: ~~~ 如果這個進程正在用戶態忙著做別的事(例如在計算兩個矩陣的乘積),那就強行打斷之,調用事先注冊的信號處理函數, 這個函數可以決定何時以及如何處理這個異步任務。 由于信號處理函數是突然闖進來的,因此跟中斷處理程序一樣,有很多事情是不能做的, 因此保險起見,一般是把事件 “登記” 一下放進隊列, 然后返回該進程原來在做的事。 如果這個進程正在內核態忙著做別的事,例如以同步阻塞方式讀寫磁盤,那就只好把這個通知掛起來了,等到內核態的事情忙完了,快要回到用戶態的時候,再觸發信號通知。 如果這個進程現在被掛起了,例如無事可做 sleep 了,那就把這個進程喚醒,下次有 CPU 空閑的時候,就會調度到這個進程,觸發信號通知。 ~~~ 異步 API 說來輕巧,做來難,這主要是對 API 的實現者而言的。Linux 的異步 IO(AIO)支持是 2.6.22 才引入的,還有很多系統調用不支持異步 IO。Linux 的異步 IO 最初是為數據庫設計的,**因此通過異步 IO 的讀寫操作不會被緩存或緩沖,這就無法利用操作系統的緩存與緩沖機制**。 **很多人把 Linux 的 O_NONBLOCK 認為是異步方式,但事實上這是前面講的同步非阻塞方式**。需要指出的是,雖然 Linux 上的 IO API 略顯粗糙,但每種編程框架都有封裝好的異步 IO 實現。操作系統少做事,把更多的自由留給用戶,正是 UNIX 的設計哲學,也是 Linux 上編程框架百花齊放的一個原因。 提下 IO多路復用除了需要阻塞之外,select 函數所提供的功能(異步阻塞 IO)與 AIO 類似。不過,它是對通知事件進行阻塞,而不是對 IO 調用進行阻塞 # 5中I/O模型的對比 從同步、異步,以及阻塞、非阻塞兩個維度來劃分來看: ![](https://box.kancloud.cn/f0ea21f58ecd9fb80334f44f26e57101_558x362.png) # 零拷貝 CPU不執行拷貝數據從一個存儲區域到另一個存儲區域的任務,這通常用于在網絡上傳輸文件時節省CPU周期和內存帶寬 # 緩存 IO 緩存 IO 又被稱作標準 IO,大多數文件系統的默認 IO 操作都是緩存 IO。在 Linux 的緩存 IO 機制中,操作系統會將 IO 的數據緩存在文件系統的頁緩存( page cache )中,也就是說,數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。 緩存 IO 的缺點:數據在傳輸過程中需要在應用程序地址空間和內核進行多次數據拷貝操作,這些數據拷貝操作所帶來的 CPU 以及內存開銷是非常大的
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看