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
    • 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 Versions and GitHub Sync Note Insights 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
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Linux 核心專題: 核心模式的網頁伺服器 > 執行人: ginsengAttack > [解說影片](https://www.youtube.com/watch?v=guPlLPipAcc) ### Reviewed by `Max042004` > 明明可以採用 blocking 的 kernel_accept 進行連線監聽即可? 你應該是指 non blocking 的 kernel_accept 吧? 我也有相同的問題 > 我的想法是,該專題講求大量的 I/O 處理,所以使用這種能夠同時處理多個連線的機制。之後會測試這種方法可不可以引入 ktcp ,提升併行處理的能力。 ### Reviewed by `Denny0097` 引入 CMWQ 機制之後,為什麼可以使效能有顯著的提升?可以進行更詳細的說明。 > CMWQ 的機制會在核心建立 Worker Pool ,核心會依 workqueue 旗標 (如 CPU 綁定、優先順序等) 自動安排 worker 並在適當時機切換與重用,降低 idle worker 數量並提升系統使用率。 > >使用 kthread 的話會在新連線進入才生成一個 kernel thread ,CMWQ 為基礎的實作透過 thread pool 持續保留空閒 worker,work item 進入佇列後即可指派至可用執行緒,無須再建立 kthread  ## 任務簡述 依循 [ktcp](https://hackmd.io/@sysprog/linux2025-ktcp/) 作業規範,進行開發,務必提交給進 khttpd 的 pull request 並接受 review。適度引入[第 11 週測驗題](https://hackmd.io/@sysprog/linux2025-quiz11)提及的 kweb 設計,將 ktthpd 調整為高度並行設計,並排除開發過程遇到的缺失。 ## 閱讀 ktcp 課程教材 ### khttpd 功能 透過 `open_listen_socket` 監聽特定 port 等待連線,並使用 `kthread_run` 建立背景程式 `http_server_daemon`。 `http_server_daemon` 接收連線請求,並產生獨立的 thread 服務每一個連線,為教材[事件驅動伺服器:原理和實例](https://hackmd.io/@sysprog/linux-io-model/https%3A%2F%2Fhackmd.io%2F%40sysprog%2Fevent-driven-server),中提到的事件驅動伺服器。 ### 運行`http_server_worker` 負責服務單一連線,以 [HTTP parser](https://github.com/nodejs/http-parser) 解析 http request ,目前該專案已經停止維護,後續會改用 [picohttpparser](https://github.com/h2o/picohttpparser) 以更輕量的方法替代。 ### 支援 HTTP keep-alive 模式 TCP 連線需要 three way handshake,這是一個龐大開銷,所以 HTTP 支援 keep-alive 模式,降低通訊本。 對應到 `http_server.c` 中的第176-187行: ```c while (!kthread_should_stop()) { ... if (request.complete && !http_should_keep_alive(&parser)) break; ... } ``` 支援單次連線多次傳輸。 ## 閱讀 kweb 程式碼 與 Khttpd 不同,不會給每個連線一個獨立的 thread,而是產生與 CPU 數量相同的 thread,以一個 `listener` 作為生產者,將資料( socket )放進 buffer ,由作為消費者的 worker 取出後服務客戶端。 當中使用了 `vfs_poll` 幫助 worker 處理多個客戶端的連線,此為核心提供的 [I/O Multiplexing](https://hackmd.io/@sysprog/linux-io-model/https%3A%2F%2Fhackmd.io%2F%40sysprog%2Fevent-driven-server) api。 但我有疑問,為何 listener 同樣採用此 api: ```c while (HHHH) { if (!(vfs_poll(ctx0->listen_file, NULL) & POLLIN)) { schedule_timeout_interruptible(msecs_to_jiffies(10)); continue; } for (;;) { struct socket *cs = NULL; int err = kernel_accept(ctx0->listen_sock, &cs, O_NONBLOCK); ... } cond_resched(); } ``` 明明可以採用 blocking 的 `kernel_accept` 進行連線監聽即可? 我的想法是,該專題講求大量的 I/O 處理,所以使用這種能夠同時處理多個連線的機制。 之後進行效能量測,研究引入此種方法是否更具吞吐量。 而之所以使用 `schedule_timeout_interruptible` 讓模組暫時睡眠,是為了避免 busy waiting 耗費過多 CPU 效能。 ## 開發問題整理 1. 管理資源,使用到的資源在用完時要自行釋放,否則模組將無法正確卸載。 2. stack 大小要注意, overflow 會使整個系統崩潰。 3. 要進行錯誤處理,不能預設每個操作都會順利,在這邊貪圖方便,會導致後續排查問題更麻煩,後果也更嚴重。 ## TODO: 導入 Concurrency Managed Workqueue 以 `alloc_workqueue` 創建 workqueue,並在移除模組的時候使用: ```c flush_workqueue(...); destroy_workqueue(...); ``` 將 workqueue 移除。 workqueue 當中的 work item 資料結構為: ```c typedef void (*work_func_t)(struct work_struct *work); struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif }; ``` 想要把 `kthread_run` 改動為 workqueue,還要考慮資料結構的問題,原程式使用 `void *arg` 作為傳入執行緒的參數,而 CMWQ 指定以 `struct work_struct` 作為參數。 但是我們必須將 socket 傳輸到對應執行緒,所以要採用作業一提到的方法,以契合 linux 核心風格的方式,將 `work_struct` 嵌入到其他結構,並透過 `contianer_of` 取出資訊。 :::danger 務必依循課程教材規範的術語,見 https://hackmd.io/@sysprog/it-vocabulary ::: 接著,以把 work item 加入 workqueue 的方式取代原有的 `kthread_run` : ```c INIT_WORK(...);//將函數封裝進已經定義好的結構 queue_work(...,...);//加入 workqueue ``` 透過教材提供的 hstress ,量測效能: ```c requests: 100000 good requests: 100000 [100%] bad requests: 0 [0%] socket errors: 0 [0%] seconds: 1.422 requests/sec: 70299.857 ``` 提升至: ```c requests: 100000 good requests: 100000 [100%] bad requests: 0 [0%] socket errors: 0 [0%] seconds: 0.992 requests/sec: 100788.570 ``` 另外,如果要 commit ,會遇到靜態分析問題: ``` suppress=unusedStructMember:http_server.h ``` 將 `pre-commit.hook` 中的這個抑制移除即可。 具體程式碼: > commit:[5a57bcd](https://github.com/sysprog21/khttpd/commit/5a57bcd665ad8affd40f95bcec66254362e2bd26) ## TODO: directory listing 功能 參考教材提到的核心 api : `iterate_dir ` 具體定義如下 ```c #include<linux/fs.h> int iterate_dir(struct file *filp, struct dir_context *ctx); ``` 輸入的兩項參數中,前者為檔案結構,後者則包裝著回調函數,我們將特定的檔案位置輸入後,此函數會走訪該目錄底下的所有檔案,並呼叫包裝在 `dir_context *ctx` 中的函數。 基本功能為:走訪目錄每一個檔案->讀取檔名->製作相應 html 欄位->傳輸 。 考慮到網路佇列的行為,網卡限制 MTU ,所以單個封包大小是有限制的,從使用者的觀點來看,訊息是完整的被傳輸,但是在傳輸層的角度,封包卻是需要被切割的,由目的端的傳輸層進行重組。 我們可以利用這個特點,分批的傳輸資訊,也就是可以直接在走訪每一個檔名的時候直接用 `kernel_sendmsg` 進行傳輸。 接著我們要準備回調函數,定義的結構為: ```c int (*actor)(struct dir_context *ctx, const char *name, int namelen, loff_t offset, u64 ino, unsigned int d_type); ``` 比照教材宣告: ```c static int tracedir(struct dir_context *dir_context, const char *name, int namelen, loff_t offset, u64 ino, unsigned int d_type) ``` 因為該函數內部需要呼叫到 `http_server_send` ,所以必須要能夠接收 socket 等資訊,採用跟之前一樣的方法: ```c struct http_request { struct socket *socket; enum http_method method; char request_url[128]; int complete; struct dir_context dir_context;//<- new insert }; ``` 同時應進行修改,並移除原專案長度限制: ```c "Content-Type: text/plain" -> "Content-Type: html/plain" ``` 此時檔案資訊可以正常傳輸。 但考慮到上述分段傳輸,接收端難以得知訊息是否傳輸完成,所以接收端網頁會不停載入,甚至無法顯示,這邊提供兩種方式: 1. `kernel_sock_shutdown` 為關閉連線的 api ,透過在傳輸結束時呼叫該 api,即可手動的關閉連線,告知接收端傳輸結束 2. 使用 chunked 方法一不是優秀的選擇,因為他不是通知接收端 http 傳輸結束,而是直接關閉 tcp 連線,與原專案支援 keep-alive 的功能背道而馳,所以應該要採用 chunked 也就是分塊傳輸編碼。 ### 讀取檔案 既然已經能夠輸出目錄內容,便要能傳輸目錄中的檔案,核心以 `inode` 管理檔案屬性。 並提供[巨集](https://github.com/torvalds/linux/blob/master/include/uapi/linux/stat.h): ```c #define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) #define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) #define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) #define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) ``` 之後會需要用到 `S_ISDIR` 和 `S_ISREG` 兩者,前者判斷是否為目錄,啟用上述 directory listing 功能,後者判斷是否為檔案,進行接下來的檔案傳輸功能以此進行判斷。 首先根據客戶端傳送的網址,查找對應目錄,判斷為檔案後,以 `i_size` 分配與檔案同等大小的空間,並使用 `read_file` 將檔案儲存至該空間,接下來就可以直接傳出資料,最後要把剛剛分配的空間釋放。 > Commit: [a36f550](https://github.com/sysprog21/khttpd/commit/a36f550768a9d03615ec1a052a68e33b63690037) ### MIME 上面的功能已經可以傳送檔案,可是在 HTTP header 中有 `Content-Type` 的欄位提供正確檔案類型,可以參考[資料](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types),以此生成對應標頭。 透過查表的方式進行搜尋,將對應的資料寫入在另一個 `mime.h` 檔案中。 教材採用循序的搜尋,是否可以改用雜湊提高速度? > Commit: [670c3bb](https://github.com/sysprog21/khttpd/commit/670c3bbe3a03fa7d7945d4797445ce86bad100ac) ## 使用 Ftrace 定位效能瓶頸並改善 (http parser) 在 [assessment](https://hackmd.io/J8nSa524R52WMX5CqhDpJw?view)中已根據教材內容操作 Ftrace。 瓶頸為: ```c http_parser_execute() http_parser_callback_message_complete() http_server_send() http_server_recv() ``` I/O 操作本來佔據的時間就多,必須引入更複雜的機制處理,這邊先討論 `http_parser_execute()` 這是一個 http 請求解析工具,採用狀態機模型,會針對輸入資料 進行逐字元處理, 雖然設計簡潔、具備良好可攜性,但在核心環境中處理封包時效率不彰。每個字元都會觸發一次狀態轉換與條件分支 (switch 或 if-else) ,導致頻繁跳躍與 cache miss,且難以批次處理。 可以比照作業需求,採用更輕量化的解析工具 [picohttpparser](https://github.com/h2o/picohttpparser) 。 該專案關鍵 api 為: ```c int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len); ``` 並注意,該專案沒有提供 `http_should_keep_alive ` 這種 api ,所以我們要自行解析,檢查 HTTP header 中的 `"Connection"` 欄位,判斷是 "keep-alive" 還是 "Close" 。 ```c for (int i = num_headers-1; i>=0; i--) { if (!strncasecmp(headers[i].name, "Connection", (size_t)headers[i].name_len)) { if (!strncasecmp(headers[i].value, "keep-alive", (size_t)headers[i].value_len)) should_keep_alive = 1; else should_keep_alive = 0; break; } } ``` > Commit [0bbe237](https://github.com/sysprog21/khttpd/commit/0bbe237efb205c1d6ebaadc4bbd77c1f2a18aa86) 另外,老師的專案是在 make 的時候動態下載 http-parser,如果我直接將 picohttpparser 放在專案中,是否會出現問題? ## TODO: 支援 HTTP 壓縮 Linux 核心提供壓縮 API,可以幫助我們減少傳輸的開銷。 並且 HTTP 也有相應的支援,例如 gzip 與 deflate。 有兩個關鍵 api : `crypto_alloc_comp` 與 `crypto_comp_compress` ,前者建立壓縮實體,後者進行資料壓縮。 在上述的檔案傳輸前,使用該函數進行壓縮,並在標頭加入 `Content-Encoding` 字樣。 同時,壓縮的檔案類型也必須進行考慮,純文字檔可以直接壓縮,但是圖片檔可能被壓縮破壞無法還原。 輸出檔案大小: ```c [ 7725.472406] khttpd: original size:2687 [ 7725.472407] khttpd: compress size:736 ``` > Commit [4ba90e2](https://github.com/sysprog21/khttpd/commit/4ba90e2b91510cd88fce9b4eef5352f1e4dc9523) ## TODO: 引入 timer 以主動關閉逾期的連線 (未完成) 這邊教材使用的方法是,使用 min heap 建立 timer 的 priority queue,為每個連線設置過期期限。 同時會將背景程式的 socket 設為 non-blocking,但是這邊我認為不太適合,因為這樣會導致背景程式 busy waiting,為何不用獨立的 thread 進行? 另外,在另一篇教材中提到,我們可以將 timer 以檔案的方式進行操作,也就是所謂的 timerfd,並透過 epoll 等機制監控時間是否過期,核心法使用該技巧嗎? - [ ] 仔細思考後,發現這個想法不合理,該類機制僅能通知事件發生,實際上的處理邏輯還是要另外考慮。 ## TODO: 加入透過 sysfs 提供的控制介面 加入 sysfs 可提供核心與使用者的通訊橋梁,有兩種方法,一者是使用 kweb 的方法、一者是使用 kxo 的方法。 這裡試著比照 kxo 的方法進行。 首先定義結構體: ```c struct ktcp_attr { char enable; rwlock_t lock; }; static struct ktcp_attr attr_obj; ``` 然後定義操作: ```c static ssize_t ktcp_state_show(struct device *dev, struct device_attribute *attr, char *buf) { ... } ``` 最後透過過巨集註冊操作,然後綁定到設備之上: ```c static DEVICE_ATTR_RW(ktcp_state); ... ret = device_create_file(ktcp_dev, &dev_attr_kxo_state); ``` 後續會將此方法改為 kweb 中的方法,因為這種方法依賴字節驅動,而 ktcp 沒有這種需求,或者是後續可以開發相應的功能,把核心空間的資料傳輸到使用者空間之中? 為了達成關閉伺服器的運作,在背景程式與 `server_worker` 中寫入: ```c ///deamon if (attr_obj.enable == '0') continue; ///worker while (!kthread_should_stop() && enable == '1') ``` 並不要忘記釋放資源。 最後我們就可以透過再命令提示字元輸入: ``` echo 0 | sudo tee /sys/class/ktcp/ktcp/ktcp_state ``` 來關閉伺服器了。 > commit:[90c9f95](https://github.com/sysprog21/khttpd/commit/90c9f95a500b38a0a4dc4f18ec6149ecbdc66ecd) ## TODO: keep-alive 的效能瓶頸 (未完成) 服務某個連線的時候, thread 會在 `kernel_recv` 的地方以 blocking 的方式進行等待,效能分析也指出,此處花費時間甚多。 ## TODO: 實作 content cache (未完成) 每個連線一個獨立的 `cache` ,把曾經請求過的目錄資訊紀錄在 cache 中。 cache 以鏈結串列形式儲存,目錄的欄位一項一項的存在鏈結串列中,最後將整串指標放入雜湊表

    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