其實看APUE時就想試着寫些簡單的stdio函數了,但是一直沒實踐,看到這裡時發現書上寫得不完整,便敲代碼試了下。
第1個readline速度非常慢原因在于每次讀取字元都執行了系統調用read(),而系統調用意味着核心态和使用者态之間的切換,系統調用數量太多會導緻切換過程非常費時。是以為了快速的進行I/O,往往會定義一個緩沖區,即第2個readline中的char read_buf[MAXLINE];以及記錄讀取數量的int read_cnt;和記錄目前讀取指針的char *read_ptr;這三個靜态變量都是static類型,也就是目前檔案可用,外部檔案無法通路static變量。
static ssize_t my_read(int fd, char* ptr)
{
if (read_cnt <= 0) {
again:
if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
關鍵在于用my_read來替換read。my_read是從緩沖區中(而不是從檔案中)逐個讀取資料。
由于一開始緩沖中什麼都沒有,對應的狀态即read_cnt=0(讀取數量為0),read_ptr指向read_buf首部。是以最開始需要填滿緩沖區,讀取MAXLINE(行緩沖區大小),将檔案的資料讀到緩沖區中。然後read_cnt初始化為read函數實際讀取的數量。
比如我這裡MAXLINE是4096,但是若檔案實際總大小沒有這麼多,傳回的read_cnt就小于MAXLINE。
然後read_cnt大于0就意味着緩沖區有未讀的資料,于是每次調用my_read隻需要從read_buf中讀取1個字元,然後移動指針read_ptr到下個字元,并減少read_cnt。
由于read_cnt記錄的是read函數實際讀取的數量,是以歸0時代表緩沖區元素已經讀完了,需要再次調用read函數讀取最多MAXLINE個字元。
這裡采用了goto,原因就像書上所說的,在位元組流套接字上調用read/write輸入或輸出的位元組數可能比請求的數量少,此時并不一定是出錯,而是因為核心中用于套接字的緩沖區已經到達了極限,需要再次(反複)調用read/write函數來輸入或輸出剩下的位元組。
而errno被設定為EINTR的原因如下
EINTR The call was interrupted by a signal before any data was read;
被信号打斷,此時需要反複讀取至成功為止。
原理說完了,測試代碼如下
#define MAXLINE 4096
static int read_cnt = 0;
static char read_buf[MAXLINE];
static char* read_ptr = read_buf;
// ...
// UNPv3 P74-75的readline.c代碼
// ...
int main(void)
{
char buf[MAXLINE];
for (int i = 0; i < 5; i++) {
ssize_t n = readline(STDIN_FILENO, buf, MAXLINE);
write(STDOUT_FILENO, buf, n);
}
return 0;
}
測試代碼如上,從輸入流中讀取了5行并列印出來
用strace檢視系統調用如下
可以發現隻調用了1次read(不包含進入main前的部分),然後write了5次。
如果把if ((rc = my_read(fd, &c)) == 1) 改成if ((rc = read(fd, &c, 1)) == 1) 再調用strace
可見系統調用數量之多
轉載于:https://www.cnblogs.com/Harley-Quinn/p/6562335.html