來源:北大青鳥總部 2023年03月16日 14:08
“曾經有一份真誠的愛情放在我面前,但我沒有珍惜,等我失去的時候我才后悔莫及,人世間最痛苦的事莫過于此。”這是電影《大話西游》中的經典臺詞,大圣與紫霞仙子最終還是沒有有情人成眷屬。然而我們今天介紹的主角-I/O與用戶,二者卻是經歷了各種考驗,最后終成眷屬了~
說到IO就得說到操作系統Linux,在Linux的世界里,一切都是文件,都是一串二進制流,不管是socket、FIFO、管道、終端,全都是文件,全都是流。信息交換的過程就是對流進行數據的收發操作,也就是IO操作(Input/Output)。而計算機世界里有這么多的流,通過文件描述符就可以知道操作哪個流了,通常是創建一個socket套接字就會產生一個文件描述符,所以對文件描述符的操作也就是對socket的操作,往里寫入數據調用write,讀數據調用read。
在介紹I/O模型之前,我們要給大家先普及兩個概念:內核空間和用戶空間。簡單來說,就是操作系統在設計時為了讓各個模塊各干各的事,劃分了不同的地盤。現代操作系統采用虛擬存儲器,有32位和64位操作系統,因此尋址地址(虛擬存儲空間)包含4G(2的32次方)、17179869184G(2的64次方)。操作系統的核心是內核,對上可以訪問受保護的內存空間,對下可以訪問底層硬件設備。為了內核安全,保障用戶進程不能直接操作內核,因此操作系統把虛擬空間劃分為用戶空間和內核空間。用戶空間執行用戶自己的代碼時,處于用戶態;通過系統調用進入內核執行時,處于內核態。
講完了用戶空間、內核空間、用戶態、內核態,進入今天的主題I/O。故事的開始是用戶在前端發起的請求,當用戶在前端發起一個請求時,我們知道的路徑是該請求通過網絡傳遞給到后端,后端經過業務程序調用、數據庫調用等將結果計算出來,再傳遞給到前端展示結果給用戶。事實上,這背后還有更復雜的技術原理。從整個軟件系統來看,除了業務程序之外、還包含操作系統、硬件資源,因此整個用戶請求的處理必定是軟硬件都在參與。整個協同的過程就像人餓了要吃飯一樣,身體接收到餓了的訊息,經由大腦處理想吃肉,再通過眼睛看到食材、雙腿獲取食材、雙手烹飪,經過嘴巴消化之后,反饋給大腦飽了的訊息。
整個web請求執行的過程如下:
用戶在客戶端發起請求到服務器網卡;
服務器網卡接收到請求后轉給內核處理;
內核根據請求對應的套接字,將請求交給工作在用戶空間的web服務器進程;
web服務器進程根據用戶請求,向內核進行系統調用,申請相應資源;
當web服務器進程請求的是存放在本地磁盤上的資源,內核會通過驅動程序連接磁盤;
內核調用磁盤,獲取相應的資源;
內核將資源存放在自己的緩存區并通知web服務器進程;
web服務器進程通過系統調用取得資源,并把它復制到進程自己的緩沖區中;
web服務器進程形成響應,通過系統調用再次發給內核響應請求;
內核將響應發給網卡;
網卡把響應結果傳遞給到用戶。
在整個請求的執行過程中,有兩次IO操作,第一次是客戶端請求的網絡I/O,第二次是web服務器請求磁盤I/O。因此當應用程序處理用戶請求緩慢時,運維和研發也會關注網絡IO和磁盤IO的情況;監控系統也會包含網絡讀寫速度、網絡發送接收包數量、磁盤讀寫速度等指標的監控。
講完了為什么系統需要I/O,我們再來看看I/O有哪些模型?根據執行過程和通信過程的不同,IO模型分為阻塞型、非阻塞型、IO復用型、信號驅動型IO、異步IO。簡單來說,就是這五個模型的不同在于等待數據準備階段、將數據從內核拷貝到進程中這兩個階段不相同。
阻塞IO,即在IO操作執行時,所有其它相關的操作都不能進行,直到IO操作執行完成。當在內核執行IO操作時,應用程序調用IO的recvform函數,將應用程序調起,不給它分配CPU時間片,進入阻塞狀態后直到IO操作結束才返回結果;如果系統內核數據沒有準備好,那么就一直處理等待狀態。整個過程就像你在餐館點菜一樣,確認好菜單后服務員會傳遞給到后廚,在整個期間內你只能待在餐桌,哪里都不能去,處于阻塞等待狀態,直到菜做好了,上菜后才可以走動。
非阻塞IO,即在IO操作執行時,其它的相關操作也可以進行。用戶線程向內核發起IO請求時,因為內核執行也需要時間,所以先返回給用戶一個狀態值“EWOULDBLOCK”,在操作執行過程期間,用戶線程不斷的輪詢IO請求,直到讀取到數據。整個過程就像你在餐館點菜一樣,點好菜后服務員會給你個訂單和號碼牌,你可以到處走動,也可以去問廚師菜好了沒?直到上菜完成.
IO復用型,即阻塞多個I/O操作。在阻塞IO模型中,當有IO操作進行時,系統進程會被調起,這樣帶來的是效率低,因為一個線程只能處理一個套接字的I/O事件。改進方法就是調用select和poll函數,當監聽到有讀或寫操作的I/O函數時,select函數就會被調用,阻塞多個I/O操作,直到內核監聽到有數據返回時,select就反饋數據準備好了,系統再調用recvfrom處理數據。整個過程就好像在餐館點菜就餐一樣,餐館的客人非常多,但是廚師比較少,廚師一次只能回答一個客人的問題(如菜好了沒?),當有了服務員的存在后,我們只需要向服務員點菜、下單、取菜、付費就可以了,服務員就像我們的select函數,監聽多個客人的下單、上菜、買單事件,直到整個任務完成。
信號驅動型IO,即通過監聽信號來處理IO操作,省去了select函數的阻塞與輪詢。整個實現方法是在Socket套接字上安裝一個信號處理函數,進行信號驅動I/O,用戶進程繼續運行并不阻塞。當數據準備好時,進程就會收到一個sigio信號,此時便可調用recvfrom讀取數據。整個過程就像在餐館就餐一樣,在餐桌上有智能計菜儀,隨時感知做菜的進度,當有菜做好時,智能計菜儀便感知到了,通知服務員或客人取菜。
異步IO模型,即同時并行多件事情。用戶進程發起IO操作后,就去做其它的事情了,內核接受到用戶進程的消息時,會立刻返回結果并同時等到數據準備完成,再將數據拷貝到用戶內存,當所有的操作都完成時,內核給用戶進程發送信號告訴它事情已搞定。整個過程就像在餐館就餐一樣,客人下單后,廚師直接接單,當有菜做好時,廚師告訴用戶,菜做好了。
在上述我們介紹了五種IO模型,五個IO模型對比如下圖所示:
經過本文的講解,大家對計算機世界的IO掌握了嗎?我們的數據經過了九九八十一道關卡,才最終在電腦、iPad、手機等各個終端展現給到用戶,每一條數據都不容易,且行且珍惜噢~