--- tags: linux2022 --- # 2022q1 Homework4 (quiz4) contributed by < [Eddielin0926](https://github.com/Eddielin0926) > ## 測驗 1 已知輸入必為大於 0 的數值 (即 x > 0),以下程式碼可計算 $\lceil log_2(x) \rceil$,也就是 ceil 和 log2 的組合並轉型為整數: ```c int ceil_log2(uint32_t x) { uint32_t r, shift; x--; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; return (r | shift | x >> 1) + 1; } ``` ### 解釋上述程式碼運作原理 這個程式的概念是將輸入的 `x` 拆解成 $$ (a \times 2^{16}) \times (b \times 2^{8}) \times (c \times 2^{4}) \times (d \times 2^{2}) \times (e \times 2^{1}) \\ ans: abcde_2 + 1 \\ (a,\ b,\ c,\ d\ and\ e\ are\ all\ 0\ or\ 1) $$ 因為要取的是 $log2(x)$ 的上界,所以需要在最後的的結果加一, 而 `x--` 是用來避免掉 x 剛好是 2 的指數倍,導致的結果多一。 :::info 我認為這個程式碼有一個 bug,在 `x = 1` 時結果會是 1。但這並不符合 $\lceil log_2(x) \rceil$,正確的結果應為 0。 ::: ### x = 0 的狀況 雖然題目要求 `x > 0`,但如果 `x = 0` 時會發生甚麼事情? 結果會回傳 32,這是因為 `x--` 導致溢位讓 `x = 0xFFFFFFFF`。 我修改了原本的程式碼,移除掉 `x--`,利用 `__builtin_popcount` 來判斷 `x` 是否為 2 的指數倍,如果不是的話最後就會加 1。 ```c int ceil_log2(uint32_t x) { int cps = __builtin_popcount(x) > 1; uint32_t r, shift; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; return (r | shift | x >> 1) + cps; } ``` :::warning 因為延伸說明並沒有限制當 `x = 0` 時要回傳多少,我就先當作 0,但其實回傳 -1 是比較佳的選擇,這裡也修正了 `ceil_log2(1)` 使其等於 0。 ::: ### 在 Linux 核心原始程式碼找出 ceil 和 log2 的組合 (類似的形式) 在 [fair.c/get_update_sysctl_factor](https://github.com/torvalds/linux/blob/master/kernel/sched/fair.c#L219) 中有使用到 `1 + ilog2(cpus)`,這邊用來猜測 CPU 的數量。 ```c /* * Increase the granularity value when there are more CPUs, * because with more CPUs the 'effective latency' as visible * to users decreases. But the relationship is not linear, * so pick a second-best guess by going with the log2 of the * number of CPUs. * * This idea comes from the SD scheduler of Con Kolivas: */ static unsigned int get_update_sysctl_factor(void) { unsigned int cpus = min_t(unsigned int, num_online_cpus(), 8); unsigned int factor; switch (sysctl_sched_tunable_scaling) { case SCHED_TUNABLESCALING_NONE: factor = 1; break; case SCHED_TUNABLESCALING_LINEAR: factor = cpus; break; case SCHED_TUNABLESCALING_LOG: default: factor = 1 + ilog2(cpus); break; } return factor; } ``` :::info TODO: 這份程式碼是 [Completely Fair Scheduler](https://en.wikipedia.org/wiki/Completely_Fair_Scheduler) 的實作。 ::: --- ## 測驗 2 改寫[第 3 週測驗題](https://hackmd.io/@sysprog/linux2022-quiz3#%E6%B8%AC%E9%A9%97-11)的測驗 `11` 裡頭的 fls 函式 (fls 意謂 “find last set”),使其得以計算 [Find first set](https://en.wikipedia.org/wiki/Find_first_set) (與 `1 + __builtin_ctzl` 類似)。 ```c #define BITS_PER_BYTE 8 #define BITS_PER_LONG (sizeof(unsigned long) * BITS_PER_BYTE) #include <stddef.h> static inline size_t ffs(unsigned long x) { if (x == 0) return 0; size_t o = 1; unsigned long t = ~0UL; size_t shift = BITS_PER_LONG; shift >>= 1; t >>= shift; while (shift) { if ((x & t) == 0) { x >>= shift; o += shift; } shift >>= 1; t >>= shift; } return o; } ``` ### 解釋上述程式碼運作原理 這個程式是要計算最低位的 1 從低位數過來的位置,這邊利用了類似二分搜尋法的方式來查找 1,`unsigned long` 是 64 位元,因此從 32 位元開始切,利用 x 與 t 做 AND 來判斷 x 的右邊是否都是 0,是的話就記錄到 `o` 並且將找到的 0 都右移掉,最後再將 1 的數量減半繼續檢查。 ### 迴避 while, for, goto 等關鍵字以改寫出功能等價的實作 最簡單的方式就完全展開,並且使用常數來做 bit mask (與測驗 1 的概念類似)。 ```c #define BITS_PER_BYTE 8 #define BITS_PER_LONG (sizeof(unsigned long) * BITS_PER_BYTE) #include <stddef.h> static inline size_t ffs(unsigned long x) { if (x == 0) return 0; size_t o = 1; if ((x & 0xFFFFFFFF) == 0) { x >>= 32; o += 32; } if ((x & 0xFFFF) == 0) { x >>= 16; o += 16; } if ((x & 0xFF) == 0) { x >>= 8; o += 8; } if ((x & 0xF) == 0) { x >>= 4; o += 4; } if ((x & 0x3) == 0) { x >>= 2; o += 2; } return o + ((x & 0x1) == 0); } ``` ### include/asm-generic/bitops 探討應用案例 [ffs.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/bitops/ffs.h) 有類似的 `ffs`,差別只在於它是 `int`。 另外我們能在 [sched.h](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/include/asm-generic/bitops/sched.h) 中看到它的應用,用來查找 100-bit bitmap 中最低位 1 的位置。 ```c /* * Every architecture must define this function. It's the fastest * way of searching a 100-bit bitmap. It's guaranteed that at least * one of the 100 bits is cleared. */ static inline int sched_find_first_bit(const unsigned long *b) { #if BITS_PER_LONG == 64 if (b[0]) return __ffs(b[0]); return __ffs(b[1]) + 64; #elif BITS_PER_LONG == 32 if (b[0]) return __ffs(b[0]); if (b[1]) return __ffs(b[1]) + 32; if (b[2]) return __ffs(b[2]) + 64; return __ffs(b[3]) + 96; #else #error BITS_PER_LONG not defined #endif } ``` :::info 這邊的註解有些奇怪 > It's guaranteed that at least one of the 100 bits is cleared. 反向思考的話,就是不會出現 100 個 1,但這個情況與任何一個最低位元是 1 的資料沒有差別,似乎沒有必要寫在這邊,但這應該要繼續追蹤程式碼才能確定原因。 > 準備提交 patch 到 LKML 嗎? :notes: jserv ::: --- ## 測驗 3 ```c struct foo_consumer { int (*handler)(struct foo_consumer *self, void *); struct foo_consumer *next; }; struct foo { struct foo_consumer *consumers; unsigned long flags; }; #include <stdbool.h> /* * For foo @foo, delete the consumer @fc. * Return true if the @fc is deleted successfully * or return false. */ static bool consumer_del(struct foo *foo, struct foo_consumer *fc) { struct foo_consumer **con; bool ret = false; for (con = &foo->consumers; *con; con = &(*con)->next) { if (*con == fc) { *con = fc->next; ret = true; break; } } return ret; } ``` ### 解釋上述程式碼運作原理 ### 區分 `foo` 和 `foo_consumer` 的好處 ### 在 Linux 核心原始程式碼相似技巧的程式碼 (例如 [uprobe](https://docs.kernel.org/trace/uprobetracer.html)),並探討應用案例