---
tags: Linux
---
# 2022q1 Homework1 (lab0)
contributed by < `LJP-TW` >
# 實驗環境
```shell
$ gcc --version
gcc (Debian 10.2.1-6) 10.2.1 20210110
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 45 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 2
On-line CPU(s) list: 0,1
Vendor ID: AuthenticAMD
Model name: AMD Ryzen 7 PRO 4750U with Radeon Graphics
CPU family: 23
Model: 96
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 2
Stepping: 1
BogoMIPS: 3393.62
```
# 作業要求
## 實作 queue
嘗試盡量使用 Linux 核心風格的鏈結串列 API 進行各函數的實作。
### q_new
檢查是否能分配空間,並且做 `list_head` 的初始化。
```c
struct list_head *q_new()
{
struct list_head *head;
head = malloc(sizeof(struct list_head));
if (!head) {
return NULL;
}
INIT_LIST_HEAD(head);
return head;
}
```
### q_free
釋放整條 queue 所占用的記憶體,由於都要歸還記憶體了,已經無需再將 element 從 queue 中移除鏈結。
```c
void q_free(struct list_head *l)
{
if (!l) {
return;
}
element_t *entry, *safe;
list_for_each_entry_safe (entry, safe, l, list) {
q_release_element(entry);
}
free(l);
}
```
### q_insert_head
從 queue 的頭部新增節點,檢查是否能正常分配記憶體,字串長度需要為了 NULL byte 預留空間。
```c
bool q_insert_head(struct list_head *head, char *s)
{
element_t *element;
int len;
if (!head) {
return false;
}
element = malloc(sizeof(element_t));
if (!element) {
return false;
}
len = strlen(s) + 1;
element->value = malloc(sizeof(char) * len);
if (!element->value) {
free(element);
return false;
}
strncpy(element->value, s, len);
list_add(&element->list, head);
return true;
}
```
### q_insert_tail
與 `q_insert_head` 類似。
```c
bool q_insert_tail(struct list_head *head, char *s)
{
element_t *element;
int len;
if (!head) {
return false;
}
element = malloc(sizeof(element_t));
if (!element) {
return false;
}
len = strlen(s) + 1;
element->value = malloc(sizeof(char) * len);
if (!element->value) {
free(element);
return false;
}
strncpy(element->value, s, len);
list_add_tail(&element->list, head);
return true;
}
```
### q_remove_head
從 queue 的頭部移除一個節點,複製字串到 `sp` 時要預留 NULL byte 的空間。
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head)) {
return NULL;
}
element_t *first = list_first_entry(head, element_t, list);
list_del_init(&first->list);
if (sp) {
char *val = first->value;
char c;
while (bufsize > 1 && (c = *val)) {
*sp = c;
sp++;
val++;
bufsize--;
}
*sp = 0;
}
return first;
}
```
### q_remove_tail
與 `q_remove_head` 類似。
```c
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head)) {
return NULL;
}
element_t *last = list_last_entry(head, element_t, list);
list_del_init(&last->list);
if (sp) {
char *val = last->value;
char c;
while (bufsize > 1 && (c = *val)) {
*sp = c;
sp++;
val++;
bufsize--;
}
*sp = 0;
}
return last;
}
```
### q_release_element
沒有做改動。
```c
void q_release_element(element_t *e)
{
free(e->value);
free(e);
}
```
### q_size
需要走過 queue 計算長度,時間複雜度為 `O(n)`。
```c
int q_size(struct list_head *head)
{
if (!head || list_empty(head)) {
return 0;
}
struct list_head *node;
int len = 0;
list_for_each (node, head) {
len++;
}
return len;
}
```
### q_delete_mid
用兩個 pointer 走過 queue,`curr` 走訪速度為 `mid` 兩倍,因此當 `curr` 到達 queue 的結尾時,`mid` 自然是在 queue 的中間,找到 `mid` 後刪除該節點。
```c
bool q_delete_mid(struct list_head *head)
{
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
if (!head || list_empty(head)) {
return false;
}
if (list_is_singular(head)) {
element_t *element = q_remove_head(head, NULL, 0);
q_release_element(element);
return true;
}
struct list_head *curr, *mid;
element_t *element;
mid = curr = head->next;
while (curr != head && curr->next != head) {
curr = curr->next->next;
mid = mid->next;
}
list_del(mid);
element = list_entry(mid, element_t, list);
q_release_element(element);
return true;
}
```
### q_delete_dup
呼叫此函式前,queue 已經排序完畢,因此走訪過程中,只需要判斷下個節點的值是否與當前節點相同,作為是否要刪除節點的依據。在 `do` `while` 後,還要再刪除 `currelm`,但這個寫法不夠簡潔,還有改善空間。
```c
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if (!head) {
return false;
}
if (list_empty(head) || list_is_singular(head)) {
return true;
}
struct list_head *curr = head->next;
element_t *currelm, *nextelm;
while (curr != head && curr->next != head) {
currelm = list_entry(curr, element_t, list);
nextelm = list_entry(curr->next, element_t, list);
if (!strcmp(currelm->value, nextelm->value)) {
do {
curr = curr->next;
list_del(&currelm->list);
q_release_element(currelm);
currelm = nextelm;
if (curr->next == head) {
break;
}
nextelm = list_entry(curr->next, element_t, list);
} while (!strcmp(currelm->value, nextelm->value));
curr = curr->next;
list_del(&currelm->list);
q_release_element(currelm);
} else {
curr = curr->next;
}
}
return true;
}
```
### q_swap
每經過兩個節點就交換兩者在 queue 中的順序。`list.h` 沒有與交換節點相關的工具能夠使用,或許能夠增加相關工具 macro / function。
```c
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
if (!head || list_empty(head) || list_is_singular(head)) {
return;
}
struct list_head *peer, *curr;
curr = head->next;
while (curr != head && curr->next != head) {
peer = curr->next;
curr->next = peer->next;
curr->next->prev = curr;
peer->prev = curr->prev;
peer->prev->next = peer;
peer->next = curr;
curr->prev = peer;
curr = curr->next;
}
}
```
### q_reverse
將整個 queue 的順序倒轉,只要交換每個節點 (包含 `head`) 的 `prev` 和 `next` 即可。
```c
void q_reverse(struct list_head *head)
{
if (!head || list_empty(head) || list_is_singular(head)) {
return;
}
struct list_head *node, *safe, *tmp;
list_for_each_safe (node, safe, head) {
tmp = node->next;
node->next = node->prev;
node->prev = tmp;
}
tmp = head->next;
head->next = head->prev;
head->prev = tmp;
}
```
:::warning
TODO: 針對上方的若干操作,提取出可共用的 inline function
:notes: jserv
:::
### q_sort
使用 merge sort 進行排序,先將 doubly-linked list 的 `prev` 和 `next` 指標拆開來變成兩個 singly-linked list,以 `next` 組成的 singly-linked list 用來串接排序好的節點,形成一個個獨立排序好的 sublists,再以 `prev` 組成的 singly-linked list 串接每一個 sublists,即可在不額外配置記憶體的情況下完成 merge sort。
```c
static void merge(struct list_head **chain,
struct list_head *a,
struct list_head *b)
{
struct list_head **tail = chain;
while (a && b) {
element_t *alist = list_entry(a, element_t, list);
element_t *blist = list_entry(b, element_t, list);
if (strcmp(alist->value, blist->value) < 0) {
*tail = a;
a = a->next;
} else {
*tail = b;
b = b->next;
}
tail = &(*tail)->next;
}
if (a) {
*tail = a;
} else if (b) {
*tail = b;
}
}
/*
* 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.
*/
void q_sort(struct list_head *head)
{
if (!head || list_empty(head) || list_is_singular(head)) {
return;
}
struct list_head *list = head->next;
struct list_head *sublist = NULL, *tmp, *currsub;
// Let next chain become to singly linked list
head->prev->next = NULL;
// Divide singly linked list. Use prev chain to link each sublists
while (list) {
tmp = list->next;
list->next = NULL;
list->prev = sublist;
sublist = list;
list = tmp;
}
// Merge sublists
while (sublist->prev) {
struct list_head **chain;
currsub = sublist;
chain = &sublist;
while (currsub && currsub->prev) {
tmp = currsub->prev->prev;
merge(chain, currsub, currsub->prev);
chain = &(*chain)->prev;
currsub = tmp;
}
*chain = currsub;
}
// Fix doubly linked list
head->next = sublist;
tmp = head;
while (sublist) {
sublist->prev = tmp;
tmp = sublist;
sublist = sublist->next;
}
head->prev = tmp;
tmp->next = head;
}
```
## 在 qtest 實作命令 shuffle
首先先看一下要如何增加一個命令,在 `qtest.c` 搜尋 `ih`,試圖從 `ih` 命令怎麼實作的下手,可以看到有 `do_ih()` 函數:
```c
static bool do_ih(int argc, char *argv[])
{
...
}
```
以及在 `console_init()` 中大量使用 `ADD_COMMAND`:
```c
ADD_COMMAND(
ih,
" str [n] | Insert string str at head of queue n times. "
"Generate random string(s) if str equals RAND. (default: n == 1)");
```
檢查 `ADD_COMMAND` macro,位於 `console.h`:
```c
/* Add a new command */
void add_cmd(char *name, cmd_function operation, char *documentation);
#define ADD_COMMAND(cmd, msg) add_cmd(#cmd, do_##cmd, msg)
```
:::warning
command 是「命令」、instruction 是「指令」,請查詢英語辭典
:notes: jserv
:::
`add_cmd` 會在命令列表 `cmd_list` 中添加新命令,明白要新增一條新命令 `shuffle` 則要實作 `do_shuffle`,並在 `console_init()` 替新命令加上 `ADD_COMMAND`。
在查看 `swap`、`reverse` 命令的實作時,發現在呼叫到主要邏輯 `q_swap`、`q_reverse` 前後都有特定的函數呼叫:
```c
set_noallocate_mode(true);
if (exception_setup(true))
q_reverse(l_meta.l);
exception_cancel();
set_noallocate_mode(false);
```
在閱讀了 [K01: lab0 - Signal 處理和應用](https://hackmd.io/dPYu4WH8TuqXnAJvfxm-JA?view#Signal-%E8%99%95%E7%90%86%E5%92%8C%E6%87%89%E7%94%A8)後明白這部分是設定發生 exception 時的處理方式,這邊實作 `shuffle` 時也可將主要邏輯以 `set_noallocate_mode` 和 `exception_xxxx` 包住。
`shuffle` 主要邏輯:
```c
static bool do_shuffle(int argc, char *argv[])
{
if (argc != 1) {
report(1, "%s takes no arguments", argv[0]);
return false;
}
if (!l_meta.l) {
report(3, "Warning: Calling shuffle on null queue");
return false;
}
set_noallocate_mode(true);
if (exception_setup(true)) {
// Fisher–Yates shuffle algorithm
if (l_meta.size > 1) {
int max = l_meta.size;
int curr_idx = 1;
// 1-based indexing
struct list_head *curr = l_meta.l->next, *tail = l_meta.l->prev,
*tmpcurr;
while (max != 1) {
int r = rand() % max + 1;
// Find r-th node
while (curr_idx < r) {
curr = curr->next;
curr_idx++;
}
while (curr_idx > r) {
curr = curr->prev;
curr_idx--;
}
// Put r-th node to the tail
if (curr == tail) {
tail = tail->prev;
// curr will get updated at next round, we don't need
// to update it here.
} else {
tmpcurr = curr->next;
curr->next->prev = curr->prev;
curr->prev->next = curr->next;
tail->next->prev = curr;
curr->next = tail->next;
tail->next = curr;
curr->prev = tail;
curr = tmpcurr;
}
max--;
}
}
}
exception_cancel();
set_noallocate_mode(false);
show_queue(3);
return !error_check();
}
```
新增命令:
```c
static void console_init()
{
...
ADD_COMMAND(shuffle,
" | Use Fisher–Yates shuffle algorithm to "
"shuffle all nodes in queue");
...
}
```
## 在 qtest 實作命令 web
提供命令 `web`,並可以用參數指定 listen port,預設 port 為 9999:
```c
ADD_COMMAND(web,
" [port] | Host a web server (default: port == 9999)");
```
使用範例可以用 `curl` 進行測試:
```
# curl -v <SERVER>:<PORT>/<COMMAND>[/<ARGUMENT>][/<ARGUMENT>]...
curl -v localhost:9999/it/1
```
新增了 `web.h` 和 `web.c`,將從 [tiny-web-server](https://github.com/7890/tiny-web-server) 的主要程式碼放置於此。新增檔案後需要修改 `Makefile`:
```
OBJS := qtest.o report.o console.o harness.o queue.o \
random.o dudect/constant.o dudect/fixture.o dudect/ttest.o \
- linenoise.o
+ linenoise.o web.o
```
值得一提的是,在 `process()` 尾端要另外配置記憶體,並複製字串的部分如下:
```c
char *process(int fd, struct sockaddr_in *clientaddr)
{
...
char *ret = malloc(strlen(req.filename) + 1);
strncpy(ret, req.filename, strlen(req.filename) + 1);
printf("[Web] p: %s\n", p);
return ret;
}
```
而 `req.filename` 的設定來自於 `parse_request()`:
```c
static void parse_request(int fd, http_request *req)
{
...
url_decode(filename, req->filename, MAXLINE);
}
```
這邊最長能往 `req->filename` 寫入 `MAXLINE` 個字,也就是 1024,而 `req` 為 `http_request` 結構,其原始定義如下:
```c
typedef struct {
char filename[512];
off_t offset; /* for support Range */
size_t end;
} http_request;
```
`req` 為 `process()` 的區域變數,這就導致了有 stack-based buffer overflow 的漏洞,目前修正方式是將 `http_request` 定義方式改成如下:
```c
typedef struct {
char filename[MAXLINE];
off_t offset; /* for support Range */
size_t end;
} http_request;
```
在 `qtest.c` 新增 `do_web()`:
```c
static bool do_web(int argc, char *argv[])
{
int port;
if (argc > 2) {
report(1, "%s needs 0-1 argument", argv[0]);
return false;
} else if (argc == 2) {
port = atoi(argv[1]);
} else {
// Default port number is 9999
port = 9999;
}
// Initialize server socket
web_sock = open_listenfd(port);
if (web_sock > 0) {
report(3, "listen on port %d, fd is %d\n", port, web_sock);
} else {
perror("ERROR");
return false;
}
return true;
}
```
參照 [K01: lab0 - 整合 tiny-web-server](https://hackmd.io/dPYu4WH8TuqXnAJvfxm-JA#%E6%95%B4%E5%90%88-tiny-web-server) 的說明進行 `run_console()` 和 `cmd_select()` 的修改。
首先是 `run_console()`,當 web server 開啟時,則改成通過 `cmd_select()` 進行輸入的選擇:
```c
if (!has_infile) {
char *cmdline;
- while ((cmdline = linenoise(prompt)) != NULL) {
+
+ while (web_sock <= 0 && (cmdline = linenoise(prompt)) != NULL) {
interpret_cmd(cmdline);
linenoiseHistoryAdd(cmdline); /* Add to the history. */
linenoiseHistorySave(HISTORY_FILE); /* Save the history on disk. */
linenoiseFree(cmdline);
}
+
+ if (web_sock) {
+ while (!cmd_done()) {
+ cmd_select(0, NULL, NULL, NULL, NULL);
+ }
+ }
} else {
- while (!cmd_done())
+ while (!cmd_done()) {
cmd_select(0, NULL, NULL, NULL, NULL);
+ }
}
```
`cmd_select()` 當 server socket 有輸入時則要接收連線並處理:
```c
/* Commandline input available */
FD_CLR(infd, readfds);
result--;
- if (has_infile) {
- char *cmdline;
- cmdline = readline();
+ if (has_infile || web_sock) {
+ char *cmdline = readline();
if (cmdline)
interpret_cmd(cmdline);
}
+ } else if (readfds && FD_ISSET(web_sock, readfds)) {
+ FD_CLR(web_sock, readfds);
+ result--;
+
+ int connfd;
+ struct sockaddr_in clientaddr;
+ socklen_t clientlen = sizeof(clientaddr);
+ connfd = accept(web_sock, (SA *) &clientaddr, &clientlen);
+
+ char *p = process(connfd, &clientaddr);
+ if (p)
+ interpret_cmd(p);
+ free(p);
+ close(connfd);
}
+
return result;
}
```
使用結果 (`qtest`):
```
➜ lab0-c git:(master) ✗ ./qtest
cmd> option echo 0
cmd> new
l = []
cmd> web
listen on port 9999, fd is 3
cmd> accept request, fd is 4, pid is 18350
127.0.0.1:34124 200 - 'it 1' (text/plain)
l = [1]
cmd> accept request, fd is 4, pid is 18350
127.0.0.1:34128 200 - 'it 2' (text/plain)
l = [1 2]
cmd> accept request, fd is 4, pid is 18350
127.0.0.1:34132 200 - 'it 3' (text/plain)
l = [1 2 3]
cmd> accept request, fd is 4, pid is 18350
127.0.0.1:34136 200 - 'it 4' (text/plain)
l = [1 2 3 4]
cmd> accept request, fd is 4, pid is 18350
127.0.0.1:34140 200 - 'it 5' (text/plain)
l = [1 2 3 4 5]
cmd> accept request, fd is 4, pid is 18350
127.0.0.1:34144 200 - 'shuffle' (text/plain)
l = [4 1 3 5 2]
cmd> sort
l = [1 2 3 4 5]
cmd> accept request, fd is 4, pid is 18350
127.0.0.1:34148 200 - 'free' (text/plain)
l = NULL
cmd>
```
Client 端:
```
➜ lab0-c git:(master) ✗ curl localhost:9999/it/1
curl: (52) Empty reply from server
➜ lab0-c git:(master) ✗ curl localhost:9999/it/2
curl: (52) Empty reply from server
➜ lab0-c git:(master) ✗ curl localhost:9999/it/3
curl: (52) Empty reply from server
➜ lab0-c git:(master) ✗ curl localhost:9999/it/4
curl: (52) Empty reply from server
➜ lab0-c git:(master) ✗ curl localhost:9999/it/5
curl: (52) Empty reply from server
➜ lab0-c git:(master) ✗ curl localhost:9999/shuffle
curl: (52) Empty reply from server
➜ lab0-c git:(master) ✗ curl localhost:9999/free
curl: (52) Empty reply from server
➜ lab0-c git:(master) ✗
```
# 開發過程
## list.h
觀察 `lab0-c` 中的 [list.h](https://github.com/sysprog21/lab0-c/blob/master/list.h),與 Linux source code 中的 [include/linux/list.h](https://elixir.bootlin.com/linux/v4.1/source/include/linux/list.h) 很像。先閱讀了一下此檔案,一方面看是否有能派上用場的工具,一方面先詳讀每個工具以避免實作時錯誤使用。
### container_of
```c
#ifndef container_of
#ifdef __LIST_HAVE_TYPEOF
#define container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
#else
#define container_of(ptr, type, member) \
((type *) ((char *) (ptr) -offsetof(type, member)))
#endif
#endif
```
為何要寫 `__typeof__` 在 [6.7 Referring to a Type with typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) 提到:
> If you are writing a header file that must work when included in ISO C programs, write **\_\_typeof__** instead of typeof.
但這邊我真正的疑問是為何要分成兩種做法,實際使用兩者,編譯成執行檔,查看組語:
```c
#include <stdio.h>
#include <stddef.h>
typedef struct {
int a;
int b;
} test;
#define container_of(ptr, type, member) \
((type *) ((char *) (ptr) - offsetof(type, member)))
#define gnu_container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
int main()
{
test instance;
test* ptr = &instance;
printf("instance : %p\n", &instance);
printf("container_of : %p\n", container_of(&(ptr->b), test, b));
printf("gnu_container_of: %p\n", gnu_container_of(&(ptr->b), test, b));
}
```
組合語言:
```c
0x000055555555513d <+8>: lea rax,[rbp-0x18]
0x0000555555555141 <+12>: mov QWORD PTR [rbp-0x8],rax
0x0000555555555145 <+16>: lea rax,[rbp-0x18]
0x0000555555555149 <+20>: mov rsi,rax
0x000055555555514c <+23>: lea rdi,[rip+0xeb1] # 0x555555556004
0x0000555555555153 <+30>: mov eax,0x0
0x0000555555555158 <+35>: call 0x555555555030 <printf@plt>
// 取出 ptr
0x000055555555515d <+40>: mov rax,QWORD PTR [rbp-0x8]
// &(ptr->b)
0x0000555555555161 <+44>: add rax,0x4
// 減去 offsetof(test, b)
0x0000555555555165 <+48>: sub rax,0x4
0x0000555555555169 <+52>: mov rsi,rax
0x000055555555516c <+55>: lea rdi,[rip+0xea7] # 0x55555555601a
0x0000555555555173 <+62>: mov eax,0x0
0x0000555555555178 <+67>: call 0x555555555030 <printf@plt>
// 取出 ptr
0x000055555555517d <+72>: mov rax,QWORD PTR [rbp-0x8]
// &(ptr->b)
0x0000555555555181 <+76>: add rax,0x4
// 儲存 __pmember
0x0000555555555185 <+80>: mov QWORD PTR [rbp-0x10],rax
// 取出 __pmember
0x0000555555555189 <+84>: mov rax,QWORD PTR [rbp-0x10]
// 將 __pmember 減去 offsetof(test, b)
0x000055555555518d <+88>: sub rax,0x4
0x0000555555555191 <+92>: mov rsi,rax
0x0000555555555194 <+95>: lea rdi,[rip+0xe95] # 0x555555556030
0x000055555555519b <+102>: mov eax,0x0
0x00005555555551a0 <+107>: call 0x555555555030 <printf@plt>
```
在 [6.48 Alternate Keywords](https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html) 中提到,在使用 `-ansi` 或 `-std` 的情況下,`asm`、`inline`、`typeof` 這些關鍵字不支援,解法是需要在這些關鍵字前後加上雙底線 `__`。
`typeof` 為編譯器提供的功能,而非原本就在 C specs 中的關鍵字,因此使用 `typeof` 要看你用的編譯器有沒有支援,而 `__typeof__` 為 GNU gcc 提供的功能,在其他編譯器可能沒有,這種情況下可以用 macro `__GNUC__` 來判別編譯器是否為 GNU gcc,如下例子:
```c
#ifndef __GNUC__
#define __typeof__ typeof
#endif
```
但以上例子成立的前提是你使用的別款編譯器要支援 `typeof`。
回過頭來解釋 `container_of`,裡面用到的 `offsetof` 就有在 C specs 中,所以其一邊的定義是用了 GNU gcc 功能,一邊則是完全遵守 C specs,但似乎還是沒解釋到為何不單純用遵守 C specs 的定義就好。
### list_entry
```c
#define list_entry(node, type, member) container_of(node, type, member)
```
在 `qtest.c` 中的使用範例,可以直接從 `element_t` 的 `list` 反推取得 `element_t` 的位址:
```c
element_t *next_item;
next_item = list_entry(item->list.next, element_t, list);
```
```c
char *cur_inserts =
list_entry(l_meta.l->prev, element_t, list)->value;
```
從以上範例也可觀察到,`element_t` 中的 `list`,不管是 `prev` 或 `next` 都應該指向到 `element_t` 中的 `list`。
### list_del
```c
static inline void list_del(struct list_head *node)
{
struct list_head *next = node->next;
struct list_head *prev = node->prev;
next->prev = prev;
prev->next = next;
#ifdef LIST_POISONING
node->prev = (struct list_head *) (0x00100100);
node->next = (struct list_head *) (0x00200200);
#endif
}
```
若有啟用 `LIST_POISONING`,則可以防止被移出 list 的 node 的 `prev` 和 `next` 仍然指向有效的 node,進而防止類似 Use-After-Free 的攻擊。
### list_splice
```c
static inline void list_splice(struct list_head *list, struct list_head *head)
{
struct list_head *head_first = head->next;
struct list_head *list_first = list->next;
struct list_head *list_last = list->prev;
if (list_empty(list))
return;
head->next = list_first;
list_first->prev = head;
list_last->next = head_first;
head_first->prev = list_last;
}
```
將 list 中的 node 加到 head 的起頭,隨後若要再使用 list 需要重新初始化。
### list_for_each_entry_safe
```c
/**
* list_for_each_entry_safe - iterate over list entries and allow deletes
* @entry: pointer used as iterator
* @safe: @type pointer used to store info for next entry in list
* @head: pointer to the head of the list
* @member: name of the list_head member variable in struct type of @entry
*
* The current node (iterator) is allowed to be removed from the list. Any
* other modifications to the the list will cause undefined behavior.
*
* FIXME: remove dependency of __typeof__ extension
*/
#define list_for_each_entry_safe(entry, safe, head, member) \
for (entry = list_entry((head)->next, __typeof__(*entry), member), \
safe = list_entry(entry->member.next, __typeof__(*entry), member); \
&entry->member != (head); entry = safe, \
safe = list_entry(safe->member.next, __typeof__(*entry), member))
```
迴圈經過所有在 list 中的結構 entry 本身,而非用來連結的 pointer。
但是定義中還是使用到了 GNU gcc 的額外功能 `__typeof__`,所以才註解了 `FIXME: remove dependency of __typeof__ extension`。
## list_sort.c
[linux/lib/list_sort.c 部分程式碼](https://github.com/torvalds/linux/blob/master/lib/list_sort.c):
```c
__attribute__((nonnull(2,3)))
void list_sort(void *priv, struct list_head *head, list_cmp_func_t cmp)
{
struct list_head *list = head->next, *pending = NULL;
size_t count = 0; /* Count of pending */
if (list == head->prev) /* Zero or one elements */
return;
/* Convert to a null-terminated singly-linked list. */
head->prev->next = NULL;
do {
size_t bits;
struct list_head **tail = &pending;
/* Find the least-significant clear bit in count */
for (bits = count; bits & 1; bits >>= 1)
tail = &(*tail)->prev;
/* Do the indicated merge */
if (likely(bits)) {
struct list_head *a = *tail, *b = a->prev;
a = merge(priv, cmp, b, a);
/* Install the merged result in place of the inputs */
a->prev = b->prev;
*tail = a;
}
/* Move one element from input list to pending */
list->prev = pending;
pending = list;
list = list->next;
pending->next = NULL;
count++;
} while (list);
...
}
```
這邊非常巧妙,可以使得 pending lists 滿足幾個假設:
1. 每個 pending list size 都為 $2^k$
2. 每種 size 的 pending list 個數為 0 ~ 2
3. 較小 size 的 pending list 位置會比較靠前
`pending` 以 `prev` 連結成一個 singly linked list,每個 `prev` 指向的是一個 size $2^k$ sublists,等待著被合併。
以下表格逐步說明 count 增長時,pending lists 內部中各個 sublists 的變化,每一個數字表示一個 sublist size,寫在越後面的表示是在 `pending` 這張 singly linked list 越前面的位置:
| count | pending lists |
| -------- | -------- |
| 0b0 | NULL |
| 0b1 | 1 |
| 0b10 | $2^1$ |
| 0b11 | $2^1$, 1 |
| 0b100 | $2^1$, $2^1$ |
| 0b101 | $2^2$, 1 |
| 0b110 | $2^2$, $2^1$ |
| 0b111 | $2^2$, $2^1$, 1 |
| 0b1000 | $2^2$, $2^1$, $2^1$ |
| 0b1001 | $2^2$, $2^2$, 1 |
| 0b1010 | $2^2$, $2^2$, $2^1$ |
| 0b1011 | $2^3$, $2^1$, 1 |
| 0b1100 | $2^3$, $2^1$, $2^1$ |
| 0b1101 | $2^3$, $2^2$, 1 |
| 0b1110 | $2^3$, $2^2$, $2^1$ |
| 0b1111 | $2^3$, $2^2$, $2^1$, 1 |
再對比自己的實作,發現自己的實作問題是在 worst case 的情況下,在 merge 時兩邊 list 的 size 沒有限制,導致單次 merge 最差的時間複雜度會是 $O(n)$;反過來看 Linux 的實作方式,最差的狀況兩邊 list size 為 $2^{(k+1)}$ 和 $2^k$,單次 merge 過慢的發生機率就會顯著下降。
## trace-17-complexity
在完成 queue 的實作後首次推上 github,[CI 的結果](https://github.com/LJP-TW/lab0-c/actions/runs/1880381686/attempts/1)不如預期。在本地端測試時,主要是 trace-14-perf 一直過不了;但 CI 的結果反而是卡在 trace-17-complexity,而這部分是測試 `q_insert_tail`、`q_insert_head`、`q_remove_tail`、`q_remove_head` 是否為 constant time,這我反而很有信心應該要通過。錯誤訊息中有大量的 `not enough measurements` 訊息,可能與之相關。[第二次重新跑一次 CI](https://github.com/LJP-TW/lab0-c/actions/runs/1880381686/attempts/2) 就通過了,需要再研究為何會有這樣的結果發生。
:::warning
TODO: 對照閱讀論文
:notes: jserv
:::
```
meas: 0.01 M, not enough measurements (430 still to go).
meas: 0.01 M, not enough measurements (320 still to go).
meas: 0.01 M, not enough measurements (210 still to go).
meas: 0.01 M, not enough measurements (100 still to go).
meas: 0.01 M, max t: +62.42, max tau: 6.24e-01, (5/tau)^2: 6.42e+01.
ERROR: Probably not constant time
--- Trace Points
+++ TESTING trace trace-01-ops:
--- trace-01-ops 5/5
+++ TESTING trace trace-02-ops:
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
--- trace-05-ops 6/6
+++ TESTING trace trace-06-ops:
--- trace-06-ops 6/6
+++ TESTING trace trace-07-string:
--- trace-07-string 6/6
+++ TESTING trace trace-08-robust:
--- trace-08-robust 6/6
+++ TESTING trace trace-09-robust:
--- trace-09-robust 6/6
+++ TESTING trace trace-10-robust:
--- trace-10-robust 6/6
+++ TESTING trace trace-11-malloc:
--- trace-11-malloc 6/6
+++ TESTING trace trace-12-malloc:
--- trace-12-malloc 6/6
+++ TESTING trace trace-13-malloc:
--- trace-13-malloc 6/6
+++ TESTING trace trace-14-perf:
--- trace-14-perf 6/6
+++ TESTING trace trace-15-perf:
--- trace-15-perf 6/6
+++ TESTING trace trace-16-perf:
--- trace-16-perf 6/6
+++ TESTING trace trace-17-complexity:
--- trace-17-complexity 0/5
--- TOTAL 95/100
make: *** [Makefile:56: test] Error 1
```