# 2024q1 Homework2 (quiz1+2) contributed by < [TSAICHUNGHAN](https://github.com/jeremy90307) > ## [第一周測驗題](https://hackmd.io/@sysprog/linux2024-quiz1) ### 測驗`1` : quicksort 參考 : [vax-r](https://hackmd.io/@vax-r/linux2024-homework2) #### 運作原理 鏈結串列結構體: ```c typedef struct __node { struct __node *left, *right; struct __node *next; long value; } node_t; ``` `list_add()` 使用 `list_add` 將 `node_t` 加入到鏈結串列開頭 ```c void list_add(node_t **list, node_t *node_t) { node_t->next = *list; *list = node_t; } ``` ```graphviz digraph list_add { rankdir=LR; node [shape=box, fontname = "Helvetica"]; "*list" -> node1; label="Before Adding New Node"; node1 -> node2 -> null; } ``` ```graphviz digraph list_add { rankdir=LR; node [shape=box, fontname = "Helvetica"]; "*list" -> node1; node_t->node1 label = ""; node1 -> node2 -> null; } ``` ```graphviz digraph list_add { rankdir=LR; node [shape=box, fontname = "Helvetica"]; "*list" -> node_t; label="After Adding New Node"; node_t->node1 -> node2 -> null; } ``` `list_tail()` 從 `*left` 開始訪問每個節點,直到訪問到尾部 `(*left)->next = NULL` 就將其回傳。 ```c node_t *list_tail(node_t **left) { while ((*left) && (*left)->next) left = &((*left)->next); return *left; } ``` `list_length()` 方法類似 `list_tail()` ,只是在每次走訪節點時會記錄一次,最終回傳走過的節點數。 ```c int list_length(node_t **left) { int n = 0; while (*left) { ++n; left = &((*left)->next); } return n; } ``` `list_construct()` 創建一個 `node` 節點並分配記憶體空間,再將其節點插入鏈結串列的開頭。 ```c node_t *list_construct(node_t *list, int n) { node_t *node = malloc(sizeof(node_t)); node->next = list; node->value = n; return node; } ``` `list_free()` 逐一走訪每個節點,並釋放記憶體空間直到尾巴(即 `*list = NULL` ) ```c void list_free(node_t **list) { node_t *node = (*list)->next; while (*list) { free(*list); *list = node; if (node) node = node->next; } } ``` `quick_sort()` 定義 `L` 、`R` , `pivot` 指向 `L` , `p` 指向 `pivot` 的下個節點,並將 `pivot` 斷開,後續會將其歸類在單獨 `begin[i+1]` 中。 ```c begin[0] = *list; end[0] = list_tail(list); while (i >= 0) { node_t *L = begin[i], *R = end[i]; if (L != R) { node_t *pivot = L; value = pivot->value; node_t *p = pivot->next; pivot->next = NULL; ``` ```graphviz digraph quicksort{ rankdir=LR; pivot [label="pivot" shape=plaintext] L [label="L" shape=plaintext] R[label="R" shape=plaintext] P[label="P" shape=plaintext] 4->1->3->5->2->7 {rank=same;pivot->4} {rank=same;L->4} {rank=same;R->7} {rank=same;P->1} } ``` `*p` 由左至右訪問每個節點,若節點的 `value` 小於 `pivot` 將其放至於 `*left` ,若大於 `pivot` 將其放至於 `*right` ,分類完成後如下圖所示 : ```c while (p) { node_t *n = p; p = CCCC; list_add(n->value > value ? &right : &left, n); } begin[i] = left; end[i] = DDDD; begin[i + 1] = pivot; end[i + 1] = pivot; begin[i + 2] = right; end[i + 2] = EEEE; ``` ```graphviz digraph quicksort{ rankdir=LR; begin2 [label="begin[2]" shape=plaintext] begin1 [label="begin[1]" shape=plaintext] begin0 [label="begin[0]" shape=plaintext] pivot [label="pivot" shape=plaintext] begin2 ->7->5 {rank=same;pivot->7} begin1 ->4 begin0 ->2->3->1 } ``` 此時 `pivot` 換成 `begin[2]` 所指向鏈結串列的第一個,重複一樣的分類方式。 ```graphviz digraph quicksort{ rankdir=LR; begin0 [label="begin[0]" shape=plaintext] begin1 [label="begin[1]" shape=plaintext] begin2 [label="begin[2]" shape=plaintext] begin3 [label="begin[3]" shape=plaintext] begin0 ->2->3->1 begin1 ->4 begin2 ->5 begin3 ->7 } ``` 由於 `L` 與 `R` 都只剩一個節點,因此 `L == R` 的結果會依序將 `begin[3]` 、` begin[2]` 、 `begin[1]` 送入 `result` 中,如下圖 : ```c if (L != R) { ...; } else { if (L) list_add(&result, L); i--; } ``` ```graphviz digraph quicksort{ rankdir=LR; result [label="result" shape=plaintext] begin0 [label="begin[0]" shape=plaintext] pivot [label="pivot" shape=plaintext] begin0 ->2->3->1; {rank=same;pivot->2} result ->4->5->7; } ``` 此時經過一連串 `i--` 回到了 `begin[0]` ,重複以上動作如圖所示 : ```graphviz digraph quicksort{ rankdir=LR result [label="result" shape=plaintext] begin0 [label="begin[0]" shape=plaintext] begin1 [label="begin[1]" shape=plaintext] begin2 [label="begin[2]" shape=plaintext] begin0 ->1 begin1 ->2 begin2 ->3 result ->4->5->7; } ``` 最終結果 : ```graphviz digraph quicksort{ rankdir=LR result [label="result" shape=plaintext] result ->1->2->3->4->5->7; } ``` #### 延伸問題: **引入 Linux Kernel API** 引入 `list.h` 將 `node_t` 結構體改寫成 : ```diff typedef struct __node { - struct __node *left, *right; - struct __node *next; + struct list_head *list; long value; } node_t; ``` 改寫 `quicksort` : ```c void quick_sort(struct list_head **head) { if (!*head || list_empty(*head)) return; int n = list_size(*head); int i = 0; int max_level = 2 * n; struct list_head *begin[max_level]; for (int i = 1; i < max_level; i++) begin[i] = list_new(); struct list_head *result = list_new(), *left = list_new(), *right = list_new(); begin[0] = *head; int stage = 0; // printf("%ld\n", list_entry(begin[0]->prev, node_t, list)->value); while (i >= 0) { fprintf(stdout, "This is %d stage -> ", stage++); if (begin[i]->next != begin[i]->prev) { fprintf(stdout, "If\n"); node_t *pivot = list_entry(begin[i]->next, node_t, list); node_t *entry, *safe; list_for_each_entry_safe (entry, safe, begin[i], list) { if (entry->value > pivot->value) { list_del(&entry->list); list_add(&entry->list, right); } else if (entry->value < pivot->value) { list_del(&entry->list); list_add(&entry->list, left); } } begin[i] = left; list_del(&pivot->list); list_add(&pivot->list, begin[i + 1]); begin[i + 2] = right; INIT_LIST_HEAD(left); INIT_LIST_HEAD(right); i += 2; } else { fprintf(stdout, "else \n"); if(!list_empty(begin[i])) list_splice_init(begin[i], result); i--; } } fprintf(stdout, "Sort Complete !\n"); for (int j = 1; j < max_level; j++) { list_free(begin[j]); } struct list_head *q = result; list_for_each(q, result){ printf("%ld\n",list_entry(q,node_t,list)->value); } // list_free(left); // show(result); // list_free(right); // show(result); *head = result; } ``` 完整程式碼請參考:[quiz1+2](https://github.com/jeremy90307/quiz1-2) ### 測驗`2` : Timsort #### Timesort 運作原理 特性: **`merge()`** ```c for (;;) { /* if equal, take 'a' -- important for sort stability */ if (cmp(priv, a, b) <= 0) { *tail = a; tail = &a->next; a = a->next; if (!a) { *tail = b; break; } } else { *tail = b; tail = &b->next; b = b->next; if (!b) { *tail = a; break; } } } ``` 逐一比較兩個已排序的鏈結串列 `a` 跟 `b` 的頭節點,若較小的值則放入 `head` 的鏈結串列的尾部,其中 `tail` 指向鏈結串列的尾部,比較直到一方指向 `NULL` ,則另一方接續 `tail` 。 **`build_prev_link()`** ```c tail->next = list; do { list->prev = tail; tail = list; list = list->next; } while (list); /* The final links to make a circular doubly-linked list */ tail->next = head; head->prev = tail; ``` 建立 `prev` 連接上個節點,使鏈結串列形成環狀雙向鏈結串列。 **`merge_final()`** 與 `merge()` 相似,但多了 `prev` 的連結,及 `build_prev_link()` ,來形成環狀雙向鏈結串列。 **`find_run()`** 在 Timsort 中用於找到已經排序好的鏈結串列並計算子序列大小 `len` ,若為降序則將其反轉。 **`merge_at()`** 在特定位置 `at` 連接兩個已排序的 run 並更新 run 大小,及減少堆疊的大小 `stk_size` 。 **`merge_force_collapse(),*merge_collapse()`** 當 run 的數量超過一定值時將進行合併,用於提升合併的效率。 來源:[測驗 2](https://hackmd.io/@sysprog/linux2024-quiz1#%E6%B8%AC%E9%A9%97-2) Timsort 採用一組堆疊 (stack) 來暫存 run,此舉是為了避免在一開始就掃描整個資料範圍來產生 run,從而降低記憶體開銷。過程中,Timsort 運用 `merge_collapse` 函式來確保堆疊上的 run 長度保持平衡。該函式主要檢查堆疊頂端的 3 個 run 是否滿足以下原則: 1. A 的長度要大於 B 和 C 的長度總和。 2. B 的長度要大於 C 的長度。 當不符合這些條件時,函式會決定是否進行合併。例如,考慮 3 段 run: A 的長度為 30,B 的長度為 20,C 的長度為 10,由於 A 的長度不大於 B 加 C 的總和(亦即 $A>B+C$),且 C 的長度小於 A,因此會選擇將 C 與 B 進行合併,形成兩個新的 run - A:30 - BC:30 **`Timesort()`** 使用 `find_run()` 函數來尋找 run ,找到 run 之後將其添加到 stack 中,同時使用 `merge_collapse()` 與 `merge_force_collapse()` 進行合併,最後使用 `build_prev_link()` 將其形成環狀鏈結串列。 #### 待完成: 1. 提出改進方案並予以實作。 2. 將改進過的 Timsort 實作整合進 sysprog21/lab0-c,作為另一個有效的排序命令,應設計效能評比的測試程式,確保測試案例符合 Timsort 設想的資料分布樣式。 ## [第二周測驗題](https://hackmd.io/@sysprog/linux2024-quiz2) ### 測驗`1` : Binary Tree Linux Kernel 裡 hash table 結構體: 代表 `hlist_head` 的頭節點 ```c struct hlist_head { struct hlist_node *first; }; ``` Linux 核心的 hash table 實作中,用以處理 hash 數值碰撞的 `hlist_node` : ```c struct hlist_node { struct hlist_node *next, **pprev; }; ``` ```graphviz digraph G { rankdir=LR; node[shape=record]; map [label="hash_table |<ht0> |<ht1>hlist_head\nfirst |<ht2> |<ht3> |<ht4> |<ht5> "]; node[shape=none] null1 [label=NULL] subgraph cluster_1 { style=filled; color=lightgrey; node [shape=record]; hn1 [label="hlist_node1 | {<prev>pprev | <next>next}"]; } subgraph cluster_2 { style=filled; color=lightgrey; node [shape=record]; hn2 [label="hlist_node2 | {<prev>pprev | <next>next}"]; } map:ht1 -> hn1 hn1:next -> hn2 hn2:next -> null1 hn2:prev:s -> hn1:next:s hn1:prev:s -> map:ht1 } ``` **`INIT_HLIST_HEAD()`** 初始化 `hlist_head` 的 `first` 使其指向 `NULL` ```c static inline void INIT_HLIST_HEAD(struct hlist_head *h) { h->first = NULL; } ``` **`hlist_add_head()`** 在 `hlist` 鏈結串列的頭插入一個新節點 ```c static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) { if (h->first) h->first->pprev = &n->next; n->next = h->first; n->pprev = &h->first; h->first = n; } ``` 參考[ hlist 数据结构图示说明](https://zhuanlan.zhihu.com/p/82375193)裡面有更完整的圖示表達插入頭節點的過程。 其中說明 `pprev` 為何要宣告為指標的指標的目的。 ![image](https://hackmd.io/_uploads/B1-xHiK6T.png) ![image](https://hackmd.io/_uploads/Hy5xSsYaT.png) ```c n->pprev = &h->first; h->first = n; ``` 新節點的 `pprev` 指向**指著自身節點的指標**,因此將 `h->first` 指向新節點時新節點的 `pprev` 也會順道更新。 圖解: ```graphviz digraph G { rankdir = LR; splines = false; node[shape = "record"] list_head[label = "<m>list_head | <n>first"] node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list]; node_new[label = "<m>hlist_node_new | {<p>pprev | <n>next}", group = list]; NULL[shape = plaintext, label = "NULL", group = list] {rank = "min" list_head} list_head -> node_1 [ weight = 10, style = invis ] list_head:n -> node_1:m; node_1:p -> list_head:n; node_1:n -> NULL; } ``` ```graphviz digraph G { rankdir = LR; node[shape = "record"] list_head[label = "<m>list_head | <n>first"] node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list]; node_new[label = "<m>hlist_node_new | {<p>pprev | <n>next}", group = list]; NULL[shape = plaintext, label = "NULL", group = list] {rank = "min" list_head} list_head -> node_1 [ weight = 10, style = invis ] list_head:n -> node_1:m; node_1:p -> node_new:n; node_new:n -> node_1:m node_1:n -> NULL; } ``` ```graphviz digraph G { rankdir = LR; node[shape = "record"] list_head[label = "<m>list_head | <n>first"] node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list]; node_new[label = "<m>hlist_node_new | {<p>pprev | <n>next}", group = list]; NULL[shape = plaintext, label = "NULL", group = list] {rank = "min" list_head} list_head -> node_1 [ weight = 10, style = invis ] list_head:n -> node_1:m; node_1:p -> node_new:n; node_new:n -> node_1:m node_new:p -> list_head:n node_1:n -> NULL; } ``` ```graphviz digraph G { rankdir = LR; splines = false; node[shape = "record"] list_head[label = "<m>list_head | <n>first"] node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list]; node_new[label = "<m>hlist_node_new | {<p>pprev | <n>next}", group = list]; NULL[shape = plaintext, label = "NULL", group = list] {rank = "min" list_head} list_head -> node_new -> node_1 [ weight = 10, style = invis ] list_head:n -> node_new:m; node_new:p -> list_head:n; node_new:n -> node_1:m; node_1:p -> node_new:n; node_1:n -> NULL; } ``` **`find()`** 在雜湊表中查找指定值的位置索引 idx,如果找到則返回該值在列表中的索引,否則返回 -1。 ```c static int find(int num, int size, const struct hlist_head *heads) { struct hlist_node *p; int hash = (num < 0 ? -num : num) % size; hlist_for_each (p, &heads[hash]) { struct order_node *on = list_entry(p, struct order_node, node); if (num == on->val) return on->idx; } return -1; } ``` 其中 `int hash = (num < 0 ? -num : num) % size;` 計算指定數值 num 的哈希值,並將其與哈希列表的大小取餘數,以確保得到的哈希值在合法範圍內。 `hlist_for_each (p, &heads[hash])` 訪問雜湊表中特定的 `bucket` 若找到對應 num 則回傳對應的位置索引,若無回傳 -1 。 Hash Table 相關資料 : [資料結構學習筆記:雜湊表(Hash Table)](https://medium.com/@ralph-tech/%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-%E9%9B%9C%E6%B9%8A%E8%A1%A8-hash-table-15f490f8ede6) [Linux 核心的 hash table 實作](https://hackmd.io/rpIi8FeqQ9eBJ-UTFgb--A?view#%E6%A6%82%E6%B3%81) **`struct TreeNode *dfs()`** 由 `inorder` / `preorder` 重建二元樹。 由前序( preorder )可知哪個節點在上面 (越前面的越上面)、而由中序( inorder )可知哪個節點靠左(越前面的越左邊),於是可得知前序的第一個元素一定是根,且該元素對應到中序後,左邊的值就在左邊,右邊的值就在右邊,在從右邊的值對應 preorder 即可找出頂點,以此類推可以建構出樹。 遞迴的中止條件是在 inorder 中找不到對應元素。 ```c static struct TreeNode *dfs(int *preorder, int pre_low, int pre_high, int *inorder, int in_low, int in_high, struct hlist_head *in_heads, int size) { if (in_low > in_high || pre_low > pre_high) return NULL; struct TreeNode *tn = malloc(sizeof(*tn)); tn->val = preorder[pre_low]; int idx = find(preorder[pre_low], size, in_heads); tn->left = dfs(preorder, pre_low + 1, pre_low + (idx - in_low), inorder, in_low, idx - 1, in_heads, size); tn->right = dfs(preorder, pre_high - (in_high - idx - 1), pre_high, inorder, idx + 1, in_high, in_heads, size); return tn; } ``` **`node_add()`** 將新的節點添加到哈希列表中,以便後續可以根據特定的值快速查找到該節點。 ```c static inline void node_add(int val, int idx, int size, struct hlist_head *heads) { struct order_node *on = malloc(sizeof(*on)); on->val = val; on->idx = idx; int hash = (val < 0 ? -val : val) % size; hlist_add_head(&on->node, &heads[hash]); } ``` **` struct TreeNode *buildTree()`** 先將 inoreder 節點建立 hash table ,再傳回 `dfs()` 函式 來建樹。 ```c static struct TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { struct hlist_head *in_heads = malloc(inorderSize * sizeof(*in_heads)); for (int i = 0; i < inorderSize; i++) INIT_HLIST_HEAD(&in_heads[i]); for (int i = 0; i < inorderSize; i++) node_add(inorder[i], i, inorderSize, in_heads); return dfs(preorder, 0, preorderSize - 1, inorder, 0, inorderSize - 1, in_heads, inorderSize); } ``` 延伸問題: 1. 撰寫完整的測試程式,指出其中可改進之處並實作 - 測試內容 : 2. 在 Linux 核心的 cgroups 相關原始程式碼,找出 pre-order walk 程式碼並探討 - 參考 Linux kernel 裡的 [cgroup.c](https://github.com/torvalds/linux/blob/master/kernel/cgroup/cgroup.c) 中搜尋 `pre-order walk` 會找到這段: ```c struct cgroup_subsys_state * css_next_descendant_pre(struct cgroup_subsys_state *pos, struct cgroup_subsys_state *root) { struct cgroup_subsys_state *next; cgroup_assert_mutex_or_rcu_locked(); /* if first iteration, visit @root */ if (!pos) return root; /* visit the first child if exists */ next = css_next_child(NULL, pos); if (next) return next; /* no child, visit my or the closest ancestor's next sibling */ while (pos != root) { next = css_next_child(pos, pos->parent); if (next) return next; pos = pos->parent; } return NULL; } ``` 在了解這段程式碼之前先來了解什麼是 `cgroups` ,其全名為 control groups ,他是 Linux Kernel 中的功能,可以用來限制,控制與隔離 Process 所使用到的系統資源,例如 CPU, Memory, Disk I/O, Network…等。 > 參考來源:[第一千零一篇的 cgroups 介紹](https://medium.com/starbugs/%E7%AC%AC%E4%B8%80%E5%8D%83%E9%9B%B6%E4%B8%80%E7%AF%87%E7%9A%84-cgroups-%E4%BB%8B%E7%B4%B9-a1c5005be88c) 以 `root` 自身當成第一個走訪的節點`next = css_next_child(NULL, pos)`,若子節點存在走訪相鄰子節點 `next = css_next_child(pos, pos->parent)` ,若子節點不存在則尋先前的 root 走訪 `pos = pos->parent` 。 ### 測驗`2` : LRU Cache Leetcode : [146. LRU Cache](https://leetcode.com/problems/lru-cache/description/) LRU 說明:[Least recently used (LRU)](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)) 由於 `hlist` 大致邏輯相同於`lab0-c 的 list.h` ,因此直接探討 LRU Cache 的部份 LRU 結構體 ```c typedef struct { int capacity; int count; struct list_head dhead; struct hlist_head hhead[]; } LRUCache; typedef struct { int key; int value; struct hlist_node node; struct list_head link; } LRUNode; ``` `dhead` : 雙向鏈結串列的頭節點 `hhead[]` : 一個 `hash` 頭節點的數 `node` : 用於將緩存項目連接到 `hash` `link` : 用於將此緩存項目連接到雙向鏈結的結構 **`lRUCacheCreate()`** 開頭 `int capacity` 表 Cache 的最大容量 `INIT_LIST_HEAD(&cache->dhead)` 初始化雙向鏈結串列 `INIT_HLIST_HEAD(&cache->hhead[i])` 這個循環初始化了哈希表的各個 bucket 的頭部,使其成為空的哈希列表 ```c LRUCache *lRUCacheCreate(int capacity) { LRUCache *cache = malloc(2 * sizeof(int) + sizeof(struct list_head) + capacity * sizeof(struct list_head)); cache->capacity = capacity; cache->count = 0; INIT_LIST_HEAD(&cache->dhead); for (int i = 0; i < capacity; i++) INIT_HLIST_HEAD(&cache->hhead[i]); return cache; } ``` **`lRUCacheFree()`** 用 `LRUNode *cache = list_entry(pos, LRUNode, link)` 獲取該LRU節點的結構,並做釋放的動作。 ```c void lRUCacheFree(LRUCache *obj) { struct list_head *pos, *n; list_for_each_safe (pos, n, &obj->dhead) { LRUNode *cache = list_entry(pos, LRUNode, FFFF); list_del(GGGG); free(cache); } free(obj); } ``` `FFFF` 為 `link`,`GGGG` 為 `&cache->link` **`lRUCacheGet()`** 與測驗`1`的 `find()` 函式相似, hash 透過 `hash = key % obj->capacity` 取得,在用 `hlist_for_each()` 走訪 hash 值對應的 `hhead[hash]` 鏈結串列,若有找到對應的 key 值將及節點放到雙向鏈結串列的頭並回傳該節點的 `value` 。 ```c int lRUCacheGet(LRUCache *obj, int key) { int hash = key % obj->capacity; struct hlist_node *pos; hlist_for_each (pos, &obj->hhead[hash]) { LRUNode *cache = list_entry(pos, LRUNode, HHHH); if (cache->key == key) { list_move(IIII, &obj->dhead); return cache->value; } } return -1; } ``` `HHHH` 為 `node` , `IIII` 為 `&cache->link` **`lRUCachePut()`** 在訪問每個節點的過程若找到對應的 key 使用 `list_move(KKKK, &obj->dhead)` 將該節點移動至雙向鏈結串列的頭,即最近使用過。 若 `cache = NULL` 表示 Cache Miss ,然後有兩種補同情況的處理方式: 1. 若 **Cache 已滿**將雙向鏈結串列 `dhead` 的最尾端節點刪除 ,並將節點插到 `hhead` 的頭部 2. 若 **Cache 還沒滿**,創建一個新節點,並將其加到 `hhead` 的頭部,同時添加到 `dhead` 的前面 ```c void lRUCachePut(LRUCache *obj, int key, int value) { LRUNode *cache = NULL; int hash = key % obj->capacity; struct hlist_node *pos; hlist_for_each (pos, &obj->hhead[hash]) { LRUNode *c = list_entry(pos, LRUNode, JJJJ); if (c->key == key) { list_move(KKKK, &obj->dhead); cache = c; } } if (!cache) { if (obj->count == obj->capacity) { cache = list_last_entry(&obj->dhead, LRUNode, link); list_move(&cache->link, &obj->dhead); hlist_del(&cache->node); hlist_add_head(&cache->node, &obj->hhead[hash]); } else { cache = malloc(sizeof(LRUNode)); hlist_add_head(&cache->node, &obj->hhead[hash]); list_add(&cache->link, &obj->dhead); obj->count++; } cache->key = key; } cache->value = value; } ``` `JJJJ` 為 `node` , `KKKK` 為 `&c->link` #### 延伸問題 1. 撰寫完整的測試程式,指出其中可改進之處並實作 - 測試內容:[LRUCacheTest](https://github.com/jeremy90307/quiz1-2/tree/main/LRUCache) 3. 在 Linux 核心找出 LRU 相關程式碼並探討 ### 測驗`3` : find_nth_bit 參考: [yu-hsiennn](https://hackmd.io/@yuu907/Sk7txXNaa#%E6%B8%AC%E9%A9%97-3) 的測驗`3` `BITMAP_LAST_WORD_MASK(nbits)` : 利用 mask 將不用的位元過濾,因電腦的未有可能為 32-bit 或 64-bit `__const_hweight64(w)`:計算 Hamming 權重,及二進位有幾個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))))) #define __const_hweight16(w) (__const_hweight8(w) + __const_hweight8((w) >> 8)) #define __const_hweight32(w) \ (__const_hweight16(w) + __const_hweight16((w) >> 16)) #define __const_hweight64(w) \ (__const_hweight32(w) + __const_hweight32((w) >> 32)) ``` **`__ffs`** ```c static inline unsigned long __ffs(unsigned long word) { int num = 0; #if BITS_PER_LONG == 64 if ((word & AAAA) == 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; } ``` `AAAA` 為 `0xffffffff` 舉例說明: ``` word = 0b1010101000000000 num = 0 經過 if ((word & 0xff) == 0) 成立 word = 0b10101010 num = 8 經過 if ((word & 0x1) == 0) 成立 num = 9 // __ffs = 9 最終答案 ``` **`__clear_bit`** 巨集 `BIT_MASK(nr)` 為只有第 nr 個 bit 為 1 的二進位值 巨集 `BIT_WORD(nr)` 位元位置在 `BITS_PER_LONG` 之內 若要指定清除 `*p` 指定的第 `nr` 個 bit 只須將 `mask` 取反,及 `~mask` ```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 &= BBBB; } ``` `BBBB` 為 `~mask` **`fns`** 基於 `__ffs` 可以求出第 n 個被 set 過的 bit 的所在位置。 ```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; } ``` 舉例說明: ``` fns(1010101, 4) word = 1010101 n = 4 -- 第一次迭代 -- n = 4 bit = 2 word = 1010100 -- 第二次迭代 -- n = 3 bit = 4 word = 1010000 -- 第三次迭代 -- n = 1 bit = 6 word = 1000000 -- 第四次迭代 -- n = 0 bit = 6 return 6 ``` 延伸問題: 1. 在 Linux 核心原始程式碼找出 find_nth_bit 的應用案例,並予以解說和分析,需要涵蓋 CPU affinity 相關的原始程式碼。 -