# 2019q1 Homework1 (list)
contributed by < `ldotrg` >
###### tags: `linux2019`
## 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本?
- 使用 marco 可以減少執行時期 操作satck 所需的記憶體與時間成本
:::info
在 header 中使用 inline 時,常搭配 static 出現,這樣的 static inline 函式搭配編譯器最佳化,則有 macro 的效果,並得以進行參數形態檢查
:::
TODO: 做實驗檢查
## Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量
### 1. 新增一個Character device [linux/cdev.h](https://github.com/torvalds/linux/blob/6f0d349d922ba44e4348a17a78ea51b7135965b1/include/linux/cdev.h#L14)
#### INIT_LIST_HEAD
Kernel Module 要註冊使用 char device 就必須新增一個 list head.
```clike=
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
```
#### list_add
每次 user space 呼叫 open 裝置檔(/dev/xxx), 就會增加一個node.
[Linux Code](https://github.com/torvalds/linux/blob/6f0d349d922ba44e4348a17a78ea51b7135965b1/fs/char_dev.c#L376)
```clike=
static int chrdev_open(struct inode *inode, struct file *filp)
{
const struct file_operations *fops;
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
...
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj);
spin_lock(&cdev_lock);
/* Check i_cdev again in case somebody beat us to it while
we dropped the lock. */
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
...
}
```
## GNU extension 的 [typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) 有何作用?在程式碼中扮演什麼角色?
typeof 允許我們傳入一個變數,代表的會是該變數的型態。
舉例來說:
``` clike
int a;
typeof(a) b = 10; // equals to "int b = 10;
char s[6] = "Hello";
char *ch;
typeof(ch) k = s; // equals to "char *k = s;
```
Real world Example: 為了避免重複呼叫函式`f`第三次
```clike=
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
max(f(1), f(2));
```
## 解釋以下巨集的原理
```clike
#define container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
```
Why we need the `container_of`?
__If strcut x contains y, we could find the pointer to x given the pointer to y__
### 拆解
### `__extension__`
翻閱 gcc 文件中的 [Alternative Keywords](https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html#Alternate-Keywords) 提到
> You can prevent such warnings within one expression by writing `__extension__` before the expression.
GNU C Extension裏面有使用到非標準的ANSI C的語法, 因此會產生警告.
如果不想要看見這個警告,只要在表達式之加入`__extension__`修飾字.
### `__typeof__`
gcc 文件中的 [Alternative Keywords](https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html#Alternate-Keywords) 可以得知`__`是為了相容性考量而加的,作用和 keyword `typeof` 相同,在 [Typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html#Typeof) 章節中可以得知 typeof 是用來獲取變數的型別。
### `((type *) 0)->member`
照理來說 NULL pointer dereference 應該會出錯.
> The operand of typeof is evaluated for its side effects if and only if it is an expression of variably modified type or the name of such a type.
> 黑人問號??
### `offsetof`
:::danger
找出 C99 規格中對應的說明。注意 [offsetof](http://man7.org/linux/man-pages/man3/offsetof.3.html)
:notes: jserv
:::
macro 定義
```clike=
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
```
這裡的 `&((TYPE *)0)->MEMBER` 並不是求值後取地址,struct 中的成員變數地址是相對於 struct 的初始地址的,並且編譯時期就可以確定也就沒必要在運行時求值了,所以在此得到的是 `0 + offset of MEMBER in struct`
:::success
將 $0$ 改成 $n\in N$,會發現回傳值會增加 $n$
:::
延伸閱讀: [How to Use C's offsetof() Macro](https://barrgroup.com/Embedded-Systems/How-To/C-Offsetof-Macro?fbclid=IwAR0H5IGZ7nJUmoRE2gyeyCGllQglZBhZNJkyFDNDHUdf1DDWwsQajhFg1GA)
### `{}` compound statement
第一次看到時覺得很奇怪,這 macro 到底要怎麼 return(廣義上的) 結果阿?
gcc docs 的 [Statement-Exprs](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html) 章節中提到,這被稱為 **compound statement** 且回傳值是最後一個 expression 的結果
> The last thing in the compound statement should be an expression followed by a semicolon; the value of this subexpression serves as the value of the entire construct.
### `(type *) ((char *) __pmember - offsetof(type, member));`

- Run yourself:
```clike=
#include <stdio.h>
#define pointer(T) typeof(T *)
#define array(T, N) typeof(T [N])
#define my_offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define my_container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__ptrmember = (ptr); \
(type *) ((char *) __ptrmember - my_offsetof(type, member));\
})
__attribute__((packed, aligned(4))) struct hello {
int aa;
int bb;
unsigned long cc;
int *dd;
};
int main (char *argv[], int argc) {
int aa;
__typeof__(aa) bb;
struct hello hel_obj;
array (pointer (char), 4) y;
aa = 5566;
bb = 7788;
printf("%d/%d sizeof y: %d \n", aa, bb, sizeof(y));
printf("pointer of member dd 0x%x\n", &hel_obj.dd);
printf("pointer of hel_obj: 0x%x\n", my_container_of(&hel_obj.dd, struct hello, dd));
return 0;
}
```
- [ ] 除了你熟悉的 add 和 delete 操作,`list.h` 還定義一系列操作,為什麼呢?這些有什麼益處?
## `LIST_POISONING` 這樣的設計有何意義?
```clike=
#ifdef LIST_POISONING
node->prev = (struct list_head *) (0x00100100);
node->next = (struct list_head *) (0x00200200);
#endif
```
故意將已刪除節點的指標指向特定的位址.
這些特別的位址可以作為分析kernel panic
Reference: [Kernel Debugging wiki](https://wiki.litmus-rt.org/litmus/KernelDebugging)
## linked list 採用環狀是基於哪些考量?
- 用$O(1)$ 的時間複雜度將新節點插入至尾端,無須走訪全部節點.
- 可參考`list_add_tail`實做
```clike=
static inline void list_add_tail(struct list_head *node, struct list_head *head)
{
// 1. get the tail node
struct list_head *prev = head->prev;
// 2. update tail node
prev->next = node;
// 3. Update new node
node->next = head;
node->prev = prev;
// 4. Update the head
head->prev = node;
}
```
- [ ] 什麼情境會需要對 linked list 做排序呢?舉出真實世界的應用,最好在作業系統核心出現
- [ ] 什麼情境會需要需要找到 [第 k 大/小元素](https://www.geeksforgeeks.org/kth-smallestlargest-element-unsorted-array/) 呢?又,你打算如何實作?
- [ ] `list_for_each_safe` 和 `list_for_each` 的差異在哪?"safe" 在執行時期的影響為何?
- [ ] for_each 風格的開發方式對程式開發者的影響為何?
* 提示:對照其他程式語言,如 Perl 和 Python
- [ ] 程式註解裡頭大量存在 @ 符號,這有何意義?你能否應用在後續的程式開發呢?
提示: 對照看 Doxygen
- [ ] tests/ 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何?
- [ ] tests/ 目錄的 unit test 可如何持續精進和改善呢?
## Implementation [Merge Sort](https://github.com/ldotrg/linux-list/blob/master/examples/merge-sort.c)
- list_split
- 利用fast與slow pointer找到需要切割的點
- 再使用`list_cut_position` 分成左右兩半的list
- list_merge
- leetcode經典題,merge two sorted list, merge K sorted list
- 動畫 https://www.youtube.com/watch?v=odUJXFJR6Q4
- 使用的list工具:`list_splice_tail`,`list_first_entry` , `list_move_tail`
- list_merge_sort
其實最難的不是實做,而是要怎麼測試
1. 在實做時,當然先用最簡單的`printf`觀察排序結果.
2. 接著通過題目的基本測試
- [ ] 以上基本的流程通過後,接著就是要測試效能啦(ToDo)
- [ ] 研究unit test 的技巧(ToDo)
- [ ] 導入像lab0作業的自動評分系統`qtest`(ToDo)
> 想要做的事情好多阿
### Original Assignment info: [F02: list](/u4FPWOKHQXiKrn4MMvkvWA)
###### tags: `Linux Linked List`