---
tags: linux2023
---
# [2023q1](http://wiki.csie.ncku.edu.tw/linux/schedule) 第 3 週測驗題
:::info
目的: 檢驗學員對 bitwise, alignment, C 語言程式設計的認知
:::
==[作答表單: 測驗 1-2](https://docs.google.com/forms/d/e/1FAIpQLSeBT6aUILfntZvv4I73IU3qw-rbpdu7GuosBz1gnw7Er5lkuw/viewform)== (針對 Linux 核心「設計」課程)
==[作答表單: 測驗 3-4](https://docs.google.com/forms/d/e/1FAIpQLSeSwhdbw3Rh7fghROajaiuFlN55SsMHYt-yazpqjWZiSPN8QQ/viewform)== (針對 Linux 核心「實作」課程)
### 測驗 `1`
考慮一個 [Tree sort](https://en.wikipedia.org/wiki/Tree_sort) 的實作,搭配 [Red–black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) 作為排序所需的自我平衡樹,原始程式碼可見 [treesort.c](https://gist.github.com/jserv/920766d1fd893d0efd66ac7fdaf23401)。部分程式碼已遮蔽,請補完程式碼,使其運作符合預期。
作答規範:
* `AAAA` : 利用〈[你所不知道的 C 語言:記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/c-memory)〉提到利用 ABI 特性來「標示不用的節點」手段,以 bitwise 操作,對給定的引數 `(r)` 計算合法的記憶體地址 (記得要考慮 alignment)。
* `BBBB` 和 `CCCC` : 運用〈[你所不知道的 C 語言:數值系統篇](https://hackmd.io/@sysprog/c-numerics)〉和〈[你所不知道的 C 語言: bitwise 操作](https://hackmd.io/@sysprog/c-bitwise)〉,以 bitwise 操作分別設定紅黑數節點的顏色。
* `DDDD`, `EEEE`, `HHHH` 是 C 語言敘述
* `FFFF` 和 `GGGG` 則運用〈[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)〉,是合法的 C 語言敘述
* 當使用 bitwise 操作時,應該將變數/引數名稱放在左側,常數 (儘量用最精簡的形式!) 放在右側,例如 `r | 1`
* 上述皆不包含 `;`,並該依循 [CONTRIBUTING.md](https://github.com/sysprog21/lab0-c) 程式碼風格規範 (注意空白!),用最精簡的程式碼書寫
:::success
延伸問題:
1. 指出 `treesort.c` 程式碼的缺失並改進
2. 利用 Linux 核心的 [list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) 標頭檔改寫並整合進第一次作業的實驗程式碼中,探討 [Tree sort](https://en.wikipedia.org/wiki/Tree_sort) 的效率
3. 研讀 Linux 核心的 [Red–black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) 程式碼,比較給定的 `treesort.c` 實作手法,指出後者的改進空間並予以實作
:::
---
### 測驗 `2`
考慮一個使用 [AVL tree](https://en.wikipedia.org/wiki/AVL_tree) 實作的 [priority queue](https://en.wikipedia.org/wiki/Priority_queue),部分程式碼如下:
```c
#include "avltree.h"
struct avl_prio_queue {
struct avl_root root;
struct avl_node *min_node;
};
static inline void avl_prio_queue_init(struct avl_prio_queue *queue)
{
INIT_AVL_ROOT(&queue->root);
queue->min_node = NULL;
}
static inline void avl_prio_queue_insert_unbalanced(
struct avl_prio_queue *queue,
struct avlitem *new_entry)
{
struct avl_node *parent = NULL;
struct avl_node **cur_nodep = &queue->root.node;
struct avlitem *cur_entry;
int isminimal = 1;
while (*cur_nodep) {
cur_entry = avl_entry(*cur_nodep, struct avlitem, avl);
parent = *cur_nodep;
if (cmpint(&new_entry->i, &cur_entry->i) <= 0) {
cur_nodep = &((*cur_nodep)->left);
} else {
cur_nodep = &((*cur_nodep)->right);
isminimal = 0;
}
}
if (isminimal)
queue->min_node = &new_entry->avl;
avl_link_node(&new_entry->avl, parent, cur_nodep);
}
static inline struct avlitem *avl_prio_queue_pop_unbalanced(
struct avl_prio_queue *queue)
{
struct avlitem *item;
bool removed_right;
if (!queue->min_node)
return NULL;
item = avl_entry(queue->min_node, struct avlitem, avl);
queue->min_node = avl_next(queue->min_node);
avl_erase_node(&item->avl, &queue->root, &removed_right);
return item;
}
tatic inline void avl_prio_queue_insert_balanced(
struct avl_prio_queue *queue,
struct avlitem *new_entry)
{
struct avl_node *parent = NULL;
struct avl_node **cur_nodep = &queue->root.node;
struct avlitem *cur_entry;
int isminimal = 1;
while (*cur_nodep) {
cur_entry = avl_entry(*cur_nodep, struct avlitem, avl);
parent = *cur_nodep;
if (cmpint(&new_entry->i, &cur_entry->i) <= 0) {
cur_nodep = &((*cur_nodep)->left);
} else {
cur_nodep = &((*cur_nodep)->right);
isminimal = 0;
}
}
if (isminimal)
queue->min_node = &new_entry->avl;
avl_insert(&new_entry->avl, parent, cur_nodep, &queue->root);
}
static inline struct avlitem *avl_prio_queue_pop_balanced(
struct avl_prio_queue *queue)
{
struct avlitem *item;
if (!queue->min_node)
return NULL;
item = avl_entry(queue->min_node, struct avlitem, avl);
queue->min_node = avl_next(queue->min_node);
avl_erase(&item->avl, &queue->root);
return item;
}
```
其中 `avltree.h` 的程式碼可見 [avltree.h](https://gist.github.com/jserv/d03098d57fec03c89fdb2a449d7f6be2)。部分程式碼已遮蔽,請補完程式碼,使其運作符合預期。
作答規範:
* `IIII`, `JJJJ`, `KKKK`, `LLLL` 利用〈[你所不知道的 C 語言:記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/c-memory)〉提到利用 ABI 特性來「標示不用的節點」手段,以 bitwise 操作,計算合法的記憶體地址 (記得要考慮 alignment)。
* `MMMM` 和 `NNNN` 為函式名稱
* 當使用 bitwise 操作時,應該將變數/引數名稱放在左側,常數 (儘量用最精簡的形式!) 放在右側,例如 `r | 1`
* 上述皆不包含 `;`,並該依循 [CONTRIBUTING.md](https://github.com/sysprog21/lab0-c) 程式碼風格規範 (注意空白!),用最精簡的程式碼書寫
:::success
延伸問題
1. 解釋上述程式碼運作原理,並比較用紅黑樹實作的 [priority queue](https://en.wikipedia.org/wiki/Priority_queue),應提供完整的測試程式和效能分析
2. Linux 核心也有 AVL tree 的身影,但陸續被紅黑樹替代,請揣摩 Linux 核心開發者的考慮因素,並搭配 git log 來解讀
> 見 commit [b145425](https://github.com/torvalds/linux/commit/b145425f269a17ed344d737f746b844dfac60c82)
3. 研讀 Linux 核心原始程式碼,指出曾經/現在使用 AVL tree 的部分,對照 Linux 核心的 `rbtree.h`,探究是否可用後者替換,從而爭取到貢獻程式碼到 Linux 核心的機會
:::
---
### 測驗 `3`
![](https://hackmd.io/_uploads/H1ChcH00j.png)
[Linear feedback shift register](https://en.wikipedia.org/wiki/Linear-feedback_shift_register) (LFSR) 是指給定前一狀態,將該輸出的線性函數作為輸入的移位暫存器,其應用包括 [PRNG](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) 場景。
LFSR 範例:
![](https://hackmd.io/_uploads/SJn1iB0Cs.png)
1. 初始狀態 $s = [s_2, s_1, s_0] = [1, 0, 0]$
2. 反饋 $p = [p_2, p_1, p_0] = [0, 1, 1]$
3. 線性運算 `s[n+1] = s[n]*p[2] ^ s[n-1]*p[1] ^ s[n-2]*p[0]`
考慮一個運用 LFSR 來產生隨機數,並使其均勻分佈於 64 位元無號整數的有效空間 (distribution of uniformly arbitrarily large `uint64_t`)。參考執行輸出如下:
```
0:91263 | 1:93093 | 2:92113 | 3:92503 | 4:92253 |
5:91833 | 6:93023 | 7:92283 | 8:93473 | 9:92983 |
10:90433 | 11:92373 | 12:91733 | 13:93733 | 14:92193 |
...
105:81623 | 106:82253 | 107:82573 | 108:82393 | 109:82483 |
110:82643 | 111:81483 | 112:82473 | 113:80623 | 114:82833 |
115:81703 | 116:82213 | 117:80603 | 118:81143 | 119:81253 |
```
程式碼可參見 [bucket_uniform.c](https://gist.github.com/jserv/15c6ef4383d5d010326cdc4382ffdd1d) (部分程式碼遮蔽):
* `log2_64` : 計算以 2 為底的 x 的對數 (logarithm of x to the base b),且下取整函數 (floor)
* `bucket_number` : 在指定區間內,使產生的 LFSR 數值得以均勻分佈
請補上程式碼,使程式碼的執行結果符合參考輸出。作答規範:
* `AAAA`, `BBBB`, `CCCC`, `DDDD`, `EEEE`, `FFFF`, `GGGG`, `HHHH` 皆為大於或等於 `0` 的整數
* `IIII` 和 `JJJJ` 是 `mask111` 或 `mask011`
:::success
延伸問題:
1. 解釋 [Linear feedback shift register](https://en.wikipedia.org/wiki/Linear-feedback_shift_register) (LFSR) 的運作原理,搭配在 [Linux 核心原始程式碼](https://github.com/torvalds/linux)中,用 `git log` 和 `grep` 找出 LFSR 的應用案例
2. 解釋上述程式碼運作的原理
3. 在 Linux 核心原始程式碼中找到 $\log_2(x)$ 的實作 (整數),要涵蓋不同的整數型態寬度,以及是否能運用微處理器提供的指令來加速。也從 Linux 核心找出整數 $\log_2(x)$ 的應用案例並解說。
4. `lab0-c` 提供類似的實作 [log2_lshift16.h](https://github.com/sysprog21/lab0-c/blob/master/log2_lshift16.h),解釋其原理並嘗試改進其效能
:::
---
### 測驗 `4`
已知輸入必為大於 `0` 的數值 (即 `x > 0`),以下程式碼可計算 $\lceil log_2(x) \rceil$ ,也就是 [ceil](https://man7.org/linux/man-pages/man3/ceil.3.html) 和 [log2](https://man7.org/linux/man-pages/man3/log2.3.html) 的組合並轉型為整數:
```cpp
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 (KKKK) + 1;
}
```
請補完,使其行為符合預期。作答規範:
* `KKKK` 應以最簡潔的形式撰寫,且符合[作業一](https://hackmd.io/@sysprog/linux2023-lab0)排版規範 (近似 Linux 核心程式碼排版風格)
* `KKKK` 為表示式,限制使用 `^`, `&`, `|`, `<<`, `>`> 這幾個運算子,可能會出現常數
* `KKKK` 不該包含小括號 (即 `(` 和 `)`)
* 為了自動批改的便利,變數出現的順序 (可從缺) 從左到右為 `r`, `shift`, `x`
* `KKKK` 不可包含 `,` 和 `;` 這樣的分隔符號 (separator)
:::success
延伸問題:
1. 解釋上述程式碼運作原理
2. 改進程式碼,使其得以處理 `x = 0` 的狀況,並仍是 branchless
3. 在 Linux 核心原始程式碼找出 ceil 和 log2 的組合 (類似的形式),並解說應用案例,例如在 Linux 核心排程器就有
> 善用 [lxr](https://elixir.bootlin.com/linux/latest/source) 和 `git log`
:::
---