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緩衝區中的位置。html
fgetc
讀一個字節,
fgetc
有可能從內核中預讀1024個字節到I/O緩衝區中,再返回第一個字節,這時該文件在內核中記錄的讀寫位置是1024,而在
FILE
結構體中記錄的讀寫位置是1。注意返回值類型是
ssize_t
,表示有符號的
size_t
,這樣既能夠返回正的字節數、0(表示到達文件末尾)也能夠返回負值-1(表示出錯)。
read
函數返回時,返回值說明了
buf
中前多少個字節是剛讀上來的。有些狀況下,實際讀到的字節數(返回值)會小於請求讀的字節數
count
,例如:
讀常規文件時,在讀到count
個字節以前已到達文件末尾。例如,距文件末尾還有30個字節而請求讀100個字節,則read
返回30,下次read
將返回0。算法
從終端設備讀,一般以行爲單位,讀到換行符就返回了。編程
從網絡讀,根據不一樣的傳輸層協議和內核緩存機制,返回值可能小於請求的字節數,後面socket編程部分會詳細講解。小程序
write
函數向打開的設備或文件中寫數據。
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); 返回值:成功返回寫入的字節數,出錯返回-1並設置errno
write
的返回值一般等於請求寫的字節數
count
,而向終端設備或網絡寫則不必定。
讀常規文件是不會阻塞的,無論讀多少字節,read
必定會在有限的時間內返回。從終端設備或網絡讀則不必定,若是從終端輸入的數據沒有換行符,調用read
讀終端設備就會阻塞,若是網絡上沒有接收到數據包,調用read
從網絡讀就會阻塞,至於會阻塞多長時間也是不肯定的,若是一直沒有數據到達就一直阻塞在那裏。一樣,寫常規文件是不會阻塞的,而向終端設備或網絡寫則不必定。數組
sleep
指定的睡眠時間到了)它纔有可能繼續運行。與睡眠狀態相對的是運行(Running)
狀態,在Linux內核中,處於運行狀態的進程分爲兩種狀況:
正在被調度執行。CPU處於該進程的上下文環境中,程序計數器(eip
)裏保存着該進程的指令地址,通用寄存器裏保存着該進程運算過程的中間結果,正在執行該進程的指令,正在讀寫該進程的地址空間。緩存
就緒狀態。該進程不須要等待什麼事件發生,隨時均可以執行,但CPU暫時還在執行另外一個進程,因此該進程在一個就緒隊列中等待被內核調度。系統中可能同時有多個就緒的進程,那麼該調度誰執行呢?內核的調度算法是基於優先級和時間片的,並且會根據每一個進程的運行狀況動態調整它的優先級和時間片,讓每一個進程都能比較公平地獲得機會執行,同時要兼顧用戶體驗,不能讓和用戶交互的進程響應太慢。bash
#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);
return0;
}
$ ./a.out hello(回車) hello $ ./a.out hello world(回車) hello worl$ d bash: d: command not found
第一次執行a.out
的結果很正常,而第二次執行的過程有點特殊,如今分析一下:網絡
Shell進程建立a.out
進程,a.out
進程開始執行,而Shell進程睡眠等待a.out
進程退出。socket
a.out
調用read
時睡眠等待,直到終端設備輸入了換行符才從read
返回,read
只讀走10個字符,剩下的字符仍然保存在內核的終端設備輸入緩衝區中。函數
a.out
進程打印並退出,這時Shell進程恢復運行,Shell繼續從終端讀取用戶輸入的命令,因而讀走了終端設備輸入緩衝區中剩下的字符d和換行符,把它當成一條命令解釋執行,結果發現執行不了,沒有d這個命令。
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得不到及時處理。
while
循環中一直不停地查詢(這稱爲Tight Loop
),而是每延遲等待一下子來查詢一下,以避免作太多無用功,在延遲等待的時候能夠調度其它進程執行。
while(1) { 非阻塞read(設備1); if(設備1有數據到達) 處理數據; 非阻塞read(設備2); if(設備2有數據到達) 處理數據; ... sleep(n); }
select(2)
函數能夠阻塞地同時監視多個設備,還能夠設定阻塞等待的超時時間,從而圓滿地解決了這個問題。
O_NONBLOCK
標誌。因此就像
例 28.2 「阻塞讀終端」
同樣,讀標準輸入是阻塞的。咱們能夠從新打開一遍設備文件
/dev/tty
(表示當前終端),在打開時指定
O_NONBLOCK
標誌。
read
讀終端設備就會阻塞
#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;
}
直到按下回車把以前的輸入輸出(最多10個),而後中止。
如下是用非阻塞I/O實現等待超時的例子。既保證了超時退出的邏輯又保證了有數據到達時處理延遲較小。
例 28.4. 非阻塞讀終端和等待超時
read:既能夠返回正的字節數、0(表示到達文件末尾)也能夠返回負值-1(表示出錯)
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "timeout\n"
int main(void)
{
char buf[10];
int fd, n, i;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0){
perror("open /dev/tty");
exit(1);
}
for(i=0; i<5; i++){
n = read(fd, buf,10);
if(n>=0)
break;
if(errno!=EAGAIN){
perror("read /dev/tty");
exit(1);
}
sleep(1);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
}
if(i==5)
write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
else
write(STDOUT_FILENO, buf, n);
close(fd);
return0;
}