# 2021q1 Homework2 (quiz2) contributed by < `Jings1017` > ###### tags: `linux2021` ## 測驗 1 ### 程式碼分析 ### list.h * list_add_tail Add a list node to the end of the 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; } ``` ```graphviz digraph{ rankdir = LR node [shape="box"] edge [dir = "both"] omit1[label="...", shape="none"] pre[label="prev"] head[label="head"] omit2[label="...", shape="none"] omit1->pre head->omit2 pre->head } ``` ```graphviz digraph{ rankdir = LR node [shape="box"] edge [dir = "both"] omit1[label="...", shape="none"] pre[label="prev"] node0[label="node" color="red"] head[label="head"] omit2[label="...", shape="none"] omit1->pre head->omit2 pre->node0[color=red] node0->head[color=red] } ``` * list_del Remove a list node from the list ```c= static inline void list_del(struct list_head *node) { struct list_head *next = node->next, *prev = node->prev; next->prev = prev; prev->next = next; } ``` ```graphviz digraph{ rankdir = LR node [shape="box"] edge [dir = "both"] omit1[label="...", shape="none"] pre[label="prev"] node0[label="node" color="red"] head[label="next"] omit2[label="...", shape="none"] omit1->pre head->omit2 pre->node0[color=red] node0->head[color=red] } ``` ```graphviz digraph{ rankdir = LR node [shape="box"] edge [dir = "both"] omit1[label="...", shape="none"] pre[label="prev"] head[label="next"] omit2[label="...", shape="none"] omit1->pre head->omit2 pre->head } ``` * list_splice_tail Add list nodes from a list to end of another list ```c= 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; // blue double arrow head->prev = list_last; list_last->next = head; // red double arrow list_first->prev = head_last; head_last->next = list_first; } ``` ```graphviz digraph{ node [shape=box] edge[dir=both minlen=3] ell1,ell2,ell3,ell4 [label="..." shape=none] lprev[label="prev of list"] lnext[label="next of list"] hprev[label="prev of head"] { rank = same ell1->lprev lprev->list[dir=forward color=gray] list->lprev[dir=forward] list->lnext[dir=forward] lnext->list[dir=forward color=gray] lnext->ell2 } { rank = same ell3->hprev hprev->head[color=gray] head->ell4 } lnext->hprev[color=red] lprev->head[color=blue] } ``` * list_cut_position Move beginning of a list to another list ```c= 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; } // red double arrow head_from->next = node->next; head_from->next->prev = head_from; // green doube arrow head_to->prev = node; node->next = head_to; // blue double arrow head_to->next = head_from_first; head_to->next->prev = head_to; } ``` ```graphviz digraph{ rankdir = LR node [shape=box] edge [dir=both] ell1,ell2,ell3 [label="..." shape=none] cut [label="node"] { ell1->head_from head_from->head_from_first[color=gray] head_from_first->ell2->cut cut->node_next[color=gray] node_next->ell3 head_from->node_next[color=red] head_to:s->cut:s [color=darkgreen] head_to->head_from_first[color=blue] } } ``` ### mergesort.c #### list_merge( ) 之改善 ```diff= if (list_empty(lhs)) { - list_splice_tail(lhs, head); + list_splice_tail(rhs, head); return; } if (list_empty(rhs)) { - list_splice_tail(rhs, head); + list_splice_tail(lhs, head); return; } ``` 原程式碼邏輯上有誤,若判斷 `lhs` 是否為空,則在敘述中的參數應該為 `rhs` 較為合理 同理, line 7 應修正成 line 8 ### COND1 , COND2 ```c= static list_ele_t *get_middle(struct list_head *list) { struct list_head *fast = list->next, *slow; list_for_each (slow, list) { if (COND1 || COND2) break; fast = fast->next->next; } return list_entry(TTT, list_ele_t, list); } ``` 由上方程式碼可知 slow 每經過一個節點, fast 會經過兩個節點,也就是 slow 經過的節點數約為 fast 的一半,所以可推斷出 fast 走到尾端時, slow 約在串列中間的位置。 而 if 中需放入的條件為 fast 在尾端的狀態,考慮到串列的節點數有奇有偶,故條件為 `fast->next==list` 或 `fast->next->next==list` * COND1 = ? fast->next == list ```graphviz digraph { rankdir=LR a[shape=record, label="{<h>|<n>}"] b[shape=record, label="{<h>|<n>}"] c[shape=record, label="{<h>|<n>}"] d[shape=record, label="{<h>|<n>}"] e[shape=record, label="{<h>|<n>}"] list[label="list" shape=plaintext , fontcolor=red] fast[label="fast" shape=plaintext , fontcolor=red] slow[label="slow" shape=plaintext , fontcolor=red] a:n->b:h b:n->c:h c:n->d:h d:n->e:h e:n->a:h list->a slow->c fast->e } ``` * COND2 = ? fast->next->next == list ```graphviz digraph { rankdir=LR a[shape=record, label="{<h>|<n>}"] b[shape=record, label="{<h>|<n>}"] c[shape=record, label="{<h>|<n>}"] d[shape=record, label="{<h>|<n>}"] e[shape=record, label="{<h>|<n>}"] f[shape=record, label="{<h>|<n>}"] list[label="list" shape=plaintext , fontcolor=red] fast[label="fast" shape=plaintext , fontcolor=red] slow[label="slow" shape=plaintext , fontcolor=red] a:n->b:h b:n->c:h c:n->d:h d:n->e:h e:n->f:h f:n->a:h list->a fast->e slow->c } ``` ### MMM ```c= void list_merge_sort(queue_t *q) { if (list_is_singular(&q->list)) return; queue_t left; struct list_head sorted; INIT_LIST_HEAD(&left.list); list_cut_position(&left.list, &q->list, MMM); list_merge_sort(&left); list_merge_sort(q); list_merge(&left.list, &q->list, &sorted); INIT_LIST_HEAD(&q->list); list_splice_tail(&sorted, &q->list); } ``` 由 line 9 可知,MMM 應該放入切斷點。 又這裡採用 merge sort , 所以切斷點為中間點,可以用 `get_middle(&q->list)`求得。 * MMM = ? &get_middle(&q->list)->list ### TTT ```c= static list_ele_t *get_middle(struct list_head *list) { struct list_head *fast = list->next, *slow; list_for_each (slow, list) { if (COND1 || COND2) break; fast = fast->next->next; } return list_entry(TTT, list_ele_t, list); } ``` 上述分析中已知 slow 指向中間節點,又此函式需回傳中間節點位置,所以 `TTT = slow` * TTT = ? slow --- ## 測驗 2 接受一個 16 位元無號整數 N ,並回傳小於或等於 N 的 power-of-2 ### 想法 2 的冪寫成 pattern 是 `1000...00` 所以我們可以先將原 N 經過運算得到 `11...11111` 再加 1 即變成 `100...000` 最後再 `>> 1` 就是小於或等於 N 的 2 的冪 :::warning 注意[冪](https://zh.wikipedia.org/wiki/%E5%86%AA)的用法。$b^n$ 讀作「b 的 n 次方」或「b 的 **n 次**==冪==」,但不是「冪次」,不管幾次,就是 b 的[冪](https://zh.wikipedia.org/wiki/%E5%86%AA) :notes: jserv ::: ### 解題 關鍵在於考慮極值 2^15^ , 每次運算可讓1的數量變為兩倍,十六位數故作 4 次右移,分別為 1, 2, 4, 8 ``` 1000 0000 0000 0000 | 0100 0000 0000 0000 = 1100 0000 0000 0000 1100 0000 0000 0000 | 0011 0000 0000 0000 = 1111 0000 0000 0000 1111 0000 0000 0000 | 1111 1111 0000 0000 = 1111 1111 0000 0000 1111 1111 0000 0000 | 0000 0000 1111 1111 = 1111 1111 1111 1111 1111 1111 1111 1111 + 1 = 1 0000 0000 0000 0000 1 0000 0000 0000 0000 >> 1 = 1000 0000 0000 0000 ``` * X = ? `2` * Y = ? `4` * Z = ? `8` --- ## 測驗 3 * RRR = ? data<<=read_lhs * DDD = ? mask |= write_mask[write_lhs+bitsize] ### 分析 bitcpy() ```c= 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) ``` * void *_dest : 可傳入任何記憶體位置, 再將 type 轉成 uint8_t * const void *_src : 同上 * uint8_t : 8 位元 unsigned int ```diff= size_t read_lhs = _read & 7; size_t read_rhs = 8 - read_lhs; - const uint8_t *source = (const uint8_t *)_src + (_read / 8); + 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 / 8); + uint8_t *dest = (uint8_t *)_dest + (_write >> 3); ``` 上方程式碼 `(_read / 8)` 可用 `(_read >> 3)` 代替,以減少運算成本。 同理,`(_write / 8)` 可用 `(_write >> 3)` 代替 ```c= while (count > 0) { uint8_t data = *source++; size_t bitsize = (count > 8) ? 8 : count; if (read_lhs > 0) { data <<= read_lhs; if (bitsize > read_rhs) data |= (*source >> read_rhs); } if (bitsize < 8) data &= read_mask[bitsize]; uint8_t original = *dest; uint8_t mask = read_mask[write_lhs]; if (bitsize > write_rhs) { /* Cross multiple bytes */ *dest++ = (original & mask) | (data >> write_lhs); original = *dest & write_mask[bitsize - write_rhs]; *dest = original | (data << write_rhs); } else { // Since write_lhs + bitsize is never >= 8, no out-of-bound access. mask |= write_mask[write_lhs + bitsize]; *dest++ = (original & mask) | (data >> write_lhs); } count -= bitsize; } ``` * 先判斷 count 是否大於 8 ,是的話 bitsize 設成 8,小於等於則設成 count 之值 * ```RRR = data <<= read_lhs ```的理由是要使 data 補到 8 bits * 根據 line 21 的註解,可知 ```write_lhs + bitsize is never >= 8```,所以 ```DDD = mask |= write_mask[write_lhs + bitsize]; ``` * input/output 變數位元順序示意圖 ![](https://i.imgur.com/WpjBDuk.jpg) 以下舉例說明 假設 * ==read_lhs = 3== * ==_read_rhs = 5== * ==bit_size = 7== ```c= if (read_lhs > 0) { data <<= read_lhs; if (bitsize > read_rhs) data |= (*source >> read_rhs); } ``` ```graphviz digraph{ node [shape=record, width=3, fixedsize=true]; a [label="x |x |x |1 |1 |0 |1 |0", fontsize=20]; b [label="1 |1 |0 |1 |0 |0 |0 |0", fontsize=20]; node [shape=plaintext, fontcolor=black, fontsize=100, width=1]; "|" node [shape=record, width=3, fixedsize=true]; c [label="1 |0 |0 |x |x |x |x |x", fontsize=20]; d[label="0 |0 |0 |0 |0 |1 |0 |0", fontsize=20]; node [shape=plaintext, fontcolor=black, fontsize=30, width=1]; "*source"->a [style=invis] "*(source + 1)"->c [style=invis] node [shape=plaintext, fontcolor=black, fontsize=50, width=1]; "=" a->b [label=" << 3", fontsize=25] c->d [label=" >> 5", fontsize=25] b->"=" [label="=", minlen = 3, style=invis] node [shape=record, fontcolor=black, fontsize=14, width=3, fixedsize=true]; e[label="1 |1 |0 |1 |0 |1 |0 |0", color=black, fillcolor=white, style=filled, fontsize=20]; { rank=same; a; c} { rank=same; b; d; "|";} { rank=same; "="; e} } ``` 所以可知結果由 `source` 右 5 bit及 `source+1`左 3 bit 所組成的 8 bit 而 `mask` 是用來將沒有寫入的 bits 保留起來,以防止被 `data` 複寫的機制, ```c= if (bitsize > write_rhs) { /* Cross multiple bytes */ *dest++ = (original & mask) | (data >> write_lhs); original = *dest & write_mask[bitsize - write_rhs]; *dest = original | (data << write_rhs); } else { // Since write_lhs + bitsize is never >= 8, no out-of-bound access. mask |= write_mask[write_lhs + bitsize]; *dest++ = (original & mask) | (data >> write_lhs); } ``` 此段程式碼是用來判斷欲寫入的 bit 是否橫跨了兩個 byte 。 如果橫跨了兩個 byte ,則需要 `original & mask` 來記錄不須更改的 bit ,再用 `data >> write_lhs` 來把欲寫入的 bit 移至 `write_lhs` 為開頭的位置,兩者做 `|` 運算,即是 `data` 更新後的結果。`write_mask[bitsize - write_rhs]` 則是下一個 byte 需寫入的 bits 位置,將尚未寫入的 bits (即 `data << write_lhs`)寫入到下一個 byte 中。 若沒有橫跨兩個 byte,則 `mask |= write_mask[write_lhs + bitsize]` 用來記錄不須寫入的 bits 的位置,而 `mask` 在欲寫入的位置為 0,其他位置與 `dest` 原先的 bit 相同。最後再將 `(original & mask)` 與 `(data >> write_lhs)` 做 `|` 運算,即可得到更新後的資料。 --- ## 測驗 4 ### 程式目的 string interning 將字串存入 hash 中,當想使用的時候只要將指標指向字串即可, 不需再額外存取,如此一來可改善記憶體的使用效率 ### 定義結構 ```c= typedef struct __cstr_data { char *cstr; uint32_t hash_size; uint16_t type; uint16_t ref; } * cstring; ``` ```c= typedef struct __cstr_buffer { cstring str; } cstr_buffer[1]; ``` ```c= struct __cstr_node { char buffer[CSTR_INTERNING_SIZE]; struct __cstr_data str; struct __cstr_node *next; }; struct __cstr_pool { struct __cstr_node node[INTERNING_POOL_SIZE]; }; struct __cstr_interning { int lock; int index; unsigned size; unsigned total; struct __cstr_node **hash; struct __cstr_pool *pool; }; static struct __cstr_interning __cstr_ctx; ``` ### xalloc( ) 記憶體配置,失敗則 `exit(-1)` ```c= static void *xalloc(size_t n) { void *m = malloc(n); if (!m) exit(-1); return m; } ``` ### insert_node( ) ```c= 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; } ``` ### expand( ) ```c= 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; } ``` ### interning( ) ```c= 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; } ``` ### cstr_interning( ) ```c= 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; } ``` ### hash_blob( ) ```c= 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; } ``` ### cstr_clone( ) ```c= 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; void *ptr = (void *) (p + 1); p->cstr = ptr; p->type = 0; p->ref = 1; memcpy(ptr, cstr, sz); ((char *) ptr)[sz] = 0; p->hash_size = 0; return p; } ``` ### cstr_grab( ) ```c= 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; } ``` ### cstr_release( ) ```c= void cstr_release(cstring s) { if (s->type || !s->ref) return; if (__sync_sub_and_fetch(&s->ref, 1) == 0) free(s); } ``` ### cstr_hash( ) ```c= static size_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; } ``` ### cstr_equal( ) ```c= 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; } uint32_t hasha = cstr_hash(a); uint32_t hashb = cstr_hash(b); if (hasha != hashb) return 0; return !strcmp(a->cstr, b->cstr); } ``` ### cstr_cat2( ) ```c= 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 + 1); 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; } ``` ### cstr_cat( ) ```c= cstring cstr_cat(cstr_buffer sb, const char *str) { cstring s = sb->str; if (s->type == CSTR_ONSTACK) { int i = s->hash_size; // CCC 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; } ```