# 2024q1 Homework2 (quiz1+2) contributed by < [p96114175](https://github.com/p96114175) > [2024q1 第 1 週測驗題](https://hackmd.io/@sysprog/linux2024-quiz1) [2024q1 第 2 週測驗題](https://hackmd.io/@sysprog/linux2024-quiz2) # quiz2(第二週) ## test 2 以下程式為 LRU Cache 的實現 實現 LRU Cache 所需的資料結構,如下 ```c typedef struct { int capacity; int count; struct list_head dhead; struct hlist_head hhead[]; } LRUCache; ``` `capacity` 表示該 cache 最大的容量 `count` 表示目前該 cache 的容量 ```c typedef struct { int key; int value; struct hlist_node node; struct list_head link; } LRUNode; ``` **lRUCacheCreate** 1. 採用 `lRUCacheCreate` 來為 Cache 分配記憶體以及相關參數初始化。 2. 應用 `cache->capacity = capacity` 來告訴 cache 的最大容量為多少。`cache->count = 0` 表示該 Cache 目前為空。 3. `INIT_LIST_HEAD(&cache->dhead)` 建立了一個雙向鏈結串列,`其中next` 和 `prev` 指標指向自身。 4. 接下來根據 capacity 的大小,遍尋 hhead[] 逐個初始化 hlist_head 以利之後可以執行插入和刪除操作。 ``` 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** 應用 list_for_each_safe 走訪每一個 LRUNode,並將其相關的記憶體釋放。當釋放完全部的 LRUNode 後,將 LRUCache 的記憶體也釋放。 ``` 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); } ``` **lRUCacheGet** 1. 透過 `key % obj->capacity` 求出 hash 值。 2. 接著應用 `hlist_for_each (pos, &obj->hhead[hash])` 來走訪 hhead[hash] 的 pos,再對 pos 應用 list_entry 巨集找出 LRUNode。 3. 比對該 LRUNode 的 key 值是否等於我們所尋找的 key 值。如果找到了,便會將該 LRUNode 搬移至 `&obj->dhead` 的下一個節點。從中可以知道 dhead 維護著 LRUNode 的更新順序,離 dhead 越遠的 LRUNode 意味著,越久沒有被訪問。 ```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(&cache->link, &obj->dhead); return cache->value; } } return -1; } ``` **lRUCachePut** 在對 Cache 執行 Put 操作前,會先對 hhead 中每一個 LRUNode 走訪,確認是否有該 key 值存在於 hhead 中。如果有找到便直接返回該 cache 值。 ``` 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; } } ... cache->value = value; } ``` 假設在 hhead 中 key 值不存在,該 cache 為 NULL,便會通過 ` if (!cache)`接下來存在兩種情況。 情況一,Cache 的 capacity ==滿了==。接著便會找到離 dhead 最遠的 LRUNode(表示最少被使用到) 搬移至 dhead 旁,使其成為 dhead 的下一個節點,然後讓 `&obj->hhead[hash]` 取代 LRUNode(表示最少被使用到),表示為最近被訪問到的 LRUNode。 ```c 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]); ``` 情況二,Cache 的 capacity ==沒有滿==。將目前要加入的 cache 新增至 `&obj->hhead[hash]` 中,並將位置移動到 dhead 的下一個節點,表示為最近被訪問到的 LRUNode。同時將當前 count 加一。 ```c cache = malloc(sizeof(LRUNode)); hlist_add_head(&cache->node, &obj->hhead[hash]); list_add(&cache->link, &obj->dhead); obj->count++; ``` ## test 3 這一個則是在找出 word 中的第一個 bit。我們可以觀察到該程式並非採取逐個 bit 取確認,然後跑完 64*O(1),這樣沒效率的方式。而是在每一次判斷中,採用 binary search 的邏輯,每一次會確認 word 的一半,然後將確認完的部分做 shift right,方便下一個 if 判斷式的執行。這個程式每次執行皆為固定時間。 假設輸入為 $00000000000000001000000000001000$ 1.`if ((word & 0xffffffff) == 0)`,==00000000000000001000000000001000==,num=0 2.`if ((word & 0xffff) == 0)`,0000000000000000==1000000000001000==,num=0 3.`if ((word & 0xff) == 0)`,000000000000000010000000==00001000==,num=0 4.`if ((word & 0xf) == 0)`,0000000000000000100000000000==1000==,num=0 5.`if ((word & 0x3) == 0)`,000000000000000010000000000010==00==,num=2 6.`if ((word & 0x1) == 0)`,00000000000000001000000000001==0==00,num=3 ```c /* find first bit in word. * @word: The word to search * * Undefined if no bit exists, so code should check against 0 first. */ static inline unsigned long __ffs(unsigned long word) { int num = 0; #if BITS_PER_LONG == 64 if ((word & 0xffffffff) == 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; } ``` 1. 透過將 nr 輸入至 BIT_MASK 找出我們需要的 mask(裡面記錄著哪些 bit 要進行 clean)。 2. addr 則透過 BIT_WORD 來找出 `*p &= BBBB` 所需的運算元。 3. 在 *p &= ~mask 時,對 mask 取反,然後對著 p 中做 AND operation,這樣就可以對特定位置的 bit 做 clean 了。打個比方,假設 maks 為 0001,在 ~mask 後 就可以得到 1110,你可以觀察到 ~mask 中 0 bit 所在的位置,便是準備將 bit 進行 clean 的地方,如果你的 *p 為 1111 ,那麼在進行 clean 操作後,*p 應為 1110。 ```c #define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) ``` ```c #define BIT_WORD(nr) ((nr) / BITS_PER_LONG) ``` ```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 &= ~mask; } ``` fns 該程式主要是在找 word 中第 n 個 set bit。這裡我舉個例子做說明,假設你 word 如下 ```c void main() { unsigned long word = 0b00000000000000001000001000001000; printf("%ld", fns(word, 0)); } ``` 那麼你可以得到以下輸出結果為 3 ,這個 3 表示LSB set 設為 1 的 index,這部分可以參考 [Find first set wiki](https://en.wikipedia.org/wiki/Find_first_set) 的說明 > find first set (ffs) or find first one is a bit operation that, given an unsigned machine word,[nb 1] designates the index or position of the least significant bit set to one in the word counting from the least significant bit position. 後續我將 n 設為 1 和 2 及 3 ,依序可以得到 9, 15, 64 從程式碼觀察你可以發現一件事,假設你的 word 為 $0b00000000000000001000001000001000$,n = 1,在 while loop 第一次的時候,bit 為 3,然後將 n 減一,接著對該 bit 執行 `__clear_bit(bit, &word)` 進行 clean。在 while loop 第二次的時候,bit 為 9 而不是 3,這是因為 3 已經被 clean out,此時 n=0,判斷成立,接著 return bit。 :::info 該程式我認為存在一個耗時問題,當你為你的 n 指定值時,勢必要跑過 n+1 次 __ffs function,但我還在思考有什麼方法可以直接做到跟 fns 一樣的效果。 ::: ```c= /* find N'th set bit in a word * @word: The word to search * @n: Bit to find */ 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; } ```