# [2020q1](http://wiki.csie.ncku.edu.tw/linux/schedule) 第 11 週測驗題
###### tags: `linux2020`
:::info
目的: 檢驗學員對 Linux 記憶體管理、POSIX shared memory, POSIX semaphore 和 split/epoll 系統呼叫的認知
:::
==[作答表單](https://docs.google.com/forms/d/e/1FAIpQLSe_Ka_m1O9oj0KjULO3VpXBDnsU2c4eliFJxtJQ_HF58X7tiA/viewform)==
---
### 測驗 `1`
考慮一個使用 POSIX shared memory 和 semaphore 開發的 [key-value store](https://en.wikipedia.org/wiki/Key-value_database) 實作,測試程式碼:
```cpp
static void sig_handler(int dummy) {
kv_delete_db();
exit(0);
}
int main()
{
signal(SIGINT, sig_handler);
signal(SIGQUIT, sig_handler);
signal(SIGTSTP, sig_handler);
// Check create
kv_store_create("/STORE");
// Check write
kv_store_write("MyKey", "content [A]");
kv_store_write("MyKey", "content [B]");
for (size_t k = 0; k < 17; k++) {
char str[5];
sprintf(str, "[%zu]", k);
kv_store_write("MyKey", str);
}
for (int i = 0;; i++) {
char *p = kv_store_read("MyKey");
if (!p) break;
free(p);
}
// Check read_all
char **v = kv_store_read_all("MyKey");
for (size_t i = 0; v[i]; i++) {
printf("%zu => '%s'\n", i, v[i]);
free(v[i]);
}
free(v);
// Check destroy
kv_store_destroy("/STORE");
return 0;
}
```
參考執行輸出:
```
0 => 'content [A]'
1 => 'content [B]'
2 => '[0]'
3 => '[1]'
4 => '[2]'
5 => '[3]'
6 => '[4]'
7 => '[5]'
8 => '[6]'
9 => '[7]'
10 => '[8]'
11 => '[9]'
12 => '[10]'
13 => '[11]'
14 => '[12]'
15 => '[13]'
16 => '[14]'
17 => '[15]'
18 => '[16]'
```
原始程式碼可見 [kv.c](https://gist.github.com/jserv/9db8ca1fe174a99eaccbfaebbf47b2bb)。
這份實作應該能在 multi-process 和 multi-thread 環境運作。
請補完上述 `kv.c` 程式碼。
==作答區==
AAA = ?
* `(a)` `open`
* `(b)` `shm_open`
BBB = ?
* `(a)` `store->clients`
* `(b)` `store->clients++`
* `(c)` `++store->clients`
* `(d)` `store->clients--`
* `(e)` `--store->clients`
CCC = ?
* `(a)` 0
* `(b)` `cache_idx`
* `(c)` `cache_idx++`
* `(d)` `cache_idx--`
DDD = ?
* `(a)` 0
* `(b)` `cache_idx`
* `(c)` `cache_idx++`
* `(d)` `cache_idx--`
:::success
延伸問題:
1. 解釋上述程式碼運作原理,指出實作缺陷並改進
2. 設計實驗並探討改進存取效率的手法
:::
---
### 測驗 `2`
以下程式碼嘗試透過 epoll 和 splice 系統呼叫,實作出具體而微的 [Port forwarding](https://en.wikipedia.org/wiki/Port_forwarding)。考慮一個情境:我們對外有一台防火牆,在 DNS 設定方面,我們設定 `ftp.mydomain.com` 及 `www.mydomain.com` 都指向這台防火牆。但我們希望所有 HTTP 連線都重新導向到內部的 `192.168.0.2` 這台機器上,而所有 FTP 連線都交由 `192.168.0.3` 來處理。這時候我們就可以使用 port forwarding 的方式來達成。對應的 NAT (Network Address Translation) 的設定如下:
```=
redirect_port tcp 192.168.0.2:80 80
redirect_port tcp 192.168.0.3:20 20
redirect_port tcp 192.168.0.3:21 21
```
第一行的目的就是將 port 80 的 tcp 連線重新導向到 `192.168.0.2` 的 port 80,而第 2 和第 3 行是將 port 20 及 port 21 的連線交由 `192.168.0.3` 來處理。在 1`92.168.0.2` 及 `192.168.0.3` 這二台機器上,我們只要設定它們的 gateway 為防火牆的 IP,例如 `192.168.0.1` 即可。
使用 splice 系統呼叫,我們有機會在網路介面控制器的支援下,達到 Zero-copy 資料傳輸。
原始程式碼可見 [proxy.c](https://gist.github.com/jserv/99ad84d0b9ef8a5836cf93fc42b4f435),其 `list.h` 取自 [list.h](https://github.com/sysprog21/linux-list/blob/master/include/list.h),改寫自 Linux 核心原始程式碼。
假設本地機器系統 port 80 已有網頁伺服器在等待連線。`proxy` 的測試方式為
```shell
$ ./proxy 8082 localhost 80
```
等程式執行後,在另一個終端機畫面中輸入下列命令:
```shell
$ telnet localhost 8082
```
接著你就可以輸入 HTTP 請求字串,如 `GET /index.html`。
此外,你還可以把 port 8082 轉向到 Google 首頁:
先找出 `www.google.com` 的 IP 地址:
```shell
$ nslookup www.google.com
```
得到以下輸出:
```
Name: www.google.com
Address: 216.58.200.228
```
修改上述命令:
```shell
./proxy 8082 216.58.200.228 80
```
重複上述 `telnet` 命令,這時候就會看到 Google 首頁的字串。
請補完程式碼。
==作答區==
EEE = ?
* `(a)` `num_events`
* `(b)` `num_events++`
* `(c)` `num_events--`
* `(d)` `++num_events`
* `(e)` `--num_events`
FFF = ?
* `(a)` 不需要加入程式碼
* `(b)` `buf->bytes += n`
* `(c)` `buf->bytes -= n`
* `(d)` `buf->bytes--`
* `(e)` `buf->bytes++`
:::success
延伸問題:
1. 解釋上述程式碼運作原理,指出實作缺陷並改進
2. 將 DNS 解析的機制納入,允許用 [FQDN](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) 作為輸入
3. 嘗試克服連線數量的限制,設計實驗並探討如此 port forwarding 的效率
:::