# [2022q1](http://wiki.csie.ncku.edu.tw/sysprog/schedule) 第 15 週測驗題 ###### tags: `linux2022` :::info 目的: 檢驗學員對 **[針對事件驅動的 I/O 模型演化](https://hackmd.io/@sysprog/linux-io-model)** 和 **[bitwise 操作](https://hackmd.io/@sysprog/c-bitwise)** 的認知 ::: 作答表單: * ==[測驗 `1`](https://docs.google.com/forms/d/e/1FAIpQLSfhuSGXXfC4TcupHhYA6KBv1eNdIMapBkwq4qOk7HyMNG2lxQ/viewform)== (Linux 核心設計) ### 測驗 `1` 在作業 [seHTTPd](https://hackmd.io/@sysprog/linux2022-sehttpd) 和 [高效 Web 伺服器開發](https://hackmd.io/@sysprog/fast-web-server) 論及 HTTP 連線管理時,Web 伺服器和客戶端網頁瀏覽器保持著一個長期連線的狀況下,遇到客戶端網路離線,通訊協定無法立刻知悉,所以僅能透過伺服器引入 timer 逾時事件來克服。timer 可透過 priority queue 實作,後者又常用 binary heap 實作。 > [Priority Queue:Binary Heap](https://alrightchiu.github.io/SecondRound/priority-queuebinary-heap.html) 以下是 [min heap](https://en.wikipedia.org/wiki/Binary_heap) 的一個實作: ```c #include <assert.h> #include <inttypes.h> #include <stdint.h> struct heap_item { uint64_t key; }; #if __WORDSIZE == 64 #define clz64(x) __builtin_clzl(x) #define clz32(x) __builtin_clz(x) #define log2_floor(x) log2_floor64(x) #elif __WORDSIZE == 32 #define clz64(x) __builtin_clzll(x) #define clz32(x) __builtin_clzl(x) #define log2_floor(x) log2_floor32(x) #else #error "Bad machine word size" #endif static inline unsigned int log2_floor64(uint64_t val) { return 64 - (clz64(val) + 1); } static inline unsigned int log2_floor32(uint32_t val) { return 32 - (clz32(val) + 1); } static inline unsigned long parent(unsigned long idx) { return idx >> 1; } static inline unsigned long left(unsigned long idx) { return idx << 1; } static inline unsigned long right(unsigned long idx) { return (idx << 1) + 1; } static void do_sift_up(unsigned long idx, unsigned long nr_items, struct heap_item *h) { assert(idx > 0); if (idx == 1) return; uint64_t v = h[idx].key; unsigned long pidx = parent(idx); uint64_t pv = h[pidx].key; if (UUUU) return; struct heap_item tmp = h[pidx]; h[pidx] = h[idx]; h[idx] = tmp; do_sift_up(pidx, nr_items, h); } static void do_sift_down(unsigned long idx, unsigned long nr_items, struct heap_item *h) { unsigned long l = left(idx); if (l > nr_items) return; unsigned long r = right(idx); unsigned long smallest; if (r > nr_items) { smallest = l; } else { smallest = h[l].key < h[r].key ? l : r; } if (DDDD) return; struct heap_item tmp = h[smallest]; h[smallest] = h[idx]; h[idx] = tmp; do_sift_down(smallest, nr_items, h); } void minheap_init(unsigned long nr_items, struct heap_item *h) { unsigned long lf = log2_floor(nr_items); unsigned long f = (1 << lf) - 1, c = (1 << lf); for (unsigned long i = f; i < nr_items; i++) do_sift_up(i + 1, nr_items, h); c = (c - (nr_items - f)) >> 1; for (unsigned long i = parent(nr_items); i < parent(nr_items) + c; i++) do_sift_up(i + 1, nr_items, h); } void minheap_sift_down(unsigned long nr_items, struct heap_item *h) { do_sift_down(1, nr_items, h); } void minheap_sift_up(unsigned long nr_items, struct heap_item *h) { do_sift_up(nr_items, nr_items, h); } #include <stdio.h> #include <stdlib.h> #include <unistd.h> void print_heap(unsigned long nr_items, struct heap_item *h) { for (unsigned long i = 0; i < nr_items; i++) printf("%" PRId64 " ", h[i + 1].key); printf("\n"); } static unsigned int xs; /* 16-Bit Xorshift Pseudorandom Numbers. * http://www.retroprogramming.com/2017/07/xorshift-pseudorandom-numbers-in-z80.html */ static inline unsigned int xorshift() { xs ^= xs << 7; xs ^= xs >> 9; xs ^= xs << 8; return xs; } int main(int argc, char **argv) { unsigned long nr = atoi(argv[1]); struct heap_item *h = calloc(nr, sizeof(*h)); h--; xs = getpid(); for (unsigned long i = 0; i < nr; i++) h[i + 1].key = xorshift() & 127; printf("init: "); print_heap(nr, h); minheap_init(nr, h); printf("heapified: "); print_heap(nr, h); while (nr) { printf("%" PRId64 ": ", h[1].key); h[1] = h[nr--]; minheap_sift_down(nr, h); print_heap(nr, h); } printf("\n"); h[++nr].key = 100; minheap_sift_up(nr, h); h[++nr].key = 300; minheap_sift_up(nr, h); h[++nr].key = 200; minheap_sift_up(nr, h); while (nr) { printf("%" PRId64 ": ", h[1].key); h[1] = h[nr--]; minheap_sift_down(nr, h); print_heap(nr, h); } return EXIT_SUCCESS; } ``` 參考執行輸出: (前 10 個數值可能會變化) ``` $ ./minheap 10 init: 95 62 66 71 36 34 67 30 72 82 heapified: 30 95 34 62 36 66 67 71 72 82 30: 34 95 66 62 36 82 67 71 72 34: 66 95 67 62 36 82 72 71 66: 67 95 71 62 36 82 72 67: 71 95 72 62 36 82 71: 72 95 82 62 36 72: 36 95 82 62 36: 62 95 82 62: 82 95 82: 95 95: 100: 200 300 200: 300 300: ``` 請補完程式碼,使其運作符合預期。作答規範: * `UUUU` 和 `DDDD` 皆為表示式,且有 ==`<`== 運算子,不包含小括號 (即 `(` 和 `)`) * 以合法且最精簡的 C99 語法撰寫 :::success 延伸問題: 1. 解釋上述程式碼運作原理 2. 將上述實作整合進 [seHTTPd](https://github.com/sysprog21/sehttpd) ::: --- #### 修正 根據 kdnvt 同學的觀察,使用上述程式碼考慮以下情境: ```graphviz digraph BST { node [fontname="Arial" ]; l1 [ label = "100" ]; l21 [ label = "2" ]; l22 [ label = "3" ]; l31 [ label = "4" ]; l32 [label = "5" ]; l33 [color=red shape=rect label = "6" ]; l34 [color=red shape=rect label = "7" ]; l41 [color=blue shape=rect label = "8" ]; l42 [color=blue shape=rect label = "9" ]; l43 [color=blue shape=rect label = "10" ]; l1 -> { l21 l22 }; l21 -> { l31 l32 }; l22 -> { l33 l34 }; l31 -> { l41 l42 }; l32 -> { l43} } ``` 原版程式在檢查後發現 8, 9, 10 的 parent 都比較小後就不會繼續遞迴往上 heapify。同理, 6, 7 的 parent 小於兩者,所以不會繼續往更上面的 node 做 heapify。但是除了 3, 4, 5,我們不能保證其他節點的位置是否正確,因此可以透過修改 do_sift_up 來確保正確性: ```cpp static void do_sift_up(unsigned long idx, unsigned long nr_items, struct heap_item *h) { ... if (pv < v){ do_sift_up(pidx, nr_items, h); return; } ... } ``` 當 parent 小於 child 時不會中止遞迴,會繼續往 parent 上檢查。