---
# System prepended metadata

title: 2022q1 Homework1 (quiz1)
tags: [linux2022, ' linux']

---

---
tags: linux2022, linux
---

# 2022q1 Homework1 (quiz1)
contributed by < [Julian-Chu](https://github.com/Julian-Chu) >
> [GitHub](https://github.com/Julian-Chu/linux2022-quizzes/quiz1)

## 測驗 1
## 測驗 2
### answer
```cpp=
  struct ListNode *deleteDuplicates(struct ListNode *head){    
      if(!head)    
          return NULL;    
      
      if(head->next && head->val == head->next->val){    
          while(head->next && head->val == head->next->val)    
              head = head->next;    
          //return deleteDuplicates(head->next);    
      }    
      
      head->next = deleteDuplicates(head->next);    
      return head;    
  }    
```
需要填入的程式碼相當直觀, 需要先檢查 `head->next` 存在才進行比較。
另外題目裏 `line 8` 的 `return` 會導致 linked list 斷開。

#### 簡化後
```cpp
  struct ListNode *deleteDuplicates(struct ListNode *head){    
      if(!head) return NULL;    
      
      while(head->next && head->val == head->next->val)    
         head = head->next;    
      
      head->next = deleteDuplicates(head->next);    
      return head;    
  }    
```
#### pointer to pointer
```cpp
  struct ListNode *deleteDuplicates(struct ListNode *head){    
      struct ListNode **indirect = &head;                                                                                                                                              
      while(*indirect && (*indirect)->next && (*indirect)->val == (*indirect)->next->val)      
          *indirect = (*indirect)->next;      
        
      if((*indirect)->next)      
          (*indirect)->next = deleteDuplicates((*indirect)->next);      
      return *indirect;  
  }
```
### 迭代版本
```cpp
  struct ListNode *deleteDuplicates_iter(struct ListNode *head){    
      if(!head)    
          return NULL;    
      
      struct ListNode *cur = head;    
      while(cur->next){    
          if(cur->val == cur->next->val){    
              cur->next = cur->next->next;
              continue
          }    
          cur = cur->next;       
      }    
      return head;    
  }
```

```cpp
  struct ListNode *deleteDuplicates_iter(struct ListNode *head){
      struct ListNode **indirect = &head;
  
      while(*indirect && (*indirect)->next){
          if((*indirect)->val == (*indirect)->next->val){
              *indirect = (*indirect)->next;
              continue;
          }
          indirect = &(*indirect)->next; 
      }
  
      return head;
```

[測試程式碼](https://github.com/Julian-Chu/linux2022-quizzes/blob/main/quiz1/question2/question2.c#L93)

### 用 linux 風格 circular doubly-linked list 改寫
#### 前置準備
```cpp
  #include <stddef.h>                                            
  #include <stdlib.h>    
  #include <stdio.h>    
  #include <assert.h>    
      

  #define container_of(ptr, type, member)               \    
      ({                                                \    
          void *__mptr = (void *) (ptr);                \    
          ((type *) (__mptr - offsetof(type, member))); \    
      })    
      
  struct list_head {    
      struct list_head *next, *prev;    
  };    
      
  struct ListNode{    
      int val;    
      struct list_head node;    
  };   
```
#### 遞迴
```cpp
  struct list_head *deleteDuplicates(struct list_head *head)
  {
      if(!head)
          return NULL;
  
      struct ListNode *headNode = container_of(head, struct ListNode, node);
      if(head->next){
          struct ListNode *nextNode = container_of(head->next, struct ListNode, node);
          while(head->next && headNode->val == nextNode->val){
              head = head->next;
              headNode = nextNode;
              if(head->next)
                 nextNode = container_of(head->next, struct ListNode, node);
          }
      }
     
      head->next = deleteDuplicates(head->next);
      return head;
  }

```
#### 迭代 
```cpp
  struct list_head *deleteDuplicates_iter(struct list_head *head)
  {
      if(!head)
          return NULL;
  
      struct list_head *cur = head;
      struct ListNode *curNode, *nextNode;
      curNode = container_of(head, struct ListNode, node);
  
      while(cur->next){
          nextNode = container_of(cur->next, struct ListNode, node);
          if(curNode->val == nextNode->val){
              cur->next = cur->next->next; 
          }else{
              cur = cur->next;
              curNode = nextNode;
          }
      }
     
      return head;
  }
```



[測試程式碼](https://github.com/Julian-Chu/linux2022-quizzes/blob/main/quiz1/question2/question2_linux_style.c#L112)

:::info
TODO: 
- 將函數簽章從 list_head 改成 ListNode
- 改用 linux/list.h 的 API 
:::

---

## 測驗 3
### answer
```cpp
  LRUCache *lRUCacheCreate(int capacity)
  {
      LRUCache *obj = malloc(sizeof(*obj) + capacity * sizeof(struct list_head));
      obj->count = 0;             
      obj->capacity = capacity;   
      INIT_LIST_HEAD(&obj->dhead);
      for (int i = 0; i < capacity; i++)
          INIT_LIST_HEAD(&obj->hheads[i])
      return obj;
  }
  
  void lRUCacheFree(LRUCache *obj)
  {
      LRUNode *lru, *n;
      list_for_each_entry_safe(lru, n, &obj->dhead, dlink){
          list_del(&lru->dlink);
          free(lru);
      }
      free(obj);
  }

  int lRUCacheGet(LRUCache *obj, int key){
      LRUNode *lru;
      int hash = key % obj->capacity;
      list_for_each_entry(lru, &obj->hheads[hash], hlink){
          if(lru->key == key){
              list_move(&lru->dlink, &obj->dhead);
              return lru->value;
          }
      }
      return -1;
  }

  void lRUCachePut(LRUCache *obj, int key, int value){
      LRUNode *lru;
      int hash = key% obj->capacity;
      list_for_each_entry(lru, &obj->hheads[hash], hlink){
          if(lru->key == key){
              list_move(&lru->dlink, &obj->dhead);
              lru->value = value;
              return;
          }
      }
  
      if(obj->count == obj->capacity){
          lru = list_last_entry(&obj->dhead, LRUNode, dlink);
          list_del(&lru->dlink);
          list_del(&lru->hlink);
      }else{
          lru = malloc(sizeof(LRUNode));
          obj->count++;
      }
      lru->key = key;
      list_add(&lru->dlink, &obj->dhead);
      list_add(&lru->hlink, &obj->hheads[hash]);
      lru->value = value;
  }  

```

### 延伸問題 1
```cpp
  typedef struct{
      int capacity, count;
      struct list_head dhead, hheads[];
  } LRUCache;
  
  typedef struct{
      int key, value;
      struct list_head hlink, dlink;
  } LRUNode;   
```

LRUCache 內部使用兩個資料結構，分別爲 list, hashtable

#### hashtable
```cpp
      int hash = key % obj->capacity;
      // 利用 hash 尋找 hashtable 中的 head, 然後進行 list 查找
      list_for_each_entry(lru, &obj->hheads[hash], hlink){
          if(lru->key == key){
              // 找到後將 cache 更新到 dhead 的最前端, 避免最近使用的 cache 因為容量已滿被移除
              list_move(&lru->dlink, &obj->dhead);
              return lru->value;
          }
      }
```

hashtable 用於搜索資料, 相較於單一 list 的全路徑搜尋，利用 hashtable 可以有效的縮短搜尋路徑，單一 list 爲 $O(n)$ 時間複雜度， 搭配 hashtable 後理想狀況可變為 $O(\frac{n}{m})$, m 爲 hashtable 的容量
:::warning
明確說明是「時間」抑或「空間」複雜度。
:notes: jserv
:::

#### list
```cpp
      // 容量已滿
      if(obj->count == obj->capacity){
          //  list 上最後一個 Cache
          lru = list_last_entry(&obj->dhead, LRUNode, dlink);
          list_del(&lru->dlink);
          list_del(&lru->hlink);
      }else{
          lru = malloc(sizeof(LRUNode));
          obj->count++;
      }
```
list 用於判斷該 cache 是否可以移除, 當容量已滿，會將距離上次使用最久的 cache 移除

#### 測試程式碼和改進
測試程式碼
```cpp
  int main(void){    
      printf("testing LRUCache with capacity 4\n");    
      LRUCache *cache = lRUCacheCreate(4);    
      printf("put kvs: (1,1), (2,2), (3,3), (4,4)\n");    
      lRUCachePut(cache, 1,1);    
      lRUCachePut(cache, 2,2);    
      lRUCachePut(cache, 3,3);    
      lRUCachePut(cache, 4,4);    
      LRUNode *lru;    
      lru = list_last_entry(&cache->dhead, LRUNode, dlink);    
      assert(lru->key==1);    
      printf("now least recently key is 1\n");    
      assert(lRUCacheGet(cache, 1) == 1);    
      printf("get key 1 with its value 1\n");    
      lru = list_last_entry(&cache->dhead, LRUNode, dlink);    
      assert(lru->value==2);    
      printf("now least recently used key is 2\n");    
      printf("put kv: (5,5)\n");    
      lRUCachePut(cache, 5, 5);    
      lru = list_first_entry(&cache->dhead, LRUNode, dlink);    
      assert(lru->value==5);    
      assert(lRUCacheGet(cache, 2)==-1);    
      lru = list_last_entry(&cache->dhead, LRUNode, dlink);    
      assert(lru->value==3);    
      printf("now least recently used key is 3, 2 is removed\n");    
      printf("test passed\n");    
  }  
```
```shell
testing LRUCache with capacity 4
put kvs: (1,1), (2,2), (3,3), (4,4)
now least recently used key is 1
get key 1 with its value 1
now least recently used key is 2
put kv: (5,5)
now least recently used key is 3, 2 is removed
test passed
```
##### 嘗試改進:
- 將 cache 的容量限制爲 2^N 改用 GOLDEN_RATIO位元運算取代除法 hash (仿照測驗1 map)
- 考慮 temporal locality 在 Get 與 Put 增加對 dhead 的 key 檢查，


```cpp
  #define GOLDEN_RATIO_32 0x61C88647
  static inline unsigned int hash(unsigned int val, unsigned int bits) {
      /* High bits are more random, so use them. */
      return (val * GOLDEN_RATIO_32) >> (32 - bits);
  }

  int lRUCacheGet(LRUCache *obj, int key){
      LRUNode *lru;
      // 新增，檢查第一個 node
      lru = list_first_entry(&obj->dhead, LRUNode, dlink);
      if(lru->key == key) return lru->value;
  
      list_for_each_entry(lru, &obj->hheads[hash(key, obj->bits)], hlink){
          if(lru->key == key){
              list_move(&lru->dlink, &obj->dhead);
              // 新增： 將最新取用的 cache 在 hashtable 的位置移到 head 
              list_move(&lru->hlink, &obj->hheads[hash(key, obj->bits)]);

              return lru->value;
          }
      }
      return -1;
  }
  
  void lRUCachePut(LRUCache *obj, int key, int value){
      LRUNode *lru;
      list_for_each_entry(lru, &obj->hheads[hash(key, obj->bits)], hlink){
          if(lru->key == key){
              list_move(&lru->dlink, &obj->dhead);
              // 新增： 將最新取用的 cache 在 hashtable 的位置移到 head 
              list_move(&lru->hlink, &obj->hheads[hash(key, obj->bits)]);
              lru->value = value;
              return;
          }
      }
  
      if(obj->count == obj->capacity){
          lru = list_last_entry(&obj->dhead, LRUNode, dlink);
          list_del(&lru->dlink);
          list_del(&lru->hlink);
      }else{
          lru = malloc(sizeof(LRUNode));
          obj->count++;
      }
      lru->key = key;
      list_add(&lru->dlink, &obj->dhead);
      list_add(&lru->hlink, &obj->hheads[hash(key, obj->bits)]);
      lru->value = value;
  }

```

TODO: 測試性能差異

### 延伸問題 2
#### DRBD
[Distributed Replicated Block Device](https://en.wikipedia.org/wiki/Distributed_Replicated_Block_Device)
[lru_cache.h](https://github.com/torvalds/linux/blob/master/include/linux/lru_cache.h)
[lru_cache.c](https://github.com/torvalds/linux/blob/master/lib/lru_cache.c)

Distributed Replicated Block Device 是 linux 上的分散式存儲系統, lru_cache 主要用來記錄追蹤遠端節點的狀態, 避免網路故障或是節點故障回復後的全狀態同步， 而用來追蹤狀態的 object 的記憶體空間實際上不會被釋放，會利用 LRU 選出 object 進行重複使用


#### Memory management(Page reclaim/Page Replacement)
[Page Frame Reclamation](https://www.kernel.org/doc/gorman/html/understand/understand013.html)
[mm/list_lru.c](https://github.com/torvalds/linux/blob/master/mm/list_lru.c)
[list_lru.h](https://github.com/torvalds/linux/blob/master/include/linux/list_lru.h)
[mm_types.h](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/include/linux/mm_types.h#L70)
[mm/workingset](https://github.com/torvalds/linux/blob/master/mm/workingset.c)

LRU 大量的使用在記憶體管理, 藉由 page 的最近使用時間來決定，當記憶體不足的時候該釋放那部分的記憶體



---

## 測驗 4
### answer
```cpp
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
#include <assert.h>

struct seq_node{
    int num;
    struct list_head link;
};

// 根據值與給定的數列長度在 hash table 裡面尋找目標節點
static struct seq_node *find(int num, int size, struct list_head *heads)
{
    struct seq_node *node;
    int hash = num < 0? -num%size:num%size;
    // 根據 hash 值從 head 開始查找
    list_for_each_entry(node, &heads[hash], link){
        // hash 值可能會重複，仍然需要比對數值本身
        if (node->num == num)
            return node;
    }
    return NULL;
}

int longestConsecutive(int *nums, int n_size){
    int hash, length = 0;
    struct seq_node *node;
    // 劃分記憶體空間給 hashtable
    struct list_head *heads = malloc(n_size * sizeof(*heads));

    // 初始化 hashtable 
    for (int i = 0; i<n_size; i++)
        INIT_LIST_HEAD(&heads[i]);

    // 將給定的數列中的數字放入 hashtable 中
    for(int i = 0; i < n_size; i++){
        if(!find(nums[i], n_size, heads)){
           hash = nums[i] < 0? -nums[i] % n_size:nums[i] % n_size;
           node = malloc(sizeof(*node));
           node->num = nums[i];
           list_add(&node->link, &heads[hash]);
        }
    }

    for (int i = 0; i < n_size; i++){
        int len = 0;
        int num;
        // 在 hashtable 中取出對應的節點
        node = find(nums[i], n_size, heads);
        while(node){
            len++;
            num = node->num;
            // 將已取用的節點從 hashtable 中移除
            list_del(&node->link);

            // 以 node 的值爲中心點, 加減一尋找下一個相鄰的數字
            int left = num, right = num;
            // 需要將值 -1 後在傳入，所以使用 --left 而不是 left--
            while ((node = find(--left, n_size, heads))){
                // 找到相鄰的節點長度加一並從 hashtable 中移除該節點
                len++;
                list_del(&node->link);
            }
            // 需要將值 +1 後在傳入，所以使用 ++right 而不是 right++
            while ((node = find(++right, n_size, heads))){
                len++;
                list_del(&node->link);
            }

            // 新的長度大於已知的最大長度就更新值
            length = len > length ? len:length;
        }
    }

    return length;
}

```

### 測試程式碼
```cpp
int main(void){
    int nums1[4] = {0, 1, 2, 4};
    assert(longestConsecutive(nums1, 4)== 3);

    int nums2[5] = {-1, 0, 1, 3, 4};
    assert(longestConsecutive(nums2, 5)== 3);
    printf("tests passed\n");
}
```