---
tags: linux kernel, linux2022
---
# 2022q1 Homework3 (quiz3)
> [測驗題目](https://hackmd.io/@sysprog/linux2022-quiz3)
## 測驗 `1`
### 程式碼運作原理
#### 功能
產生連續的 bitmask
- `GENMASK(6, 4)` 產生 01110000~2~
- `GENMASK(39, 21)` 產生 0x000000ffffe00000 (64 位元)
#### GENMASK 巨集定義
首先,我們可以看到巨集內有兩個 `~0UL` (0xFFFFFFFFFFFFFFFF) 先做 shift 操作,接著做 AND
因此,可以推測其作法是將
* `~0UL` 右移產生高位 `63 - h` bits 為 0 的 `unsigned long`
* `~0UL` 左移產生低位 `l` bits 為 0 的 `unsigned long`
所以 `LEFT = 63 -h` 用以產生高位 `63 - h` bits 個 0 ,而 `RIGHT = l` 用以產生低位 `l` bits 個 0
```c
#define GENMASK(h, l) \
(((~0UL) >> (LEFT)) & ((~0UL) >> (l) << (RIGHT)))
```
## 測驗 `2`
### 程式碼運作原理
#### 功能
將 `fd`結構體的第一個成員的地址對 4 個位元組進行向下對齊。
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
address[label ="<h>Memory address|<a1>0x0|<a2>0x1|<a3>0x2|<a4>0x3|<a1>0x4|<a2>0x5|<a3>0x6|<a4>0x7",width = 1.8]
space_before[label ="<h>Memory space before||<d1>data|<d2>data|<d3>data|<d4>data|||",width = 2.5]
space_after[label ="<h>Memory space after|<d1>data|<d2>data|<d3>data|<d4>data||||",width = 2.5]
address->space_before->space_after[weight = 10, style = invis]
space_before->space_after
}
```
> 圖片參考 [jim12312321](https://hackmd.io/NJrI-98tT4i0B_Ri-BMXsw?view#%E6%B8%AC%E9%A9%97-2-%EF%BC%9A-%E8%A8%98%E6%86%B6%E9%AB%94%E5%90%91%E4%B8%8B%E5%B0%8D%E9%BD%8A)
#### 記憶體向下對齊實作
首先,從結構體 `fd` 包含 `struct foo *foo` 以及 `unsigned int flags`。
接著可以根據 `fd` 推測 `EXP1` 應該包含 `struct foo *`。
```c
struct foo;
struct fd {
struct foo *foo;
unsigned int flags;
};
```
因為要作 4 `bytes` 的的向對齊,低位 2 `bits` clear,所以用 bitmask 的方式讓 `v` 與 `~3` 作 AND。
因此 `EXP1 = (struct foo *)(v & ~3)`
```c
enum {
FOO_DEFAULT = 0,
FOO_ACTION,
FOO_UNLOCK,
} FOO_FLAGS;
static inline struct fd to_fd(unsigned long v)
{
return (struct fd){EXP1, v & 3};
}
```
## 測驗 `3`
> Leetcode [190. Reverse Bits](https://leetcode.com/problems/reverse-bits/)
### 程式碼運作原理
#### 功能
輸入的 8 位元無號整數逐一位元反轉。
- Input = 00100101
- Output = 11011010
#### 實作
利用 AND 與 Shift 達到高位移到低位;低位移到高位,接著將兩者用 OR 合併。
1. 將高位 4 `bits` 與低位 4 `bits` 交換
2. 將高、低位 4 `bits` 內的高位 2 `bits` 與低位 2 `bits` 交換
3. 將高、低位 2 `bits` 內的高位 1 `bits` 與低位 1 `bits` 交換
`EXP2`、`EXP3` 為負責處理低位 4 `bits` 的部份,所以
* `EXP2 = (x & 0x33) << 2`
* `EXP3 = (x & 0x55) << 1`
```c
#include <stdint.h>
uint8_t rev8(uint8_t x)
{
x = (x >> 4) | (x << 4);
x = ((x & 0xCC) >> 2) | (EXP2);
x = ((x & 0xAA) >> 1) | (EXP3);
return x;
}
```
## 測驗 `4`
### 程式碼運作原理
#### foreach 巨集
遍歷集合中每個成員。
```c
int i;
foreach_int(i, 0, -1, 100, 0, -99) {
printf("i is %i\n", i);
}
const char *p;
foreach_ptr(p, "Hello", "world") {
printf("p is %s\n", p);
}
```
預期輸出:
```
i is 0
i is -1
i is 100
i is 0
i is -99
p is Hello
p is world
```
foreach 與 for 不相同的地方是 foreach 通常不維護明確的計數器
#### 擴充 foreach 巨集定義
foreach 的巨集定義為 for 迴圈的實做,因此 `EXP4`, `EXP5` 的部份應該是更新 `_foreach_i` (index)。
由於每次迴圈都要對 `i` 賦值,因此要先更新 `foreach_i` 接著再對 `i` 賦值,所以:
* `EXP4 = ++_foreach_i`
* `EXP5 = ++_foreach_i`
```c
#include <assert.h>
#define _foreach_no_nullval(i, p, arr) \
assert((i) >= sizeof(arr) / sizeof(arr[0]) || (p))
#define foreach_int(i, ...) \
for (unsigned _foreach_i = (((i) = ((int[]){__VA_ARGS__})[0]), 0); \
_foreach_i < sizeof((int[]){__VA_ARGS__}) / sizeof(int); \
(i) = ((int[]){__VA_ARGS__, 0})[EXP4])
#define foreach_ptr(i, ...) \
for (unsigned _foreach_i = \
(((i) = (void *) ((typeof(i)[]){__VA_ARGS__})[0]), 0); \
(i); (i) = (void *) ((typeof(i)[]){__VA_ARGS__, \
NULL})[EXP5], \
_foreach_no_nullval(_foreach_i, i, \
((const void *[]){__VA_ARGS__})))
```
## 測驗 `5`
> LeetCode [29. Divide Two Integers](https://leetcode.com/problems/divide-two-integers/)
### 程式碼運作原理
#### 規則
輸入兩正整數,將兩數相除,並將成果無條件捨去。
#### 沒有除法的實做
先處理兩數處法的 sign bit, 用 `signal` 紀錄 sign bit,若 `dividend < 0` 則對 `dvd` 作二的補數,接著若 `divisor < 0` 則對 `dvs` 作二的補數。sign bit 用 `signal *= -1` 來處理,若只有一者為負,則 `signal = -1`,若兩者皆為負或皆不為負,則 `signal = 0`。
```c
int signal = 1;
unsigned int dvd = dividend;
if (dividend < 0) {
signal *= -1;
dvd = ~dvd + 1;
}
unsigned int dvs = divisor;
if (divisor < 0) {
signal *= -1;
dvs = ~dvs + 1;
}
```
`shift` 負責將 `dvs` 的最高位元對齊 `dvd` 的最高位元。
所以 `EXP6 = dvs << shift`
```c
int shift = 0;
while (dvd > (EXP6))
shift++;
```
`res` 負紀錄商,而 `EXP7` = `dvd -= dvs << shift` 將被除數減去除數。
```c
unsigned int res = 0;
while (dvd >= dvs) {
while (dvd < (dvs << shift))
shift--;
res |= (unsigned int) 1 << shift;
EXP7;
}
```
先判斷 `res` 是否 overflow,最後將 `res` 乘上 `signal` 即為兩數相除的商。
```c
if (signal == 1 && res >= INT_MAX)
return INT_MAX;
return res * signal;
```
## 測驗 `6`
> LeetCode [149. Max Points on a Line](https://leetcode.com/problems/max-points-on-a-line/)
### 程式碼運作原理
#### 規則
## 測驗 `7`
### 程式碼運作原理
#### 功能
針對給定的 32 位元無號數,計算扣除開頭的 0,最少需要多少位元才可儲存
#### 實作
利用 Binary Search 的方式,判斷最高位的 1 的位子。
比教最高位的 1 是否在前半部,若在前半部,將後半部 shift 掉。
可知 `EXP10` = `(v > 3) << 1`
而 `EXP11` 只有一行,負責處理最後兩個 bits。
所以 `EXP11` = `ret += v > 1`
```c
int ilog32(uint32_t v)
{
int ret = v > 0;
int m = (v > 0xFFFFU) << 4;
v >>= m;
ret |= m;
m = (v > 0xFFU) << 3;
v >>= m;
ret |= m;
m = (v > 0xFU) << 2;
v >>= m;
ret |= m;
m = EXP10;
v >>= m;
ret |= m;
EXP11;
return ret;
}
```
## 測驗 `8`
### 程式碼運作原理
#### 用 indirect pointer 改寫 `remove_data`
在第一個 while loop 的地方,他是負責 binary search 部份,因此 `EXP12` 與 `EXP13` 應該是根據判斷 `d` 與 `(*p)->data` 的大小進行向下搜尋。
因此:
* `EXP12` = `p = &(*p)->left`
* `EXP13` = `p = &(*p)->right`
```c
void remove_data(tree &t, int d)
{
tnode **p = &t;
while (*p != 0 && (*p)->data != d) {
if (d < (*p)->data)
EXP12;
else
EXP13;
}
tnode *q = *p;
if (!q)
return;
if (!q->left)
*p = q->right;
else if (!q->right)
*p = q->left;
else {
tnode **r = &q->right;
while ((*r)->left)
r = EXP14;
q->data = (*r)->data;
q = *r;
*r = q->right;
}
delete q;
}
```
## 測驗 `9`
### 程式碼運作原理
#### 記憶體地址對齊 (alignment) 的巨集
先對地址加上對齊大小(16),因此 `MMM` = `16`
接著捨去最低 4 位元,因此應該要與最低 4 位元皆為 0 其餘皆為 1 的數進行 AND 運算,所以 `NNN` 為 `MAX_ALIGNMENT - 1`。
```c
/* maximum alignment needed for any type on this platform, rounded up to a
power of two */
#define MAX_ALIGNMENT 16
/* Given a size, round up to the next multiple of sizeof(void *) */
#define ROUND_UP_TO_ALIGNMENT_SIZE(x) \
(((x) + MAX_ALIGNMENT - MMM) & ~(NNN))
```
## 測驗 `10`
### 程式碼運作原理
#### 功能
處理二個表示式進行整數除法操作時,最接近的整數值。
參考執行成果:
* `DIVIDE_ROUND_CLOSEST(7, 3) = 2`
* `DIVIDE_ROUND_CLOSEST(6, 3) = 2`
* `DIVIDE_ROUND_CLOSEST(5, 3) = 2`
#### 巨集實作
$round(a/b) = (a+b/2)/b$
分為兩種情況:
1. `x` 或 `divisor` 為 unsigned 或同號
- `RRR` = `(__x)+(__d)>>1`
2. `x` 與 `divisor` 為異號
- `SSS` = `-(__x)+(__d)>>1`
```c
#define DIVIDE_ROUND_CLOSEST(x, divisor) \
({ \
typeof(x) __x = x; \
typeof(divisor) __d = divisor; \
(((typeof(x)) -1) > 0 || ((typeof(divisor)) -1) > 0 || \
(((__x) > 0) == ((__d) > 0))) \
? ((RRR) / (__d)) \
: ((SSS) / (__d)); \
})
```
## 測驗 `11`
### 程式碼運作原理
#### 不用除法的開平方根實作
返回 `word` 的最高有效位元的位置。
```c
static inline unsigned long fls(unsigned long word)
{
int num = 64 - 1;
if (!(word & (~0ul << 32))) {
num -= 32;
word <<= 32;
}
if (!(word & (~0ul << (64 - 16)))) {
num -= 16;
word <<= 16;
}
if (!(word & (~0ul << (64 - 8)))) {
num -= 8;
word <<= 8;
}
if (!(word & (~0ul << (64 - 4)))) {
num -= 4;
word <<= 4;
}
if (!(word & (~0ul << (64 - 2)))) {
num -= 2;
word <<= 2;
}
if (!(word & (~0ul << (64 - 1))))
num -= 1;
return num;
}
```
```c
m = 1UL << (fls(x) & ~1UL);
```
```c
while (m) {
b = y + m;
XXX;
if (x >= b) {
YYY;
y += m;
}
ZZZ;
}
```