---
tags: linux kernel
---
# 2022q1 Homework1 (lab0)
contributed by <[jimmy-liu1021](https://github.com/jimmy-liu1021)>
### Reviewed by `kevinshieh0225`
- 使用 `&node->list` 來取得 dereference list of address. 因為在運算次序中 `derefence ->` 高於 `&`。這樣不必使用 `// cppcheck-suppress memleak`
- 請善用 linux kernel api `list_for_each`, `list_for_each_entry`, `list_for_each_safe`, `list_for_each_entry_safe`
- `q_remove_head` 重複做了 `list_del(head->next);` 可再簡化。
## 實驗環境
```bash
$ gcc -v
gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 39 bits physical, 48 bits virtual
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 60
Model name: Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz
Stepping: 3
CPU MHz: 800.000
CPU max MHz: 3900.0000
CPU min MHz: 800.0000
BogoMIPS: 6999.56
Virtualization: VT-x
L1d cache: 128 KiB
L1i cache: 128 KiB
L2 cache: 1 MiB
L3 cache: 8 MiB
NUMA node0 CPU(s): 0-7
```
## 開發紀錄
:::success
[作業要求](https://hackmd.io/@sysprog/linux2022-lab0) (詳閱連結內容)
- [x] q_new: 建立新的「空」佇列
- [x] q_free: 釋放佇列所佔用的記憶體
- [x] q_insert_head: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則)
- [x] q_insert_tail: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則)
- [x] q_remove_head: 在佇列開頭 (head) 移去 (remove) 給定的節點
- [x] q_release_element: 釋放特定節點的記憶體空間
- [x] q_size: 計算目前佇列的節點總量
- [x] q_delete_mid: 移走佇列中間的節點
- [x] q_delete_dup: 在已經排序的狀況,移走佇列中具備重複內容的節點
- [x] q_swap: 交換佇列中鄰近的節點
- [x] q_reverse: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點
- [x] q_sort: 以遞增順序來排序鏈結串列的節點
:::
---
### q_new
Create a empty queue.
Return NULL if could not allocate space.
- 流程
1.使用 `malloc()` 分配記憶體位置,並由 `new` 指標指向。如果空間分配失敗,則 `malloc` 會回傳 `NULL`,此時函式回傳`NULL`
2.使用 `list.h` 中的 `INIT_LIST_HEAD` 初始化
:::info
`INIT_LIST_HEAD` 的功能為將 linked list 的 `next` 及 `prev`指向本身
:::
```c
static inline void INIT_LIST_HEAD(struct list_head *head)
{
head->next = head;
head->prev = head;
}
```
:::spoiler q_new 實際程式碼
```c
struct list_head *q_new()
{
element_t *new = malloc(sizeof(element_t));
if (!new)
return NULL;
INIT_LIST_HEAD(&(new->list));
// cppcheck-suppress memleak
return &(new->list);
}
```
:::
---
### q_free
Free all storage used by queue
- 流程
1.先檢查 `head` 是否為空,若為空則直接`return` 結束函式
2.使用 `container_of` 取出節點的記憶體位置,並逐一釋放空間。
3.釋放 `head` 的記憶體空間
:::info
參考 [Linux 核心原始程式碼巨集: container_of](/odsx15lMRDiqsuQDL8LE8g),得知 `container_of` 可以用來得到包含 `ptr` 之 `object` 的起始記憶體位址
:::
:::spoiler q_free 實際程式碼
```c
void q_free(struct list_head *l)
{
if (!l)
return;
struct list_head *ptr = l->next;
while (ptr != l) {
element_t *node = container_of(ptr, element_t, list);
ptr = ptr->next;
q_release_element(node);
}
element_t *node = container_of(ptr, element_t, list);
free(node);
}
```
:::
---
### q_insert_head
Attempt to insert element at head of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
Argument s points to the string to be stored.
The function must explicitly allocate space and copy the string into it.
- 流程
1.如果 `head` 為 `NULL` 則 `return false`
2.使用 `malloc()` 分配 `emelent_t` 大小的記憶體空間,若失敗則 `return false`
3.使用 `strlen()` 取得字串長度,並分配複製字串所需的空間
:::info
分配時要多一個位元組存放 NULL pointer
:::
4.使用 `list.h` 中的函式 `list_add()` 將節點加在 `head`之後
:::info
`list_add()` 的功能為,將 `node` 接在 `head` 之後,並把 link 接好
:::
```c
static inline void list_add(struct list_head *node, struct list_head *head)
{
struct list_head *next = head->next;
next->prev = node;
node->next = next;
node->prev = head;
head->next = node;
}
```
:::spoiler q_insert_head 實際程式碼
```c
bool q_insert_head(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *node = malloc(sizeof(element_t));
if (!node)
return false;
int s_size = strlen(s);
char *s_copy = malloc(s_size + 1);
if (!s_copy) {
free(node);
return false;
}
memcpy(s_copy, s, s_size);
s_copy[s_size] = 0;
node->value = s_copy;
list_add(&node->list, head);
return true;
}
```
:::
---
### q_insert_tail
Attempt to insert element at tail of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
Argument s points to the string to be stored.
The function must explicitly allocate space and copy the string into it.
- 流程
`q_insert_tail()` 與 `q_insert_head()` 幾乎相同,區別只有加入節點時使用的是 `list_add_tail()`
:::spoiler q_insert_tail 實際程式碼
```c
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *node = malloc(sizeof(element_t));
if (!node)
return false;
int s_size = strlen(s);
char *s_copy = malloc(s_size + 1);
if (!s_copy) {
free(node);
return false;
}
memcpy(s_copy, s, s_size);
s_copy[s_size] = 0;
node->value = s_copy;
list_add_tail(&node->list, head);
return true;
}
```
:::
---
### q_remove_head
Attempt to remove element from head of queue.
Return target element.
Return NULL if queue is NULL or empty.
If sp is non-NULL and an element is removed, copy the removed string to *sp
(up to a maximum of bufsize-1 characters, plus a null terminator.)
NOTE: "remove" is different from "delete"
The space used by the list element and the string should not be freed.
The only thing "remove" need to do is unlink it.
REF: https://english.stackexchange.com/questions/52508/difference-between-delete-and-remove
- 流程
1.檢查 `head` 是否為 `NULL` 及 queue 是否為空,若成立則回傳 `NULL`
2.因為要用到第一個節點的 `value` ,所以用 `container_of` 取出第一個節點的位址,並使用 `memcpy` 將其所存的字串複製下來,複製的大小根據 `bufsize`
:::spoiler q_remove_head 實際程式碼
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
element_t *node = container_of(head->next, element_t, list);
if (!sp) {
list_del(head->next);
return node;
}
memcpy(sp, node->value, bufsize - 1);
list_del(head->next);
sp[bufsize - 1] = 0;
return node;
}
```
:::
---
### q_remove_tail
Attempt to remove element from tail of queue.
Other attribute is as same as q_remove_head.
- 流程
與 `q_remove_head` 相似,不同處為取出的點為 `head->prev` 即最後一個節點
:::spoiler q_remove_tail 實際程式碼
```c
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
element_t *node = container_of(head->prev, element_t, list);
if (!sp) {
list_del(head->prev);
return node;
}
memcpy(sp, node->value, bufsize - 1);
list_del(head->prev);
sp[bufsize - 1] = 0;
return node;
}
```
:::
---
### q_size
Return number of elements in queue.
Return 0 if q is NULL or empty
- 流程
使用 `list.h` 中的巨集函式 `list_for_each()` 走訪每個節點,以計算linked list的長度
:::spoiler q_size 實際程式碼
```c
int q_size(struct list_head *head)
{
if (!head)
return 0;
int len = 0;
struct list_head *li;
list_for_each (li, head)
len++;
return len;
}
```
:::
---
### q_delete_mid
Delete the middle node in list.
The middle node of a linked list of size n is the
⌊n / 2⌋th node from the start using 0-based indexing.
If there're six element, the third member should be return.
Return true if successful.
Return false if list is NULL or empty.
- 流程
1.參考 [你所不知道的 C 語言: linked list 和非連續記憶體](/YA7RMqd6RE2UL8OmmjU9PQ),案例探討: Leetcode 2095. Delete the Middle Node of a Linked List,使用快慢指標的技巧
:::info
此案例中,快指標每一次往前進兩個節點,而慢指標每一次往前進一個節點。如此一來,當快指標指向最後一個節點時,慢指標恰好指向中間的節點
:::
2.並且搭配 indirect pointer 的技巧,省去配置暫時節點的空間
:::spoiler q_delete_mid 實際程式碼
```c
bool q_delete_mid(struct list_head *head)
{
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
if (!head)
return false;
if (list_empty(head))
return false;
struct list_head **indir_slow = &head->next;
for (struct list_head *fast = head->next;
head != fast && head != fast->next; fast = fast->next->next) {
indir_slow = &(*indir_slow)->next;
}
element_t *remove_point = container_of(*indir_slow, element_t, list);
list_del(*indir_slow);
q_release_element(remove_point);
return true;
}
```
:::
---
### q_delete_dup
Delete all nodes that have duplicate string,
leaving only distinct strings from the original list.
Return true if successful.
Return false if list is NULL.
Note: this function always be called after sorting, in other words,
list is guaranteed to be sorted in ascending order.
- 流程
1.取出首個節點的字串,接著將字串跟後續節點的字串依序比較是否相同,若相同則刪去
2.並以 `dup_flag` 去記錄刪去這個行為是否有被執行,若有 (表示有與首個節點重複的點) 則將首個節點也刪去
:::info
實作時原本以為只需要刪去重複的多餘字串 (留下1個,e.g. 112233 留下123) 即可,但在 `make test` 時失敗,才發現要刪去所有重複的(e.g. 11223345 只留下 45),使用了`flag` 的方式去紀錄,但是造成程式碼大量重複
:::
:::spoiler q_delete_dup 實際程式碼
```c
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if (!head)
return false;
struct list_head *ptr1 = head->next, *ptr2 = ptr1->next;
element_t *node1 = container_of(ptr1, element_t, list);
bool dup_flag = false;
for (; ptr2 != head; ptr2 = ptr1->next) {
element_t *node2 = container_of(ptr2, element_t, list);
if (strcmp(node1->value, node2->value) == 0) {
list_del(ptr2);
q_release_element(node2);
dup_flag = true;
} else {
if (dup_flag) {
list_del(ptr1);
q_release_element(node1);
dup_flag = false;
}
ptr1 = ptr2;
node1 = container_of(ptr1, element_t, list);
}
}
if (dup_flag) {
list_del(ptr1);
q_release_element(node1);
}
return true;
}
```
:::
---
### q_swap
Attempt to swap every two adjacent nodes.
- 流程
Step1. 將 `ptr` 指標指向`head` ,將 `tmp` 指標指向`third`
```graphviz
digraph {
rankdir=LR
ptr[shape=plaintext]
ptr->head[color=red]
head->first->second->third
third->second->first->head[color=blue]
tmp[shape=plaintext]
tmp->third[color=red]
}
```
Step2. `second` 的 `next` 指向 `first`
```graphviz
digraph {
rankdir=LR
ptr[shape=plaintext]
ptr->head
head
first
second
third
head->first->second
third->second->first->head[color=blue]
second->first[color=red,label=next]
tmp[shape=plaintext]
tmp->third
}
```
Step3. `head` 的 `next` 指向 `second`
```graphviz
digraph {
rankdir=LR;
ptr[shape=plaintext]
ptr->head
head
first->second
third->second->first->head[color=blue]
second->first
tmp[shape=plaintext]
tmp->third
head->second[color=red,label=next]
}
```
Step4. `first` 的 `next` 指向 `third`,以上步驟將 `next` 皆布置完畢
```graphviz
digraph {
rankdir=LR;
ptr[shape=plaintext]
ptr->head
head
third->second->first->head[color=blue]
second->first
tmp[shape=plaintext]
tmp->third
head->second
first->third[color=red,label=next]
}
```
Step5. 將各個節點的 `prev` 接好,並將 `ptr` 指向 `third` 準備進行下一個循環
```graphviz
digraph {
rankdir=LR;
head
third->first->second->head[color=red]
second->first
ptr[shape=plaintext]
ptr->third[color=red]
head->second
first->third
}
```
:::spoiler q_swap 實際程式碼
```c
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
if (!head || list_empty(head))
return;
struct list_head *ptr = head;
while (ptr->next != head && ptr->next->next != head) {
struct list_head *tmp = ptr->next->next->next;
ptr->next->next->next = ptr->next;
ptr->next = ptr->next->next;
ptr->next->next->next = tmp;
ptr->next->next->next->prev = ptr->next->next;
ptr->next->next->prev = ptr->next;
ptr->next->prev = ptr;
ptr = ptr->next->next;
}
}
```
:::
---
### q_reverse
Reverse elements in queue
No effect if q is NULL or empty
This function should not allocate or free any list elements
(e.g., by calling q_insert_head, q_insert_tail, or q_remove_head).
It should rearrange the existing ones.
- 流程
將每個節點與其下一個節點做 swap 即可達成整個 queue reverse
:::spoiler q_reverse 實際程式碼
```c
void q_reverse(struct list_head *head)
{
if (!head || list_empty(head))
return;
struct list_head *tmp = NULL, *ptr = NULL;
for (ptr = head->next; ptr != head; ptr = ptr->prev) {
tmp = ptr->next;
ptr->next = ptr->prev;
ptr->prev = tmp;
}
tmp = ptr->next;
ptr->next = ptr->prev;
ptr->prev = tmp;
}
```
:::
---
### q_sort
Sort elements of queue in ascending order
No effect if q is NULL or empty. In addition, if q has only one element, do nothing.
- 流程
參考 [你所不知道的 C 語言: linked list 和非連續記憶體](/YA7RMqd6RE2UL8OmmjU9PQ) 案例探討: LeetCode 21.Merge Two Sorted Lists
:::spoiler q_sort 實際程式碼
```c
struct list_head *mergeTwoLists(struct list_head *L1, struct list_head *L2)
{
struct list_head *head = NULL, **ptr = &head, **node;
for (node = NULL; L1 && L2; *node = (*node)->next) {
element_t *L1_node = container_of(L1, element_t, list);
element_t *L2_node = container_of(L2, element_t, list);
node = (strcmp(L1_node->value, L2_node->value) <= 0) ? &L1 : &L2;
*ptr = *node;
ptr = &(*ptr)->next;
}
*ptr = (struct list_head *) ((uintptr_t) L1 | (uintptr_t) L2);
return head;
}
struct list_head *mergesort_list(struct list_head *head)
{
if (!head->next)
return head;
struct list_head *slow = head;
for (struct list_head *fast = head->next; fast && fast->next;
fast = fast->next->next)
slow = slow->next;
struct list_head *mid = slow->next;
slow->next = NULL;
struct list_head *left = mergesort_list(head), *right = mergesort_list(mid);
return mergeTwoLists(left, right);
}
void q_sort(struct list_head *head)
{
if (!head || list_empty(head))
return;
head->prev->next = NULL;
head->next = mergesort_list(head->next);
struct list_head *ptr = head;
for (; ptr->next; ptr = ptr->next)
ptr->next->prev = ptr;
ptr->next = head;
head->prev = ptr;
}
```
:::