# 2024q1 Homework2 (quiz1+2)
contributed by < `404allen404` >
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 12
On-line CPU(s) list: 0-11
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
CPU family: 6
Model: 165
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
Stepping: 2
CPU max MHz: 5000.0000
CPU min MHz: 800.0000
BogoMIPS: 5199.98
```
# [quiz1](https://hackmd.io/@sysprog/linux2024-quiz1)
# [quiz2](https://hackmd.io/@sysprog/linux2024-quiz2)
### 測驗 3
測驗 3 的程式碼先定義了許多巨集和函數,在此先說明它們的功能。
**BIT_MASK(nr)** 這個巨集用來產生第 nr 個位元為 1 的 mask , mask 的長度為 BITS_PER_LONG 個位元。要加上 % 的原因是因為要避免產生的 mask 變為 0 。
```c
#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
```
**BIT_WORD(nr)** 這個巨集用來換算 nr 是幾個 word ,所以直接把 nr 除以 BITS_PER_LONG 就好。
```c
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
```
**small_const_nbits(nbits)** 這個巨集透過 __buildin_constant_p 確認 nbits 是一個常數而不是變數,並且確認 nbits 是一個大於零的數字且小於等於 BITS_PER_LONG 。
:::info
想問一個問題,為什麼需要使用 __buildin_constant_p 去確認 nbits 是否為常數呢?這樣會幫助編譯器去最佳化我們的程式嗎?
:::
```c
#define small_const_nbits(nbits) \
(__builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG && (nbits) > 0)
```
**GENMASK(h, l)** 這個巨集可以分成兩部份來解釋,第一個部份是 ((~0UL) - (1UL << (l)) + 1) ,這一個部份是用來確保第 0 個位元到第 l - 1 個位元是 0 ,而另外一個部份是 (~0UL >> (BITS_PER_LONG - 1 - (h))) ,這個部份用來確保第 l + 1 個位元到最後一個位元的值都是 0 ,最後把這兩個部份做 & 運算,就可以得到從第 l 個位元到第 h 個位元的值都是 1 ,且剩餘的位元都是 0 的 mask 。
```c
#define GENMASK(h, l) \
(((~0UL) - (1UL << (l)) + 1) & (~0UL >> (BITS_PER_LONG - 1 - (h))))
```
**__const_hweight8(w)** 這個巨集是用來計算 w 的低 8 個位元有幾個 1 。
```c
#define __const_hweight8(w) \
((unsigned int) ((!!((w) & (1ULL << 0))) + (!!((w) & (1ULL << 1))) + \
(!!((w) & (1ULL << 2))) + (!!((w) & (1ULL << 3))) + \
(!!((w) & (1ULL << 4))) + (!!((w) & (1ULL << 5))) + \
(!!((w) & (1ULL << 6))) + (!!((w) & (1ULL << 7)))))
```
**__const_hweight16(w)** 這個巨集使用了 __const_hweight8(w) 這個巨集去算出 w 的低 16 個位元有幾個 1 。
```c
#define __const_hweight16(w) (__const_hweight8(w) + __const_hweight8((w) >> 8))
```
**__const_hweight32(w)** 這個巨集使用了 __const_hweight16(w) 這個巨集去算出 w 的低 32 個位元有幾個 1 。
```c
#define __const_hweight32(w) \
(__const_hweight16(w) + __const_hweight16((w) >> 16))
```
**__const_hweight64(w)** 這個巨集使用了 __const_hweight32(w) 這個巨集用來計算 w 總共有幾個位元是 1 。
```c
#define __const_hweight64(w) \
(__const_hweight32(w) + __const_hweight32((w) >> 32))
```
**hweight_long** 這個函數用來計算 w 總共有幾個位元是 1 。
```c
static inline unsigned long hweight_long(unsigned long w)
{
return __const_hweight64(w);
}
```
**min(x, y)** 這個巨集用來得到 x 和 y 之中比較小的那個值。
```c
#define min(x, y) (x) < (y) ? (x) : (y)
```
**BITMAP_LAST_WORD_MASK(nbits)** 這個巨集主要是用來得到低 nbits 位元都是 1 的 mask ,剩餘的位元都是 0 。接著做更詳細的討論,假設 nbits 是非負整數,當 $64\ge nbits\ge 0$ , 時,可以得到低 nbits 位元都是 1 的 mask ,而當 $nbits>64$ 時,可以得到低 nbits % 64 個位元都是 1 的 mask 。
```c
#define BITMAP_LAST_WORD_MASK(nbits) (~0UL >> (-(nbits) & (BITS_PER_LONG - 1)))
```
**FIND_NTH_BIT** 這個巨集用來找從 FETCH 這個位置開始尋找第 num 個位元是 1 的位置。首先先來解釋 for 迴圈的作用,當 size 大於 BITS_PER_LONG 的時候,代表有可能改變 idx 用來得到更後面記憶體中的值,所以才需要這個 for 迴圈找到合適的 idx 值。在 for 迴圈內,透過 hweight_long 得到 tmp 有幾個位元是 1 , 就可以透過 w 是否大於 nr ,知道是否要讓 for 迴圈繼續下一回合,因為如果 w 小於等於 nr 就代表在 FETCH[idx] 的值已經可以找到第 num 的 1 了,可以直接用 goto 跳至 found 這個標籤開始尋找。最後解釋一下 for 迴圈出來後的 if ,這個 if 是用來檢查 sz 是不是 BITS_PER_LONG 的倍數,如果不是的話,就產生一個低 sz % 64 位元都是 1 的 mask ,讓 tmp 只保留低 sz % 64 位元的值,剩下的位元都變成 0 ,這樣才能確保 fns 不會檢查超過 size 的範圍。
```c
#define FIND_NTH_BIT(FETCH, size, num) \
({ \
unsigned long sz = (size), nr = (num), idx, w, tmp; \
\
for (idx = 0; (idx + 1) * BITS_PER_LONG <= sz; idx++) { \
if (idx * BITS_PER_LONG + nr >= sz) \
goto out; \
\
tmp = (FETCH); \
w = hweight_long(tmp); \
if (w > nr) \
goto found; \
\
nr -= w; \
} \
\
if (sz % BITS_PER_LONG) \
tmp = (FETCH) & BITMAP_LAST_WORD_MASK(sz); \
found: \
sz = min(idx * BITS_PER_LONG + fns(tmp, nr), sz); \
out: \
sz; \
})
```
**__ffs** 這個函數是用來找到一個第一個值為 1 的位元是哪一個。如果 if 裡面檢查到非零,代表在這個檢查的範圍裡面就有 1 出現了,不用再把值往右移檢查,只需要縮小檢查的範圍為當前檢查的範圍的一半就好。所以 AAAA 要填入 0xffffffff 。
```c
/* find first bit in word.
* @word: The word to search
*
* Undefined if no bit exists, so code should check against 0 first.
*/
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;
}
```
**__clear_bit** 這個函數用來把第 nr 個位元變成 0 ,所以 BBBB 要是 ~mask 才對,這樣才能確保只有 第 nr 個位元會變成 0 ,剩下的位元都維持原樣。而第 4 行後面要加 BIT_WORD(nr) 是因為如果 nr 大於等於 BITS_PER_LONG ,就需要加 offset ,因為 p 這個指標變數一次只能操作寬度為 BITS_PER_LONG 位元的值。
```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** 這個函數用來找到一個 word 裡面第 n 個位元是 1 的位置,透過 __ffs 和 __clear_bit 這兩個函數不斷的尋找和清除,直到 n 變成 0 ,就找到第 n 個位元是 1 的位置了。
```c
/* find N'th set bit in a word
* @word: The word to search
* @n: Bit to find
*/
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;
}
```
**find_nth_bit** 這個函數從 addr 這個位置開始尋找第 n 個位元是 1 的位置,而 size 代表要搜尋的最大範圍。當 $n\ge size$ 的時候,代表不可能找到,所以直接 return size 。接著透過 small_const_nbits 確認 size 的範圍是 $0<size\le BITS\_PER\_LONG$ ,這樣就可以透過 GENMASK 產生一個只有要搜尋的範圍的位元是 1 的 mask ,將 mask 和 *addr 做 & 運算,就可以得到一個把要搜尋的範圍以外的位元都變成 0 的 val ,才可以透過 fns 尋找我們要的位置,如果沒找到,一樣 return size ,如果找到了,就 return fns 的結果。會用到 FIND_NTH_BIT 的狀況是當 $size>BITS\_PER\_LONG$ 的時候,因為上面的方法只能處理 $size\le BITS\_PER\_LONG$ 的狀況。
```c
/* find N'th set bit in a memory region
* @addr: The address to start the search at
* @size: The maximum number of bits to search
* @n: The number of set bit, which position is needed, counting from 0
*
* The following is semantically equivalent:
* idx = find_nth_bit(addr, size, 0);
* idx = find_first_bit(addr, size);
*
* Returns the bit number of the N'th set bit.
* If no such, returns @size.
*/
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);
}
```
了解了 find_nth_bit 這個函數的作用後,我開始尋找在 linux 核心的程式碼哪裡有應用到這個函數。
在 `include/linux/cpumask.h` 內的 cpumask_nth 函數有使用到 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));
}
```