sysprog
      • 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
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners 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 New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
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
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners 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
    3
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Linux 核心專題: CPU 排程器研究 > 執行人: linhoward0522 > [專題解說錄影](https://youtu.be/la0i6O4JS48) :::success :question: 提問清單 * BORE 排程器跟CFS比犧牲了什麼??數據看的出來嗎 > 我認為犧牲的是「公平性」,可以從下面實驗看出來 > 公平性 : 兩個具有相同優先權級的任務應該要運行幾乎相同的時間 > #### CPU-bound and yield > 在處理器核編號 2 上執行的任務分別是 CPU-bound 與 yield 類型的任務 > - CPU_bound 任務 : 在 main 函數中有一個 for 迴圈,每次迭代都會進行矩陣乘法,共迭代四次 > - yield 任務 : 與 CPU-bound 任務相同,只是每次迭代進行矩陣乘法後,會呼叫 `sched_yield` 主動放棄處理器資源 > - [ ] BORE >![](https://hackmd.io/_uploads/BJInXCTIn.png) > - [ ] CFS >![](https://hackmd.io/_uploads/rJIXw06I3.png) > >由實驗結果可以看出當每次輪到 yield 類型的任務使用 CPU 資源時, BORE 會給予更多的 timeslice (因為可以看到最後面 CPU-bound 任務使用一大段時間的 CPU 資源,表示 yield 類型的任務被給予更多的 timeslice),所以 yield 類型的任務會比 CPU-bound 類型的任務還要早執行完成。而 CFS 則是非常公平的平均分配 timeslice 給兩種任務。此實驗結果也與 BORE 原作者所說的 : 「稍微偏離 CFS 原有的 complete fairness 原則」相同。 * 「EEVDF 非常頻繁的切換執行,因為 EEVDF 的 timeslice 較短」這陳述是否意味著 EEVDF 的執行成本較高? > 在處理器核編號 2,3 號上使用 [stress](https://linux.die.net/man/1/stress) 來各產生 2 個 CPU-bound 任務,並且利用 taskset 將其指定到對應的處理器核編號上 > 我利用 [perf](https://perf.wiki.kernel.org/index.php/Main_Page) 來簡單觀察一下,以下是收集 10 秒鐘的結果 - CFS ``` ------------------------------------------------------------------------------------------------------------------------------------------- Task | Runtime ms | Switches | Avg delay ms | Max delay ms | Max delay start | Max delay end | ------------------------------------------------------------------------------------------------------------------------------------------- stress:(4) | 20001.528 ms | 1255 | avg: 15.907 ms | max: 16.008 ms | max start: 101497.845449 s | max end: 101497.861457 s migration/3:34 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s migration/2:28 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s ----------------------------------------------------------------------------------------------------------------- TOTAL: | 20001.528 ms | 1257 | --------------------------------------------------- ``` - BORE ``` ------------------------------------------------------------------------------------------------------------------------------------------- Task | Runtime ms | Switches | Avg delay ms | Max delay ms | Max delay start | Max delay end | ------------------------------------------------------------------------------------------------------------------------------------------- stress:(4) | 20001.590 ms | 3308 | avg: 6.042 ms | max: 8.014 ms | max start: 101527.792998 s | max end: 101527.801012 s migration/3:34 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s migration/2:28 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s ----------------------------------------------------------------------------------------------------------------- TOTAL: | 20001.590 ms | 3310 | --------------------------------------------------- ``` - EEVDF ``` ------------------------------------------------------------------------------------------------------------------------------------------- Task | Runtime ms | Switches | Avg delay ms | Max delay ms | Max delay start | Max delay end | ------------------------------------------------------------------------------------------------------------------------------------------- stress:(4) | 20005.383 ms | 5007 | avg: 3.994 ms | max: 4.009 ms | max start: 21546.291197 s | max end: 21546.295206 s migration/3:34 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s migration/2:28 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s ----------------------------------------------------------------------------------------------------------------- TOTAL: | 20005.383 ms | 5009 | --------------------------------------------------- ``` 可以看出 EEVDF 的切換次數遠高於 CFS , BORE,切換次數越高表示執行成本較高。 ::: ## 任務簡述 研讀《Demystifying the Linux CPU Scheduler》,更新書中關於 [BORE](https://github.com/firelzrd/bore-scheduler) 及 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 的描述,採用 Linux v6.1。 ## 事前準備 [Demystifying the Linux CPU Scheduler 筆記](https://hackmd.io/@linhoward0522/HyC28W9N3) ### 更新至 Linux v6.1 :::spoiler 後來發現應該要使用自行編譯的 Linux 核心,改用以下方法: 查看目前版本 ```shell $ uname -mrs Linux 5.19.0-41-generic x86_64 ``` 更新所有 package ```shell $ sudo apt update ``` 新增 `cappelikan` repository 以及下載 Ubuntu Mainline Kernel Installer ```shell $ sudo add-apt-repository ppa:cappelikan/ppa -y $ sudo apt install mainline -y ``` 打開 Ubuntu Mainline Kernel Installer 找出所需要的版本並安裝,最後重開機即可 ```shell $ uname -mrs Linux 6.1.28-060128-generic x86_64 ``` ::: 下載需要的套件 ```shell $ sudo apt install gcc make libncurses-dev flex bison libssl-dev libelf-dev initramfs-tools-bin ``` 下載指定的核心版本並解壓縮 ```shell $ wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.29.tar.xz $ tar -xvf linux-6.1.29.tar.xz ``` 進入該目錄,並複製核心開啟的檔案到當下目錄的 config 檔 ```shell $ cd linux-6.1.29 $ cp /boot/config-5.19.0-41-generic .config ``` 建立編輯選單,進入選單後將 config 檔載入 ```shell $ make menuconfig ``` - 選擇 load .config - 選擇 save .config - 按兩次 ESC 離開選單 修改 `.config` 檔案,將下列兩行內容刪除,如下所示 ```shell $ vim .config CONFIG_SYSTEM_TRUSTED_KEYS="" CONFIG_SYSTEM_REVOCATION_KEYS="" ``` 編譯模組,安裝核心後重開機 ```shell $ make -j 8 $ sudo make modules_install $ sudo make install $ sudo reboot ``` 確認是否安裝成功 ```shell $ uname -mrs Linux 6.1.29 x86_64 ``` ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 $ uname -mrs Linux 6.1.29 x86_64 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 12 On-line CPU(s) list: 0-11 Vendor ID: GenuineIntel Model name: 12th Gen Intel(R) Core(TM) i5-12400 CPU family: 6 Model: 151 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 Stepping: 2 CPU max MHz: 5600.0000 CPU min MHz: 800.0000 BogoMIPS: 4992.00 Virtualization features: Virtualization: VT-x Caches (sum of all): L1d: 288 KiB (6 instances) L1i: 192 KiB (6 instances) L2: 7.5 MiB (6 instances) L3: 18 MiB (1 instance) NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0-11 ``` ## TODO: 在 Linux v6.1 測試 [BORE](https://github.com/firelzrd/bore-scheduler) 並解釋其原理 參見 [BORE (Burst-Oriented Response Enhancer) CPU Scheduler](https://hackmd.io/@foxhoundsk/bore-sched) 及 [BMQ 排程器研究](https://hackmd.io/@SmallHanley/linux2022-projects),搭配《Demystifying the Linux CPU Scheduler》的第 3 章,在 Linux v6.1 (LTS) 重現實驗,解讀實驗數據,並評估是否該更新電子書描述。 > 留意 [BORE](https://github.com/firelzrd/bore-scheduler) 專案頁面的 "How it works",已更新 BORE 的設計描述和相關圖表,其中 EEVDF 可參照 LWN: [An EEVDF CPU scheduler for Linux ](https://lwn.net/Articles/925371/) ### BORE(Burst-Oriented Response Enhancer) 是 CFS 和 EEVDF 的改進,在既有的程式碼基礎之上,儘快對應用程式作出回應。 BORE 為每個任務引入 `burstiness` ,稍微偏離 CFS 原有的 complete fairness 原則。 `Burstiness` 是指一個任務在明確放棄 CPU 後消耗的 CPU 時間所得出的分數,無論是進入睡眠、 I/O waiting 或放棄 CPU 資源。 利用這個 `burstiness` 指標,BORE 動態調整每個任務的權重和延遲。因此在不同類型負載的系統中,BORE 會優先處理需要快速回應的任務,以提高整體系統回應能力。 #### How it works - 排程器會追蹤每個任務的 burst time,也就是任務從上次放棄 CPU 資源、睡眠或 I/O waiting 以來消耗的 CPU 時間。 - 當任務處於活動狀態時,透過正規化的 burst time 以及使用預設的偏移量跟係數來計算 burst score。 - burst score 的功能類似於 niceness ,介於 0-39 之間。當數值每減少 1,任務可以消耗大約 1.25 倍長的 timeslice。 - burst score 會影響剛睡醒任務搶佔目前執行任務的積極度。(只有 CFS 版本,EEVDF 版本沒有)。 - 較不貪婪的任務被賦予更多 timeslice 以及睡醒任務搶佔目前執行任務的積極度;貪婪任務則被賦予較少的權重。 - 最終是希望在各種工作負載共存的情況下,在 CPU-bound 與 I/O-bound 任務之間取得平衡,提高整體系統回應能力。 ### BORE 程式碼解釋 >![](https://hackmd.io/_uploads/r1rlhmsB3.png) BORE 主要實作於 `update_curr()` 中,用來更新目前行程在 CPU 上執行所花費的實際時間以及 vruntime 首先在 `include/linux/sched.h` 中的 [`struct sched_entity`](https://github.com/torvalds/linux/blob/master/include/linux/sched.h#L549) 增加以下成員 ```diff @@ -555,6 +555,12 @@ struct sched_entity { u64 sum_exec_runtime; u64 vruntime; u64 prev_sum_exec_runtime; +#ifdef CONFIG_SCHED_BORE + u64 prev_burst_time; + u64 burst_time; + u64 max_burst_time; + u8 penalty_score; +#endif // CONFIG_SCHED_BORE ``` #### `update_curr()` ```diff= @@ -905,6 +1005,14 @@ static void update_curr(struct cfs_rq *cfs_rq) curr->sum_exec_runtime += delta_exec; schedstat_add(cfs_rq->exec_clock, delta_exec); +#ifdef CONFIG_SCHED_BORE + curr->burst_time += delta_exec; + curr->max_burst_time = max(curr->max_burst_time, curr->burst_time); + update_burst_score(curr); + if (sched_bore & 1) + curr->vruntime += penalty_scale(calc_delta_fair(delta_exec, curr), curr); + else +#endif // CONFIG_SCHED_BORE curr->vruntime += calc_delta_fair(delta_exec, curr); update_min_vruntime(cfs_rq); ``` - 第 6 行是更新 burst_time ,delta_exec 是指任務到目前為止的執行時間 - 第 7 行是更新 max_burst_time ,取 burst_time 與 max_burst_time 的最大值 - 若呼叫 `dequeue_task_fair()` 或 `yield_task_fair()` 函式時,會透過 `restart_burst()` 函式將 burst_time 與 prev_burst_time 做正規化(依照目前設定是相加除以二)來更新 max_burst_time - 第 8 行是呼叫 `update_burst_score()` 函式來更新 burst_score - 第 10 行是透過 `penalty_scale()` 函式來更新 vruntime - 呼叫 `calc_delta_fair()` 函式來更新 vruntime - 將 vruntime 乘上對 `sched_prio_to_wmult` 查表後得到的數值 - burst_score 越高,查表得到的數值越大,相乘後使得 vruntime 成長幅度越大 #### `update_burst_score()` ```diff= +#define FIXED_SHIFT 10 +static void update_burst_score(struct sched_entity *se) { + u64 burst_time = se->max_burst_time; + + int msb = fls64(burst_time); + fixed integer_part = msb << FIXED_SHIFT; + fixed fractional_part = burst_time << (64 - msb) << 1 >> (64 - FIXED_SHIFT); + fixed greed = integer_part | fractional_part; + + fixed tolerance = sched_burst_penalty_offset << FIXED_SHIFT; + fixed penalty = max(0, (s32)greed - (s32)tolerance); + fixed scaled_penalty = penalty * sched_burst_penalty_scale >> 10; + + u8 score = min(39U, scaled_penalty >> FIXED_SHIFT); + se->penalty_score = score; +} ``` - 第 3 行是把 burst_time 設定成透過 `update_curr()` 函式中更新過的 max_burst_time - 第 5 ~ 8 行是將 burst_time 以 2 為底取對數並加上 1 - integer_part 是以 2 為底取對數並加上 1 的結果 - fractional_part 是以 2 為底取對數時所失去的小數部份。最後會儲存在 `greed` 中較低位元的 `FIXED_SHIFT` 長度 - 第 11 行是將 `penalty` 設為 `greed` 減去 `tolerance` 並與 0 取 `max` - `tolerance` 是透過預設的偏移量所計算出來 - `sched_burst_penalty_offset` 增加這個數值可防止較短 burst_time 的任務過於強大。 - 第 12 ~ 15 行是先對 `penalty` 做 scaling ,並右移 10 位將小數部份去掉,最後將該數值與 39 取 min 作為 burst score - `sched_burst_penalty_scale` 增加這個數值會使 burst score 隨著 burst time 的增加而快速增長。導致 CPU-Bound 任務可能很難被執行到 ### EEVDF (Earliest Eligible Virtual Deadline First) 某些行程可能不需要大量的 CPU 時間,但當行程需要 CPU 時間時,必須盡快分配給該行程。 CFS 沒有給行程表示 `latency requirements` 的方法,確保某些行程(即時任務)可以快速存取 CPU 。 與 CFS 一樣,EEVDF 會公平的分配 CPU 時間。較低的 nice value 具有較高優先級。 對於每個行程,EEVDF 計算行程本應獲得的時間與實際獲得的時間之間的差值,稱為 `lag` 。 具有 `positive lag value` 的行程表示未獲得其應獲得的時間,要比具有 `negative lag value` 的行程更早被排程到。 當行程的 `lag value` 大於等於 0 ,才會被認為是 `eligible` 。任何 `negative lag value` 行程都沒有資格運行。在未來某個時間,該行程本應獲得的時間追上實際獲得的時間,會再次被認為是 `eligible` ,該時間稱為 `eligible time` 。 > 依據[劍橋辭典](https://dictionary.cambridge.org/dictionary/english/eligible),"eligible" 寓意是合格、具備特定資格者,用於 CPU 排程器就是挑選出有資格成為下個任務的候選者 `lag value` 的計算是 EEVDF 排程的關鍵部分,而另一個因素是 `virtual deadline` 。是行程收到其應有的 CPU 時間的最早時間,透過行程分配到的 timeslice 加上 `eligible time` 來計算的。 EEVDF 會優先執行 `virtual deadline` 最早的行程。 > 例如一個行程具有 10 ms timeslice,其 `eligible time` 為未來 20 ms,則 `virtual deadline` 為未來 30 ms。 具有較短 timeslice 的行程會有較小的 `virtual deadline` ,所以 EEVDF 會分配較短的 timeslice ,讓有 `latency requirements` 的行程優先執行,能夠快速回應事件。 ### 重現 BORE 實驗 使用 `0001-linux6.1.y-bore2.2.8.patch` 補丁並重新編譯核心 ```shell $ patch -p1 -i 0001-linux6.1.y-bore2.2.8.patch ``` 同時編譯另一個使用 EEVDF 排程器的核心。先使用 EEVDF 的補丁,接著再使用 EEVDF-BORE 的補丁 以 [`jitterdebugger`](https://github.com/igaw/jitterdebugger) 搭配 [`stress-ng`](https://wiki.ubuntu.com/Kernel/Reference/stress-ng) 測量, `jitterdebugger` 扮演的是 interactive task,而 `stress-ng` 扮演的則是背景工作負載。 > `jitterdebugger` 測量 wake up latency。 > `jitterdebugger` 在每個 CPU 上使用一個執行緒然後進行睡眠。在睡眠結束後,獲取當下 timestamp,也就是實際 wake up 時間,並與預期的 wake up 時間相減,即是 wake up latency 可以透過以下命令來切換要實驗的排程器 ```shell $ sudo sysctl -w kernel.sched_bore=3 ``` 若是基於 CFS 版本的 BORE - 3 是 BORE - 0 是 CFS 若是基於 EEVDF 版本的 BORE - 1 是 BORE - 0 是 EEVDF 產生在背景執行的工作負載以測試排程器並製圖。設定 stressor 個數為 48,測試 10 分鐘 ```shell $ sudo ./jitterdebugger -D 10m -c 'stress -c 48' -o result $ ./jitterplot --output res.jpg hist result/results.json ``` - CFS 版本的 BORE ![](https://hackmd.io/_uploads/HyCrOIs8n.png) - CFS ![](https://hackmd.io/_uploads/HkuU_LjU3.png) - EEVDF 版本的 BORE ![](https://hackmd.io/_uploads/BkQwdUsL2.png) - EEVDF ![](https://hackmd.io/_uploads/ryZud8j83.png) 上面為 `jitterdebugger` 的測量結果,橫軸為 scheduling latency,縱軸為次數。 由實驗結果可以看出 CFS 的 max scheduling latency 是所有排程器中最差的(cpu5 達到 1068)。而兩種版本的 BORE 多數的 max scheduling latency 都相當的低,表示 BORE 的確會優先處理需要快速回應的任務。而 avg scheduling latency 的表現其實所有排程器都差不多。 :::warning TODO: 搭配上方排程器設計的描述,解讀實驗結果,特別是為何 EEVDF 與宣稱不同。 :notes: jserv ::: ## TODO: 理解 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 原理並實驗 INRIA 的 Julia Lawall 教授在 FOSDEM 2023 有場精彩的演講〈[Graphing tools for scheduler tracing](https://fosdem.org/2023/schedule/event/sched_tracing/)〉(內附簡報檔案和錄影),談及 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 這工具,預期產出: * 在本頁面介紹 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 原理 * 設計實驗 (對照《Demystifying the Linux CPU Scheduler》第 7 章),並以 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 展現 CFS 排程行為 * 對照 [BORE](https://github.com/firelzrd/bore-scheduler) 和 CFS 行為,使用 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 展現 ### 介紹 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 原理 > 參考資料 : > * [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) > * [FOSDEM 2023 演講](https://fosdem.org/2023/schedule/event/sched_tracing/) > * [Graphing Tools for Scheduler Tracing](https://inria.hal.science/hal-04001993/preview/RR-9498.pdf) `trace-cmd` 是用於收集和視覺化追蹤執行的 `ftrace` 的前端工具。 我們主要使用的工具是 [`schedgraph`](https://gitlab.inria.fr/schedgraph/schedgraph),由 `dat2graph` 和 `running_waiting` 組成。這兩個工具都依賴於 `trace-cmd`。 我們使用 `dat2graph` 和 `running_waiting` 而不是 `trace-cmd` 或是它的圖形化介面 `kernelshark` ,是因為較難追蹤擁有大量處理器核系統的排程器活動。 首先介紹一些名詞 : - Work conservation : 如果有處理器核空閒,則不應使處理器核過載。也就是當一個任務準備好執行時,排程器應該傾向於將任務放在一個空閒的處理器核上,而不是放在有任務已經在執行的處理器核上。 - Locality : NUMA 機器的影響很大,其中處理器核對本地記憶體的存取速度較快,而對其他 NUMA 節點上的記憶體存取速度較慢。 work conservation 與 locality 會發生衝突,將任務放在目前空閒但位於不同 NUMA 節點的處理器核上,可能會降低存取速度。 :::warning 注意用語: * kernel : 核心 * (processor) core : 處理器核 (避免寫「核心」) > 了解,接下來會注意用語。 ::: `dat2graph` 和 `running_waiting` 分別提供在處理器核上的任務活動的視覺化,了解大型多核機器上排程器的行為。兩種不同的方法提供了互補的資訊 : - `dat2graph2` : 了解何時、在哪個處理器核執行。只有顯示正在執行的任務,沒有顯示正在等待的任務。 ![](https://hackmd.io/_uploads/ryo9s1G83.png) - x 軸是執行時間(秒) - y 軸是處理器核,顏色代表 PID 從上圖可以看出並非所有任務都在相同的處理器核上執行,例如接近 3 秒和 15 秒時,任務可能會移動到其他處理器核上或暫停執行。 若任務移動頻繁,可能會喪失 Locality。 - `running_waiting` : 有多少任務在執行或是等待 ![](https://hackmd.io/_uploads/HkBhskMUh.png) - x 軸是執行時間(秒) - y 軸是任務數 - 綠線表示正在執行的任務數量 - 紅線表示可執行任務的數量,即正在執行與正在等待的任務 - 黑色水平虛線表示機器的處理器核數 如果紅線可視,即高於綠線,則表示某些可執行任務正在等待,即出現過載。 如果紅線在黑色虛線上方,則任務多於處理器核數,則過載是不可避免的。但如果紅線可視,且低於黑色虛線,則表示存在 Work conservation 問題,代表一些處理器核過載、但有些處理器核是空閒的。也就是上圖中 15 到 20 秒之間。 ### 設計實驗並以 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 展現 CFS 排程行為 > 參考資料 : > - [trace-cmd](https://github.com/rostedt/trace-cmd) > - [Ocaml 安裝](https://ocaml.org/install) > - [Ocaml 安裝手冊](https://ocaml.org/docs/up-and-running#first-steps-with-ocaml) > - [jgraph](http://web.eecs.utk.edu/~jplank/plank/jgraph/jgraph.html) #### 安裝必要工具 為了執行 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 專案,首先需要安裝一些必要的工具。 安裝 trace-cmd 以及相關套件 ```shell $ sudo apt-get install build-essential git pkg-config -y $ sudo apt-get install libtracefs-dev libtraceevent-dev -y $ git clone https://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git $ cd libtraceevent $ make $ sudo make install $ git clone https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git $ cd libtracefs $ make $ sudo make install $ git clone https://github.com/rostedt/trace-cmd.git $ cd trace-cmd $ make $ sudo make install $ sudo make install_libs ``` [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 專案利用 OCaml 程式語言撰寫,所以我們需要安裝 OCaml 以及其套件管理器 opam ```shell $ bash -c "sh <(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)" ``` 安裝完後對 opam 進行初始化,接著下載開發工具 ```shell $ opam init $ opam install dune merlin ocaml-lsp-server odoc ocamlformat utop dune-release ``` 安裝特定版本的 OCaml 並運行該版本 ```shell $ opam switch create 4.14.0 $ eval opam env ``` > 實驗過若下載 5.0.0 版本, schedgraph 目前不支援 確認版本是否正確 ```shell $ which ocaml /home/howard/.opam/4.14.0/bin/ocaml $ ocaml -version The OCaml toplevel, version 4.14.0 ``` 安裝製圖工具 ```shell $ sudo apt-get install jgraph $ sudo apt-get install ps2eps $ sudo apt-get install texlive-font-utils ``` 取得 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 專案程式碼 ```shell $ git clone https://gitlab.inria.fr/schedgraph/schedgraph.git $ cd schedgraph $ make all ``` #### 將行程固定在特定的 CPU 中執行 修改 /etc/default/grub 內的 `GRUB_CMDLINE_LINUX_DEFAULT` ,加入 `isolcpus=2,3` 來指定編號 2,3 的處理器核不會被排程器賦予任務,修改完成後需 `sudo update-grub` 來更新設定,最後重新開機 ```shell $ sudo vi /etc/default/grub GRUB_CMDLINE_LINUX_DEFAULT="quiet splash isolcpus=2,3" $ sudo update-grub $ sudo reboot ``` 重新開機後檢查 CPU affinity ```shell $ cat /sys/devices/system/cpu/isolated 2-3 ``` #### 實驗 利用 `mktasks` 程式產生 workload ,並執行 `start.py` 來利用以下命令進行追蹤並產生對應的 `.dat` 檔案 ```shell $ sudo trace-cmd record -e sched -v -e sched_stat_runtime ``` - `record` : 記錄追蹤的事件 - `-e sched` : 追蹤所有排程相關的事件 - `-v -e sched_stat_runtime` : 忽略統計收集事件,因為它們數量多且與理解為什麼將任務放置在各個處理核上無關 接著利用 `dat2graph` 和 `running_waiting` 來產生追蹤事件的視覺化圖片 #### Difference between FIFO and RR 參數設定與書中第七章的設定一樣。首先分為兩組,每一組各有三個任務。執行的任務都是 CPU-bound 類型。第一組的任務利用 FIFO 排程,第二組的任務利用 RR 排程,並分別運行在處理核編號 3 跟 2 號。 使用下列命令來產生 `dat2graph` 圖片 ```shell $ ./dat2graph2 --save-tmp /path/to/fifo-rr-trace.dat $ jgraph fifo-rr-trace.jgr | ps2eps -q -l -r 600 | epstopdf --filter > fifo-rr-trace.pdf ``` 利用 `--save-tmp` 命令列引數來儲存產生的 jgraph 檔案,接著利用 jgraph 命令來將檔案轉換成 pdf 檔 ![](https://hackmd.io/_uploads/HkhFl4DU2.png) 可以看得很清楚處理核編號 3 的任務是利用 FIFO 來做排程,而處理核編號 2 的任務是利用 RR 來做排程。但仔細看的話可以發現在執行結束時,有出現非常小的點。所以我利用以下命令來放大來看 ```shell $ ./dat2graph2 --color-by-command --min 1.136 --max 1.138 --save-tmp /path/to/fifo-rr-trace.dat $ jgraph fifo-rr-trace_color_from_1.315_upto_1.318_color.jgr | ps2eps -q -l -r 600 | epstopdf --filter > fifo-rr-trace_color_zoomin.pdf ``` `--color-by-command` 會根據正在執行的任務名稱來上色 ![](https://hackmd.io/_uploads/SyNffNwUn.png) 可以發現任務執行結束時, kworker 會執行。所以我利用 kernelshark 去細看資訊時發現當任務執行結束時,kworker 會去執行 swapper 。因為沒有其它可執行的任務。 使用下列命令來產生 `running_waiting` 圖片 ```shell $ ./running_waiting --save-tmp --range 0.0-1.4 /path/to/fifo-rr-prio-trace.dat $ jgraph fifo-rr-prio-trace_rw_from_0.0_upto_1.4.jgr | ps2eps -q -l -r 600 | epstopdf --filter > fifo-rr-prio-trace_rw.pdf ``` ![](https://hackmd.io/_uploads/Sy81pVvLn.png) 圖中可以看到有紅線,表示存在 Work conservation 問題,但是是正常的。因為我們只有在兩個處理核上執行任務,其它尚未被排程到的任務只能等待。 #### Priority of FIFO and RR 一樣分為兩組,每一組各有四個任務。執行的任務都是 CPU-bound 類型。第一組的任務利用 FIFO 排程,第二組的任務利用 RR 排程,且任務的優先權級分別為 97,98,98,99 ,並分別運行在處理核編號 3 跟 2 號。 觀察 `dat2graph` 圖片 ![](https://hackmd.io/_uploads/B1JZfSwUh.png) 處理核編號 3 號的任務一樣是利用 FIFO 來做排程,會按照優先權級依序執行。而處理核編號 2 號的任務是利用 RR 來做排程,會先執行優先權級最高的任務,然後因為有兩個任務的優先權級相同,所以當它們的 timeslice 消耗完時會切換執行,最後執行優先權級最低的任務。 一樣可以發現在執行結束時,有出現非常小的點,將這個部份放大來看。 ![](https://hackmd.io/_uploads/HJt5QSwLh.png) 一樣利用 kernelshark 來查看詳細資訊,因為沒有其它可執行的任務,所以 kworker 會去執行 swapper 。 觀察 `running_waiting` 圖片 ![](https://hackmd.io/_uploads/BJcU4HDUn.png) 一樣存在 Work conservation 問題,因為尚未被排程到的任務只能等待。 #### Block the FIFO :::info 實驗過 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 必須將任務運行在多個處理核上,故讓處理器核編號 3 上也有執行任務,但任務內容為不做任何事。 ::: 處理核編號 2 號有 I/O_block 跟 yield 類型的任務各兩個,皆是利用 FIFO 來做排程 觀察 `dat2graph` 圖片 ![](https://hackmd.io/_uploads/H1CHj0aIh.png) 因為任務數量較多搭配以下 `dat2graph` 圖片來觀察 ![](https://hackmd.io/_uploads/rJqq20aIh.png) 可以發現在處理核編號 2 號上是依序執行 I/O_block 跟 yield 類型的任務。當任務進行 I/O 操作或是呼叫 `sched_yield` 時,會釋放 CPU 資源並讓下一個任務執行 觀察 `running_waiting` 圖片 ![](https://hackmd.io/_uploads/Sk8QRCpLh.png) 同樣存在 Work conservation 問題 ### 對照 [BORE](https://github.com/firelzrd/bore-scheduler) , [EEVDF](https://lwn.net/Articles/925371/) 和 CFS 行為,使用 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 展現 ::: info 以下實驗皆有在處理器核編號 3 上執行任務,任務內容為不做任何事。僅為了能讓 [schedgraph](https://gitlab.inria.fr/schedgraph/schedgraph) 正常運作 而出現 unknown 是因為 trace-cmd 初始化的副作用所產生 ::: 為了方便觀察 BORE , EEVDF 與 CFS 的排程行為,所以我設計以下三種實驗。分別是扮演使用 CPU 時間較長的 CPU-bound 類型的任務以及扮演使用 CPU 時間較少(會主動放棄 CPU )的任務,並都具有相同的優先權級 #### CPU-bound and yield 在處理器核編號 2 上執行的任務分別是 CPU-bound 與 yield 類型的任務 - CPU_bound 任務 : 在 main 函數中有一個 for 迴圈,每次迭代都會進行矩陣乘法,共迭代四次 - yield 任務 : 與 CPU-bound 任務相同,只是每次迭代進行矩陣乘法後,會呼叫 `sched_yield` 主動放棄處理器資源 #### BORE ![](https://hackmd.io/_uploads/BJInXCTIn.png) #### EEVDF ![](https://hackmd.io/_uploads/BJjVyMg_n.png) #### CFS ![](https://hackmd.io/_uploads/rJIXw06I3.png) 由實驗結果可以看出當每次輪到 yield 類型的任務使用 CPU 資源時, BORE 會給予更多的 timeslice (因為可以看到最後面 CPU-bound 任務使用一大段時間的 CPU 資源,表示 yield 類型的任務被給予更多的 timeslice),所以 yield 類型的任務會比 CPU-bound 類型的任務還要早執行完成。因為優先權級一樣,所以 EEVDF 會公平的分配 CPU 時間,但可以發現 EEVDF 非常頻繁的切換執行,因為 EEVDF 的 timeslice 較短。最後 CFS 則是非常公平的平均分配 timeslice 給兩種任務。 #### CPU-bound and sleep 在處理器核編號 2 上執行的任務分別是 CPU-bound 與 sleep 類型的任務 - CPU_bound 任務 : 迭代進行矩陣乘法 - sleep 任務 : 每次迭代進行矩陣乘法後,會呼叫 `usleep(1)` 讓任務去睡眠 1 微秒 #### BORE ![](https://hackmd.io/_uploads/BkHvE0aUh.png) #### EEVDF ![](https://hackmd.io/_uploads/r12v1zgO3.png) #### CFS ![](https://hackmd.io/_uploads/Hklc4C6I3.png) 由實驗結果可以看出每次輪到 sleep 類型的任務使用 CPU 資源時, BORE 同樣給予更多的 timeslice (可以看到最後面 CPU-bound 任務使用一大段時間的 CPU 資源)。同樣的 EEVDF 非常頻繁的切換執行,同時公平分配 CPU 時間。而 CFS 則是平均分配 timeslice 。 #### CPU-bound and I/O_block 在處理器核編號 2 上執行的任務分別是 CPU-bound 與 I/O_block 類型的任務 - CPU-bound 任務 : 迭代進行矩陣乘法 - I/O_block 任務 : 每次迭代進行矩陣乘法後,會進行 I/O 操作 #### BORE ![](https://hackmd.io/_uploads/HyoyHCaL2.png) #### EEVDF ![](https://hackmd.io/_uploads/SJ0Fyfluh.png) #### CFS ![](https://hackmd.io/_uploads/rJgpNRT82.png) 由實驗結果可以看出每次輪到 I/O_block 類型的任務使用 CPU 資源時, BORE 有給予更多的 timeslice ,所以執行過程中沒有出現 idle 。同樣的 EEVDF 非常頻繁的切換執行,且當 I/O_block 類型的任務完成寫檔後,又繼續與 CPU-bound 任務公平分配 CPU 時間,因為具有相同的優先權級。而 CFS 則是因為平均分配 timeslice ,導致有 idle 的情況發生。因為 CPU-bound 任務已經完成,但是 I/O_block 任務還在進行 I/O 操作且尚未完成。 ::: info 結論 : BORE 會讓長時間使用 CPU 資源的任務賦予它更高的 vruntime ,所以該任務會較不容易被排程到。而使用 CPU 資源較少的任務會有較高的 burst score ,也就是會被賦予更多的 timeslice 並且會增加搶佔目前執行任務的積極度。 ::: :::warning TODO: 針對 EEVDF 進行實驗,可能需要更新的 Linux 核心: [linux-cachyos](https://github.com/CachyOS/linux-cachyos)。BORE 的作者在 CachyOS 的 Telegram 很活躍。 ::: ### [BORE 及 schedgraph 的英文描述](https://hackmd.io/@linhoward0522/HyHb3BsPh) ## TODO: 探討 CPU 排程器延遲的議題 研讀以下技術報告和論文: * [Reducing CPU scheduler latency in Linux](https://www.diva-portal.org/smash/get/diva2:1630380/FULLTEXT01.pdf) * [Reducing latency spikes by tuning the CPU scheduler](https://www.scylladb.com/2016/06/10/read-latency-and-scylla-jmx-process/) * [Efficient Scheduler Live Update for Linux Kernel with Modularization](https://dl.acm.org/doi/abs/10.1145/3582016.3582054) 解讀 CPU 排程器的延遲和調整機制,分析如何設計實驗來理解實作的表現。 ### 解讀 CPU 排程器的延遲和調整機制 > 參考資料 : > - [perf 用法](https://jasonblog.github.io/note/linux_tools/perf_--_linuxxia_de_xi_tong_xing_neng_diao_you_gon2.html) > - [perf bench](https://man7.org/linux/man-pages/man1/perf-bench.1.html) > - [sysctl output changed from kernel 5.10 to 5.13](https://forum.endeavouros.com/t/sysctl-output-changed-from-kernel-5-10-to-5-13-why/17097) > - [hackbench](https://lkml.iu.edu//hypermail/linux/kernel/0112.1/0702.html) 排程延遲 : 當一個行程可以運行卻不能有進展,因為排程器將 CPU 時間分配給其它不同的行程 首先在處理器核編號 2,3 號上使用 [stress](https://linux.die.net/man/1/stress) 來各產生 2 個 CPU-bound 任務,並且利用 taskset 將其指定到對應的處理器核編號上 ```shell $ taskset -c 2 stress -c 2 $ taskset -c 3 stress -c 2 ``` 接著我們利用 [perf](https://perf.wiki.kernel.org/index.php/Main_Page) 來觀察排程延遲的時間,並利用 `sudo perf sched latency` 來產生報告 ```shell $ sudo perf sched record -C 2,3 sleep 10 $ sudo perf sched latency ``` - `sched` 表示獲取排程相關的資訊 - `record` 紀錄資訊 - `-C` 指定收集該處理器核的資訊 - `sleep` 表示要收集幾秒鐘 以下是收集 10 秒鐘的結果 - CFS ``` ------------------------------------------------------------------------------------------------------------------------------------------- Task | Runtime ms | Switches | Avg delay ms | Max delay ms | Max delay start | Max delay end | ------------------------------------------------------------------------------------------------------------------------------------------- stress:(4) | 20001.528 ms | 1255 | avg: 15.907 ms | max: 16.008 ms | max start: 101497.845449 s | max end: 101497.861457 s migration/3:34 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s migration/2:28 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s ----------------------------------------------------------------------------------------------------------------- TOTAL: | 20001.528 ms | 1257 | --------------------------------------------------- ``` - BORE ``` ------------------------------------------------------------------------------------------------------------------------------------------- Task | Runtime ms | Switches | Avg delay ms | Max delay ms | Max delay start | Max delay end | ------------------------------------------------------------------------------------------------------------------------------------------- stress:(4) | 20001.590 ms | 3308 | avg: 6.042 ms | max: 8.014 ms | max start: 101527.792998 s | max end: 101527.801012 s migration/3:34 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s migration/2:28 | 0.000 ms | 1 | avg: 0.000 ms | max: 0.000 ms | max start: 0.000000 s | max end: 0.000000 s ----------------------------------------------------------------------------------------------------------------- TOTAL: | 20001.590 ms | 3310 | --------------------------------------------------- ``` 可以看出 CFS 的平均以及最大排程延遲時間都大於 BORE 的數值,因為 BORE 會比較快回應任務,所以 BORE 的切換次數遠高於 CFS 所以我想透過調整 CFS 的排程器參數,來實驗看看是否這些參數會影響排程延遲的時間 首先,進入 root 權限並列出所有 CFS 的排程器參數 ```shell $ cd /sys/kernel/debug/sched/ $ ls debug latency_warn_ms numa_balancing domains latency_warn_once preempt features migration_cost_ns tunable_scaling idle_min_granularity_ns min_granularity_ns verbose latency_ns nr_migrate wakeup_granularity_ ``` > 現在使用 `sudo sysctl -a | grep "sched" | grep -v "domain"` 命令僅能列出以下資訊 : >``` >kernel.sched_autogroup_enabled = 1 >kernel.sched_cfs_bandwidth_slice_us = 5000 >kernel.sched_child_runs_first = 0 >kernel.sched_deadline_period_max_us = 4194304 >kernel.sched_deadline_period_min_us = 100 >kernel.sched_energy_aware = 1 >kernel.sched_rr_timeslice_ms = 100 >kernel.sched_rt_period_us = 1000000 >kernel.sched_rt_runtime_us = 950000 >kernel.sched_schedstats = 0 >kernel.sched_util_clamp_max = 1024 >kernel.sched_util_clamp_min = 1024 >kernel.sched_util_clamp_min_rt_default = 1024 >``` 我使用 `sched_min_granularity_ns` , `sched_wakeup_granularity_ns` 這兩個參數來進行實驗 - `sched_min_granularity_ns` : 表示分配給每個任務 timeslice 的下限,也就是每個任務被搶佔之前能夠執行的最短時間。 目前預設值為 : 3000000 ns - `sched_wakeup_granularity_ns` : 表示當另外一個任務睡醒時,會延遲當前正在執行任務的搶佔,目的是為了不要頻繁的排程。所以在設定的範圍內,非常接近當前任務 vruntime 數值的任務不會搶佔當前任務。 目前預設值為 : 4000000 ns ### 設計實驗 為了進行測量排程延遲的時間,我使用 [hackbench](https://lkml.iu.edu//hypermail/linux/kernel/0112.1/0702.html) 工具來進行實驗。 [hackbench](https://lkml.iu.edu//hypermail/linux/kernel/0112.1/0702.html) 是在核心中透過 IPC 的方式來測量延遲的工具。 [hackbench](https://lkml.iu.edu//hypermail/linux/kernel/0112.1/0702.html) 會建立幾對任務,同一組內的任務可以互相通訊。這些任務來回通訊,而延遲即是每對任務通訊的時間。 我寫了一隻測試程式,調整 `sched_min_granularity_ns` , `sched_wakeup_granularity_ns` 數值的同時,使用以下命令來測量排程延遲的時間 > [測試程式](https://github.com/linhoward0522/Latency_test) ```shell $ sudo perf record -e sched:* perf bench sched messaging -p -t -g 20 -l 1000 $ sudo perf sched latency ``` - `-p` : 透過使用 pipe 來消除 socket communication 的開銷 - `-t` : 透過使用執行緒來消除建立行程的開銷 - `-g` : 指定組的數量。每一組裡面各有 20 個 sender 跟 receiver 執行緒。同組的任務互相通訊 - `-l` : 指定通訊次數 #### sched_min_granularity_ns - 執行時間 ![](https://hackmd.io/_uploads/SyC4IYtDh.png) - 平均延遲時間 ![](https://hackmd.io/_uploads/By2HLFKD2.png) - 最大延遲時間 ![](https://hackmd.io/_uploads/S1SIIttPn.png) #### sched_wakeup_granularity_ns - 執行時間 ![](https://hackmd.io/_uploads/rkvv8KFP3.png) - 平均延遲時間 ![](https://hackmd.io/_uploads/SkUuUYYP2.png) - 最大延遲時間 ![](https://hackmd.io/_uploads/r1lFUKKD2.png) :::info 由以上實驗可以觀察出,調整 `sched_min_granularity_ns` 數值似乎對於執行時間或是平均,最大延遲時間沒有太大的影響。反而是調整 `sched_wakeup_granularity_ns` 數值比較有影響。 當 `sched_wakeup_granularity_ns` 數值越高時,執行時間與最大延遲時間越低。而 `sched_wakeup_granularity_ns` 數值介於 5000000 ~ 20000000 ns 時平均延遲時間的表現比較好。因為透過減少了不必要的搶佔來提昇效能。 ::: #### sched_min_granularity_ns vs sched_wakeup_granularity_ns - 執行時間 ![](https://hackmd.io/_uploads/rkr4DFFDn.png) - 平均延遲時間 ![](https://hackmd.io/_uploads/HyIrDtYPh.png) - 最大延遲時間 ![](https://hackmd.io/_uploads/r1gUwFYw3.png) :::info 同時調整 `sched_min_granularity_ns` , `sched_wakeup_granularity_ns`數值。對於執行時間來說,調整 `sched_wakeup_granularity_ns` 數值比較有影響。而對於平均延遲時間來說, `sched_wakeup_granularity_ns` 數值介於 5000000 ~ 15000000 ns 時較好,與調整 `sched_min_granularity_ns` 較無關連。最後是對於最大延遲時間, `sched_wakeup_granularity_ns` 數值越高時,最大延遲時間較好。同樣是因為透過減少不必要的搶佔來提昇效能。 :::

    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