蔡忠翰
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 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 相關的原始程式碼。 -

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully