# **2024q1 Homework2 (quiz1+2)**
contributed by [<YeeeLiang](https://github.com/YeeeLiang/2024HW2)>
## **第一周 測驗1**
```diff
node_t *list_tail(node_t **left)
{
while ((*left) && (*left)->next)
- left = &(AAAA);
+ left = &((*left)->next);
return *left;
}
int list_length(node_t **left)
{
int n = 0;
while (*left) {
++n;
- left = &(BBBB);
+ left = &((*left)->next);
}
return n;
}
```
### 延伸問題
#### 1. 原始程式碼的解釋:
* 節點結構 (node_t):
代表鏈結串列中的一個節點。
包含指向左右子節點,以及一個指向下一個節點的指標。
* 串列操作函數:
**list_add:** 在串列的開頭添加一個節點。
**list_tail:** 返回串列的尾部。
**list_length:** 返回串列的長度。
**list_construct:** 建構一個新節點並將其添加到串列的開頭。
**list_free:** 釋放串列佔用的記憶體。
* quick_sort:
使用輔助堆疊(begin 和 end)實現快速排序的非遞迴,來跟蹤子串列。
* main:
創建一個整數陣列,對其進行洗牌,構建一個鏈結串列,使用快速排序進行排序。
#### 2. 改寫的程式碼:
```c
#include <linux/list.h>
#include <linux/kernel.h>
typedef struct __node {
struct list_head list;
long value;
} node_t;
void quick_sort(struct list_head *list);
void print_list(struct list_head *list);
int main(void) {
LIST_HEAD(my_list);
quick_sort(&my_list);
print_list(&my_list);
return 0;
}
void quick_sort(struct list_head *list) {
}
void print_list(struct list_head *list) {
}
```
## **第一周 測驗2**
```diff
struct list_head *head;
- struct list_head **tail = AAAA;
+ struct list_head **tail = &(head->next);
for (;;) {
if (cmp(priv, a, b) <= 0) {
*tail = a;
- tail = BBBB;
+ tail = &((*tail)->next);
a = a->next;
if (!a) {
*tail = b;
break;
}
} else {
*tail = b;
- tail = CCCC;
+ tail = &((*tail)->next);
b = b->next;
if (!b) {
*tail = a;
break;
}
}
}
return head;
}
```
```diff
- DDDD = head;
- EEEE = tail;
+ (stk1->prev) = head
+ (stk0->prev) = tail;
```
### 延伸問題
* run_size 函數:計算當前非遞減順序的元素序列(run)的大小。
* merge 函數:將兩個run合併為一個。
* build_prev_link 函數:為合併後的run建立前一個連結。
* merge_final 函數:在合併主run後,合併剩餘的runs。
* find_run 函數:在輸入列表中找到下一個run。
* merge_at 函數:在指定位置合併兩個相鄰的runs。
* merge_force_collapse 函數:強制折疊,不斷合併相鄰的runs。
* merge_collapse 函數:根據設定條件合併runs。
* timsort 函數:協調整個Timsort演算法,查找並合併runs,直到堆疊被折疊。
* 改進後程式碼:
```c
static struct list_head *merge(void *priv,
list_cmp_func_t cmp,
struct list_head *a,
struct list_head *b)
{
struct list_head *head = malloc(sizeof(struct list_head));
struct list_head **tail = &(head->next);
return head;
}
```
## **第二周 測驗1**
```diff
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->next = h->first;
n->pprev = &h->first;
h->first = n;
}
```
```diff
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) {
+ hlist_for_each (p, head) {
- struct order_node *on = CCCC(p, struct order_node, node);
+ struct order_node *on = container_of(p, struct order_node, node);
if (num == on->val)
return on->idx;
}
return -1;
}
```
```diff
{
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);
+ hlist_add_head(&on->node, heads[hash]);
}
```
### 延伸問題
#### 1.
這段程式碼主要是用在構建二叉樹,基於給定的先序遍歷(preorder)和中序遍歷(inorder)序列,通過深度優先搜索(DFS)的方式構建對應的二叉樹。其中,使用了哈希表(Hash table)和鍊錶(linked lists)的概念,來實現尋找元素在中序序列中的索引功能。
程式碼中的主要結構和功能包括:
* 定義一個單向鏈錶的節點結構 **struct hlist_node** 和一個哈希鏈錶的頭結構 **struct hlist_head**。
* 定義一個二叉樹節點的結構 **struct TreeNode**。
* 使用了一個哈希鏈錶結構 **struct order_node** 來保存中序序列中元素的值、索引,並連接到哈希鏈錶中。
* 構建二叉樹的主函數 **buildTree**,可初始化哈希鏈錶,並調用 **dfs** 開始構建二叉樹。**dfs** 為使用 DFS 遞迴構建二叉樹的函數 。
```c
#include <stdio.h>
int main() {
int preorder[] = {3, 9, 20, 15, 7};
int inorder[] = {9, 3, 15, 20, 7};
int preorderSize = sizeof(preorder) / sizeof(preorder[0]);
int inorderSize = sizeof(inorder) / sizeof(inorder[0]);
struct TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize);
return 0;
}
```
* 可改進之處:
1. 程式碼中使用了哈希值計算索引的方式,可以考慮使用現有的哈希函數,而非簡單的 (num < 0 ? -num : num) % size。
2. 記憶體管理:在程式中使用了 **malloc** 分配內存,但缺乏相應的 free 釋放內存的操作。
3. 沒有處理例外情況:程式碼沒有處理 **malloc** 失敗的情況,應該在分配內存後,設置檢查是否成功。
#### 2.
**cgroups**(Control Groups)是 Linux 內核中的一個機制,用於限制、分類和監控進程及其資源的使用。cgroup 組成一個樹狀結構,而其中每個節點代表一個 cgroup。**Pre-order walk**是一種樹狀結構中的走訪方式,它會先訪問父節點,然後再訪問子節點。
**pre-order walk** 程式碼:
```c
// kernel/cgroup/cgroup.c
#include <linux/cgroup.h>
static void cgroup_pre_order(struct cgroup_root *root,
void (*proc)(struct cgroup *, void *),
void *data)
{
struct cgroup *cgrp, *child;
rcu_read_lock();
proc(&root->top_cgroup, data);
list_for_each_entry(cgrp, &root->top_cgroup.children, sibling) {
proc(cgrp, data);
list_for_each_entry(child, &cgrp->children, sibling)
proc(child, data);
}
rcu_read_unlock();
}
EXPORT_SYMBOL_GPL(cgroup_pre_order);
```
## **第二周 測驗2**
```diff
struct hlist_node *next = n->next, **pprev = n->pprev;
*pprev = next;
if (next)
- EEEE = pprev;
+ pprev = pprev;
```
```diff
void lRUCacheFree(LRUCache *obj)
{
struct list_head *pos, *n;
list_for_each_safe (pos, n, &obj->dhead) {
- LRUNode *cache = list_entry(pos, LRUNode, FFFF);
+ LRUNode *cache = list_entry(pos, LRUNode, link);
- list_del(GGGG);
+ list_del(link);
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);
+ LRUNode *cache = list_entry(pos, LRUNode, node);
if (cache->key == key) {
- list_move(IIII, &obj->dhead);
+ list_move(&cache->link, &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);
+ LRUNode *c = list_entry(pos, LRUNode, node);
if (c->key == key) {
- list_move(KKKK, &obj->dhead);
+ list_move(&cache->link, &obj->dhead);
cache = c;
}
}
```
### 延伸問題
#### 1.
雙向鏈表(struct list_head):
* 用於根據元素的使用情況維護緩存中元素的順序。
* INIT_LIST_HEAD:初始化一個空的雙向鏈表。
* __list_add:在指定的元素之後將新元素添加到鏈表中。
* __list_del:從鏈表中刪除元素。
哈希表(struct hlist_head):
* 用於根據鍵快速訪問緩存中的元素。
* INIT_HLIST_HEAD:初始化一個空的哈希表。
* hlist_add_head:將新元素添加到哈希表中。
* hlist_del:從哈希表中刪除元素。
LRUCache 結構:
* 維護一個雙向鏈表(dhead)來跟蹤元素的使用順序。
* 使用哈希表數組(hhead)以便高效地根據鍵訪問緩存元素。
* LRUNode 結構表示緩存中的鍵值對,具有雙向鏈表節點和哈希表節點。
LRUCache 操作:
* lRUCacheCreate:初始化並分配 LRUCache 結構的內存。
* lRUCacheFree:釋放 LRUCache 結構及其元素使用的內存。
* lRUCacheGet:從緩存中檢索與給定鍵相關聯的值。
* lRUCachePut:在緩存中插入或更新鍵值對。
改進的地方:
* 在 **lRUCachePut** 中的 **list_move** 函數存在潛在問題:因為在為 cache 分配值之前,它就已被移動。這可能導致未定義的行為。應該在分配值之前移動項目(&c->link)而不是 &cache->link。
* **lRUCacheFree**函數使用**list_entry** 從列表中訪問 LRUNode,但雙向鏈表的用法為 **list_entry**。故應該更改為**list_entry**。
```c
#include <stdio.h>
int main() {
LRUCache *cache = lRUCacheCreate(3);
lRUCachePut(cache, 1, 1);
lRUCachePut(cache, 2, 2);
lRUCachePut(cache, 3, 3);
printf("Get key 2: %d\n", lRUCacheGet(cache, 2));
lRUCachePut(cache, 4, 4);
printf("Get key 1: %d\n", lRUCacheGet(cache, 1));
lRUCacheFree(cache);
return 0;
}
```
#### 2.
在 Linux 核心的虛擬記憶體管理和緩存中,LRU 策略的實現是系統性能的關鍵部分。這些代碼確保了對於使用頻率較低的頁面進行有效管理,以最大程度地減少對物理內存和磁盤 I/O 的需求。
LRU相關程式碼:
```c
#include <stdio.h>
#include <stdlib.h>
#define CACHE_SIZE 3
typedef struct {
int key;
int value;
} CacheItem;
typedef struct {
CacheItem *cacheArray;
int *order;
int size;
int count;
} LRUCache;
LRUCache *initCache(int size) {
LRUCache *cache = (LRUCache *)malloc(sizeof(LRUCache));
cache->cacheArray = (CacheItem *)malloc(size * sizeof(CacheItem));
cache->order = (int *)malloc(size * sizeof(int));
cache->size = size;
cache->count = 0;
for (int i = 0; i < size; i++) {
cache->order[i] = -1;
}
return cache;
}
int findItem(LRUCache *cache, int key) {
for (int i = 0; i < cache->size; i++) {
if (cache->cacheArray[i].key == key) {
return i;
}
}
return -1;
}
void updateOrder(LRUCache *cache, int index) {
for (int i = 0; i < cache->size; i++) {
if (cache->order[i] >= 0) {
cache->order[i]++;
}
}
cache->order[index] = 0;
}
void addItem(LRUCache *cache, int key, int value) {
if (cache->count >= cache->size) {
int maxOrderIndex = 0;
for (int i = 1; i < cache->size; i++) {
if (cache->order[i] > cache->order[maxOrderIndex]) {
maxOrderIndex = i;
}
}
cache->cacheArray[maxOrderIndex].key = key;
cache->cacheArray[maxOrderIndex].value = value;
updateOrder(cache, maxOrderIndex);
} else {
int index = cache->count;
cache->cacheArray[index].key = key;
cache->cacheArray[index].value = value;
updateOrder(cache, index);
cache->count++;
}
}
int get(LRUCache *cache, int key) {
int index = findItem(cache, key);
if (index >= 0) {
updateOrder(cache, index);
return cache->cacheArray[index].value;
} else {
return -1;
}
}
int main() {
LRUCache *cache = initCache(CACHE_SIZE);
addItem(cache, 1, 10);
addItem(cache, 2, 20);
addItem(cache, 3, 30);
printf("Item with key 2: %d\n", get(cache, 2));
addItem(cache, 4, 40);
printf("Item with key 1: %d\n", get(cache, 1));
printf("Item with key 2: %d\n", get(cache, 2));
free(cache->cacheArray);
free(cache->order);
free(cache);
return 0;
}
```
## **第二周 測驗3**
```diff
#if BITS_PER_LONG == 64
- if ((word & AAAA) == 0) {
+ if ((word & 0xFFFFFFFF00000000UL) == 0) {
num += 32;
word >>= 32;
}
```
```diff
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;
+ *p &= ~BIT_MASK(nr);
}
```
### 延伸問題
#### 1.
這是一組用於操作和搜尋位元,在由無號長整數陣列表示的位圖巨集和內嵌函數。旨在以高效的方式進行位元操作和搜尋,1並考慮到不同的字大小(8、16、32 和 64 位元)。**find_nth_bit** 函數是核心部分,提供了在位圖中查找第 n 個設置位元位置的通用方法。
位元操作巨集:
* BITS_PER_LONG:定義了無號長整數中的位元數(假定為 64 位元)。
* BIT_MASK(nr):創建具有第 n 位元設置的位遮罩。
* BIT_WORD(nr):確定包含第 n 位元的無號長整數的索引。
* BITMAP_LAST_WORD_MASK(nbits):為位圖中的最後一個單詞創建一個位遮罩。
向上取整的除法巨集:
* DIV_ROUND_UP(n, d):將 n 除以 d 並向上取整。
位元計數巨集:
* __const_hweight8(w):計算 8 位元字中設置位元的數量。
* __const_hweight16(w)、__const_hweight32(w)、__const_hweight64(w):將位元計數擴展到 16、32 和 64 位元。
* hweight_long(w):使用 64 位元計數的包裝函數,對無號長整數進行計數。
最小值巨集:
* min(x, y):返回兩個值的最小值。
查找第 n 個設置位元:
* FIND_NTH_BIT(FETCH, size, num):查找在由 FETCH 定義的位圖中的第 n 個設置位元的位置。它使用輔助函數 hweight_long 和 fns 來實現。
查找第一個設置位元的函數:
* __ffs(word):查找字中第一個設置位元的位置。
清除位元的函數:
* __clear_bit(nr, addr):在給定地址上清除第 n 位元。
查找第 n 個設置位元的函數:
* find_nth_bit(addr, size, n):查找由 addr 和 size 指定的位圖中的第 n 個設置位元的位置。
其他巨集:
* small_const_nbits(nbits):檢查位元數是否是常數且在單個無號長整數的範圍內。
* GENMASK(h, l):生成具有第 l 到第 h 位元設置的遮罩。
#### 2.
`find_nth_bit`函數通常用於在位元運算中找到某個位元序列的第 n 個設定(或清除)的位元,可應用在 CPU affinity 設定中。而在 Linux 核心中,CPU affinity 相關的原始碼通常與進程排程和任務管理有關。
一個常見的使用案例是在 `sched.h`中,可能會涉及到 cpumask_t 類型的位元遮罩,而 `find_nth_bit` 可能用於在這些位元遮罩中找到特定的 CPU。