# 2021q1 Homework2 (quiz2) contributed by < `jasonmatobo` > :::info :alien: 補充 * 書本模式請使用以下連結 : [Linux 核心設計筆記(2021)](https://hackmd.io/@jasonmatobo/Linux_Kernel_Note_2021/) ::: # 測驗 1 ## container_of 舉一個實際的例子 [pcie_pme_work_fn](https://elixir.bootlin.com/linux/latest/source/drivers/pci/pcie/pme.c#L216) ```cpp static void pcie_pme_work_fn(struct work_struct *work) { struct pcie_pme_service_data *data = container_of(work, struct pcie_pme_service_data, work); struct pci_dev *port = data->srv->port; u32 rtsta; ``` 這個 `pcie_pme_work_fn` 中傳入的參數是 `struct work_struct` `struct work_struct` 其實是包在 `struct pcie_pme_service_data` 中的 `member` [pcie_pme_service_data](https://elixir.bootlin.com/linux/latest/source/drivers/pci/pcie/pme.c#L41) ```cpp struct pcie_pme_service_data { spinlock_t lock; struct pcie_device *srv; struct work_struct work; ----> Here!!! bool noirq; /* If set, keep the PME interrupt disabled. */ }; ``` 當你想控制` struct` 其他 `member` 時,可以使用 `container_of` ```cpp static void pcie_pme_work_fn(struct work_struct *work) { // 使用 container_of struct pcie_pme_service_data *data = container_of(work, struct pcie_pme_service_data, work); // 可以使用 struct 中其他的 member ---> srv struct pci_dev *port = data->srv->port; u32 rtsta; ``` 以下是個範例程式 ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stddef.h> #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) typedef struct TEST_DATA{ int v1; char v2[16]; int v3; char v4[32]; unsigned long v5; }*pData,sData; void print_all(void *pMember) { // 透過 container_of ,可以存取其他 member pData pB = container_of( pMember, struct TEST_DATA, v3); printf("========================\n"); printf("B = Addr: %p \n",(void *) pB); printf("B = Addr: %p value: %d \n",(void *) &(pB->v1),pB->v1); printf("B = Addr: %p value: %s \n",(void *) &(pB->v2),pB->v2); printf("B = Addr: %p value: %d \n",(void *) &(pB->v3),pB->v3); printf("B = Addr: %p value: %s \n",(void *) &(pB->v4),pB->v4); printf("B = Addr: %p value: %ld \n",(void *) &(pB->v5),pB->v5); } void main(void) { pData pA = (struct TEST_DATA*) malloc (sizeof(sData)); // Assign pA->v1 = 10; strncpy( pA->v2, "Hello", sizeof(pA->v2) ); pA->v3 = 20; strncpy( pA->v4, "World", sizeof(pA->v4) ); pA->v5 = 30; // Print Address & value printf("========================\n"); printf("A = Addr: %p \n",(void *) pA); printf("A = Addr: %p value: %d \n",(void *) &(pA->v1),pA->v1); printf("A = Addr: %p value: %s \n",(void *) &(pA->v2),pA->v2); printf("A = Addr: %p value: %d \n",(void *) &(pA->v3),pA->v3); printf("A = Addr: %p value: %s \n",(void *) &(pA->v4),pA->v4); printf("A = Addr: %p value: %ld \n",(void *) &(pA->v5),pA->v5); // 只有傳 v3 給 function print_all(&pA->v3); free(pA); } ``` 執行結果如下 ```shell ======================== A = Addr: 0x5555d4e4a2a0 A = Addr: 0x5555d4e4a2a0 value: 10 A = Addr: 0x5555d4e4a2a4 value: Hello A = Addr: 0x5555d4e4a2b4 value: 20 A = Addr: 0x5555d4e4a2b8 value: World A = Addr: 0x5555d4e4a2d8 value: 30 ======================== B = Addr: 0x5555d4e4a2a0 B = Addr: 0x5555d4e4a2a0 value: 10 B = Addr: 0x5555d4e4a2a4 value: Hello B = Addr: 0x5555d4e4a2b4 value: 20 B = Addr: 0x5555d4e4a2b8 value: World B = Addr: 0x5555d4e4a2d8 value: 30 ``` 以下討論 `container_of` 作法 ```cpp #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) ``` ### __extension__ 這個 `__extension__` 是給編譯器看的 如果你的 C 程式寫法不是標準 ANSI C 寫法,那編譯時會出現 `Warning` 加上 `__extension__` 是告訴編譯器,即使不是標準 ANSI C 寫法也不要出現`Warning` ### ({}) `container_of` 前後用 `({})` 包住,這個寫法就不是標準的 ANSI C 所以加上`__extension__` 不要出現 `Warning` 而這個 `({})` 是 GCC 編譯器支援的功能,所以是 GCC 看得懂這種寫法 可以參考 [Statements and Declarations in Expressions](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html) ### typeof() `typeof` 也是 GCC 提供的功能,可以取出傳入參數的類型 可以參考 [Referring to a Type with typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) 舉例: ```cpp int a; typeof(a) b; // 等於 int b ``` ### ((type *) 0)->member 這裡 `type` & `member` 都是傳進來的參數 以上述範例來說 ```cpp container_of( pMember, struct TEST_DATA, v3); type = struct TEST_DATA member = v3 ((type *) 0)->member ===> ((struct TEST_DATA *) 0)->v3 // 外面在加上 typeof 取類型等於取 v3 的類型 = int // 所以 __typeof__(((type *) 0)->member) = int ``` ### offsetof(struct , member) 可以算出 `member` 在 `struct` 的 `offset`,舉例: ```cpp struct foo { char a; char b[10]; char c; }; int main () { printf ("offsetof(struct foo,a) is %d\n",(int)offsetof(struct foo,a)); printf ("offsetof(struct foo,b) is %d\n",(int)offsetof(struct foo,b)); printf ("offsetof(struct foo,c) is %d\n",(int)offsetof(struct foo,c)); return 0; } // Output: // offsetof(struct foo,a) is 0 // offsetof(struct foo,b) is 1 // offsetof(struct foo,c) is 11 ``` ### container_of 結論 可以利用 `gcc -E` 來觀察 `Preprocessor` 的結果 ```cpp pData pB = __extension__({ const __typeof__(((struct TEST_DATA *) 0)->v3) *__pmember = (pMember); (struct TEST_DATA *) ((char *) __pmember - ((size_t)&(((struct TEST_DATA *)0)->v3))); }); // 簡化 pData pB = const int *__pmember = v3; // 取得 v3 address,假設等於 0x5555d4e4a2b4 __pmember - offsetof(struct TEST_DATA, v3); // 簡化 pData pB = 0x5555d4e4a2b4 - 0x14 = 0x5555d4e4a2a0; // 所以 pB = 0x5555d4e4a2a0 = 此 struct 的開頭 ``` ## list.h 解讀 :::info `list_head` 中有 2 個 `member`,都是指向 `list_head` 的指標 ::: ```cpp struct list_head { struct list_head *prev; struct list_head *next; }; ``` ```graphviz digraph list_add_node_t { node [shape=record]; struct1 [shape=record,label="{ <f0> prev | <f1> next}"]; } ``` :::info 回傳 `list_head` 型態的變數,並且初始化 `prev` & `next` 指向自己 ::: ```cpp #define LIST_HEAD(head) struct list_head head = {&(head), &(head)} static inline void INIT_LIST_HEAD(struct list_head *head) { head->next = head; head->prev = head; } ``` ```graphviz digraph list_add_node_t { node [shape=record]; n_head [shape=record,style=filled,fillcolor=green,label="{ <p> [head] prev | <n> [head] next}"]; n_head:p->n_head:p; n_head:n->n_head:p; } ``` :::info 看 code 流程是插入 1 個 `node` 在 `prev` 和 `head` 中間 不知為什麼會取名為 `add_tail`? ::: ```cpp 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; } ``` ```graphviz digraph list_add_node_t { node [shape=record]; n_pre [shape=record,label="{ <p> [pre] prev | <n> [pre] next}"]; n_node [shape=record,style=filled,fillcolor=green,label="{ <p> [node] prev | <n> [node] next}"]; n_head [shape=record,label="{ <p> [head] prev | <n> [head] next}"]; n_pre:n->n_node:p; n_node:p->n_pre:p; n_node:n->n_head:p; n_head:p->n_node:p; } ``` :::info 將傳入的 `node` 刪除 ::: ```cpp 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; } ``` ```graphviz digraph list_add_node_t { node [shape=record]; n_pre [shape=record,label="{ <p> [pre] prev | <n> [pre] next}"]; n_node [shape=record,style=filled,fillcolor=red,label="{ <p> del : [node] prev | <n> del : [node] next}"]; n_head [shape=record,label="{ <p> [head] prev | <n> [head] next}"]; n_pre:n->n_head:p; n_head:p->n_pre:p; } ``` :::info 檢查 `list_head` 是否為空,這邊判斷是如果 `next` 指向自己代表示空 ::: ```cpp static inline int list_empty(const struct list_head *head) { return (head->next == head); } ``` ```graphviz digraph list_add_node_t { node [shape=record]; n_head [shape=record,label="{ <p> [head] prev | <n> [head] next}"]; n_head:n->n_head:p; } ``` :::info 檢查 `list_head` 是否"只"存在 1 個 `node` ::: ```cpp static inline int list_is_singular(const struct list_head *head) { return (!list_empty(head) && head->prev == head->next); } ``` ```graphviz digraph list_add_node_t { node [shape=record]; n_pre [shape=record,label="{ <p> [pre] prev | <n> [pre] next}"]; n_head [shape=record,label="{ <p> [head] prev | <n> [head] next}"]; n_pre:n->n_head:p; n_head:p->n_pre:p; } ``` :::info 將 `list` 加到 `head` 的 `last` ::: ```cpp static inline void list_splice_tail(struct list_head *list, struct list_head *head) { struct list_head *head_last = head->prev; struct list_head *list_first = list->next; struct list_head *list_last = list->prev; if (list_empty(list)) return; head->prev = list_last; list_last->next = head; list_first->prev = head_last; head_last->next = list_first; } ``` ```cpp static inline void list_cut_position(struct list_head *head_to, struct list_head *head_from, struct list_head *node) { struct list_head *head_from_first = head_from->next; if (list_empty(head_from)) return; if (head_from == node) { INIT_LIST_HEAD(head_to); return; } head_from->next = node->next; head_from->next->prev = head_from; head_to->prev = node; node->next = head_to; head_to->next = head_from_first; head_to->next->prev = head_to; } ``` ```graphviz digraph list_add_node_t { node [shape=record]; n_1 [shape=record,label="{ <p> [head_from] prev | <n> [head_from] next}"]; n_2 [shape=record,style=filled,fillcolor=green,label="{ <p> [xxx] prev | <n> [xxx] next}"]; n_3 [shape=record,label="{ <p> [head_to] prev | <n> [head_to] next}"]; n_4 [shape=record,label="{ <p> [head_from_first] prev | <n> [head_from_first] next}"]; n_5 [shape=record,style=filled,fillcolor=salmon,label="{ <p> [node] prev | <n> [node] next}"]; n_1:n->n_2:p; n_2:p->n_1:p; n_2:n->n_3:p; n_3:p->n_2:p; n_3:n->n_4:p; n_4:p->n_3:p; n_5:n->n_2:p; } ``` ## 分析程式 記憶體分析 : Address Sanitizer & valgrind 沒有出現錯誤 ```shell= $ gcc -I ./. -fsanitize=address -g -o a.out main.c $ ./a.out $ rm a.out $ valgrind --leak-check=full ./a.out $ ./a.out ==12588== Memcheck, a memory error detector ==12588== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==12588== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info ==12588== Command: ./a.out ==12588== ==12588== ==12588== HEAP SUMMARY: ==12588== in use at exit: 0 bytes in 0 blocks ==12588== total heap usage: 187,656 allocs, 187,656 frees, 4,529,436 bytes allocated ==12588== ==12588== All heap blocks were freed -- no leaks are possible ==12588== ==12588== For lists of detected and suppressed errors, rerun with: -s ==12588== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) ``` [perf 介紹](https://tigercosmos.xyz/post/2020/08/system/perf-basic/) ``` sudo sh -c " echo -1 > /proc/sys/kernel/perf_event_paranoid" ``` ## 問題解答 ``` COND1 = (c) fast->next == list COND2 = (b) fast->next->next == list MMM = (e) &get_middle(&q->list)->list TTT = (a) slow ``` # 測驗 2 ## Linux 實作機制 [__roundup_pow_of_two](https://elixir.bootlin.com/linux/latest/source/include/linux/log2.h#L174) # 測驗 3 # 測驗 4 ###### tags `lab`