# [2019q3](http://wiki.csie.ncku.edu.tw/sysprog/schedule) 第 11 週測驗題
:::info
目的:
1. 檢驗學員對 C 語言程式設計的認知
2. 檢驗學員對 mmap 系統呼叫及對應記憶體管理的認知
:::
---
### 測驗 `1`
在 [PLDI'19](https://pldi19.sigplan.org/) 論文 [Computing Summaries of String Loops in C for Better Testing and Refactoring](https://srg.doc.ic.ac.uk/files/papers/loops-pldi-19.pdf) 提到開發者會希望對 loop summarisation 進行替換,例如 [GNU patch](http://www.gnu.org/software/patch/) 這支程式原本程式碼是:
```cpp
while (*s != '\n')
s++;
```
替換為 GNU extension:
```cpp
s = rawmemchr(s, '\n');
```
其中 [rawmemchr](https://linux.die.net/man/3/rawmemchr) 是自從 glibc-2.1 即提供的擴充函式:
> The rawmemchr() function is similar to memchr(): it assumes (i.e., the programmer knows for certain) that an instance of c lies somewhere in the memory area starting at the location pointed to by s, and so performs an optimized search for c (i.e., no use of a count argument to limit the range of the search). If an instance of c is not found, the results are unpredictable. The following call is a fast means of locating a string's terminating null byte:
> ```cpp
> char *p = rawmemchr(s, '\0');
> ```
以下程式碼嘗試運用 [你所不知道的C語言:數值系統篇](https://hackmd.io/@sysprog/c-numerics) 提及的 Detect NULL 技巧,來加速 `rawmemchr` 的實作,適用於 32 位元和 64 位元的 little-endian 架構:
```cpp
#include <limits.h>
#define UNALIGNED(X) ((long) X & (sizeof(long) - 1))
/* How many bytes are loaded each iteration of the word copy loop. */
#define LBLOCKSIZE (sizeof(long))
#if LONG_MAX == 2147483647L
#define DETECTNULL(X) (((X) -0x01010101) & ~(X) &0x80808080)
#else
#if LONG_MAX == 9223372036854775807L
#define DETECTNULL(X) (((X) -0x0101010101010101) & ~(X) &0x8080808080808080)
#else
#error long int is not a 32bit or 64bit type.
#endif
#endif
#ifndef DETECTNULL
#error long int is not a 32bit or 64bit byte
#endif
/* DETECTCHAR returns nonzero if (long)X contains the byte used
to fill (long)MASK. */
#define DETECTCHAR(X, MASK) (DETECTNULL(X ^ MASK))
void *rawmemchr(const void *src_void, int c) {
const unsigned char *src = (const unsigned char *) src_void;
unsigned char d = c;
while (UNALIGNED(src)) {
if (*src == d)
return (void *) src;
src++;
}
/* If we get this far, src is word-aligned. */
unsigned long *asrc = (unsigned long *) src;
unsigned long mask = d << 8 | d;
mask = mask << AAA | mask;
for (unsigned long i = BBB; i < LBLOCKSIZE * 8; i <<= 1)
mask = (mask << i) | mask;
while (1) {
if (DETECTCHAR(*asrc, mask))
break;
asrc++;
}
/* resort to a bytewise loop. */
for (src = (unsigned char *) asrc;; src++) {
if (*src == d)
return (void *) src;
}
}
```
請補完程式碼,應要能在 x86_64 架構獲得效能提升。
==作答區==
AAA = ?
* `(a)` 4
* `(b)` 8
* `(c)` 16
* `(d)` 32
* `(e)` 64
BBB = ?
* `(a)` 4
* `(b)` 8
* `(c)` 16
* `(d)` 32
* `(e)` 64
:::success
延伸問題:
1. 解釋上述程式碼運作原理,並探討是否適用於 Big-endian 硬體;
2. 比照 [CS:APP Assign5.18](https://hackmd.io/@aben20807/rkdzvWJTX) 在 GNU/Linux 上透過 perf 一類的效能工具,分析上述兩種 loop summarisation 實作的效能落差,並運用 [CS:APP](https://hackmd.io/@sysprog/CSAPP) 第 5 章的 CPE 分析手法
3. 研讀論文 [Computing Summaries of String Loops in C for Better Testing and Refactoring](https://srg.doc.ic.ac.uk/files/papers/loops-pldi-19.pdf),嘗試探討裡頭 [Symbolic Execution](https://en.wikipedia.org/wiki/Symbolic_execution) 的手法,還有為何能做到自動分析程式碼並給予 refactor 建議
* 延伸閱讀: [Binary 自動分析的那些事](https://hitcon.org/2016/CMT/slide/day1-r1-a-1.pdf), [MIT 6.858 Computer Systems Security: Symbolic Execution](https://youtu.be/yRVZPvHYHzw)
:::
---
### 測驗 `2`
考慮到 [cirbuf](https://github.com/sysprog21/cirbuf) 這個 [circular buffer](https://en.wikipedia.org/wiki/Circular_buffer) 實作,嘗試透過 [mmap 系統呼叫](http://man7.org/linux/man-pages/man2/mmap.2.html)以化簡緩衝區邊界處理的議題。
為何 circular buffer 初始化時要呼叫三次 mmap 呢?
* 第一次呼叫
* 向作業系統請求配置一塊足以容納兩倍 circular buffer 空間的記憶體
* 第二次呼叫:
* 將稍早用 mkstemp 建立的暫存檔映射到第一次呼叫 mmap 所取得的記憶體中,映射大小為 buffer 的大小
* 第三次呼叫:
* 以第二次要求映射的記憶體位置 (即第一次得到的位置) 加上 buffer 大小的記憶體位置作為要求配置的位置,映射大小同樣為 buffer 的大小,並也是映射到同樣的 fd 上(注意,兩次呼叫傳入的 offset 均為0)
* 如此一來,第二次與第三次映射的記憶體位置即指向同一塊記憶體
有了這個特性後,當我們使用 memcpy 在寫入/讀取 buffer 時即可不用考慮到邊界問題,進而改善存取效率,否則,我們必須考慮到目前 index 是否會超出邊界等等,這將對效能造成衝擊,在 [Using black magic to make a fast circular buffer](https://lo.calho.st/posts/black-magic-buffer/) 一文指出,這兩種方法的效率差距可達三倍之譜。
對照 [cirbuf.h](https://github.com/sysprog21/cirbuf/blob/master/cirbuf.h) 和 [test-cirbuf.c](https://github.com/sysprog21/cirbuf/blob/master/tests/test-cirbuf.c) 以得知具體用法,請補完以下程式碼:
```cpp
static inline int cirbuf_offer(cirbuf_t *cb,
const unsigned char *data,
const int size)
{
/* prevent buffer from getting completely full or over commited */
if (cirbuf_unusedspace(cb) <= size)
return 0;
int written = cirbuf_unusedspace(cb);
written = size < written ? size : written;
memcpy(cb->data + cb->tail, data, written);
cb->tail += written;
MM1
return written;
}
static inline unsigned char *cirbuf_peek(const cirbuf_t *cb)
{
if (cirbuf_is_empty(cb))
return NULL;
MM2
}
```
==作答區==
MM1 = ?
* `(a)` if (cb->size > cb->tail) cb->tail = 0;
* `(b)` if (cb->size < cb->tail) cb->tail = 0;
* `(c)` if (cb->size < cb->tail) cb->tail -= cb->size;
* `(d)` if (cb->size < cb->tail) cb->tail %= cb->size;
* `(e)` if (cb->size < cb->tail) cb->tail++;
* `(f)` if (cb->size < cb->tail) cb->size = 0;
MM2 = ?
* `(a)` return cb->data;
* `(b)` returb cb->head;
* `(c)` returb cb->data + cb->head;
* `(d)` return NULL;
:::success
延伸問題:
1. 解析 test suite 運作原理,嘗試強化現有 cirbuf 的例外處理機制,並且實作於 unit test 內部;
2. 儘管已有 12 個 test case,但涵蓋層面仍不夠廣泛,請指出現有實作的缺陷並著手改善; (提示: 數值範圍及多個 PAGE_SIZE 的空間)
3. 學習 [Using black magic to make a fast circular buffer](https://lo.calho.st/posts/black-magic-buffer/),指出 fast circular buffer 實作的技巧,並分析 cirbuf 的效能,並逐步量化及改善效率;
:::