# 2024q1 Homework2 (quiz1+2) contributed by < [Ackerman666](https://github.com/Ackerman666) > ## 第二週測驗題 ### [測驗 `3`](https://hackmd.io/@sysprog/linux2024-quiz2) ### 程式碼運作原理 #### `__ffs` 尋找 `word` 的 first set (位元從右至左,第一個為1的位置) 程式邏輯會將尚未檢查的 bits 之下半部做 mask 1. 如果結果為 0 ,代表 first set bit會出現在上半部 -> 將 `num` 增加,並對 `word` 做 right shift > 如 word = 0xf000000000000000,在做第一次檢查 `word & 0xffffffff` 結果為 0 > 因此得知 first set bit 在上半部,因此將 `num += 32` and `word >>= 32` > 之後再將位移過後的 `word` 做後續相同的檢查 2. 如果結果不為 0 ,代表 first set bit出現在下半部,則再繼續往下半部的下半部來檢查 ```c static inline unsigned long __ffs(unsigned long word) { int num = 0; #if BITS_PER_LONG == 64 if ((word & 0xffffffff) == 0) { num += 32; word >>= 32; } #endif if ((word & 0xffff) == 0) { num += 16; word >>= 16; } if ((word & 0xff) == 0) { num += 8; word >>= 8; } if ((word & 0xf) == 0) { num += 4; word >>= 4; } if ((word & 0x3) == 0) { num += 2; word >>= 2; } if ((word & 0x1) == 0) num += 1; return num; } ``` :::warning TODO: 改寫成其他形式。 > Done ::: 可將多個判別式濃縮在同個迴圈處理來簡化程式篇幅 迴圈內的處理邏輯和原版程式差不多 講解如何形成 `mask` > 假設現在迴圈中 `i` 值為 4,那麼 `(1 << i)` 會得10000$_2$ 也就是16 > `(1UL << (1 << i)) - 1` 就會得到 0xffff 的 `mask` > 以此類推當 i = 3 時就會得到 0xff 的 `mask` ```c static inline unsigned long __ffs(unsigned long word) { int num = 0; #if BITS_PER_LONG == 64 if ((word & 0xffffffff) == 0) { num += 32; word >>= 32; } #endif for (int i = 4; i >= 0; i--) { unsigned long mask = (1UL << (1 << i)) - 1; if ((word & mask) == 0) { num += (1 << i); word >>= (1 << i); } } return num; } ``` #### `__clear_bit` 用於將 `addr` 所指向數值的第 `nr` 個 bit 做 clear (即 1 -> 0) **解釋會用到的巨集** `BIT_MASK` 會對 `nr` 產生對應的 mask > BIT_MASK(8) 會產生 0x0000000000000100 的 mask ```c #define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) ``` `BIT_WORD` 會根據 `nr` 算出 offset ```c #define BIT_WORD(nr) ((nr) / BITS_PER_LONG) ``` 首先透過 `BIT_MASK` 得到對應的 mask 而 `addr` 本身可能指向到一個陣列,所以可透過加 offset 方式來指到指定位置 最後透過 `*p &= ~mask` 達到將 `addr` 所指向之數值的特定的 bit 做 clear ```c static inline void __clear_bit(unsigned long nr, volatile unsigned long *addr) { unsigned long mask = BIT_MASK(nr); unsigned long *p = ((unsigned long *) addr) + BIT_WORD(nr); *p &= ~mask; } ``` #### `fns` 用來找到第 N'th set bit 透過 `__ffs` 找到第一個 set bit 的位置,並判斷是否已找到 n 個 set bit 若還未找到,會透過 `__clear_bit` 將當前的第一個 set bit 給 clear 掉,並繼續執行迴圈 ```c static inline unsigned long fns(unsigned long word, unsigned int n) { while (word) { unsigned int bit = __ffs(word); if (n-- == 0) return bit; __clear_bit(bit, &word); } return BITS_PER_LONG; } ``` :::danger 注意用語。 $\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary) ::: 然而這個 <s>function</s> 有一點不直觀,當我執行以下程式 回傳的 `ans` 結果會是 3,但直觀上第 3 個 set bit 應該是要在第 2 個位置 ``` unsigned int word = 0xf, ans; ans = fns(word, 3); ``` :::warning TODO: 參照 Linux 核心的文件 (不要用 Google 搜尋,核心原始程式碼就有),揣摩上述用法的考量。 ::: #### `find_nth_bit` 從 `addr` 指向的地方開始,在給定的記憶體範圍內,找到第 N 個 set bit **解釋會用到的巨集** `small_const_nbits` 判斷 `nbits` 是否為 const,且 0 < `nbits` < 64,是則回傳 1,反之回傳 0 ```c #define small_const_nbits(nbits) \ (__builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG && (nbits) > 0)` ``` `GENMASK` 給定連續的範圍,得到特定的 bitmask > 以GENMASK(3, 0)舉例 會得 00001111$_2$ > 左式 `((~0UL) - (1UL << (l)) + 1)` 會得到 0xffff 0xffff 0xffff 0xffff > 右式 `(~0UL >> (BITS_PER_LONG - 1 - (h)))` 會得到 0x0000 0x0000 0x0000 0x000f > 兩者做 and 運算後會得到 0x0000 0x0000 0x0000 0x000f > 即得到 0x000000000000000f 的 mask ```c #define GENMASK(h, l) \ (((~0UL) - (1UL << (l)) + 1) & (~0UL >> (BITS_PER_LONG - 1 - (h)))) ``` 當 `if (small_const_nbits(size))` 為真時 會透過 `GENMASK` 產生對應 mask,並對 `addr` 做 and 運算 得到的值 `val` 就能確保只會有 `size` 數量的 bits 被 set 最後 `fns` 就會從 `size` 數量的 bits 中找到第 N 個 set bit ```c static inline unsigned long find_nth_bit(const unsigned long *addr, unsigned long size, unsigned long n) { if (n >= size) return size; if (small_const_nbits(size)) { unsigned long val = *addr & GENMASK(size - 1, 0); return val ? fns(val, n) : size; } return FIND_NTH_BIT(addr[idx], size, n); } ``` ### Linux 核心 `find_nth_bit` 應用範例 [cpumask.h](https://github.com/torvalds/linux/blob/master/include/linux/cpumask.h) 中有相關應用存在 在 Linux 核心中,使用 struct cpumask 來表示 CPU 的狀態 狀態是以 bitmap 形式來呈現,每一個位元即代表一個 CPU #### `cpumask_nth` 該函式用來找到在 cpumask 中的第 N 個CPU 因為狀態是用 bitmap 表示,因此可以利用 `find_nth_bit` 直接尋找 ```c /** * cpumask_nth - get the Nth cpu in a cpumask * @srcp: the cpumask pointer * @cpu: the Nth cpu to find, starting from 0 * * Return: >= nr_cpu_ids if such cpu doesn't exist. */ static inline unsigned int cpumask_nth(unsigned int cpu, const struct cpumask *srcp) { return find_nth_bit(cpumask_bits(srcp), small_cpumask_bits, cpumask_check(cpu)); } ```