# 2022q1 Homework4 (quiz4)
contributed by < `jim12312321` >
> [測驗題目](https://hackmd.io/@sysprog/linux2022-quiz4)
## 測驗 1 : ceil $log_2$
```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 (EXP1) + 1;
}
```
解析:
這題跟 [第三周測驗題第 7 題](https://hackmd.io/NJrI-98tT4i0B_Ri-BMXsw#%E6%B8%AC%E9%A9%97-7-%EF%BC%9A-ilog32)想法上大致一致,一樣是用 binary search,每次對半檢查,檢查當前 bits 中的上半部分是否有值,之後再位移 `x` 並將結果相加得到答案。
> 不要加上「的概念」,本質上就是 binary search!
> :notes: jserv
本題在第 14 行時檢查到 `0x3` ,也就是剩最後兩個 bits ,經過位移後就回傳答案了,因此我們可知題目將以下步驟濃縮在 `(EXP1)+1` 中。
```c=
r |= shift;
shift = (x > 0x1) << 0; //為了寫法一致,所以加上 `<< 0`
r |= shift;
```
其中第三行中的 `shift` 可以直接用 `(x > 1)` 取代,因此目前可以簡化成 $\downarrow$
```c
r |= (shift | (x > 1));
```
而又因為現在的 `x` 只剩 2 個 bits ,因此判斷是否大於 1 等價於右移 1 ,程式碼可以進一步簡化 $\downarrow$
```c
r |= (shift | (x >> 1));
```
`
EXP1`
$\rightarrow$ r | shift | x >> 1
### 延伸問題 2
>改進程式碼,使其得以處理 x = 0 的狀況,並仍是 branchless
```c=
int ceil_log2(u_int32_t x)
{
u_int32_t r, shift;
/*if input 0,than x will be 0xFFFFFFFF*/
x--;
r = (x > 0xFFFF) << 4;
x >>= r;
/* let x still be 0xFFFFFFFF if input 0*/
x <<= (x == 0xFFFF) << 4;
x += (0xFFFF & -(x == 0xFFFF0000));
shift = (x > 0xFF) << 3;
x >>= shift;
r |= shift;
/* let x still be 0xFFFFFFFF if input 0*/
x <<= (x == 0xFFFFFF) << 3;
x += (0xFF & -(x == 0xFFFFFF00));
shift = (x > 0xF) << 2;
x >>= shift;
r |= shift;
/* let x still be 0xFFFFFFFF if input 0*/
x <<= (x == 0xFFFFFFF) << 2;
x += (0xF & -(x == 0xFFFFFFF0));
shift = (x > 0x3) << 1;
x >>= shift;
r |= shift;
/* let x still be 0xFFFFFFFF if input 0*/
x <<= (x == 0x3FFFFFFF) << 1;
x += (0x3 & -(x == 0xFFFFFFFC));
r |= shift;
shift = (x > 0x1) << 0;
x >>= 1;
r |= shift;
/* let x still be 0xFFFFFFFF if input 0*/
x <<= (x == 0x7FFFFFFF) << 1;
x += (0x1 & -(x == 0xFFFFFFFE));
return (r | x )+1;
}
```
主要想法就是針對輸入 0 這個特例進行處理。
一開始的 `x--`會使值從 0 變為 0xFFFFFFFF ,之後每次位移後都將值補回去讓其隨時都為 0xFFFFFFFF ,最後 0xFFFFFFFF +1 就會等於 0。
:::warning
思考如何藉由巨集來簡化程式碼,注意 bitmask/generator 的設計
:notes: jserv
:::
---
## 測驗 2 : find first set
```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 ((EXP2) == 0) {
x >>= shift;
EXP3;
}
shift >>= 1;
t >>= shift;
}
return o;
}
```
解析:
這題一樣是利用 binary search ,每次對半檢查 `x` 中的下半部 (e.g. 假設現在 `x` 為 16 bits ,那下半部就是第 0 到 7 個 bits) 是否有值,若有值則表示應該繼續找下去,若沒有值則表示 first set bit 在目前的上半部,因此要將當前字串右移並累加結果。
`EXP2`
$\rightarrow$ x & t
`EXP3`
$\rightarrow$ o += shift
### 延伸問題 1
> 迴避 while, for, goto 等關鍵字以改寫出功能等價的實作
naïve 版本:
因為本質上是 binary search ,而對於 unsigned long (64 bits in LP64) 中最多就是重複 6 次,直接展開即可。
```c=
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;
if ((x & t) == 0) {
x >>= shift;
o += shift;
}
shift >>= 1;
t >>= shift;
if ((x & t) == 0) {
x >>= shift;
o += shift;
}
shift >>= 1;
t >>= shift;
if ((x & t) == 0) {
x >>= shift;
o += shift;
}
shift >>= 1;
t >>= shift;
if ((x & t) == 0) {
x >>= shift;
o += shift;
}
shift >>= 1;
t >>= shift;
if ((x & t) == 0) {
x >>= shift;
o += shift;
}
shift >>= 1;
t >>= shift;
if ((x & t) == 0) {
x >>= shift;
o += shift;
}
return o;
}
```
遞迴版本:
除了比較簡短之外,對應不同型態的數值也不用重新思考要對切幾次,可以直接修改 `BITS_PER_LONG`, `t`, `x` 的資料型態即可,另外要特別注意,因為希望在遞迴執行時 `shift`, `t`, `o` 不用特別傳入且始終為同一個,因此變數宣告時要加上 `static` 修飾詞。
```c=
static inline size_t ffs(unsigned long x)
{
if (x == 0)
return 0;
static size_t o = 1;
static unsigned long t = ~0UL;
static size_t shift = BITS_PER_LONG;
shift >>= 1;
t >>= shift;
if ((x & t) == 0) {
x >>= shift;
o += shift;
}
if(shift){
ffs(x);
}else{
return o;
}
}
```
暫時想不到更精簡的寫法了。
### 延伸問題 2
>在 Linux 核心原始程式碼的 include/asm-generic/bitops 目錄找出相關程式碼,並探討應用案例
>
在 [include/asm-generic/bitops](https://github.com/torvalds/linux/tree/master/include/asm-generic/bitops) 中可以看到許多相關實現方法,如: [__ffs](https://github.com/torvalds/linux/blob/master/include/asm-generic/bitops/__ffs.h), [__fls](https://github.com/torvalds/linux/blob/master/include/asm-generic/bitops/__fls.h), [ffs](https://github.com/torvalds/linux/blob/master/include/asm-generic/bitops/ffs.h), [fls](https://github.com/torvalds/linux/blob/master/include/asm-generic/bitops/fls.h) 等方法。
我們來追蹤 [__ffs](https://github.com/torvalds/linux/blob/master/include/asm-generic/bitops/__ffs.h) 的應用情境,可以在 [drivers/clk/ti/clkt_dpll.c](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/drivers/clk/ti/clkt_dpll.c#L181) 中的 `_omap2_dpll_is_in_bypass` 找到他。
```c=174
/**
* _omap2_dpll_is_in_bypass - check if DPLL is in bypass mode or not
* @v: bitfield value of the DPLL enable
*
* Checks given DPLL enable bitfield to see whether the DPLL is in bypass
* mode or not. Returns 1 if the DPLL is in bypass, 0 otherwise.
*/
static int _omap2_dpll_is_in_bypass(u32 v)
{
u8 mask, val;
mask = ti_clk_get_features()->dpll_bypass_vals;
/*
* Each set bit in the mask corresponds to a bypass value equal
* to the bitshift. Go through each set-bit in the mask and
* compare against the given register value.
*/
while (mask) {
val = __ffs(mask);
mask ^= (1 << val);
if (v == val)
return 1;
}
return 0;
}
```
就像註解所說的,在這裡 `__ffs` 的目的就是為了從 mask 的高位 set bit 逐步走訪所有 set bit (而 `mask ^= (1 << val);` 就是為了消除找到的 first set bit) 並且判斷是否為 bypass mode 。
---
## 測驗 3 ︰ a pointer of a pointer
```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 sfccessfully
* 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; EXP4) {
if (*con == fc) {
*con = EXP5;
ret = true;
break;
}
}
return ret;
}
```
解析︰
這題只是單純考驗指標的指標運用技巧,在 `comsumer_del` 中需要走訪 `foo->comsumers` 找到指定移除的結點 `fc` 。
所以我們知道在 `EXP4` 中需要讓 `con` 繼續走訪 linked list 並在 `EXP5` 時移除找到的結點。
`EXP4`
$\rightarrow$ con = &(*con)->next
`EXP5`
$\rightarrow$ fc->next
另外在延伸問題 1 中也要求以下問題 $\downarrow$
> 區分 `foo` 和 `foo_consumer` 的好處
我認為這樣的用意主要是為了封裝,模組化比起大雜燴來得好維護,另一個好處則是增加程式易讀性,方便理解和維護。
---
## 測驗 4 ︰ coroutine
```c=
static void task_switch(struct list_head *tasklist)
{
jmp_buf env;
if (!list_empty(tasklist)) {
struct task *t = list_first_entry(tasklist, struct task, list);
EXP6;
memcpy(env, t->env, sizeof(jmp_buf));
free(t);
longjmp(env, 1);
}
}
```
```c=
static void task_join(struct list_head *tasklist)
{
jmp_buf env;
while (!list_empty(tasklist)) {
struct task *t = list_first_entry(tasklist, struct task, list);
EXP7;
memcpy(env, t->env, sizeof(jmp_buf));
free(t);
longjmp(env, 1);
}
}
```
在 `task_switch` 和 `task_join` 中都需要將當前的 task 從 task list 中移除。
`EXP6`
$\rightarrow$ list_del(&t->list)
`EXP7`
$\rightarrow$ list_del(&t->list)
```c=
void task0(void *arg)
{
jmp_buf env;
static int n;
n = *(int *) arg;
printf("Task 0: n = %d\n", n);
if (setjmp(env) == 0) {
task_add(&tasklist, env);
EXP8;
}
for (int i = 0; i < n; i++) {
if (setjmp(env) == 0) {
task_add(&tasklist, env);
task_switch(&tasklist);
}
printf("Task 0: resume\n");
}
printf("Task 0: complete\n");
longjmp(sched, 1);
}
```
```c=
void task1(void *arg)
{
jmp_buf env;
static int n;
n = *(int *) arg;
printf("Task 1: n = %d\n", n);
if (setjmp(env) == 0) {
task_add(&tasklist, env);
EXP9;
}
for (int i = 0; i < n; i++) {
if (setjmp(env) == 0) {
task_add(&tasklist, env);
task_switch(&tasklist);
}
printf("Task 1: resume\n");
}
printf("Task 1: complete\n");
longjmp(sched, 1);
}
```
在 `task0` 或 `task1` 中執行完 `task_add` 後需要返回到 `schedule` 中。
`EXP8`
$\rightarrow$ longjmp(sched, 1)
`EXP9`
$\rightarrow$ longjmp(sched, 1)
---
## 測驗 5 : READ_ONCE
```c=
#define READ_ONCE(x) \
({ \
union { \
typeof(x) __val; \
DECL0; \
} __u; \
__read_once_size(&(x), __u.__c, sizeof(x)); \
__u.__val; \
})
```
union 有別於 struct 的特點在於 union 所佔的記憶體空間大小以 union 中占最大空間的成員,因此 `DECL0` 需要選擇所占空間最小的型態。
`DECL0`
$\rightarrow$ char __c[1]
---