wurrrrrrrr
    • 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
    # Linux Suspend/Resume 實驗(一) ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ 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): 16 On-line CPU(s) list: 0-15 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz CPU family: 6 model: 165 Thread(s) per core: 2 Core(s) per socket: 8 Socket(s): 1 Socket(s): 5 CPU max MHz: 4800.0000 CPU min MHz: 800.0000 BogoMIPS: 5799.77 ``` 目前正在 PC 上使用 [pm-graph](https://github.com/intel/pm-graph/tree/master) 工具進行 suspend/resume 行為的效能測試與分析,藉此觀察各裝置在休眠與喚醒過程中的時間分布,並找出影響效能的關鍵元件。 首先先來介紹 [pm-graph](https://github.com/intel/pm-graph/tree/master)。 ## pm-graph pm-graph 這個專案可以使用 `sleepgraph` 和 `bootgraph` 這兩個工具,來視覺化 Linux 系統在 `suspend`(休眠)、`resume`(喚醒)與 `boot`(開機)過程中的活動。 針對電源管理模式中的 suspend/resume 效能進行優化極為重要,因為進入與退出低功耗模式所花的時間越長,系統可用時間就越少。 `sleepgraph` 和 `bootgraph` 這些工具擷取 `dmesg` 與 `ftrace` 資料。接著,這些資料會被轉換成時間軸(timeline)與呼叫圖(call graph),用以快速、深入地分析是哪些裝置或 kernel 程序佔用了最多時間。 這些工具會輸出一個 HTML 檔案,這些 HTML 檔可在任何 Linux 瀏覽器中開啟,例如 Firefox 或 Chromium。 ## 安裝與設定 **python 安裝步驟** ```shell sudo apt-get install python python-configparser python-requests linux-tools-common ``` **安裝步驟** ```shell git clone http://github.com/intel/pm-graph.git cd pm-graph sudo make install ``` **Kernel 編譯選項**(所有核心都需要): ```shell CONFIG_DEVMEM=y CONFIG_PM_DEBUG=y CONFIG_PM_SLEEP_DEBUG=y CONFIG_FTRACE=y CONFIG_FUNCTION_TRACER=y CONFIG_FUNCTION_GRAPH_TRACER=y CONFIG_KPROBES=y CONFIG_KPROBES_ON_FTRACE=y ``` ## 使用方式 1. 首先,依照前一節的說明設定好 kernel(啟用必要的設定),然後重新編譯、安裝並開機進入該核心,如果原本都有就不用重新編譯 kernel。 1. 開啟終端機,執行以下指令來列出可用的電源模式(power modes): ```shell sudo ./sleepgraph.py -modes ``` 範例: ``` wu@wu-Pro-E500-G6-WS720T:~/pm-graph$ sudo ./sleepgraph.py -modes [sudo] password for wu: ['freeze', 'mem', 'disk', 'mem-s2idle', 'disk-platform', 'disk-shutdown', 'disk-reboot', 'disk-suspend', 'disk-test_resume'] ``` 3. 使用其中一種電源模式(例如 mem,對應 S3 suspend)來執行測試: ```shell sudo ./sleepgraph.py -m mem -rtcwake 15 ``` 或者使用設定檔進行測試: ```shell sudo ./sleepgraph.py -config config/suspend.cfg ``` 4. 系統進入 suspend,然後在指定時間(這裡是 15 秒)後自動喚醒。 5. 當系統喚醒後,腳本會完成測試流程並在測試目錄中產生輸出檔案。這些檔案會儲存在類似以下格式的子目錄中: ``` suspend-mmddyy-HHMMSS ``` 在裡面你會看到這些輸出檔案: * HTML 分析報告:<主機名>_<電源模式>.html * 原始 dmesg 輸出:<主機名>_<電源模式>_dmesg.txt * 原始 ftrace 輸出:<主機名>_<電源模式>_ftrace.txt 可以用 **Firefox 或 Chrome 瀏覽器**打開 .html 檔案來檢視時間線報告。 ### 開發者模式 開發者模式會將底層原始函式呼叫資訊加入時間線中。工具會在所有延遲函式(如 sleep、mutex 等)上設定 kprobes,藉此觀察哪些裝置正在等待,以及它們的等待時間點。此外,也會在子系統相關的函式上設置一組 kprobes,以更完整地呈現時間線資訊。 工具還會顯示出一些平常不會出現在時間線中的 kernel 執行緒。這對於了解裝置彼此之間的依賴關係非常有幫助。例如,scsi_eh 執行緒(SCSI 錯誤處理器)是所有 SATA 硬碟在 resume 時所依賴的程序,它必須完成才能讓硬碟繼續 resume。 由於開發者模式會顯示更多細節,時間線會比基本模式大很多,因此**建議搭配 -mindev 參數**來過濾掉太短小的裝置事件區塊,以便更清晰地觀察主要裝置。 範例指令: ``` sudo ./sleepgraph.py -m mem -rtcwake 15 -mindev 1 -dev ``` 或使用設定檔方式: ``` sudo ./sleepgraph.py -config config/suspend-dev.cfg ``` ### 使用者程序模式 使用者程序模式(proc mode)會將使用者層程序的資訊加入時間線中。這類似 bootchart 工具的功能,bootchart 會將系統開機時 init 程序的執行流程做成圖表,而此工具則是在 suspend/resume 前後進行類似的記錄。 為了讓時間線能顯示出程序的活動資訊,需要在 suspend 前後插入一些延遲,因為程序會在 suspend_prepare 時凍結,在 resume_complete 時解凍。你可以**使用 -predelay 和 -postdelay 參數**來插入這些時間。 另外,也可以使用 -x2 模式(進行兩次 suspend/resume),加上 x2delay,這樣你就能看到 resume 前後,以及兩次 suspend 中間的程序活動狀況。 範例指令如下: ``` sudo ./sleepgraph.py -m mem -rtcwake 15 -x2 -x2delay 1000 -predelay 1000 -postdelay 1000 -proc ``` 或使用設定檔方式: ``` sudo ./sleepgraph.py -config config/suspend-proc.cfg ``` ## 長時間穩定性測試 評估一個系統健康狀態的最佳方式,是在長時間內反覆執行多次 suspend/resume(休眠/喚醒)測試,並分析其行為表現。 這可以透過 sleepgraph 的 **-multi** 參數來達成。 你只需指定兩個數字: 1. 要執行的測試次數 或 測試持續的天數/小時數/分鐘數 1. 每次測試之間的延遲秒數 你可以加入任何其他選項來產生所需的資料。建議使用 dev 模式收集時間軸資訊,因為 kprobes 對系統效能影響極小,但卻能提供更多深入的資訊。 測試完成後,輸出資料夾中會包含每次測試的子資料夾,以及放在根目錄下的摘要報告頁面。 * summary.html:以表格列出各次測試的資訊與連結 * summary-issues.html:彙整所有測試中偵測到的 kernel 問題 * summary-devices.html:彙整所有裝置的效能資料 ``` suspend-xN-{date}-{time}: summary.html summary-issues.html summary-devices.html suspend-{date}-{time} (1) suspend-{date}-{time} (2) ... ``` 以下是進行測試時常用的重要參數: `-m mode` * 指定要啟動的 suspend 模式,例如 mem、freeze、standby(預設為 mem) `-rtcwake t` * 使用 rtcwake 指令在 t 秒後自動喚醒系統(預設為 15 秒) `-gzip(可選)` * 將 trace 和 dmesg 日誌壓縮成 gzip 格式以節省空間。工具也支援讀取已壓縮的 log 檔,能有效減少多次測試所產生的資料夾大小。 `-dev(可選)` * 將 kernel 原始碼函式呼叫與執行緒(threads)記錄到時間軸中(預設為停用) `-multi n d` * 執行 n 次連續的測試,每次測試之間間隔 d 秒。 所有輸出將會儲存在名為 suspend-xN-{date}-{time} 的新資料夾中。 測試完成後,工具會自動執行 `-summary` 指令產生所有資料的 HTML 摘要頁面,除非你加上 `-skiphtml`。 使用 `-skiphtml` 可以略過時間軸與摘要報表的產生,大幅加快測試流程。 之後你仍可使用 `-summary` 與 `-genhtml` 再次執行來補產報表。 `-skiphtml(可選)` * 執行測試並擷取 trace 日誌,但略過時間軸與摘要 HTML 檔案的產生。 這能大幅提升整體測試速度。 之後你可以將資料複製到效能更高的主機上,再使用 -summary 與 -genhtml 來產生時間軸與摘要報告。 以下是在測試完成後可使用的相關指令: `-summary indir` 為一次 -multi 多次測試產生或重新產生摘要報告。 這個指令會在目前的資料夾中建立三個檔案: * summary.html:以表格方式列出所有測試,依照 kernel/主機/模式分類,並附有對應 HTML 報告的連結 * summary-issues.html:彙整所有測試中在 dmesg 中偵測到的 kernel 問題 * summary-devices.html:彙整所有測試中裝置的 suspend/resume 耗時資料 `-genhtml` * 搭配 -summary 使用,用來根據 dmesg 和 ftrace 日誌重新產生缺失的 HTML 時間軸檔案。 * 若測試次數非常多,這個動作可能會花上不少時間。 ### 使用範例 啟動一次多重測試: ``` sudo ./sleepgraph.py -m mem -rtcwake 10 -dev -gzip -multi 2000 0 ``` 這個指令會執行 2000 次 suspend/resume 測試,每次之間無延遲,並使用: * mem 模式(S3 suspend) * 10 秒後自動喚醒 * 開啟開發者模式(-dev) * 將 log 壓縮(-gzip) 想加快測試速度,可略過時間軸與摘要報告的產生: ``` sudo ./sleepgraph.py -m mem -rtcwake 10 -dev -gzip -multi 2000 0 -skiphtml ``` ### 若要針對既有的 multitest 資料夾產生摘要報告: ``` cd suspend-x2000-{date}-{time} sleepgraph.py -summary . ``` ### 若要補產 timeline 的 HTML 報告: ``` cd suspend-xN-{date}-{time} sleepgraph.py -summary . -genhtml ``` 這會根據每次測試中的 dmesg 和 ftrace 日誌,產出缺失的 HTML 時間軸檔案。 ## 配置檔案 config 目錄中包含了各種常見的使用情境範例。不同的電源模式也有對應的設定檔可用: 基本的 suspend/resume + 時間軸(mem/freeze/standby): * config/suspend.cfg * config/freeze.cfg * config/standby.cfg 開發者模式(含 dev timeline): * config/suspend-dev.cfg * config/freeze-dev.cfg * config/standby-dev.cfg 包含呼叫圖(callgraph)的時間軸: * config/suspend-callgraph.cfg * config/freeze-callgraph.cfg * config/standby-callgraph.cfg proc 模式的 x2 測試範例(使用 mem 模式): * config/suspend-x2-proc.cfg 編輯時間軸函式的範例(將內建函式匯入 config): * config/custom-timeline-functions.cfg 對 serio 子系統的除錯設定檔: * config/debug-serio-suspend.cfg ### 使用範例: 執行一個基本的 mem suspend 測試: ``` sudo ./sleepgraph.py -config config/suspend.cfg ``` 執行一個含呼叫圖的 mem suspend 測試: ``` sudo ./sleepgraph.py -config config/suspend-callgraph.cfg ``` 執行一個包含 dev 模式詳細資訊的 mem suspend 測試: ``` sudo ./sleepgraph.py -config config/suspend-dev.cfg ``` 還有 suspend.cfg 可以設定 ## NETFIX Netfix 是 pm-graph 工具套件中的一個網路修復工具,用來在 Linux 系統執行 suspend/resume(睡眠/喚醒)測試後,自動檢查與恢復網路連線(Wi-Fi、有線)。 (待補) ## 自訂時間軸事件 (待補) ## 在消費型 Linux 作業系統上進行測試 (待補) ## 除錯設定 在閱讀在 [Demystifying Linux Kernel Initialization](https://thenewstack.io/demystifying-linux-kernel-initialization/) `include/linux/init.h` 中定義了 八個 initcall 呼叫階段(等級),用來控制核心啟動時不同行為的初始化執行順序。其定義如下: ```c #define pure_initcall(fn) __define_initcall(fn, 0) #define core_initcall(fn) __define_initcall(fn, 1) #define core_initcall_sync(fn) __define_initcall(fn, 1s) #define postcore_initcall(fn) __define_initcall(fn, 2) #define postcore_initcall_sync(fn) __define_initcall(fn, 2s) #define arch_initcall(fn) __define_initcall(fn, 3) #define arch_initcall_sync(fn) __define_initcall(fn, 3s) #define subsys_initcall(fn) __define_initcall(fn, 4) #define subsys_initcall_sync(fn) __define_initcall(fn, 4s) #define fs_initcall(fn) __define_initcall(fn, 5) #define fs_initcall_sync(fn) __define_initcall(fn, 5s) #define rootfs_initcall(fn) __define_initcall(fn, rootfs) #define device_initcall(fn) __define_initcall(fn, 6) #define device_initcall_sync(fn) __define_initcall(fn, 6s) #define late_initcall(fn) __define_initcall(fn, 7) #define late_initcall_sync(fn) __define_initcall(fn, 7s) ``` 這些不同階段會在 Linux 開機時依序執行,從 pure(0)到 late(7)等級,目的是確保初始化順序正確、安全。 後面的數字為階段編號,s 為"sync"(同步) 的意思 啟用 `initcall_debug` 這個 KNL(核心啟動參數,`Kernel start-up parameter)` 可以讓我們追蹤每個 `initcall` 的執行過程。這對於理解 Linux 核心的開機流程、除錯早期的核心崩潰(kernel panic),以及測量每個 initcall 所花費的初始化時間非常有幫助。 另一個有用的 KNL 參數是 `initcall_blacklist`,它可以讓你指定一組要跳過執行的 initcall 函數清單。這對於除錯內建模組或特定 initcall 行為也非常實用。 **啟用 initcall_debug**,並使用 dmesg 來解析 initcall 資訊: 第一步是確保你的核心有啟用以下兩個選項: * `CONFIG_PRINTK_TIME`:讓 dmesg 輸出包含每行訊息的時間戳記。 * `CONFIG_KALLSYMS`:允許顯示符號名稱(不然只能看到地址)。 如果這兩個選項未啟用,你就必須自行編譯、安裝並啟動一個新的核心。 一旦這些選項已經啟用,你可以編輯 `/etc/default/grub` 檔案,把下列參數加到 `GRUB_CMDLINE_LINUX` 這一行中: 先輸入 ``` sudo nano /etc/default/grub ``` 找到 `GRUB_CMDLINE_LINUX` 將後面參數填入 "earlyprintk=vga printk.time=1 initcall_debug" ``` GRUB_CMDLINE_LINUX="earlyprintk=vga printk.time=1 initcall_debug" ``` 現在,重新開機系統。系統啟動完成後,執行以下指令來觀察開機期間的 initcall 訊息: ``` dmesg -t -x ``` 需要先把目前的核心開機訊息(包含 initcall log)輸出成 dmesg.out 檔案,再執行分析指令。 ``` sudo dmesg -t -x > dmesg.out ``` 以下這段可以顯示那些執行時間較長的 initcall 函數: ``` cat dmesg.out | grep "initcall" | sed "s/\(.*\)after\(.*\)/\2 \1/g" | sort -n ``` 這行指令會: 1. 讀取 dmesg.out(包含 initcall log 的檔案) 1. 找出所有包含 initcall 的行 1. 把每行中 "after XXX usecs" 的耗時資訊移到開頭 1. 最後依照執行時間從小到大排序 範例: ``` 7082 usecs kern :debug : initcall hid_init+0x0/0xff0 [usbhid] returned 0 7108 usecs kern :debug : initcall init_encrypted+0x0/0x100 returned 0 11846 usecs kern :debug : initcall jent_mod_init+0x0/0x100 returned 0 15295 usecs kern :debug : initcall hdmi_driver_init+0x0/0xff0 [snd_hda_codec_hdmi] returned 0 16184 usecs kern :debug : initcall inet_init+0x0/0x330 returned 0 20982 usecs kern :debug : initcall serial_pci_driver_init+0x0/0x30 returned 0 23919 usecs kern :debug : initcall msr_init+0x0/0xff0 [msr] returned 0 ``` 啟用 `initcall_debug` 之後,核心輸出的訊息數量會增加。請確認核心設定中 `CONFIG_LOG_BUF_SHIFT` 被設為 18,這樣可以把日誌緩衝區的大小設定為 256K,以避免訊息被截斷或遺失。 為了加快開機過程,你也可以啟用 `driver_async_probe` 這個核心啟動參數(KNL),它可以讓驅動程式的探測(probe)以非同步模式執行。啟用這個選項後,你可以指定一組驅動名稱,讓它們以非同步方式初始化。 ## 第一次測試 ( pm-graph ) 首先我先測試 `freeze` 的系統狀態並輸入下列命令: ``` $ sudo ./sleepgraph.py -m freeze -rtcwake 15 ``` 由下列這張圖可以發現到 `ata1`(SATA 裝置)花了 605.769 ms 進入 `suspend` 狀態是耗時最久的裝置。 ![image](https://hackmd.io/_uploads/rJ0zJ9wllx.png) 根據下面這張圖可以發現裝置名稱:**nvidia @ 0000:01:00.0** `resume` 最久的: **Total Suspend 時間**:`211.969 ms` * 其中主體 `suspend` 操作耗時約 `199.800 ms` **Total Resume 時間**:`548.301 ms` * `Resume` 操作在黃色區段內明顯佔據了很大一段時間,長達 `535.737 ms` ![image](https://hackmd.io/_uploads/H1uOJqwegx.png) 從這兩張圖可以發現這兩個裝置是整體 **suspend/resume 的 bottleneck**。 為了測試系統在 `mem` 模式下的休眠狀態,我們將 `mem_sleep` 設定為 `deep`,並執行以下指令以進行 `suspend` 操作: ``` $ sudo ./sleepgraph.py -m mem -rtcwake 15 ``` ``` wu@wu-Pro-E500-G6-WS720T:~/pm-graph$ cat /sys/power/mem_sleep s2idle [deep] ``` 由下列這張圖可以發現到 `ata1`(SATA 裝置)花了 `604.203 ms` 進入 `suspend` 狀態也是一樣是耗時最久的裝置。 ![image](https://hackmd.io/_uploads/SJoS9_wggg.png) 根據下面這張圖可以發現裝置名稱:**e1000e @ 0000:00:1f.6** 為 `resume` 最久的: * `Suspend` 耗時:`110.230 ms` * `Resume` 耗時:`977.777 ms` ![image](https://hackmd.io/_uploads/HkGP9dDelg.png) 經過多次測試後發現,在系統 resume 的過程中,偶爾會出現雙螢幕其中一邊亮起但無顯示畫面的情況,目前原因尚未明確。但是強制重新初始化 HDMI-0 螢幕輸出流程後就可以顯示: ``` xrandr --output HDMI-0 --off sleep 1 xrandr --output HDMI-0 --mode 1920x1080 --rate 60 --primary ``` 所以推斷可能是 NVIDIA 驅動在 resume(喚醒)階段漏掉了 HDMI encoder 的 re-init 初始化或狀態恢復,因此我輸入了下列這段指令來確認: ``` journalctl -b | grep -i nvidia ``` * journalctl: 查看 systemd 的 system log(包括核心、驅動、服務等日誌)。 * -b: 只顯示本次開機(boot)後的 log。 * |: 管道符號,把 journalctl 的輸出交給後面的 grep。 * grep -i nvidia: 搜尋關鍵字 nvidia 發現到下列這段兩段: ``` 五 07 14:57:12 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (II) NVIDIA(0): Setting mode "DP-4: 3440x1440_100 @3440x1440 +0+0 {ViewPortIn=3440x1440, ViewPortOut=3440x1440+0+0}, HDMI-0: 1920x1080_240 @1920x1080 +3440+0 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}五 07 16:24:37 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (--) NVIDIA(GPU-0): Microstep MAG 276CXF (DFP-4): connected 五 07 16:24:37 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (--) NVIDIA(GPU-0): Microstep MAG 276CXF (DFP-4): Internal TMDS 五 07 16:24:37 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (--) NVIDIA(GPU-0): Microstep MAG 276CXF (DFP-4): 600.0 MHz maximum pixel clock 五 07 16:24:37 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (--) NVIDIA(GPU-0): ``` 可以發現到驅動在 resume 之後掃描 HDMI 輸出埠(DFP-4),發現螢幕已連接,並列出 TMDS 類型與最大頻寬。 ``` 五 07 16:24:37 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (II) NVIDIA(0): Setting mode "DP-4: 3440x1440_100 @3440x1440 +0+0 {ViewPortIn=3440x1440, ViewPortOut=3440x1440+0+0}, HDMI-0: 1920x1080_240 @1920x1080 +3440+0 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}" 五 07 16:24:37 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (--) NVIDIA(GPU-0): DFP-4: disconnected 五 07 16:24:37 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (--) NVIDIA(GPU-0): DFP-4: Internal TMDS 五 07 16:24:37 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (--) NVIDIA(GPU-0): DFP-4: 165.0 MHz maximum pixel clock 五 07 16:24:37 wu-Pro-E500-G6-WS720T /usr/libexec/gdm-x-session[1470]: (--) NVIDIA(GPU-0): ``` 驅動正在同時設定 DP-4 與 HDMI-0 的顯示模式時,顯示器本來是 connected,但設定顯示模式後,立即變成 **disconnected**,還連 pixel clock 資訊都變了(從 600 降到 165 MHz,顯示為 fallback) ## 第一次測試 ( perfetto ) 在進行 Perfetto 測試之前,請先根據官網提供的步驟安裝所需套件。你可以參考這篇文章:[ Quickstart: Record traces on Linux ](https://perfetto.dev/docs/quickstart/linux-tracing)。按照教學完成安裝後,即可開始執行測試。 首先,建立一個用於測試的檔案,檔名為 `xxxxx.cfg`,並將其內容設置為你所需的設定。由於今天的測量目標是 `suspend` 和 `resume`,因此我將檔案命名為 `suspend_resume.cfg`。設定好檔名後,接著填入相應的配置內容,如何設定可以參考檔案中範例和[ TraceConfig ](https://perfetto.dev/docs/reference/trace-config-proto)跟[ TracePacket ](https://perfetto.dev/docs/reference/trace-packet-proto)。 我的 cfg 檔設置如下: ``` buffers { size_kb: 100024 fill_policy: RING_BUFFER } data_sources { config { name: "linux.ftrace" target_buffer: 0 ftrace_config { ftrace_events: "sched_switch" ftrace_events: "sched_waking" ftrace_events: "sched_wakeup_new" ftrace_events: "sched_process_exec" ftrace_events: "sched_process_exit" ftrace_events: "sched_process_fork" ftrace_events: "sched_process_free" ftrace_events: "sched_process_hang" ftrace_events: "sched_process_wait" ftrace_events: "irq_handler_entry" ftrace_events: "irq_handler_exit" ftrace_events: "suspend_resume" } } } duration_ms: 20000 ``` 輸入以下指令就能開始測試: ``` sudo ./out/linux/tracebox -o trace.perfetto-trace --txt -c test/configs/suspend_resume.cfg ``` 測試完後會產生 trace.perfetto-trace 檔案,隨後要執行這行程式碼: ``` sudo chown $USER:$USER trace.perfetto-trace ``` 這條命令的作用是將 trace.perfetto-trace 文件的擁有者和群組更改為當前登入的用戶,這樣用戶便能對該文件擁有完全的控制權。如果沒有執行這步驟,生成的文件可能無法在 [ ui.perfetto](https://ui.perfetto.dev/) 中進行分析,因為文件的權限設定可能會限制對該文件的讀取或修改。 [ ui.perfetto](https://ui.perfetto.dev/) 這個網站可以將你執行的結果轉換為可視化的圖表和數據,幫助你更直觀地分析和理解追蹤數據。 此為第一次測試的結果: ![Screenshot from 2025-05-10 16-56-06](https://hackmd.io/_uploads/HJU3E5hxgx.png) 可以看到,由於測量範圍過大,刻度的細節無法清晰呈現,因此無法進行更精確的觀察。不過,這個頁面允許使用 W, A, S, D 鍵進行縮放和移動,讓你可以更近距離地觀察數據。 下圖為放大後的結果: ![Screenshot from 2025-05-10 17-16-47](https://hackmd.io/_uploads/B1TtKq2glx.png) 由此圖可以得知當執行 `sudo systemctl suspend` 這行指令後系統會建立一個 `systemd-sleep thread` 並開始執行下列為此 `thread` 的資訊: ![Screenshot from 2025-05-10 17-26-16](https://hackmd.io/_uploads/B1UTscnxee.png) 在影片中提到在 `suspend_enter` 階段,系統會進入休眠準備過程,這時可能會執行 `sync_filesystems()` 操作,即將文件系統中的修改(如尚未寫回磁碟的數據)同步到磁碟中,這樣可以確保在系統進入休眠狀態後不會遺失任何數據。 在這個階段,系統還會嘗試將 `dirty pages`寫回磁碟,以保證文件系統的一致性。這是一個非常關鍵的操作,因為如果系統在這個過程中被中斷,可能會導致數據丟失或文件系統損壞。 此時,userspace 無法中斷或干涉這個操作,因為它是由內核處理的,並且它需要保證在進入休眠前,所有關鍵數據都已經正確地寫入磁碟。因此,進程在這段時間內會處於 `Uninterruptible Sleep` 狀態也就是下圖中黃色區段,這意味著它們不會接受來自 userspace 的信號或中斷,直到文件系統同步完成,並且系統準備好進入休眠狀態。 因此我嘗試不去執行 `sync_filesystems()` 操作,而 `sync_on_suspend_enabled` 這個變數為會不會執行這 `sync_filesystems()` 的關鍵,由 [linux/kernel/power/suspend.c](https://github.com/torvalds/linux/blob/master/kernel/power/suspend.c#L364) 可以得知 ``` static int enter_state(suspend_state_t state) { ... if (sync_on_suspend_enabled) { trace_suspend_resume(TPS("sync_filesystems"), 0, true); ksys_sync_helper(); trace_suspend_resume(TPS("sync_filesystems"), 0, false); } ``` 在系統中 `sync_on_suspend_enabled` 這個變數預設為 `true`,因此接下來嘗試將它關閉來觀察 `suspend_enter` 這個階段是否能加快,因此首先必須先找到`sync_on_suspend_enabled` 這個變數的位置,這個變數的位置為下列這個目錄: ``` /sys/power/sync_on_suspend ``` 將其值改為 false 要輸入下列指令: ``` echo 0 | sudo tee /sys/power/sync_on_suspend ``` 隨後開始觀察是否會加快 `suspend_enter` 這個階段的速度,下列為觀察結果: * 輸入前: ![Screenshot from 2025-05-13 22-27-48](https://hackmd.io/_uploads/Sk7OcAxZle.png) * 輸入後: ![Screenshot from 2025-05-13 22-28-36](https://hackmd.io/_uploads/Sklpq0xWll.png) 可以發現 `suspend_enter` 的速度明顯提昇,因此改進`sync_filesystems()` 操作是非常關鍵的。 ![Screenshot from 2025-05-10 17-39-00](https://hackmd.io/_uploads/Sy46Cq2llx.png) ![Screenshot from 2025-05-10 17-52-29](https://hackmd.io/_uploads/rk31fihlex.png) 這兩張圖顯示了系統進入休眠與恢復的過程。首先,系統會分別執行 cpuhp 關閉 CPU,並同時執行 CPU 核心的遷移(migration)操作。當遷移過程完成後,接著會依次執行 cpuhp 來重新開啟 CPU 核心。 ## 樹苺派測試 當下載好 [Raspberry Pi OS](https://zh.wikipedia.org/zh-tw/Raspberry_Pi_OS) 後便可以開始開發,首先一樣是先確認下列這些: **Kernel 編譯選項**(所有核心都需要): ```shell CONFIG_DEVMEM=y CONFIG_PM_DEBUG=y CONFIG_PM_SLEEP_DEBUG=y CONFIG_FTRACE=y CONFIG_FUNCTION_TRACER=y CONFIG_FUNCTION_GRAPH_TRACER=y CONFIG_KPROBES=y CONFIG_KPROBES_ON_FTRACE=y ``` ### 重新編譯 kernel 確認完上述後由於我們的目標是去研究 suspend 跟 resume 的,因此還要去確認 `CONFIG_SUSPEND` 是否等於 y,而我檢查時發現到 `#CONFIG_SUSPEND is not set` ,因此此時需要重新編譯一個支援 CONFIG_SUSPEND 的 kernel。 一、首先要做的是先安裝必要的套件: ```shell sudo apt update sudo apt install git bc bison flex libssl-dev make libncurses-dev \ crossbuild-essential-arm64 libelf-dev libudev-dev libpci-dev \ libiberty-dev raspberrypi-kernel-headers ``` 二、下載 Raspberry Pi 官方 kernel 原始碼 ```shell git clone --depth=1 https://github.com/raspberrypi/linux cd linux ``` 三、載入目前系統的 config 並修改 ``` cp /boot/config-$(uname -r) .config make menuconfig ``` 隨後會有圖形界面並在這個圖形介面中打開以下項目: ``` [*] Suspend to RAM and standby [*] Power Management support ``` 四、編譯 kernel ``` make -j$(nproc) Image modules dtbs ``` 五、安裝編譯後的 kernel ``` sudo make modules_install sudo cp arch/arm64/boot/Image /boot/firmware/kernel8.img ``` 當所有設定完成後,執行 `cat /sys/power/state` 時會看到 `freeze` 和 `mem` 兩個選項,其中 `mem` 支援 `i2idle`。然而,當我執行 `echo freeze | sudo tee /sys/power/state` 時,發現畫面在進入 `freeze` 模式後會完全靜止。 原本我想利用檢查 `journalctl` 日誌歷史來找尋線索,但是發現輸入`echo freeze | sudo tee /sys/power/state`後連日誌都無法寫入 後來我猜測是系統缺乏喚醒的手段,因此我下命令 `sudo find /sys -name wakeup` 用來在系統中查找所有名為 wakeup 的文件或目錄。這些文件通常與 硬體裝置 或 中斷 相關,並控制它們是否能夠喚醒系統。在輸入完上述指令後找到了下列這兩段: ``` /sys/devices/platform/soc/fe201000.serial/fe201000.serial:0/fe201000.serial:0.0/tty/ttyAMA0/power/wakeup /sys/devices/platform/serial8250/serial8250:0/serial8250:0.0/tty/ttyS0/power/wakeup ``` 樹莓派的串行端口(UART)中包含一個名為 wakeup 的文件,因此我想測試是否可以使用另一台電腦與樹莓派進行通信,來觸發並喚醒樹莓派裝置。 首先要做的就是開啟 UART 功能:確認 `/boot/firmware/config.txt` 中是否有 `enable_uart=1` 和 `dtoverlay=disable-bt` 如果沒有要找到 `/boot/firmware/config.txt` 並輸入 `sudo nano /boot/firmware/config.txt` 這個指令編輯這個檔案,在下面的 [all] 加入後重新開機,隨後找到你裝置的位置輸入下列指令: ``` echo enabled | sudo tee /sys/devices/platform/soc/fe201000.serial/fe201000.serial:0/fe201000.serial:0.0/tty/ttyAMA0/power/wakeup echo enabled | sudo tee /sys/devices/platform/serial8250/serial8250:0/serial8250:0.0/tty/ttyS0/power/wakeup ``` 將之 wakeup 功能打開並輸入 `ls -l /dev/serial0` 確認是否為下列這個: ``` /dev/serial0 -> /dev/ttyAMA0 ``` 當設置好後開始執行電腦間的通訊使用 usb to ttl 跟 [minicom](https://en.wikipedia.org/wiki/Minicom) 這個 Linux console 連接工具,使用步驟為下列: 1. 電腦端(有 USB to TTL) 插上 USB to TTL,找出 ttyUSB,例如: ``` ls /dev/ttyUSB* ``` 啟動 minicom: ``` sudo minicom -b 9600 -D /dev/ttyUSB0 ``` 你打字會送出資料,經由 TXD → 傳到樹莓派 2. 樹莓派端 先接好 pin 腳([參考文章](https://ithelp.ithome.com.tw/articles/10215294)) 執行: ``` sudo minicom -b 9600 -D /dev/serial0 ``` 執行完後你就能在樹苺派的畫面上看見輸入的文字,隨後輸入 `systemctl suspend` 後在另外一台電腦上敲擊任何的文字後,樹苺派就會從 suspend 中 resume ,但是我發現到樹苺派的網路模組會載入失敗,目前不清楚為什麼。 下列為 `dmesg` 中的一部分訊息: ``` [ 328.564397] PM: suspend exit [ 328.596754] bcmgenet fd580000.ethernet: configuring instance for external RGMII (RX delay) [ 328.600046] bcmgenet fd580000.ethernet eth0: Link is Down [ 328.616230] brcmfmac: brcmf_sdio_txfail: sdio error, abort command and terminate frame [ 328.620055] brcmfmac: brcmf_sdio_txfail: sdio error, abort command and terminate frame [ 328.624007] brcmfmac: brcmf_sdio_txfail: sdio error, abort command and terminate frame [ 328.625363] brcmfmac: brcmf_sdio_dpc: sdio ctrlframe tx failed err=-84 [ 328.625463] brcmfmac: brcmf_sdio_dpc: failed backplane access over SDIO, halting operation [ 328.625578] ieee80211 phy0: brcmf_proto_bcdc_query_dcmd: brcmf_proto_bcdc_msg failed w/status -84 ``` 為了確保 resume 後網路模組仍能持續運作,需要手動在 Raspberry Pi 的 Device Tree 中,於 MMC 控制器(&mmcnr)節點加入「保留電源於休眠」屬性。修改示例如下: ```diff &mmcnr { pinctrl-names = "default"; pinctrl-0 = <&sdio_pins>; bus-width = <4>; + keep-power-in-suspend; status = "okay"; }; ``` 此為在樹苺派中測試 suspend 結果: ![Screenshot from 2025-05-13 14-59-24](https://hackmd.io/_uploads/rysRTwe-ee.png) ![Screenshot from 2025-05-13 14-58-19](https://hackmd.io/_uploads/HyOcaveZxg.png) ![Screenshot from 2025-05-13 14-58-56](https://hackmd.io/_uploads/SJa2pPxbll.png)

    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