---
# System prepended metadata

title: LINUX 核心設計 課程用書筆記
tags: [NCKU, Linux, rwe0214, 'CS:APP']

---

# LINUX 核心設計 課程用書筆記
contributed by < `rwe0214` >

###### tags: `Linux`, `rwe0214`, `CS:APP`, `NCKU`
## CS:APP Ch10 System-level I/O
`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 一個統一和方便的方法。
### 10.4 Robust Reading and Writing with the Rio Package
因為 `read` 和 `write` 的回傳值為已讀寫完的 `bytes` 數量，但在連線不穩定（例如網路 socket connect 等等）的情況下，不一定只執行一次 `read`/`write` 就可達到預期讀寫完 `n bytes` 的結果（ i.e. `short count > 0` ），所以需要基於使用 `read`/`write` 來設計更穩定的 I/O function，可分成 `unbuffered` 和 `buffered` 兩種。
#### Unbuffered I/O Function
利用回傳 `short count` 數量來判斷是否已經讀取完所需的 n bytes，若遇到 `EOF` 則回傳 `0`，
`rio_readn` and `rio_writen` 如下：
```c=
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 */
}
```
:::info
可將
```c=8
if ((nread = read(fd, bufp, nleft)) < 0) {
```
替換成 
```c=8
if ((nread = rio_read(fd, bufp, nleft)) < 0) {
```
即完成 buffered 版本，在下部分的 buffered I/O 會說明。
:::
```c=
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;
}
```
#### Buffered I/O Function
如果要計算 `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 的結構體，
```c=
#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`，
```c=
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`
```c=
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`。 
```c=
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;
}
```
#### 自我檢查清單
- [ ] tty 是什麼？緣起為何？對 UNIX 的影響又是？
- [x] 為何 socket 也是一種 file type 呢？具體使用方式為何？ [Ans](https://hackmd.io/@swchiu/Hktn-eWV8#CSAPP-Ch10-System-level-IO)
- [x] RIO package 提供 I/O 封裝的目的為何？對應到真實世界的應用為何？
:::success
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.
>
- [x] Buffered I/O 的動機和效益為何？ [Ans](https://hackmd.io/CH9uOJrBRM6Rkb8aqV2oYg?view#Buffered-IO-Function)
- [x] 在 `fork` 系統呼叫後，child process 和 parent process 的檔案分享狀況為何？(提示：閱讀 man-pages)
:::success
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
:::