# 2022q1 Homework1 (lab0)
contributed by < `zhy62396` >
## 實驗環境
```shell
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
$ 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: 94
Model name: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
Stepping: 3
CPU MHz: 3400.000
CPU max MHz: 4000.0000
CPU min MHz: 800.0000
BogoMIPS: 6799.81
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
```
## 作業要求
詳細閱讀 [lab0](https://hackmd.io/@sysprog/linux2022-lab0) 說明,依據指示著手修改 queue.[ch] 和連帶的檔案,測試後用 Git 管理各項修改
* q_new: 建立新的「空」佇列
* q_free: 釋放佇列所佔用的記憶體
* q_insert_head: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則)
* q_insert_tail: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則)
* q_remove_head: 在佇列開頭 (head) 移去 (remove) 給定的節點
* q_release_element: 釋放特定節點的記憶體空間
* q_size: 計算目前佇列的節點總量
* q_delete_mid: 移走佇列中間的節點
* q_delete_dup: 在已經排序的狀況,移走佇列中具備重複內容的節點
* q_swap: 交換佇列中鄰近的節點
* q_reverse: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點
* q_sort: 以遞增順序來排序鏈結串列的節點
## 開發過程
首先查看標頭檔
- [ ] queue.h
```c
typedef struct {
/* Pointer to array holding string.
* This array needs to be explicitly allocated and freed
*/
char *value;
struct list_head list;
} element_t;
```
- [ ] list.h
```c
struct list_head {
struct list_head *prev;
struct list_head *next;
};
```
### q_size
使用老師 [lab0](https://hackmd.io/@sysprog/linux2022-lab0) 所提供的程式碼
```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;
}
```
:::warning
除了張貼程式碼,你應該說明並指出日後可改進之處。
:notes: jserv
:::
### q_new
#### 思考過程
* 1.首先要求一塊 element_t 大小的記憶體,若沒成功回傳 NULL
* 2.element_t 之 value 給值 (NULL)
* 3.element_t 內 list_head 之 prev 和 next 給值(指向自己)
:::danger
注意書寫規範:中英文間用一個半形空白字元區隔
:notes: jserv
:::
:::info
已修正
:::
```c
struct list_head *q_new()
{
element_t *q = malloc(sizeof(element_t));
if (q == NULL) {
return NULL;
}
q->value = NULL;
q->list.prev = &q->list;
q->list.next = &q->list;
return &q->list;
}
```
重新查看`list.h`,有 `INIT_LIST_HEAD` 函式可使用,取代指標宣告的部份
:::danger
請加上標點符號,這是正式的開發紀錄,不是抒發心情的 Dcard 文。
:notes: jserv
:::
:::info
已修正
:::
```c
struct list_head *q_new()
{
element_t *q = malloc(sizeof(element_t));
if (q == NULL) {
return NULL;
}
q->value = NULL;
INIT_LIST_HEAD(&q->list);
return &q->list;
}
```
重新查看文件,發現 queue 之開頭應為 list_head,後續接上的 node 才為 element_t
#### 思考過程(修改後)
* 1.首先要求一塊 list_head 大小的記憶體,若沒成功回傳 NULL
* 2.list_head 之 prev 和 next 給值(指向自己)
```c
struct list_head *q_new()
{
struct list_head *h = malloc(sizeof(struct list_head));
if (h == NULL) {
return NULL;
}
INIT_LIST_HEAD(h);
return h;
}
```
### q_free
`container_of`用來取得 struct 之起始點,知道某結構任一成員位址,即可知道該結構起始位址
1.若 list 為空則直接回傳
2.新增一個 `list_head` 指標 (tmp) 指向 head 之 next
3.若 tmp 不指向 head (只剩 head 的情況)
4.則先用 `container_of` 從 `list_head` 找出 `element_t` 起始位址
5.先將 tmp 指向下一個 `list_head` 再將 `element_t` 刪除
重複 3 ~ 5 直到只剩下 head
6.用 `container_of` 從 `list_head` 找出 `element_t` 起始位址
7.將 `element_t` 刪除
```c
void q_free(struct list_head *l)
{
if (l == NULL) {
return;
}
struct list_head *tmp = l->next;
element_t *e = NULL;
while (tmp != l) {
e = container_of(tmp, element_t, list);
tmp = tmp->next;
q_release_element(e);
}
e = container_of(tmp, element_t, list);
q_release_element(e);
}
```
重新查看作業描述,發現 head 為list_head, 釋放只要用 free 即可
重新查看 `list.h` 發現 `list_entry` 即為 `container_of`
```c
#define list_entry(node, type, member) container_of(node, type, member)
```
重新簡化程式碼
```c
void q_free(struct list_head *l)
{
if (l == NULL)
return;
struct list_head *tmp = l->next;
struct list_head *del = NULL;
while (tmp != l) {
del = tmp;
tmp = tmp->next;
q_release_element(list_entry(del, element_t, list));
}
free(l);
}
```
:::warning
TODO:list_entry 和 container_of 的使用時機
:::
### q_insert_head
查看 `list.h` 中相關的 kernel API,發現 `list_add` 對於此函式實做有幫助
此函式為在 head 後面插入一個 node
```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;
}
```
步驟
1.判斷 queue 是否為空,若是則回傳 false
2.分配一個 `element_t` 大小的記憶體區塊 e,若不成功則回傳 false
3.分配一塊長度 s 個 char 型態的記憶體大小,並將 e 之 value 指向該塊記憶體
4.將記憶體內容由 s 複製到 e->value
```c
bool q_insert_head(struct list_head *head, char *s)
{
if (head == NULL)
return false;
element_t *e = malloc(sizeof(element_t));
if (e == NULL)
return false;
e->value = malloc(sizeof(char) * (strlen(s) + 1));
if (e->value == NULL) {
free(e);
return NULL;
}
memcpy(&e->value, s, strlen(s) + 1);
list_add(&e->list, head);
return true;
}
```
發現先分配 `element_t` ,再將分配失敗和 queue 為 NULL 的情況一起判斷,可以減少程式碼
然後 `element_t` 發現要找 value 不用 `&`
```c
bool q_insert_head(struct list_head *head, char *s)
{
element_t *e = malloc(sizeof(element_t));
if (e == NULL || head == NULL)
return false;
e->value = malloc(strlen(s) + 1);
if (e->value == NULL) {
free(e);
return false;
}
memcpy(e->value, s, strlen(s) + 1);
list_add(&e->list, head);
return true;
}
```
### q_insert_tail
同樣使用 `list.h` 中相關的 kernel API `list_add_tail`
此函式為在 `head` 和 `head->prev` 之間插入一個 node(相當於插入 list 尾端)
```c
static inline void list_add_tail(struct list_head *node, struct list_head *head)
{
struct list_head *prev = head->prev;
prev->next = node;
node->next = head;
node->prev = prev;
head->prev = node;
}
```
```c
bool q_insert_tail(struct list_head *head, char *s)
{
element_t *e = malloc(sizeof(element_t));
if (e == NULL || head == NULL)
return false;
e->value = malloc(strlen(s) + 1);
if (e->value == NULL) {
free(e);
return false;
}
memcpy(e->value, s, strlen(s) + 1);
list_add_tail(&e->list, head);
return true;
}
```
### q_remove_head
### q_remove_tail
### q_release_element
### q_delete_min
### q_delete_dup
### q_swap
### q_reverse
### q_sort