contributed by < rwe0214
>
Linux
, rwe0214
, CS:APP
, NCKU
Unix
中的 file
是一序列的 bytes
所組成,所有的 I/O ( e.g. networks, disks, terminals, … ) 皆 model as file
。
因為這樣 Unix kernel
就可提供一個simple, low-level application interface ( Unix I/O
),達到驅動所有 I/O 一個統一和方便的方法。
因為 read
和 write
的回傳值為已讀寫完的 bytes
數量,但在連線不穩定(例如網路 socket connect 等等)的情況下,不一定只執行一次 read
/write
就可達到預期讀寫完 n bytes
的結果( i.e. short count > 0
),所以需要基於使用 read
/write
來設計更穩定的 I/O function,可分成 unbuffered
和 buffered
兩種。
利用回傳 short count
數量來判斷是否已經讀取完所需的 n bytes,若遇到 EOF
則回傳 0
,
rio_readn
and rio_writen
如下:
ssize_t rio_readn(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nread;
char *bufp = usrbuf;
while (nleft > 0) {
if ((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR) /* interrupted by sig handler return */
nread = 0; /* and call read() again */
else
return -1; /* errno set by read() */
}
else if (nread == 0)
break; /* EOF */
nleft -= nread;
bufp += nread;
}
return (n - nleft); /* return >= 0 */
}
可將
if ((nread = read(fd, bufp, nleft)) < 0) {
替換成
if ((nread = rio_read(fd, bufp, nleft)) < 0) {
即完成 buffered 版本,在下部分的 buffered I/O 會說明。
ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nwritten;
char *bufp = usrbuf;
while (nleft > 0) {
if ((nwritten = write(fd, bufp, nleft)) <= 0) {
if (errno == EINTR) /* interrupted by sig handler return */
nwritten = 0; /* and call write() again */
else
return -1; /* errno set by write() */
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
}
如果要計算 file
中有幾行資料(number of '\n'
),上述 unbuffered
的作法利用多次 read
一次讀取一個 byte
並檢查是否為 '\n'
,但此方法會需要多次的 system call
所以極度沒有效率,
e.g.
file
只有一行,但是是長度非常長的一行。
使用 rio_readlineb
來減少使用 read
的次數。
透過 subroutine rio_read
呼叫一次 read
讀取長度為 buflen
的資料後,再一個char
一個 char
比對是否為 \n
,呼叫 system call
的數量便少了 buflen
倍,
以上面的例子來看,呼叫
read
的次數便有效的簡少了。
同樣的,可以將 rio_readn
擴展成 rio_readnb
,流程如下,
file → rio_buf → usrbuf(char) → usrbuf(char *)
| system call | | |
|=====read=====| | |
|-----------rio_read----------| |
|---------------rio_readlineb----------------|
or
| system call | | |
|=====read=====| | |
|-----------rio_read----------| |
|---------------rio_readnb-------------------|
為了達到以上的目標,定義了一個 rio_p 的結構體,
#define RIO_BUFSIZE 8192 typedef struct {
int rio_fd; /* Descriptor for this internal buf */
int rio_cnt; /* Unread bytes in internal buf */
char *rio_bufptr; /* Next unread byte in internal buf */
char rio_buf[RIO_BUFSIZE]; /* Internal buffer */
} rio_t;
和一個初始化的 rio_readinitb
,
void rio_readinitb(rio_t *rp, int fd) {
rp->rio_fd = fd;
rp->rio_cnt = 0;
rp->rio_bufptr = rp->rio_buf;
}
再來就是介在中間的 rio_read
,
將
rio_readn
中的read
替換成rio_read
就是rio_readnb
static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
{
int cnt;
while (rp->rio_cnt <= 0) { /* refill if buf is empty */
rp->rio_cnt = read(rp->rio_fd, rp->rio_buf,
sizeof(rp->rio_buf));
if (rp->rio_cnt < 0) {
if (errno != EINTR) /* interrupted by sig handler return */
return -1;
}
else if (rp->rio_cnt == 0) /* EOF */
return 0;
else
rp->rio_bufptr = rp->rio_buf; /* reset buffer ptr */
}
/* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */
cnt = n;
if (rp->rio_cnt < n)
cnt = rp->rio_cnt;
memcpy(usrbuf, rp->rio_bufptr, cnt);
rp->rio_bufptr += cnt;
rp->rio_cnt -= cnt;
return cnt;
}
有了以上,就能完成 rio_readlineb
。
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
int n, rc;
char c, *bufp = usrbuf;
for (n = 1; n < maxlen; n++) {
if ((rc = rio_read(rp, &c, 1)) == 1) {
*bufp++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
if (n == 1)
return 0; /* EOF, no data read */
else
break; /* EOF, some data was read */
} else
return -1; /* error */
}
*bufp = 0;
return n;
}
Ans: 因為 STDIO
在 socket
有 documented restriction,所以在 network socket 上進行 I/O 用 RIO package 會更適合。
Restriction 1: Input functions following output functions. An input function cannot follow an output function without an intervening call to fflush, fseek, fsetpos, or rewind. The fflush function empties the buffer associated with a stream. The latter three functions use the Unix I/O lseek function to reset the current file position.
Restriction 2: Output functions following input functions. An output function cannot follow an input function without an intervening call to fseek, fsetpos, or rewind, unless the input function encounters an end-of-file.
fork
系統呼叫後,child process 和 parent process 的檔案分享狀況為何?(提示:閱讀 man-pages)Ans: child 和 parent 會共享同一個 file descriptor
The child process has its own copy of the parent's descriptors. These descriptors reference the same underlying objects, so that, for instance, file pointers in file objects are shared between the child and the parent, so that an lseek(2) on a descriptor in the child process can affect a subsequent read or write by the parent. This descriptor copying is also used by the shell to establish standard input and output for newly created processes as well as to set up pipes.
Ref: manual fork