cin-cout
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 2023q1 Homework3 (quiz3) contributed by < [cin-cout](https://github.com/cin-cout) > [題目](https://hackmd.io/@sysprog/linux2023-quiz3) ## 測驗一 ### 延伸問題: 1. 指出 treesort.c 程式碼的缺失並改進 2. 利用 Linux 核心的 list.h 標頭檔改寫並整合進第一次作業的實驗程式碼中,探討 Tree sort 的效率 3. 研讀 Linux 核心的 Red–black tree 程式碼,比較給定的 treesort.c 實作手法,指出後者的改進空間並予以實作 ### Question 考慮一個 Tree sort 的實作,搭配 Red–black tree 作為排序所需的自我平衡樹,原始程式碼可見 [treesort.c](https://gist.github.com/jserv/920766d1fd893d0efd66ac7fdaf23401)。部分程式碼已遮蔽,請補完程式碼,使其運作符合預期。 先簡單介紹一下 [treesort.c](https://gist.github.com/jserv/920766d1fd893d0efd66ac7fdaf23401) 的內容,主要是利用紅黑樹去實作map。 資料結構如下: tree node 的方面, `color` 紀錄其顏色、`left` `right` 則記錄其左右子節點 `next` 是用來記錄在原本 array 中其下一個位置在 RB tree 中 node 的位置、`value` 則是記錄其值。 ```c typedef struct __node { uintptr_t color; struct __node *left, *right; struct __node *next; long value; } node_t __attribute__((aligned(sizeof(long)))); ``` map 的資料結構則會記錄 RB tree 的 root,以及其他 map 之中的一些特質(ex size)。 ```c struct cmap_internal { node_t *head; /* Properties */ size_t key_size, element_size, size; cmap_iter_t it_end, it_most, it_least; int (*comparator)(void *, void *); }; ``` #### AAAA 這邊用了一個特殊的方法來儲存顏色。因為現在電腦多數都是 64 bits (8 bytes),所以其位置二進位最後三位必為 0。所以這邊就用了 node 的 parent 的位址的第一個 bit 來儲存 node 之顏色,如此一來找 parent 只需要對其顏色做 bit wise 的處理,不須額外花費記憶體空間,紅色為 0 黑色為 1。 由上述邏輯可知要找 node 的 parent 只需把 node 的 color 前三個(或第一個) bits 設為 0 ,故 AAAA = `((r)->color & ~7)`。 ```c typedef enum { CMAP_RED = 0, CMAP_BLACK } color_t; #define rb_parent(r) ((node_t *) (AAAA)) #define rb_color(r) ((color_t) (r)->color & 1) ``` #### BBBB CCCC 同 AAAA 提到的原理,透過更改其 parent 的 last bit 來記錄顏色,在 set color 時,紅色為 0 ,黑色為 1。 故 BBBB 為 `(r)->color &= ~1`。 故 CCCC 為 `(r)->color |= 1`。 ```c #define rb_set_red(r) \ do { \ BBBB; \ } while (0) #define rb_set_black(r) \ do { \ CCCC; \ } while (0) ``` #### DDDD EEEE rb tree insert node 的程式碼如下,DDDD、EEEE 前的部分是在處理 create root 的狀況,為解答出 DDDD、EEEE,我們著重於 for 迴圈的部分。 for 迴圈處的邏輯跟 binary tree insert 的方式類似,即若小於此處的 node 則往左走,反之大於則往右走,直到走到 null 的位置即為要插入的位置。 故 DDDD 為 cur = cur->left。 故 EEEE 為 cur = cur->right。 另外,插入時會需要先 set parent,而 `cmap_fix_colors` 則會對於 ll lr rl rr 的情況去平衡紅黑數。 ```c static bool cmap_insert(cmap_t obj, node_t *node, void *value) { cmap_create_node(node); obj->size++; if (!obj->head) { /* Just insert the node in as the new head. */ obj->head = node; rb_set_black(obj->head); /* Calibrate the tree to properly assign pointers. */ cmap_calibrate(obj); return true; } /* Traverse the tree until we hit the end or find a side that is NULL */ for (node_t *cur = obj->head;;) { int res = obj->comparator(&node->value, &cur->value); if (!res) /* If the key matches something else, don't insert */ assert(0 && "not support repetitive value"); if (res < 0) { if (!cur->left) { cur->left = node; rb_set_parent(node, cur); cmap_fix_colors(obj, node); break; } DDDD; } else { if (!cur->right) { cur->right = node; rb_set_parent(node, cur); cmap_fix_colors(obj, node); break; } EEEE; } } cmap_calibrate(obj); return true; } ``` #### FFFF GGGG HHHH 最後則是實際使用 map 的情況,輸入會是一個 list 而 map 則會使用紅黑樹將其排序。 FFFF 是為將 list 內的值一一傳入 map 做 insert,故在每次 while 時都必須將 *list 改下一個值的位置。 故 FFFF = &(*list)->next for 迴圈的部分是將排序完成後的結果放回 list。每次迴圈都會更新到下一個 node ,GGGG 則是在耕莘 list 的位置。 故 GGGG 同為 &(*list)->next。 將 list 更新完畢以後,必須確保最後一個值的 next 為 null, list 在 for 結束後會落在,tail->next 的位置。 故 HHHH 為 *list = NULL。 ```c void tree_sort(node_t **list) { node_t **record = list; cmap_t map = cmap_new(sizeof(long), sizeof(NULL), cmap_cmp_int); while (*list) { cmap_insert(map, *list, NULL); list = FFFF; } node_t *node = cmap_first(map), *first = node; for (; node; node = cmap_next(node)) { *list = node; list = GGGG; } HHHH; *record = first; free(map); } ``` ### Answer :::success AAAA = `((r)->color & ~7)` BBBB = `(r)->color &= ~1` CCCC = `(r)->color |= 1` DDDD = `cur = cur->left` EEEE = `cur = cur->right` FFFF = `&(*list)->next` GGGG = `&(*list)->next` HHHH = `*list = NULL` ::: ## 測驗二 ### 延伸問題: 1. 解釋上述程式碼運作原理,並比較用紅黑樹實作的 priority queue,應提供完整的測試程式和效能分析 2. Linux 核心也有 AVL tree 的身影,但陸續被紅黑樹替代,請揣摩 Linux 核心開發者的考慮因素,並搭配 git log 來解讀 見 commit b145425 3. 研讀 Linux 核心原始程式碼,指出曾經/現在使用 AVL tree 的部分,對照 Linux 核心的 rbtree.h,探究是否可用後者替換,從而爭取到貢獻程式碼到 Linux 核心的機會 ### Question 考慮一個使用 AVL tree 實作的 priority queue。 其中 `avltree.h` 的程式碼可見 [avltree.h](https://gist.github.com/jserv/d03098d57fec03c89fdb2a449d7f6be2)。部分程式碼已遮蔽,請補完程式碼,使其運作符合預期。 AVL tree 是嚴格的平衡樹,每一個節點的左子樹高度跟右子樹的高度不差超過 1 。 先講解 AVL tree node 的資料結構: `left` `right` 是紀錄其左右子節點的位址,`parent_balance` 跟上一題 rb tree node 用了類似的手法,原因以及好處可見上題。後三位 bits 用來儲存此節點 balance 的狀況,高位元用來儲存父節點的位址。 ```c struct avl_node { unsigned long parent_balance; struct avl_node *left, *right; } AVL_NODE_ALIGNED; ``` balance 的狀況有三種,分別為: `AVL_NEUTRAL (0)` 左右子樹高度相等。 `AVL_LEFT (1)` 左子樹高 1 。 `AVL_RIGHT (2)` 右子樹高 1 。 ```c enum avl_node_balance { AVL_NEUTRAL = 0, AVL_LEFT, AVL_RIGHT, }; ``` #### IIII 如同上述的解釋,在找 parent 時,即抓取 parent_balance 的高位元。 故 IIII 為 (node)->parent_balance & ~7。 ```c static inline struct avl_node *avl_parent(struct avl_node *node) { return (struct avl_node *) (IIII); }**** ``` #### JJJJ 找 balance 也一樣,即抓取 parent_balance 的最低三位元,因為 balance 只有 0 1 2,所以只會用到最低二位,可以只取最低兩位,放 & 7 也可以。 故 JJJJ 為 (node)->parent_balance & 3。 ```c static inline enum avl_node_balance avl_balance(const struct avl_node *node) { return (enum avl_node_balance)(JJJJ); } ``` #### KKKK 更新 parent 必須要考慮到 parent 跟 balance , set parent 只要更新父節點的位置,balance 保持一樣。函式中已經有更新 parent 的實作,缺少保留 balance 的實作部分。 故 KKKK 為 (node)->parent_balance & 3。 ```c static void avl_set_parent(struct avl_node *node, struct avl_node *parent) { node->parent_balance = (unsigned long) parent | (KKKK); } ``` #### LLLL 反之,在更新 balance 時,也必須保持原 parent 的狀況。 故 LLLL 為 (node)->parent_balance & ~7。 ```c static void avl_set_balance(struct avl_node *node, enum avl_node_balance balance) { node->parent_balance = (LLLL) | balance; } ``` #### MMMM NNNN 最後則是在插入新的節點後平衡的處理。 簡單講解 AVL 樹的平衡方式,首先檢查它是其父節點的左子節點還是右子節點。對於右子節點,它根據父節點的 balance 做出不同的處理。 如果父節點的 balance 是 AVL_LEFT,意味著它的左子樹比右子樹高,這樣 node 插入右側就沒什麼問題。 若是 AVL_RIGHT,意味著它的右子樹比左子樹高,這樣 node 插入右側就則需要做旋轉的處理,處理方式則是依照 node 本身的 balance來決定,來保持 AVL 樹的平衡性。 若是 AVL_NEUTRAL,則需要繼續往上檢查到上面的節點需不需要旋轉。 對於左子節點,它的處理方式類似,只是 balance 的方向相反。 故 MMMM 為 avl_rotate_right。 故 NNNN 為 avl_rotate_leftright。 ```c void avl_insert_balance(struct avl_node *node, struct avl_root *root) { struct avl_node *parent; /* go tree upwards and fix the nodes on the way */ while ((parent = avl_parent(node))) { if (avl_is_right_child(node)) { switch (avl_balance(parent)) { default: case AVL_RIGHT: /* compensate double right balance by rotation * and stop afterwards */ switch (avl_balance(node)) { default: case AVL_RIGHT: case AVL_NEUTRAL: avl_rotate_left(node, parent, root); break; case AVL_LEFT: avl_rotate_rightleft(node, parent, root); break; } parent = NULL; break; case AVL_NEUTRAL: /* mark balance as right and continue upwards */ avl_set_balance(parent, AVL_RIGHT); break; case AVL_LEFT: /* new right child + left leaning == balanced * nothing to propagate upwards after that */ avl_set_balance(parent, AVL_NEUTRAL); parent = NULL; break; } } else { switch (avl_balance(parent)) { default: case AVL_RIGHT: /* new left child + right leaning == balanced * nothing to propagate upwards after that */ avl_set_balance(parent, AVL_NEUTRAL); parent = NULL; break; case AVL_NEUTRAL: /* mark balance as left and continue upwards */ avl_set_balance(parent, AVL_LEFT); break; case AVL_LEFT: /* compensate double left balance by rotation * and stop afterwards */ switch (avl_balance(node)) { default: case AVL_LEFT: case AVL_NEUTRAL: MMMM(node, parent, root); break; case AVL_RIGHT: NNNN(node, parent, root); break; } parent = NULL; break; } } if (!parent) break; node = parent; } } ``` ### Answer :::success IIII = `(node)->parent_balance & ~7` JJJJ = `(node)->parent_balance & 3` KKKK = `(node)->parent_balance & 3` LLLL = `(node)->parent_balance & ~7` MMMM = `avl_rotate_right` NNNN = `avl_rotate_leftright` ::: ## 測驗三 ### 延伸問題: 1. 解釋 Linear feedback shift register (LFSR) 的運作原理,搭配在 Linux 核心原始程式碼中,用 git log 和 grep 找出 LFSR 的應用案例 2. 解釋上述程式碼運作的原理 3. 在 Linux 核心原始程式碼中找到 log2(x) 的實作 (整數),要涵蓋不同的整數型態寬度,以及是否能運用微處理器提供的指令來加速。也從 Linux 核心找出整數 log2(x) 的應用案例並解說。 4. lab0-c 提供類似的實作 log2_lshift16.h,解釋其原理並嘗試改進其效能 ### Question 考慮一個運用 LFSR 來產生隨機數,並使其均勻分佈於 64 位元無號整數的有效空間 (distribution of uniformly arbitrarily large uint64_t)。完成 [bucket_uniform.c](https://gist.github.com/jserv/15c6ef4383d5d010326cdc4382ffdd1d) (部分程式碼遮蔽)。 #### AAAA ~ HHHH 先看 log2_64 這個函式,此函式的目的是找到 64 bits 數的 log2 floor int。在測驗中,這個函式的目的是找到一個 64 bits 的 FSB (第一個 1 的) 位置。 使用的方法為,使用不斷的 shift 的方式,找到 64 bits FSB 在哪一個 bytes,使用這個 bytes 去查表找到其 log2 值,最後要加上前面 shift 的數量。 ``` 舉例: 0x0100000000000000 在例子中 shift 了 48 次。 使用 1 這個 bytes 去查表時,得到的結果為 1 。 所以最後 r 為 49。 ``` 其意義為,因為找 log2 floor 意及找 FSB 的位置,在這個例子中,我們用查表得到了 1 的 log2 (意及其在這 8 bytes 中 FSB 的位置),但前面 shift 掉了 1 後面的 48 個 bits,所以最終 FSB 的位置必須加上 48。 故 AAAA 為 56 。 故 BBBB 為 48 。 故 CCCC 為 40 。 故 DDDD 為 32 。 故 EEEE 為 24 。 故 FFFF 為 16 。 故 GGGG 為 8 。 故 HHHH 為 0 。 ```c uint64_t log2_64(uint64_t v) { unsigned r; uint64_t t, tt, ttt; ttt = v >> 32; if (ttt) { tt = ttt >> 16; if (tt) { t = tt >> 8; if (t) { r = AAAA + log_table_256[t]; } else { r = BBBB + log_table_256[tt]; } } else { t = ttt >> 8; if (t) { r = CCCC + log_table_256[t]; } else { r = DDDD + log_table_256[ttt]; } } } else { tt = v >> 16; if (tt) { t = tt >> 8; if (t) { r = EEEE + log_table_256[t]; } else { r = FFFF + log_table_256[tt]; } } else { t = v >> 8; if (t) { r = GGGG + log_table_256[t]; } else { r = HHHH + log_table_256[v]; } } } return r; } ``` #### IIII JJJJ IIII JJJJ 在 `bucket_number` 這個函式中,但是在理解怎麼做此函式前,必須知道其用處。 所以我們先解釋,`fill_buckets` 這個函式: 步驟為: 1. while 迴圈會利用多次的 lfsr 找到一個 64 bits 的隨機數 x。 2. 利用這個隨機數,放入 `bucket_number` 得到一個小於設定的 N_BUCKETS 的數,所以得出來的 tmp_bucket 也可以視為 1 隨機數。 以一套這樣的方法,就可以產生固定範圍的隨機數了! 而 for 迴圈只是重複做多次上數的步驟,用來統計產生出來的隨機數。 (lfsr 原理請見題目) ```c void fill_buckets(unsigned int *buckets, unsigned int iterations) { uint64_t x = 0x98765421b16b00b5; unsigned char lfsr_iter = (N_BITS << 1); for (uint64_t i = 0; i < iterations; i++) { unsigned int tmp_bucket = bucket_number(x); *(buckets + tmp_bucket) = *(buckets + tmp_bucket) + 1; // 'turn handle' on LFSR lfsr_iteration-times! unsigned char ell = 0; while (ell < lfsr_iter) { lfsr(&x); ell++; } } } ``` 如此一來就可以來探討 `bucket_number` 這個函式了,這個函式的目的即為將傳入的 64 bits 整數轉換為一規定範圍內的數字。 首先,有兩種 mask,配合上面 log2_64 的意義,找到一個 64 bits 的 FSB (第一個 1 的) 位置,不難理解其意義: mask111:包含 FSB 之下的 bits 全為 1。 mask011:不包含 FSB 之下的 bits 全為 1。 之後 leq 的意義為判斷 64 bits 的 x 是否有大於我們設定的 N_BUCKET。 最後的 return 則分為兩種狀況: 1. 若傳入的 x 就已經比 N_BUCKET 小了,直接回傳 x。 2. 若傳入的 x 大於等於 N_BUCKET ,取 x 的高位元段回傳。 第一個很好理解是直接對 mask111 做 and,而第二個因為確保高位元段的值一定比 N_BUCKET 小,所以是跟 mask011 做 and 。 故 IIII 為 mask111。 故 JJJJ 為 mask011。 ```c unsigned int bucket_number(uint64_t x) { uint64_t mask111 = (1 << (N_BITS + 1)) - 1; uint64_t mask011 = (1 << (N_BITS)) - 1; /* one 1 less */ unsigned char leq = ((x & mask111) < N_BUCKETS); /* leq (less or equal) is 0 or 1. */ return (leq * (x & IIII)) + ((1 - leq) * ((x >> (N_BITS + 1)) & JJJJ)); /* 'x >> (N_BITS + 1)' : take different set of bits -> better uniformity. * '... & mask011' guarantees that the result is less or equal N_BUCKETS. */ } ``` ### Answer :::success AAAA = `56` BBBB = `48` CCCC = `40` DDDD = `32` EEEE = `24` FFFF = `16` GGGG = `8` HHHH = `0` IIII = `mask111` JJJJ = `mask011` ::: ## 測驗四 ### 延伸問題: 1. 解釋上述程式碼運作原理 2. 改進程式碼,使其得以處理 x = 0 的狀況,並仍是 branchless 3. 在 Linux 核心原始程式碼找出 ceil 和 log2 的組合 (類似的形式),並解說應用案例,例如在 Linux 核心排程器就有。善用 lxr 和 git log ### Question 已知輸入必為大於 0 的數值 (即 x > 0),以下程式碼可計算 ⌈log2(x)⌉,也就是 ceil 和 log2 的組合並轉型為整數: ```c int ceil_log2(uint32_t x) { uint32_t r, shift; x--; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; return (KKKK) + 1; } ``` #### KKKK 邏輯跟上一題的 log2 floor 類似,只是不是用 control flow 的方式找到 FSB 所在的位置,而是無論是哪一種情況都能成功計算 FSB 的位置。 接著解釋程式碼: ```c int ceil_log2(uint32_t x) { uint32_t r, shift; /* 因為是求 ceil ,最後結果都會加 1 , 但是對於本來就是 2 冪次的數反而會多加 1 , 所以一開始先減掉 1 避免此問題。 */ x--; /* 如果 x 前 16 bits 有值,r = 16 反之 r = 0 r 的意義跟上題類似是用來記錄目前統計到 FSB 後的值。 */ r = (x > 0xFFFF) << 4; /* x shift r 次,即後面 16 位已討論完,繼續討論前 16 位。 若 shift 0 次,即前 16 位沒值,直接討論後 16 位。 */ x >>= r; /* 同理,只是改為處理 x 前 16 bits 或後 16 bits,而非 32 bits。 */ shift = (x > 0xFF) << 3; /* 因為 r 是代表累計的 shift 數量所以,改為用 shift 紀錄。 */ x >>= shift; /* 因為 shift 是從 16 -> 8 -> 4 -> 2 討論。 所以可以利用 r |= shift 代替 r += shift。 */ r |= shift; /* 同理。 */ shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; /* 最後因為要直接 return 了,所以將 r |= shift , 直接留在 KKKK 做,而此時 x 還剩下 2 位,所以最後 累計的 shift 還要在 + x>>1 (例如 x = 0b10)。 故 KKKK 為 r | shift | x >> 1。 最後的 + 1 是因為是求 ceil。 */ return (KKKK) + 1; } ``` ### Answer :::success KKKK = `r | shift | x >> 1` :::

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully