linux系統編程之基礎必備(七):read/write函數與(非)阻塞I/O的概念

來源:互聯網
上載者:User

一、read/write 函數

read函數從開啟的裝置或檔案中讀取資料。

#include <unistd.h>

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

傳回值:成功返回讀取的位元組數,出錯返回-1並設定errno,如果在調read之前已到達檔案末尾,則這次read返回0


參數count是請求讀取的位元組數,讀上來的資料儲存在緩衝區buf中,同時檔案的當前讀寫位置向後移。注意這個讀寫位置和使用C標準I/O庫時的讀寫位置有可能不同,這個讀寫位置是記在核心中的,而使用C標準I/O庫時的讀寫位置是使用者空間I/O緩衝區中的位置。比如用fgetc讀一個位元組,fgetc有可能從核心中預讀1024個位元組到I/O緩衝區中,再返回第一個位元組,這時該檔案在核心中記錄的讀寫位置是1024,而在FILE結構體中記錄的讀寫位置是1。注意傳回值類型是ssize_t,表示有符號的size_t,這樣既可以返回正的位元組數、0(表示到達檔案末尾)也可以返回負值-1(表示出錯)。read函數返回時,傳回值說明了buf中前多少個位元組是剛讀上來的。有些情況下,實際讀到的位元組數(傳回值)會小於請求讀的位元組數count,例如:

1、讀常規檔案時,在讀到count個位元組之前已到達檔案末尾。例如,距檔案末尾還有30個位元組而請求讀100個位元組,則read返回30,下次read將返回0。從

2、終端裝置讀,通常以行為單位,讀到分行符號就返回了。

3、從網路讀,根據不同的傳輸層協議和核心緩衝機制,傳回值可能小於請求的位元組數。


write函數向開啟的裝置或檔案中寫資料。
#include <unistd.h>

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

傳回值:成功返回寫入的位元組數,出錯返回-1並設定errno

寫常規檔案時,write的傳回值通常等於請求寫的位元組數count,而向終端裝置或網路寫則不一定。


讀常規檔案是不會阻塞的,不管讀多少位元組,read一定會在有限的時間內返回。從終端裝置或網路讀則不一定,如果從終端輸入的資料沒有分行符號,調用read讀終端裝置就會阻塞,如果網路上沒有接收到資料包,調用read從網路讀就會阻塞,至於會阻塞多長時間也是不確定的,如果一直沒有資料到達就一直阻塞在那裡。同樣,寫常規檔案是不會阻塞的,而向終端裝置或網路寫則不一定。


二、(非)阻塞I/O的概念


現在明確一下阻塞(Block)這個概念。當進程調用一個阻塞的系統函數時,該進程被置於睡眠(Sleep)狀態,這時核心調度其它進程運行,直到該進程等待的事件發生了(比如網路上接收到資料包,或者調用sleep指定的睡眠時間到了)它才有可能繼續運行。與睡眠狀態相對的是運行(Running)狀態,在Linux核心中,處於運行狀態的進程分為兩種情況:

1、正在被調度執行。CPU處於該進程的上下文環境中,程式計數器(eip)裡儲存著該進程的指令地址,通用寄存器裡儲存著該進程運算過程的中間結果,正在執行該進程的指令,正在讀寫該進程的地址空間。

2、就緒狀態。該進程不需要等待什麼事件發生,隨時都可以執行,但CPU暫時還在執行另一個進程,所以該進程在一個就緒隊列中等待被核心調度。系統中可能同時有多個就緒的進程,那麼該調度誰執行呢?核心的調度演算法是基於優先順序和時間片的,而且會根據每個進程的運行情況動態調整它的優先順序和時間片,讓每個進程都能比較公平地得到機會執行,同時要兼顧使用者體驗,不能讓和使用者互動的進程響應太慢。


如果在open一個裝置時指定了O_NONBLOCK標誌,read/write就不會阻塞。以read為例,如果裝置暫時沒有資料可讀就返回-1,同時置errno為EWOULDBLOCK(或者EAGAIN,這兩個宏定義的值相同),表示本來應該阻塞在這裡(would block,虛擬語氣),事實上並沒有阻塞而是直接返回錯誤,調用者應該試著再讀一次(again)。這種行為方式稱為輪詢(Poll),調用者只是查詢一下,而不是阻塞在這裡死等,這樣可以同時監視多個裝置:

while(1) 

非阻塞read(裝置1); 

if(裝置1有資料到達) 

處理資料; 

非阻塞read(裝置2); 

if(裝置2有資料到達) 

處理資料; 

..............................

}


如果read(裝置1)是阻塞的,那麼只要裝置1沒有資料到達就會一直阻塞在裝置1的read調用上,即使裝置2有資料到達也不能處理,使用非阻塞I/O就可以避免裝置2得不到及時處理。

非阻塞I/O有一個缺點,如果所有裝置都一直沒有資料到達,調用者需要反覆查詢做無用功,如果阻塞在那裡,作業系統可以調度別的進程執行,就不會做無用功了。在使用非阻塞I/O時,通常不會在一個while迴圈中一直不停地查詢(這稱為Tight Loop),而是每延遲等待一會兒來查詢一下,以免做太多無用功,在延遲等待的時候可以調度其它進程執行。

while(1) 

非阻塞read(裝置1); 

if(裝置1有資料到達) 

處理資料; 

非阻塞read(裝置2); 

if(裝置2有資料到達) 

處理資料; 

..............................

sleep(n);

}


這樣做的問題是,裝置1有資料到達時可能不能及時處理,最長需延遲n秒才能處理,而且反覆查詢還是做了很多無用功。而select/poll/epoll 等函數可以阻塞地同時監視多個裝置,還可以設定阻塞等待的逾時時間,從而圓滿地解決了這個問題。

參考:《linux c 編程一站式學習》

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.