--- tags: linux2024 --- # [2024q1](http://wiki.csie.ncku.edu.tw/linux/schedule) 第 2 週測驗題 :::info 目的: 檢驗學員對 hash table 和 bitwise 的認知 ::: ==[作答表單: 測驗 1-2](https://docs.google.com/forms/d/e/1FAIpQLSfKFgOnbeZhnAk0QBim2iyWdO8kip8k8tqJtPK0i5zJCc7syA/viewform)== (針對 Linux 核心「設計」課程) ==[作答表單: 測驗 3](https://docs.google.com/forms/u/1/d/e/1FAIpQLSfi6D946zz-S0vjuq3m2Ih8F2MeC-_5UwYkZaBrt38AHpT8pg/viewform)== (針對 Linux 核心「實作」課程) ### 測驗 `1` LeetCode [105 Construct Binary Tree from Preorder and Inorder Traversal](https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal) 題意: > 給定由二個由整數構成陣列的前序 (preorder) 和中序 (inorder) 形式,分別是對同一棵二元樹的前序及中序走訪結果,藉此重建此二元樹。 範例 * Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] * Output: [3,9,20,null,null,15,7] ![image](https://hackmd.io/_uploads/rJDqPach6.png) 由前序可知哪個節點在上面 (越前面的越上面)、而由中序可知哪個節點靠左(越前面的越左邊),於是可得知前序的第一個元素一定是 root 的值,且該元素對應到中序後,左邊的值就在左邊,右邊的值就在右邊。 ![image](https://hackmd.io/_uploads/rylsvTc26.png) 接著就將 left 和 right 分別當作新的 root node 下去做即可。遞迴的中止條件是在 inorder 中找不到對應元素。 > 延伸閱讀: [LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal](https://hackmd.io/@Zero871015/LeetCode-105) 以下是運用 [Linux 核心的 hash table 實作](https://hackmd.io/@sysprog/linux-hashtable) 的可能解法:(部分遮蔽) ```c #include <stdio.h> #include <stdlib.h> #define container_of(ptr, type, member) \ ((type *) ((char *) (ptr) - (size_t) & (((type *) 0)->member))) #define list_entry(ptr, type, member) container_of(ptr, type, member) #define hlist_for_each(pos, head) \ for (pos = (head)->first; pos; pos = pos->next) struct hlist_node { struct hlist_node *next, **pprev; }; struct hlist_head { struct hlist_node *first; }; static inline void INIT_HLIST_HEAD(struct hlist_head *h) { h->first = NULL; } static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) { if (h->first) h->first->pprev = &n->next; n->next = AAAA; n->pprev = &h->first; h->first = n; } struct TreeNode { int val; struct TreeNode *left, *right; }; struct order_node { struct hlist_node node; int val; int idx; }; 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, BBBB) { struct order_node *on = CCCC(p, struct order_node, node); if (num == on->val) return on->idx; } return -1; } 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; } 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, DDDD); } 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); } ``` 假設 `malloc` 總是成功。請補完程式碼,使其得以符合預期運作。作答規範: * 全部應以最簡潔的形式撰寫,避免空白字元 :::success 延伸問題: 1. 解釋上述程式碼的運作,撰寫完整的測試程式,指出其中可改進之處並實作 2. 在 Linux 核心的 cgroups 相關原始程式碼,找出 pre-order walk 程式碼並探討 ::: --- ### 測驗 `2` 針對 LeetCode [146. LRU Cache](https://leetcode.com/problems/lru-cache/),以下是 [Least Recently Used](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)) (LRU) 可能的合法 C 程式實作: ```c #include <stdbool.h> #include <stdlib.h> #define container_of(ptr, type, member) \ ((type *) ((char *) (ptr) - (size_t) & (((type *) 0)->member))) #define list_entry(ptr, type, member) container_of(ptr, type, member) #define hlist_for_each(pos, head) \ for (pos = (head)->first; pos; pos = pos->next) #define hlist_for_each_safe(pos, n, head) \ for (pos = (head)->first; pos && ({ \ n = pos->next; \ true \ }); \ pos = n) #define list_first_entry(ptr, type, field) list_entry((ptr)->next, type, field) #define list_last_entry(ptr, type, field) list_entry((ptr)->prev, type, field) #define list_for_each(p, head) for (p = (head)->next; p != head; p = p->next) #define list_for_each_safe(p, n, head) \ for (p = (head)->next, n = p->next; p != (head); p = n, n = p->next) struct hlist_node; struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, **pprev; }; void INIT_HLIST_HEAD(struct hlist_head *h) { h->first = NULL; } 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; } void hlist_del(struct hlist_node *n) { struct hlist_node *next = n->next, **pprev = n->pprev; *pprev = next; if (next) EEEE = pprev; } struct list_head { struct list_head *next, *prev; }; void INIT_LIST_HEAD(struct list_head *list) { list->next = list->prev = list; } void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } void list_add(struct list_head *_new, struct list_head *head) { __list_add(_new, head, head->next); } void __list_del(struct list_head *entry) { entry->next->prev = entry->prev; entry->prev->next = entry->next; } void list_del(struct list_head *entry) { __list_del(entry); entry->next = entry->prev = NULL; } void list_move(struct list_head *entry, struct list_head *head) { __list_del(entry); list_add(entry, head); } 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; 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; } 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); } 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; } 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; } ``` 假設 `malloc` 總是成功。請補完程式碼,使其得以符合預期運作。作答規範: * 全部應以最簡潔的形式撰寫,避免空白字元 :::success 延伸問題: 1. 解釋上述程式碼的運作,撰寫完整的測試程式,指出其中可改進之處並實作 2. 在 Linux 核心找出 LRU 相關程式碼並探討 ::: --- ### 測驗 `3` 考慮 `find_nth_bit` 可在指定的記憶體空間找出第 N 個設定的位元 (即 `1`),程式碼實作如下: ```c #include <assert.h> #include <stdint.h> /* Assume 64-bit architecture */ #define BITS_PER_LONG 64 #define BITMAP_LAST_WORD_MASK(nbits) (~0UL >> (-(nbits) & (BITS_PER_LONG - 1))) #define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) #define BIT_WORD(nr) ((nr) / BITS_PER_LONG) #define DIV_ROUND_UP(n, d) (((n) + (d) -1) / (d)) #define BITS_PER_BYTE 8 #define BITS_PER_TYPE(type) (sizeof(type) * BITS_PER_BYTE) #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(long)) #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)) static inline unsigned long hweight_long(unsigned long w) { return __const_hweight64(w); } #define min(x, y) (x) < (y) ? (x) : (y) #define FIND_NTH_BIT(FETCH, size, num) \ ({ \ unsigned long sz = (size), nr = (num), idx, w, tmp; \ \ for (idx = 0; (idx + 1) * BITS_PER_LONG <= sz; idx++) { \ if (idx * BITS_PER_LONG + nr >= sz) \ goto out; \ \ tmp = (FETCH); \ w = hweight_long(tmp); \ if (w > nr) \ goto found; \ \ nr -= w; \ } \ \ if (sz CCCC BITS_PER_LONG) \ tmp = (FETCH) & BITMAP_LAST_WORD_MASK(sz); \ found: \ sz = min(idx * BITS_PER_LONG + fns(tmp, nr), sz); \ out: \ sz; \ }) /* 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 & 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; } 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; } /* 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; } #define small_const_nbits(nbits) \ (__builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG && (nbits) > 0) #define GENMASK(h, l) \ (((~0UL) - (1UL << (l)) + 1) & (~0UL >> (BITS_PER_LONG - 1 - (h)))) /* find N'th set bit in a memory region * @addr: The address to start the search at * @size: The maximum number of bits to search * @n: The number of set bit, which position is needed, counting from 0 * * The following is semantically equivalent: * idx = find_nth_bit(addr, size, 0); * idx = find_first_bit(addr, size); * * Returns the bit number of the N'th set bit. * If no such, returns @size. */ static inline unsigned long find_nth_bit(const unsigned long *addr, unsigned long size, unsigned long n) { if (n >= size) return size; if (small_const_nbits(size)) { unsigned long val = *addr & GENMASK(size - 1, 0); return val ? fns(val, n) : size; } return FIND_NTH_BIT(addr[idx], size, n); } ``` 補完程式碼,使其符合預期。 作答規範: * AAAA 為 hexadecimal integer literal,以 `0x` 開頭的 16 進位數值,小寫 * BBBB 為表示式 * CCCC 為運算子 (operator) * 以最精簡的方式撰寫,不包含空白 :::success 延伸問題: 1. 解釋上述程式碼的運作原理 2. 在 Linux 核心原始程式碼找出 `find_nth_bit` 的應用案例,並予以解說和分析,需要涵蓋 CPU affinity 相關的原始程式碼。 :::