一,read 函數從開啟的裝置或檔案中讀取資料
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
傳回值:成功返回讀取的位元組數,出錯返回-1並設定errno,如果在調read之前已到達檔案末尾,則這次
read返回0
讀上來的資料儲存在緩衝區buf 中,同時檔案的當前讀寫位置向後移。注意這個讀寫位置和使用C標準I/O庫時的讀寫位置有可能不同,這個讀寫位置是記在核心中的,而使用C標準I/O庫時的讀寫位置是使用者空間I/O緩衝區中的位置。
二,write 函數向開啟的裝置或檔案中寫資料
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
傳回值:成功返回寫入的位元組數,出錯返回-1並設定errno
三,阻塞(Block)
當進程調用一個阻塞的系統函數時,該進程被置於睡眠(Sleep)狀態,這時核心調度其它進程運行,直到該進程等待的事件發生了(比如網路上接收到資料包,或者調用sleep 指定的睡眠時間到了)它才有可能繼續運行。
睡眠狀態相對的是運行(Running)狀態,在Linux核心中,處於運行狀態的進程分為兩種情況:正在被調度執行和就緒狀態。
假設同時監視多個裝置,如果read(裝置1)是阻塞的,那麼只要裝置1沒有資料到達就會一直阻塞在裝置1的read 調用上,即使裝置2有資料到達也不能處理,使用非阻塞I/O就可以避免裝置2得不到及時處理。
在open 一個裝置時指定了O_NONBLOCK 標誌,read / write 就不會阻塞。以read 為例,如果裝置暫時沒有資料可讀就返回-1,同時置errno 為EWOULDBLOCK(或者EAGAIN,這兩個宏定義的值相同),表示本來應該阻塞在這裡(would block,虛擬語氣),事實上並沒有阻塞而是直接返回錯誤,調用者應該試著再讀一次(again)。這種行為方式稱為輪詢(Poll),調用者只是查詢一下,而不是阻塞在這裡死等,這樣可以同時監視多個裝置。
非阻塞I/O有一個缺點,如果所有裝置都一直沒有資料到達,調用者需要反覆查詢做無用功,如果阻塞在那裡,作業系統可以調度別的進程執行,就不會做無用功了。
select(2) 函數可以阻塞地同時監視多個裝置,還可以設定阻塞等待的逾時時間,從而圓滿地解決了這個問題。
四,阻塞與非阻塞讀終端執行個體
非阻塞讀終端
程式開始執行時在0、1、2檔案描述符上自動開啟的檔案就是終端,但是沒有O_NONBLOCK 標誌。所以,讀標準輸入是阻塞的。我們可以重新開啟一遍裝置檔案/dev/tty (表示當前終端),在開啟時指O_NONBLOCK 標誌。
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define MSG_TRY "try again\n"
int main(void)
{
char buf[10];
int fd, n;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0) {
perror("open /dev/tty");
exit(1);
}
tryagain:
n = read(fd, buf, 10);
if (n < 0) {
if (errno == EAGAIN) {
sleep(1);
write(STDOUT_FILENO, MSG_TRY,
strlen(MSG_TRY));
goto tryagain;
}
perror("read /dev/tty");
exit(1);
}
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}
阻塞讀終端
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
char buf[10];
int n;
n = read(STDIN_FILENO, buf, 10);
if (n < 0) {
perror("read STDIN_FILENO");
exit(1);
}
write(STDOUT_FILENO, buf, n);
return 0;}
原文:http://blog.chinaunix.net/space.php?uid=22086460&do=blog&id=404970