在linux系統(tǒng)中,i/o操作可以分為兩種模式:阻塞式i/o和非阻塞式i/o。
這兩種模式?jīng)Q定了進(jìn)程在執(zhí)行I/O操作時(shí)的行為方式,以及CPU資源的利用效率。

阻塞的本質(zhì)是進(jìn)程在無(wú)法完成某個(gè)操作時(shí),進(jìn)入休眠狀態(tài),交出了CPU控制權(quán),等待操作條件滿足再被喚醒執(zhí)行。
這種情況下,進(jìn)程會(huì)被掛起,暫停執(zhí)行其他任務(wù)。
例如,像wait()、pause()、sleep()等函數(shù)都會(huì)導(dǎo)致進(jìn)程進(jìn)入阻塞狀態(tài)。
非阻塞則是指即使操作條件尚未滿足,進(jìn)程也不會(huì)等待,而是立刻返回控制權(quán)并繼續(xù)執(zhí)行其他任務(wù)。
1
阻塞式 I/O (Blocking I/O)
阻塞式I/O是默認(rèn)的I/O操作模式。它的典型特點(diǎn)是,當(dāng)對(duì)文件或設(shè)備進(jìn)行讀寫操作時(shí),如果資源當(dāng)前不可用,操作系統(tǒng)會(huì)掛起調(diào)用者,直到資源變得可用。
例如:
對(duì)于普通文件(如文本文件),即使讀寫的數(shù)據(jù)量較大,read()或write()總會(huì)在有限時(shí)間內(nèi)返回。這是因?yàn)槠胀ㄎ募腎/O操作不會(huì)阻塞。對(duì)于特殊類型的文件,如管道文件、網(wǎng)絡(luò)套接字和字符設(shè)備文件,如果在讀取數(shù)據(jù)時(shí)文件無(wú)數(shù)據(jù)可讀,進(jìn)程會(huì)被阻塞,直到有數(shù)據(jù)可用時(shí)才被喚醒。這種行為能夠提升系統(tǒng)的響應(yīng)效率,因?yàn)楫?dāng)條件不滿足時(shí),進(jìn)程進(jìn)入阻塞狀態(tài),CPU資源可以被其他進(jìn)程利用。
2
非阻塞式 I/O (Non-blocking I/O)
非阻塞I/O則是在執(zhí)行I/O操作時(shí),不管資源是否可用,操作系統(tǒng)都不會(huì)讓進(jìn)程進(jìn)入阻塞狀態(tài),而是立即返回控制權(quán)。
通常返回一個(gè)特殊的錯(cuò)誤碼(如EAGaiN),表示當(dāng)前操作未能完成,資源暫時(shí)不可用。
非阻塞I/O一般需要輪詢資源狀態(tài)或使用異步事件通知來(lái)檢測(cè)資源是否變得可用。
3
阻塞與非阻塞 I/O 的區(qū)別
舉個(gè)例子,假設(shè)程序嘗試從管道文件或網(wǎng)絡(luò)套接字中讀取數(shù)據(jù):
阻塞式 I/O:如果當(dāng)前沒(méi)有數(shù)據(jù)可讀,調(diào)用read()函數(shù)時(shí),進(jìn)程會(huì)被掛起,直到有數(shù)據(jù)寫入管道或網(wǎng)絡(luò)緩沖區(qū)為止。這段時(shí)間,CPU可以分配給其他進(jìn)程使用。非阻塞式 I/O:即使沒(méi)有數(shù)據(jù)可讀,read()函數(shù)也會(huì)立即返回,并設(shè)置errno為EAGAIN或EWOULDBLOCK。進(jìn)程可以選擇繼續(xù)執(zhí)行其他任務(wù),或者通過(guò)輪詢方式不斷嘗試讀取,直到數(shù)據(jù)可用。
4
阻塞與非阻塞 I/O 的優(yōu)缺點(diǎn)
阻塞式 I/O 優(yōu)點(diǎn):
程序結(jié)構(gòu)簡(jiǎn)單,不需要處理I/O狀態(tài)的變化。在I/O等待時(shí),能夠讓出CPU資源,提高系統(tǒng)整體的CPU利用效率。
阻塞式 I/O 缺點(diǎn):
由于進(jìn)程可能長(zhǎng)時(shí)間阻塞,會(huì)降低系統(tǒng)的響應(yīng)性。不適合高并發(fā)場(chǎng)景,因?yàn)槊總€(gè)阻塞的進(jìn)程都會(huì)占用一個(gè)線程或進(jìn)程資源。
非阻塞式 I/O 優(yōu)點(diǎn):
提高響應(yīng)性,特別適用于需要實(shí)時(shí)交互的場(chǎng)景。適合高并發(fā)服務(wù)器程序,如網(wǎng)絡(luò)服務(wù)器,使用非阻塞I/O可以高效處理大量請(qǐng)求。
非阻塞式 I/O 缺點(diǎn):程序復(fù)雜度增加,需要管理I/O狀態(tài)和輪詢操作。
如果沒(méi)有采用合適的等待機(jī)制(如select、poll或epoll),可能會(huì)導(dǎo)致CPU資源被占用過(guò)多。
以鼠標(biāo)輸入設(shè)備文件為例,Linux中鼠標(biāo)對(duì)應(yīng)的設(shè)備文件通常位于/dev/input/目錄下,命名為mouseX(X為序號(hào))或eventX。

例如,使用od命令查看/dev/input/event3設(shè)備的數(shù)據(jù):
代碼語(yǔ)言:JavaScript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
sudo od -x /dev/input/event3
在終端中移動(dòng)鼠標(biāo)或點(diǎn)擊按鈕時(shí),會(huì)輸出相應(yīng)的數(shù)據(jù)。
如果沒(méi)有輸出,說(shuō)明該設(shè)備文件不是鼠標(biāo)對(duì)應(yīng)的設(shè)備。

編寫一個(gè)測(cè)試程序,使用阻塞式I/O讀取鼠標(biāo)的輸入:
代碼語(yǔ)言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main() { int fd = open("/dev/input/event3", O_RDONLY); if (fd == -1) { perror("Error opening device"); return 1; } char buffer[128]; printf("Reading from mouse device...n"); while (1) { // 阻塞等待,直到有數(shù)據(jù)可讀 int bytes_read = read(fd, buffer, sizeof(buffer)); if (bytes_read > 0) { printf("Read %d bytes from the mousen", bytes_read); } } close(fd); return 0;}
在這個(gè)程序中,如果鼠標(biāo)沒(méi)有輸入數(shù)據(jù),read()函數(shù)會(huì)阻塞,進(jìn)程進(jìn)入休眠狀態(tài),直到檢測(cè)到鼠標(biāo)動(dòng)作才繼續(xù)執(zhí)行。
修改程序,使其以非阻塞方式讀取鼠標(biāo)設(shè)備數(shù)據(jù):
代碼語(yǔ)言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
int main() { int fd = open("/dev/input/event3", O_RDONLY | O_NONBLOCK); if (fd == -1) { perror("Error opening device"); return 1; } char buffer[128]; printf("Reading from mouse device (non-blocking)...n"); while (1) { int bytes_read = read(fd, buffer, sizeof(buffer)); if (bytes_read > 0) { printf("Read %d bytes from the mousen", bytes_read); } else if (bytes_read == -1 && errno == EAGAIN) { // 沒(méi)有數(shù)據(jù)可讀,非阻塞操作立刻返回 printf("No data available, will try again...n"); } // 稍微休眠一下,避免CPU資源過(guò)度消耗 usleep(100000); } close(fd); return 0;}
在這個(gè)程序中,即使沒(méi)有鼠標(biāo)數(shù)據(jù),read()也會(huì)立即返回,并設(shè)置errno為EAGAIN。
此時(shí)程序不會(huì)被掛起,而是可以繼續(xù)嘗試讀取或執(zhí)行其他任務(wù)。