# [2020q1](http://wiki.csie.ncku.edu.tw/linux/schedule) 第 8 週測驗題 ###### tags: `linux2020` :::info 目的: 檢驗學員對 C 語言[流程控制](https://hackmd.io/s/B1e2AUZeM)和[前置處理器](https://hackmd.io/@sysprog/c-preprocessor)的認知 ::: ==[作答表單](https://docs.google.com/forms/d/e/1FAIpQLSdBxvLlO7Pw5XZeT6MmY7i87gIMOHndru74aVix-mM9Jss5hw/viewform)== --- ### 測驗 `1` Go 語言沒有內建例外處理機制,而是運用 defer 關鍵字,後者可指定某個函式延遲執行,直到函式即將返回之際才會執行之前 defer 指定的敘述。 :::info 依據 [Cambridge Dictionary](https://dictionary.cambridge.org/),==[defer](https://dictionary.cambridge.org/dictionary/english/defer)== 的解釋為: > to delay something until a later time; to postpone 例句: > You can order the furniture now and defer payment until Sep. ::: 例如: ```go= package main import "fmt" func deferredFunc() { fmt.Println("deferredFunc") } func main() { defer deferredFunc() fmt.Println("Hello World") } ``` 上方的程式碼中,`deferredFunc()` 之前加上 `defer` (第 7 行),因此會在 main() 函式回傳前執行,結果就是先顯示 "Hello World",再顯示 "deferredFunc"。 若有多個函式被 defer,那麼在函式回傳前,會依 `defer` 的相反順序執行,也就是 LIFO。例如: ```go= package main import "fmt" func deferredFunc1() { fmt.Println("deferredFunc1") } func deferredFunc2() { fmt.Println("deferredFunc2") } func main() { defer deferredFunc1() defer deferredFunc2() fmt.Println("Hello World") } ``` 由於先對 `deferredFunc1` 做 `defer` (第 11 行),才對 `deferredFunc2` 做 `defer` (第 12 行),因此執行結果會是 "Hello World", "deferredFunc2", "deferredFunc1" 這樣的順序。 > [A Tour of Go](https://tour.golang.org/list) 提供線上執行環境,可見 [Defer](https://tour.golang.org/flowcontrol/12) 頁面。 接著我們嘗試用 C 語言搭配 GNU extension (所以程式碼只能透過 gcc 或 clang 來編譯) 來實作 Go 語言的 `defer` 特徵。測試程式碼: (`demo.c`): ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include "defer.h" void test1(void) { DEFER_INIT; char *x = malloc(32); Defer(free(x)); Defer({ printf("x : %s\n", x); x[5] = '\0'; ; printf("now : %s\n", x); }); strcpy(x, "Hello world"); Return; } int test2(void) { DEFER_INIT; puts("0"); Defer(puts("3")); Defer(puts("2")); puts("1"); Return puts("4"); } int main(void) { test1(); test2(); return 0; } ``` 預期執行輸出: ``` x : Hello world now : Hello 0 1 2 3 4 ``` 對應的 `defer.h` 實作如下: ```cpp= #ifndef DEFER_H #define DEFER_H #ifndef __MAX_DEFERRED_STATEMENTS #define __MAX_DEFERRED_STATEMENTS 32 #endif #define DEFER_INIT \ unsigned char __deferral_num = 0; \ void *__defer_return_label = 0, \ *__deferrals[__MAX_DEFERRED_STATEMENTS] = {0} #define Defer(block) __Defer(block, __COUNTER__) #define Return __Return(__COUNTER__) #define __defer_concat(a, b) a##b #define __Defer(block, n) \ do { \ __deferrals[__deferral_num++] = &&__defer_concat(__defer_init, n); \ if (AAA) { \ __defer_concat(__defer_init, n) : block; \ if (__deferral_num) \ goto *__deferrals[BBB]; \ goto *__defer_return_label; \ } \ } while (0) #define __Return(n) \ if (__deferral_num) { \ __defer_return_label = &&__defer_fini_##n; \ goto *__deferrals[CCC]; \ } \ __defer_fini_##n : return #endif ``` 針對 GNU extensions 說明: 1. 第 20 行的 `&&` 是 [Labels as Values](https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html),搭配第 25 行 `goto *__defer_return_label` 一類的敘述使用,第 32 行同理 2. 第 13 行和第 14 行的 `__COUNTER__` 是 gcc/clang 在編譯過程中給予的遞增流水號,可搭配 label 名稱運用,詳見 [Common Predefined Macros](https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html) ==作答區== AAA = ? * `(a)` 0 * `(b)` 1 * `(c)` `deferral_num` * `(d)` `deferral_num++` * `(e)` `++deferral_num` * `(f)` `deferral_num--` * `(g)` `--deferral_num` BBB = ? * `(a)` 0 * `(b)` 1 * `(c)` `deferral_num` * `(d)` `deferral_num++` * `(e)` `++deferral_num` * `(f)` `deferral_num--` * `(g)` `--deferral_num` CCC = ? * `(a)` 0 * `(b)` 1 * `(c)` `deferral_num` * `(d)` `deferral_num++` * `(e)` `++deferral_num` * `(f)` `deferral_num--` * `(g)` `--deferral_num` :::success 延伸問題: 1. 透過 `gcc -E` 來解析前置處理器產生的程式碼,從而解釋上述 `defer.h` 運作機制,若發現程式碼的缺失,也一併修正; 2. 在 Linux 核心原始程式碼 (版本號碼 >= `5.5`) 找出上述 GNU extension 的應用案例並說明。提示: 在 `kernel/bpf/core.c` 可見 [computed goto](https://eli.thegreenplace.net/2012/07/12/computed-goto-for-efficient-dispatch-tables) 的精美應用 ::: --- ### 測驗 `2` 研讀 [A (Bare-Bones) User-Level Thread Library](https://www.hacks.moe/posts/1/A_BareBones_UserLevel_Thread_Library) 一文後,下方程式碼嘗試給予更進階的 user-level thread (ULT) 實作: (檔名: `task_sched.c`) ```cpp #define _GNU_SOURCE #include <signal.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <ucontext.h> #include <unistd.h> #include "list.h" static int preempt_count = 0; static void preempt_disable(void) { DDD; } static void preempt_enable(void) { EEE; } static void local_irq_save(sigset_t *sig_set) { sigset_t block_set; sigfillset(&block_set); HHH(&block_set, SIGINT); sigprocmask(SIG_BLOCK, &block_set, sig_set); } static void local_irq_restore(sigset_t *sig_set) { sigprocmask(SIG_SETMASK, sig_set, NULL); } #define task_printf(...) \ ({ \ preempt_disable(); \ printf(__VA_ARGS__); \ preempt_enable(); \ }) typedef void(task_callback_t)(void *arg); struct task_struct { struct list_head list; ucontext_t context; void *stack; task_callback_t *callback; void *arg; bool reap_self; }; static struct task_struct *task_current, task_main; static LIST_HEAD(task_reap); static void task_init(void) { INIT_LIST_HEAD(&task_main.list); task_current = &task_main; } static struct task_struct *task_alloc(task_callback_t *func, void *arg) { struct task_struct *task = calloc(1, sizeof(*task)); task->stack = calloc(1, 1 << 20); task->callback = func; task->arg = arg; return task; } static void task_destroy(struct task_struct *task) { list_del(&task->list); free(task->stack); free(task); } static void task_switch_to(struct task_struct *from, struct task_struct *to) { task_current = to; swapcontext(&from->context, &to->context); } static void schedule(void) { sigset_t set; local_irq_save(&set); struct task_struct *next_task = list_first_entry(&task_current->list, struct task_struct, list); if (next_task) { if (task_current->reap_self) list_move(&task_current->list, &task_reap); task_switch_to(task_current, next_task); } struct task_struct *task, *tmp; list_for_each_entry_safe (task, tmp, &task_reap, list) /* clean reaps */ task_destroy(task); local_irq_restore(&set); } union task_ptr { void *p; int i[2]; }; static void local_irq_restore_trampoline(struct task_struct *task) { JJJ(&task->context.uc_sigmask, SIGALRM); local_irq_restore(&task->context.uc_sigmask); } __attribute__((noreturn)) static void task_trampoline(int i0, int i1) { union task_ptr ptr = {.i = {i0, i1}}; struct task_struct *task = ptr.p; /* We switch to trampoline with blocked timer. That is safe. * So the first thing that we have to do is to unblock timer signal. * Paired with task_add(). */ local_irq_restore_trampoline(task); task->callback(task->arg); task->reap_self = true; schedule(); __builtin_unreachable(); /* shall not reach here */ } static void task_add(task_callback_t *func, void *param) { struct task_struct *task = task_alloc(func, param); if (getcontext(&task->context) == -1) abort(); task->context.uc_stack.ss_sp = task->stack; task->context.uc_stack.ss_size = 1 << 20; task->context.uc_stack.ss_flags = 0; task->context.uc_link = NULL; union task_ptr ptr = {.p = task}; makecontext(&task->context, (void (*)(void)) task_trampoline, 2, ptr.i[0], ptr.i[1]); /* When we switch to it for the first time, timer signal must be blocked. * Paired with task_trampoline(). */ sigaddset(&task->context.uc_sigmask, SIGALRM); preempt_disable(); FFF(&task->list, &task_main.list); preempt_enable(); } static void timer_handler(int signo, siginfo_t *info, ucontext_t *ctx) { if (preempt_count) /* once preemption is disabled */ return; /* We can schedule directly from sighandler because Linux kernel cares only * about proper sigreturn frame in the stack. */ schedule(); } static void timer_init(void) { struct sigaction sa = {.sa_handler = (void (*)(int)) timer_handler, .sa_flags = SA_SIGINFO}; sigfillset(&sa.sa_mask); sigaction(SIGALRM, &sa, NULL); } static void timer_create(unsigned int usecs) { ualarm(usecs, usecs); } static void timer_cancel(void) { ualarm(0, 0); } static void timer_wait(void) { sigset_t mask; sigprocmask(0, NULL, &mask); GGG(&mask, SIGALRM); sigsuspend(&mask); } static int cmp_u32(const void *a, const void *b, void *arg) { uint32_t x = *(uint32_t *) a, y = *(uint32_t *) b; uint32_t diff = x ^ y; if (!diff) return 0; /* *a == *b */ diff = diff | (diff >> 1); diff |= diff >> 2; diff |= diff >> 4; diff |= diff >> 8; diff |= diff >> 16; diff KKK diff >> 1; return x & diff ? 1 : -1; /* if *a > *b, then 1; if *a < *b, then -1; */ } static inline uint32_t random_shuffle(uint32_t x) { /* by Chris Wellons, see: <https://nullprogram.com/blog/2018/07/31/> */ x ^= x >> 16; x *= 0x7feb352dUL; x ^= x >> 15; x *= 0x846ca68bUL; x ^= x >> 16; return x; } #define ARR_SIZE 1000000 static void sort(void *arg) { char *name = arg; preempt_disable(); uint32_t *arr = malloc(ARR_SIZE * sizeof(uint32_t)); preempt_enable(); task_printf("[%s] %s: begin\n", name, __func__); uint32_t r = getpid(); for (int i = 0; i < ARR_SIZE; i++) arr[i] = (r = random_shuffle(r)); task_printf("[%s] %s: start sorting\n", name, __func__); qsort_r(arr, ARR_SIZE, sizeof(uint32_t), cmp_u32, name); for (int i = 0; i < ARR_SIZE - 1; i++) if (arr[i] > arr[i + 1]) { task_printf("[%s] %s: failed: a[%d]=%u, a[%d]=%u\n", name, __func__, i, arr[i], i + 1, arr[i + 1]); abort(); } task_printf("[%s] %s: end\n", name, __func__); preempt_disable(); free(arr); preempt_enable(); } int main() { timer_init(); task_init(); task_add(sort, "1"), task_add(sort, "2"), task_add(sort, "3"); preempt_disable(); timer_create(10000); /* 10 ms */ while (!list_empty(&task_main.list) || !list_empty(&task_reap)) { preempt_enable(); timer_wait(); preempt_disable(); } preempt_enable(); timer_cancel(); return 0; } ``` 其中 `list.h` 取自 [linux-list/include/list.h](https://github.com/sysprog21/linux-list/blob/master/include/list.h)。該 ULT 原理是透過 [SIGALRM signal](https://www.gnu.org/software/libc/manual/html_node/Alarm-Signals.html) 來模擬作業系統的 timer 中斷,設定的時間間隔為 10 ms,於是每 10 ms 觸發模擬的 timer 中斷,帶動 ULT 排程器,實作 round-robin 排程策略。留意到 [trampoline](https://en.wikipedia.org/wiki/Trampoline_(computing)) 的使用: > Trampolines (sometimes referred to as indirect jump vectors) are memory locations holding addresses pointing to interrupt service routines, I/O routines, etc. Execution jumps into the trampoline and then immediately jumps out, or bounces, hence the term trampoline. ![](https://i.imgur.com/EbCwNDT.png) > 出處: [Trampoline mirror may push laser pulse through fabric of the Universe](https://arstechnica.com/science/2019/09/trampoline-mirror-may-push-laser-pulse-through-fabric-of-the-universe/) 預期程式執行輸出: ``` [1] sort: begin [1] sort: start sorting [2] sort: begin [2] sort: start sorting [3] sort: begin [3] sort: start sorting [2] sort: end [3] sort: end [1] sort: end ``` 注意後 3 行的順序可能是 $3 \to 2 \to 1$ 或 $2 \to 3 \to 1$。 請補完程式碼。 參考資訊: * [Proper Locking Under a Preemptible Kernel: Keeping Kernel Code Preempt-Safe](https://www.kernel.org/doc/Documentation/preempt-locking.txt) * [What is a trampoline function?](https://stackoverflow.com/questions/189725/what-is-a-trampoline-function) * [Functional Programming 風格的 C 語言實作](https://hackmd.io/@sysprog/c-functional-programming) > trampoline function 的展現 ==作答區== DDD = ? * `(a)` `preempt_count = 0` * `(b)` `preempt_count = 1` * `(c)` `preempt_count--` * `(d)` `preempt_count++` EEE = ? * `(a)` `preempt_count = 0` * `(b)` `preempt_count = 1` * `(c)` `preempt_count--` * `(d)` `preempt_count++` FFF = ? * `(a)` `list_add` * `(b)` `list_add_tail` GGG = ? * `(a)` `sigaddset` * `(b)` `sigdelset` * `(c)` `sigfillset` HHH = ? * `(a)` `sigaddset` * `(b)` `sigdelset` * `(c)` `sigfillset` JJJ = ? * `(a)` `sigaddset` * `(b)` `sigdelset` * `(c)` `sigfillset` KKK = ? * `(a)` `=` * `(b)` `&=` * `(c)` `|=` * `(d)` `^=` :::success 延伸問題: 1. 解釋上述程式碼運作原理,以及對應到 Linux 核心原始碼的若干機制,如 `preempt_disable`, `preempt_enable`, `local_irq_save`, `local_irq_restore` 等等 2. 探討上述程式碼的 signal 使用機制,指出其正確性或效率疑慮,並著手改進 :::