# 2021q1 Homework2 (quiz2) contributed by < `XDEv11` > > [GitHub](https://github.com/XDEv11/Linux_Kernel_Internals/tree/main/homework2/quiz2) > [第 2 週測驗題](https://hackmd.io/@sysprog/linux2021-quiz2) ## 測驗 `1` ### 程式碼解說 透過 [`__extension__`](https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html) 來明確表示要使用 GNU C extensions,並避免在某些情況下產生警告。 > -pedantic and other options cause warnings for many GNU C extensions. You can prevent such warnings within one expression by writing __extension__ before the expression. __extension__ has no effect aside from this. [`({})`](https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html) 有點類似於 `,` 運算子,藉此達到類似於函式回傳值的效果。 > 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. [`__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. [`offsetof()`](https://man7.org/linux/man-pages/man3/offsetof.3.html) 得到某個成員的位置偏移量。 ```cpp /** * container_of() - Calculate address of object that contains address ptr * @ptr: pointer to member variable * @type: type of the structure containing ptr * @member: name of the member variable in struct @type * * Return: @type pointer of object containing ptr */ #ifndef container_of #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) #endif ``` doubly-linked list 的鏈結,納入新結構的成員中即可。 ```cpp /** * struct list_head - Head and node of a doubly-linked list * @prev: pointer to the previous node in the list * @next: pointer to the next node in the list */ struct list_head { struct list_head *prev, *next; }; ``` ```cpp /** * LIST_HEAD - Declare list head and initialize it * @head: name of the new object */ #define LIST_HEAD(head) struct list_head head = {&(head), &(head)} ``` ```cpp /** * INIT_LIST_HEAD() - Initialize empty list head * @head: pointer to list head */ static inline void INIT_LIST_HEAD(struct list_head *head) { head->next = head; head->prev = head; } ``` ```cpp /** * list_add_tail() - Add a list node to the end of the list * @node: pointer to the new node * @head: pointer to the head of the list */ 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; } ``` ```cpp /** * list_del() - Remove a list node from the list * @node: pointer to the node */ static inline void list_del(struct list_head *node) { struct list_head *next = node->next, *prev = node->prev; next->prev = prev; prev->next = next; } ``` ```cpp /** * list_empty() - Check if list head has no nodes attached * @head: pointer to the head of the list */ static inline int list_empty(const struct list_head *head) { return (head->next == head); } ``` ```cpp /** * list_is_singular() - Check if list head has exactly one node attached * @head: pointer to the head of the list */ static inline int list_is_singular(const struct list_head *head) { return (!list_empty(head) && head->prev == head->next); } ``` 把 `list` 接到 `node` 的尾端。 ```cpp /** * list_splice_tail() - Add list nodes from a list to end of another list * @list: pointer to the head of the list with the node entries * @head: pointer to the head of the list */ 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, *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; } ``` 把串列從 `node` 的地方切開,前半部分接到 `head_to`,後半部分留在 `head_from`。 ```cpp /** * list_cut_position() - Move beginning of a list to another list * @head_to: pointer to the head of the list which receives nodes * @head_from: pointer to the head of the list * @node: pointer to the node in which defines the cutting point */ 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; } ``` 假設自己定義的 `struct S`,裡面有一個 `list_head list`,可以透過 `list_entry(node, S, list)` 得到整個物件的指標 `struct S *`。 ```cpp /** * list_entry() - Calculate address of entry that contains list node * @node: pointer to list node * @type: type of the entry containing the list node * @member: name of the list_head member variable in struct @type */ #define list_entry(node, type, member) container_of(node, type, member) ``` ```cpp /** * list_first_entry() - get first entry of the list * @head: pointer to the head of the list * @type: type of the entry containing the list node * @member: name of the list_head member variable in struct @type */ #define list_first_entry(head, type, member) \ list_entry((head)->next, type, member) ``` ```cpp /** * list_for_each - iterate over list nodes * @node: list_head pointer used as iterator * @head: pointer to the head of the list */ #define list_for_each(node, head) \ for (node = (head)->next; node != (head); node = node->next) ``` 運用上述程式碼實作 merge sort。 `list_ele_t` 中的 `next` 與 `queue_t` 中的 `head` 和 `tail` 都不需要。 `list_ele_t` 與 `queue_t` 中都有一個 `struct list_head` 即可形成 circular doubly-linked list。 這邊進一步改進,將 `struct list_head` 放在結構中的第一個成員,存取整個物件時,只需把 `struct list_head *` 轉型為 `list_ele_t *`,而不需要使用 `list_entry()` (`container_of()`) 這個 macro,不只讓操作更便利以外,也節省運算指標位置的減法 (`- offsetof(type, member))`)。 :::warning `container_of` 用到 `offsetof`,後者是編譯時期的操作。在結構體中的第一個成員 `struct list_head list;` 需要特別標注其位置,也該考慮到 padding,例如 [Typical alignment of C structs on x86](https://en.wikipedia.org/wiki/Data_structure_alignment#Typical_alignment_of_C_structs_on_x86) 所及,第二個成員實際的記憶體位置可能會因 padding 調整 ::: :::success 根據 [C11 standard](http://open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf) 6.7.2.1 Structure and union specifiers 中第 15 點,對於第一個成員的位置是有規範的。 > Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning. ::: ```cpp typedef struct __element { struct list_head list; /* Put struct list_head in the initial(first) member */ char *value; } list_ele_t; ``` :::warning 程式碼精簡後,不難發現 `queue_t` 根本就是多餘的結構,應該一併調整。這是設計題目時,讓同學們得以進一步重構 (refactor) 的空間。 :notes: jserv ::: :::success 已經進行重構,把多餘的結構去除並改寫程式碼! ::: 回傳值改為 `list_head *`,會比較好進行串列的操作。需要時,也可透過 `list_entry()` 得到整個物件。 ```cpp static struct list_head *get_middle(struct list_head *head) { struct list_head *fast = head->next, *slow = head->next; while (fast->next != head && fast->next->next != head) { slow = slow->next; fast = fast->next->next; } return slow; } ``` ```cpp static void list_merge(struct list_head *lhs, struct list_head *rhs, struct list_head *head) { INIT_LIST_HEAD(head); while (!list_empty(lhs) && !list_empty(rhs)) { char *lv = ((list_ele_t *)lhs->next)->value; char *rv = ((list_ele_t *)rhs->next)->value; struct list_head *tmp = strcmp(lv, rv) <= 0 ? lhs->next : rhs->next; list_del(tmp); list_add_tail(tmp, head); } list_splice_tail(list_empty(lhs) ? rhs : lhs, head); } ``` ```cpp void list_merge_sort(struct list_head *q) { if (list_empty(q) || list_is_singular(q)) return; struct list_head left, sorted; INIT_LIST_HEAD(&left); list_cut_position(&left, q, get_middle(q)); list_merge_sort(&left); list_merge_sort(q); list_merge(&left, q, &sorted); INIT_LIST_HEAD(q); list_splice_tail(&sorted, q); } ``` 測試程式碼 ```cpp static bool validate(struct list_head *q) { struct list_head *node; for (node = q->next; node->next != q; node = node->next) { if (strcmp(((list_ele_t *)node)->value, ((list_ele_t *)node->next)->value) > 0) return false; } return true; } ``` ```cpp static struct list_head *q_new() { struct list_head *q = malloc(sizeof(struct list_head)); if (!q) return NULL; INIT_LIST_HEAD(q); return q; } ``` ```cpp static void q_free(struct list_head *q) { struct list_head *current = q->next; while (current != q) { struct list_head *tmp = current; current = current->next; free(((list_ele_t *)tmp)->value); free((list_ele_t *)tmp); } free(q); } ``` 這邊可以利用 `list_add_tail` 把他接到第一個元素 (`q->next`) 的前面。 ```cpp bool q_insert_head(struct list_head *q, char *s) { list_ele_t *newh = malloc(sizeof(list_ele_t)); if (!newh) return false; char *new_value = strdup(s); if (!new_value) { free(newh); return false; } newh->value = new_value; list_add_tail((struct list_head *)newh, q->next); return true; } ``` ```cpp static void q_show(struct list_head *q) { struct list_head *node; list_for_each (node, q) { printf("%s", ((list_ele_t *)node)->value); } } ``` ```cpp int main(void) { FILE *fp = fopen("cities.txt", "r"); if (!fp) { perror("failed to open cities.txt"); exit(EXIT_FAILURE); } struct list_head *q = q_new(); char buf[256]; while (fgets(buf, 256, fp)) q_insert_head(q, buf); fclose(fp); list_merge_sort(q); q_show(q); assert(validate(q)); q_free(q); return 0; } ``` --- ## 測驗 `2` ### 程式碼解說 ```cpp uint16_t func(uint16_t N) { /* change all right side bits to 1 */ N |= N >> 1; N |= N >> 2; N |= N >> 4; N |= N >> 8; return (N + 1) >> 1; } ``` `N |= N >> 1;` 會讓第一個 `1` 的位元的下一個位元變成 `1`,接著 `N |= N >> 2;` 會讓第一個 `1` 的位元開始的四個位元都變成 `1`,以此類推。 ```graphviz digraph { num0 [shape="record", label="0|0|0|0|0|0|1|?|?|?|?|?|?|?|?|?"]; num1 [shape="record", label="0|0|0|0|0|0|1|1|?|?|?|?|?|?|?|?"]; num2 [shape="record", label="0|0|0|0|0|0|1|1|1|1|?|?|?|?|?|?"]; num2 [shape="record", label="0|0|0|0|0|0|1|1|1|1|?|?|?|?|?|?"]; num3 [shape="record", label="0|0|0|0|0|0|1|1|1|1|1|1|1|1|?|?"]; num4 [shape="record", label="0|0|0|0|0|0|1|1|1|1|1|1|1|1|1|1"]; num0 -> num1; num1 -> num2; num2 -> num3; num3 -> num4; } ``` 最後得到一個第一個 `1` 的位元以下皆為 `1` 的數字,加上 1 後往左位移,即可得到第一個 `1` 的位元以下皆為 `0` 的數字。 程式碼最後一行應該改為 `return (N >> 1) + 1;`,避免 msb 為 `1` 的數字會溢位。 特別注意的是 `func(0)` 會從 `0` 變為 `1`,不過 小於或等於 `0` 的 power-of-2 本身就是未定義的,這邊就不管它了。 不過因為這邊型別是 16 位元的無號整數,利用 gdb 去看組合語言。 ``` 0x000000000000118a <+65>: add $0x1,%eax 0x000000000000118d <+68>: sar %eax 0x000000000000118f <+70>: pop %rbp 0x0000000000001190 <+71>: retq ``` 可以發現 `return (N + 1) >> 1;` 是用 32 位元的暫存器 (`%eax`) 做運算,所以不會有整數溢位的問題。 如果把寫法改成, ``` ... N += 1; return N >> 1; ``` 就會得到, ``` 0x0000000000001186 <+61>: addw $0x1,-0x4(%rbp) 0x000000000000118b <+66>: movzwl -0x4(%rbp),%eax 0x000000000000118f <+70>: shr %ax 0x0000000000001192 <+73>: pop %rbp 0x0000000000001193 <+74>: retq ``` 可以發現程式碼會使用 16 位元的暫存器做運算, 或是改成 32 位元的無號整數版本,都會產生整數溢位的問題。 :::success 根據 [C11 standard](http://open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf) 6.3.1 Arithmetic operands 6.3.1.1 Boolean, characters, and integers 中第 2 點, > If an int can represent all values of the original type (as restricted by the width, for abit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions. All other types are unchanged by the integer promotions. `return (N + (uint16_t)1) >> 1;` 在這邊一樣是用 `int` 做運算,並不會有整數溢位的問題。 ::: --- ## 測驗 `3` ### 程式碼解說 要從某個位元開始存取資料時,當前的位元組往左位移 `_read_lhs` 與下一個位元組往右移 `_read_rhs` 後,進行 bitwise or 運算,即可得到一整個位元組大小的資料。 寫入時則剛好相反,整個位元組往右位移 `_write_lhs` 寫入當前的位元組,往左位移 `_write_rhs` 則寫入下一個位元組。 `_*_rhs` 的值剛好也是代表當前位元組佔了幾個位元,所以要判斷有沒有跨越位元組時,可以用 `bitsize` 與 `_*_rhs` 進行比較。 下圖為 `_read_lhs` 等於 `3` 時的示意圖。 ```graphviz digraph { rankdir=LR byte [shape="record", label="{*|*|*|*|*|#|#|#}"]; equal [shape="plain", label="="]; byteR [shape="record", label="{#|#|#|4|3|2|1|0}"]; plus [shape="plain", label="+"]; byteL [shape="record", label="{7|6|5|*|*|*|*|*}"]; } ``` `_read / 8` 即可得知從第幾個位元組開始操作,而 `_read & 7` 則是計算從此位元組的第幾個位元開始,(`_read / 8` 可用 `_read >> 3` 替代)。 `_write` 部分的操作與 `_read` 同理。 這邊做了一些小修改, * `>> 3` 替代 `/ 8` * 去除 `origin` 這個變數,直接用 `*dest` 表示 * 去除一些不必要的條件判斷 * 將 `read_mask` 與 `write_mask` 改名,使其更易理解。 * `left_mask[b]` 代表位元 b 左邊為 `1` * `right_mask[b]` 代表位元 b 右邊及本身為 `1` ```cpp= void bitcpy(void *_dest, /* Address of the buffer to write to */ size_t _write, /* Bit offset to start writing to */ const void *_src, /* Address of the buffer to read from */ size_t _read, /* Bit offset to start reading from */ size_t count) { static const uint8_t left_mask[] = { 0x00, /* == 0 00000000b */ 0x80, /* == 1 10000000b */ 0xC0, /* == 2 11000000b */ 0xE0, /* == 3 11100000b */ 0xF0, /* == 4 11110000b */ 0xF8, /* == 5 11111000b */ 0xFC, /* == 6 11111100b */ 0xFE, /* == 7 11111110b */ 0xFF /* == 8 11111111b */ }; static const uint8_t right_mask[] = { 0xFF, /* == 0 11111111b */ 0x7F, /* == 1 01111111b */ 0x3F, /* == 2 00111111b */ 0x1F, /* == 3 00011111b */ 0x0F, /* == 4 00001111b */ 0x07, /* == 5 00000111b */ 0x03, /* == 6 00000011b */ 0x01, /* == 7 00000001b */ 0x00 /* == 8 00000000b */ }; size_t read_lhs = _read & 7; size_t read_rhs = 8 - read_lhs; const uint8_t *source = (const uint8_t *) _src + (_read >> 3); size_t write_lhs = _write & 7; size_t write_rhs = 8 - write_lhs; uint8_t *dest = (uint8_t *) _dest + (_write >> 3); while (count > 0) { size_t bitsize = (count > 8) ? 8 : count; uint8_t data = *source++; if (read_lhs > 0) { data <<= read_lhs; data |= (*source >> read_rhs); } data &= left_mask[bitsize]; uint8_t mask = left_mask[write_lhs]; if (bitsize > write_rhs) { /* Cross multiple bytes */ *dest = (*dest & mask) | (data >> write_lhs); ++dest; *dest = (*dest & right_mask[bitsize - write_rhs]) | (data << write_rhs); } else { // Since write_lhs + bitsize is never >= 8, no out-of-bound access. mask |= right_mask[write_lhs + bitsize]; *dest = (*dest & mask) | (data >> write_lhs); ++dest; } count -= bitsize; } } ``` 第 39 行開始,先把下一個位元組大小的資料放入 `data` 中,44 行再把多餘的位元利用 `left_mask` 去除。 下面用了一些遮罩,為了保存頭尾我們沒有要寫入的部分 ,取用 `left_mask` 以及 `right_mask` 的引索意義如下, * `write_lhs` 代表從當前位元組的第幾個位元開始 * `write_lhs + bitsize` 代表寫到當前位元組的第幾個位元 * `bitsize - write_rhs` 代表寫到下一位元組的第幾個位元 (上述說過,`write_rhs` 也代表在當前位元組佔了多少位元,所以 `bitsize` 扣除它後,剩下的就是在下一位元組。) 如果 `bitsize <= write_rhs`,代表寫入的部分不需要跨位元,54 行後,`mask` 示意圖如下,以 `write_lhs` 為 `3`,`bitsize` 為 `4` 為例。 ```graphviz digraph { rankdir=LR byte [shape="record", label="{1|1|1|0|0|0|0|1}"]; } ``` 如果 `bitsize > write_rhs`,`mask` 右側一定都為 `0`,下圖以 `write_lhs` 為 `3`,`bitsize` 為 `7` 為例。 ```graphviz digraph { rankdir=LR byte [shape="record", label="{1|1|1|0|0|0|0|0}"]; } ``` 51 行的 `right_mask[bitsize - write_rhs]` 則得到另一個 mask, ```graphviz digraph { rankdir=LR byte [shape="record", label="{0|0|1|1|1|1|1|1}"]; } ``` ### 效率更高的實作 中間的部分,每次剛好複製 `source` 的一個位元組,可降低讀取時的運算量。 ```cpp void bitcpy_opt(void *_dest, /* Address of the buffer to write to */ size_t _write, /* Bit offset to start writing to */ const void *_src, /* Address of the buffer to read from */ size_t _read, /* Bit offset to start reading from */ size_t count) { static const uint8_t left_mask[] = { 0x00, /* == 0 00000000b */ 0x80, /* == 1 10000000b */ 0xC0, /* == 2 11000000b */ 0xE0, /* == 3 11100000b */ 0xF0, /* == 4 11110000b */ 0xF8, /* == 5 11111000b */ 0xFC, /* == 6 11111100b */ 0xFE, /* == 7 11111110b */ 0xFF /* == 8 11111111b */ }; static const uint8_t right_mask[] = { 0xFF, /* == 0 11111111b */ 0x7F, /* == 1 01111111b */ 0x3F, /* == 2 00111111b */ 0x1F, /* == 3 00011111b */ 0x0F, /* == 4 00001111b */ 0x07, /* == 5 00000111b */ 0x03, /* == 6 00000011b */ 0x01, /* == 7 00000001b */ 0x00 /* == 8 00000000b */ }; const uint8_t *source = (const uint8_t *) _src + (_read >> 3); _read &= 7; uint8_t *dest = (uint8_t *) _dest + (_write >> 3); _write &= 7; // first byte in source size_t bitsize = count < 8 - _read ? count : 8 - _read; uint8_t data = (*source++ << _read) & left_mask[bitsize]; uint8_t mask = left_mask[_write] | (_write + bitsize < 8 ? right_mask[_write + bitsize] : 0x00); *dest = (*dest & mask) | (data >> _write); if (_write + bitsize >= 8) { ++dest; size_t remain = _write + bitsize - 8; if (_write) *dest = (*dest & right_mask[remain]) | (data << (8 - _write)); } count -= bitsize; _write = (_write + bitsize) & 7; while (count >= 8) { data = *source++; *dest = (*dest & left_mask[_write]) | (data >> _write); ++dest; if (_write) *dest = (*dest & right_mask[_write]) | data << (8 - _write); count -= 8; } // last byte in source data = *source++ & left_mask[count]; mask = left_mask[_write] | (_write + count < 8 ? right_mask[_write + count] : 0x00); *dest = (*dest & mask) | (data >> _write); if (count > 8 - _write) { ++dest; size_t remain = count - (8 - _write); *dest = (*dest & right_mask[remain]) | (data << (8 - _write)); } } ``` --- ## 測驗 `4` ### 程式碼解說 `cstr.h` ```cpp #pragma once #include <stddef.h> #include <stdint.h> #include <stdlib.h> ``` 分別用 2 的冪次代表,可以透過 `|` (bitwise or) 進行聯集形成集合。 ```cpp enum { CSTR_PERMANENT = 1, CSTR_INTERNING = 2, CSTR_ONSTACK = 4, }; ``` ```cpp #define CSTR_INTERNING_SIZE (32) #define CSTR_STACK_SIZE (128) ``` ```cpp typedef struct __cstr_data { char *cstr; uint32_t hash_size; uint16_t type; uint16_t ref; } * cstring; ``` 這邊定義 `cstr_buffer` 為 `__cstr_buffer[1]`,在大多時候,array 會退化 (decay) 成一個 pointer, 這邊的功能類似 `__cstr_buffer*`,但在使用時,卻不用寫成 ```cpp __cstr_buffer obj; cstr_buffer ptr = &obj; ``` 而是 ```cpp cstr_buffer ptr; ``` ,即可在 stack 上有一個 `__cstr_buffer` 的物件,而名稱可以當作指標使用。 可參考 [Everything you need to know about pointers in C](https://boredzo.org/pointers/#arrays) ```cpp typedef struct __cstr_buffer { cstring str; } cstr_buffer[1]; ``` ```cpp #define CSTR_S(s) ((s)->str) ``` 把 `var` 中的 `str` 初始成一個字串放在 stack 上的 `__cstr_data`。 ```cpp #define CSTR_BUFFER(var) \ char var##_cstring[CSTR_STACK_SIZE] = {0}; \ struct __cstr_data var##_cstr_data = {var##_cstring, 0, CSTR_ONSTACK, 0}; \ cstr_buffer var; \ var->str = &var##_cstr_data; ``` 初使化一個靜態的 `cstring`,並透過 [`__sync_bool_compare_and_swap()`](https://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Atomic-Builtins.html) 確保在多執行緒的環境下能夠正確執行。 ```cpp #define CSTR_LITERAL(var, cstr) \ static cstring var = NULL; \ if (!var) { \ cstring tmp = cstr_clone("" cstr, (sizeof(cstr) / sizeof(char)) - 1); \ if (tmp->type == 0) { \ tmp->type = CSTR_PERMANENT; \ tmp->ref = 0; \ } \ if (!__sync_bool_compare_and_swap(&var, NULL, tmp)) { \ if (tmp->type == CSTR_PERMANENT) \ free(tmp); \ } \ } ``` `do { } while(0)` 可參考 [ multi-line macro: do/while(0) vs scope block](https://stackoverflow.com/questions/1067226/c-multi-line-macro-do-while0-vs-scope-block); ```cpp #define CSTR_CLOSE(var) \ do { \ if (!(var)->str->type) \ cstr_release((var)->str); \ } while (0) ``` ```cpp /* Public API */ cstring cstr_grab(cstring s); cstring cstr_clone(const char *cstr, size_t sz); cstring cstr_cat(cstr_buffer sb, const char *str); int cstr_equal(cstring a, cstring b); void cstr_release(cstring s); ``` `cstr.c` ```cpp #include <errno.h> #include <stdarg.h> #include <stdio.h> #include <string.h> #include "cstr.h" #define INTERNING_POOL_SIZE 1024 #define HASH_START_SIZE 16 /* must be power of 2 */ ``` ```cpp struct __cstr_node { char buffer[CSTR_INTERNING_SIZE]; struct __cstr_data str; struct __cstr_node *next; }; ``` ```cpp struct __cstr_pool { struct __cstr_node node[INTERNING_POOL_SIZE]; }; ``` ```cpp struct __cstr_interning { int lock; int index; unsigned size; unsigned total; struct __cstr_node **hash; struct __cstr_pool *pool; }; ``` ```cpp static struct __cstr_interning __cstr_ctx; ``` ```cpp /* FIXME: use C11 atomics */ #define CSTR_LOCK() \ ({ \ while (__sync_lock_test_and_set(&(__cstr_ctx.lock), 1)) { \ } \ }) #define CSTR_UNLOCK() ({ __sync_lock_release(&(__cstr_ctx.lock)); }) ``` 進行 `malloc`,失敗則直接中止程式。 ```cpp static void *xalloc(size_t n) { void *m = malloc(n); if (!m) exit(-1); return m; } ``` 將物件放入雜湊表。 ```cpp static inline void insert_node(struct __cstr_node **hash, int sz, struct __cstr_node *node) { uint32_t h = node->str.hash_size; int index = h & (sz - 1); node->next = hash[index]; hash[index] = node; } ``` 將 `si` 中的雜湊表 (`hash`) 擴大一倍。 ```cpp static void expand(struct __cstr_interning *si) { unsigned new_size = si->size * 2; if (new_size < HASH_START_SIZE) new_size = HASH_START_SIZE; struct __cstr_node **new_hash = xalloc(sizeof(struct __cstr_node *) * new_size); memset(new_hash, 0, sizeof(struct __cstr_node *) * new_size); for (unsigned i = 0; i < si->size; ++i) { struct __cstr_node *node = si->hash[i]; while (node) { struct __cstr_node *tmp = node->next; insert_node(new_hash, new_size, node); node = tmp; } } free(si->hash); si->hash = new_hash; si->size = new_size; } ``` 嘗試進行字串駐留,若 `si` 本身為空,或是裡面的物件多於雜湊表大小的 `80%` 時,則失敗。 ```cpp static cstring interning(struct __cstr_interning *si, const char *cstr, size_t sz, uint32_t hash) { if (!si->hash) return NULL; int index = (int) (hash & (si->size - 1)); struct __cstr_node *n = si->hash[index]; while (n) { if (n->str.hash_size == hash) { if (!strcmp(n->str.cstr, cstr)) return &n->str; } n = n->next; } // 80% (4/5) threshold if (si->total * 5 >= si->size * 4) return NULL; if (!si->pool) { si->pool = xalloc(sizeof(struct __cstr_pool)); si->index = 0; } n = &si->pool->node[si->index++]; memcpy(n->buffer, cstr, sz); n->buffer[sz] = 0; cstring cs = &n->str; cs->cstr = n->buffer; cs->hash_size = hash; cs->type = CSTR_INTERNING; cs->ref = 0; n->next = si->hash[index]; si->hash[index] = n; return cs; } ``` 進行字串駐留,若失敗了,則拓展雜湊表,再進行一次。 ```cpp static cstring cstr_interning(const char *cstr, size_t sz, uint32_t hash) { cstring ret; CSTR_LOCK(); ret = interning(&__cstr_ctx, cstr, sz, hash); if (!ret) { expand(&__cstr_ctx); ret = interning(&__cstr_ctx, cstr, sz, hash); } ++__cstr_ctx.total; CSTR_UNLOCK(); return ret; } ``` 真正的雜湊函數。 ```cpp static inline uint32_t hash_blob(const char *buffer, size_t len) { const uint8_t *ptr = (const uint8_t *) buffer; size_t h = len; size_t step = (len >> 5) + 1; for (size_t i = len; i >= step; i -= step) h = h ^ ((h << 5) + (h >> 2) + ptr[i - 1]); return h == 0 ? 1 : h; } ``` 這邊 `void *ptr = (void *) (p + 1);` 應為 `char *ptr = (char *)(p + sizeof(struct __cstr_data));`,在一個 `struct __cstr_data` 之後,才是配置給字串的空間。 如果字串長度小於 `CSTR_INTERNING_SIZE`,就進行字串駐留,否則的話,直接配置一個新的 `cstring`。 ```cpp cstring cstr_clone(const char *cstr, size_t sz) { if (sz < CSTR_INTERNING_SIZE) return cstr_interning(cstr, sz, hash_blob(cstr, sz)); cstring p = xalloc(sizeof(struct __cstr_data) + sz + 1); if (!p) return NULL; char *ptr = (char *)(p + sizeof(struct __cstr_data)); p->cstr = ptr; p->type = 0; p->ref = 1; memcpy(ptr, cstr, sz); ptr[sz] = 0; p->hash_size = 0; return p; } ``` ```cpp cstring cstr_grab(cstring s) { if (s->type & (CSTR_PERMANENT | CSTR_INTERNING)) return s; if (s->type == CSTR_ONSTACK) return cstr_clone(s->cstr, s->hash_size); if (s->ref == 0) s->type = CSTR_PERMANENT; else __sync_add_and_fetch(&s->ref, 1); return s; } ``` 對字串進行記憶體釋放,這邊使用 [sync_sub_and_fetch](https://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Atomic-Builtins.html) 來確保在多執行緒的環境下能夠正確運作。 ```cpp void cstr_release(cstring s) { if (s->type || !s->ref) return; if (__sync_sub_and_fetch(&s->ref, 1) == 0) free(s); } ``` 如果 `s` 是放在 stack 上,`hash_size` 是它的長度,否則的話,確認是否計算過雜湊值。 (回傳值應改為 `uint32_t `) ```cpp static uint32_t cstr_hash(cstring s) { if (s->type == CSTR_ONSTACK) return hash_blob(s->cstr, s->hash_size); if (s->hash_size == 0) s->hash_size = hash_blob(s->cstr, strlen(s->cstr)); return s->hash_size; } ``` 如果 `a` 與 `b` 都是被駐留的字串,那只有當指標相同時才會是一樣的字串;如果字串在 stack 上,可以先用字串長度 (`hash_size`) 判斷一下,否則也可以先用雜湊值判斷一下。 ```cpp int cstr_equal(cstring a, cstring b) { if (a == b) return 1; if ((a->type == CSTR_INTERNING) && (b->type == CSTR_INTERNING)) return 0; if ((a->type == CSTR_ONSTACK) && (b->type == CSTR_ONSTACK)) { if (a->hash_size != b->hash_size) return 0; return memcmp(a->cstr, b->cstr, a->hash_size) == 0; } if (cstr_hash(a) != cstr_hash(b)) return 0; return !strcmp(a->cstr, b->cstr); } ``` 這邊 `char *ptr = (char *) (p + 1);` 應為 `char *ptr = (char *)(p + sizeof(struct __cstr_data));`,理由同上。 如果 `a` 與 `b` 的長度小於 `CSTR_INTERNING_SIZE`,會進行字串駐留,否則的話就另外配置一個 `cstring`。 ```cpp static cstring cstr_cat2(const char *a, const char *b) { size_t sa = strlen(a), sb = strlen(b); if (sa + sb < CSTR_INTERNING_SIZE) { char tmp[CSTR_INTERNING_SIZE]; memcpy(tmp, a, sa); memcpy(tmp + sa, b, sb); tmp[sa + sb] = 0; return cstr_interning(tmp, sa + sb, hash_blob(tmp, sa + sb)); } cstring p = xalloc(sizeof(struct __cstr_data) + sa + sb + 1); if (!p) return NULL; char *ptr = (char *)(p + sizeof(struct __cstr_data)); p->cstr = ptr; p->type = 0; p->ref = 1; memcpy(ptr, a, sa); memcpy(ptr + sa, b, sb); ptr[sa + sb] = 0; p->hash_size = 0; return p; } ``` 先看看原本 `sb` 中的字串是不是放在 stack,是的話嘗試把 `str` 也放入,不行的話就呼叫 `cstr_cat2` 去進行記憶體配置並完成字串連接。 ```cpp cstring cstr_cat(cstr_buffer sb, const char *str) { cstring s = sb->str; if (s->type == CSTR_ONSTACK) { int i = s->hash_size; while (i < CSTR_STACK_SIZE - 1) { s->cstr[i] = *str; if (*str == 0) return s; ++s->hash_size; ++str; ++i; } s->cstr[i] = 0; } cstring tmp = s; sb->str = cstr_cat2(tmp->cstr, str); cstr_release(tmp); return sb->str; } ``` 從 `cstr.h` 中可以看到,會得到 `cstring` 的有 `cstr_grab()` 、 `cstr_clone()` 與 `cstr_cat()`,還有 `CSTR_BUFFER` 與 `CSTR_LITERAL` 二個巨集。 可以歸納出處理字串的行為,如果字串長度小於 `CSTR_INTERNING_SIZE` 的話,就會進行字串駐留, `type` 為 `CSTR_INTERNING`,`hash_size` 中保存它的雜湊值,否則的話,就直接放在一個 `cstring` 中而已,`type` 為 `0`,`hash_size` 也為 `0`;比較特別的是,透過 `CSTR_BUFFER` 宣告的變數,其中的 `cstring` 一開始是指向放在 `stack` 上的記憶體,最多可以存放 `CSTR_STACK_SIZE - 1` 長度的字串,`type` 為 `CSTR_ONSTACK`,`hash_size` 為字串長度。 另外,`CSTR_LITERAL` 中透過 `cstr_clone()` 得到的 `cstring`,如果沒有進行字串駐留,`type` 則會變成 `CSTR_PERMANENT`,`ref` 則為 `0`。 :::info 程式碼中,會減少 `ref` 的只有 `cstr_release()`,但是當它變為 `0` 後,又會直接釋放記憶體,另一邊 `cstr_grab()` 的地方則是在 `ref` 為 `0` 時會把 `type` 轉為 `CSTR_PERMANENT`。 這邊對於 `ref` 這個成員以及 `CSTR_PERMANENT` 這樣的 `cstring`,看起來並沒有太多應用,也不太完整,或許是為了未來開發作保留的。 ::: ###### tags: `linux2021`