Try   HackMD

L11: ktcp

主講人: jserv / 課程討論區: 2023 年系統軟體課程

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
返回「Linux 核心設計」課程進度表

解說錄影 (2023 年)
解說錄影 (2022 年)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
預期目標

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
kecho: 執行在 Linux 核心模式的 TCP 伺服器

取得 kecho 原始程式碼並編譯:

$ git clone https://github.com/sysprog21/kecho
$ cd kecho
$ make

預期會見到以下:

  • 執行檔: benchuser-echo-server
  • 核心模組 kecho.kodrop-tcp-socket.ko

接著可進行測試:

$ make check

參考輸出:

Preparing...
Send message via telnet
Progress : [########################################] 100%
Complete

該操作由以下動作組成:

$ sudo insmod kecho.ko
$ telnet localhost 12345

會出現以下輸出:

Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

可輸入任何字元 (記得按下 Enter),然後就會看到 telnet 回應你剛才輸入的字元。

按下 Ctrl] 組合鍵,之後按下 q,即可離開 telnet 畫面。接著可以試著在 $ telnet localhost 12345 時不要輸入任何字元,只是等待,會看到以下的 kernel 訊息 (可用 $ dmesg 觀察):

                 cope
                 le:	    4404 kB
                 RssShmem:	       0 kB
                 VmData:	     880 kB
                 VmStk:	     132 kB
                 VmExe:	     136 kB
                 VmLib:	    6336 kB
                 VmPTE:	     212 kB
                 VmSwap:	       0 kB
                 HugetlbPages:	       0 kB
                 CoreDumping:	0
                 Threads:	1
                 SigQ:	0/31543

kecho 掛載時可指定 port 號碼: (預設是 port=12345)

$ sudo insmod kecho.ko port=1999

修改或測試 kecho 的過程,可能因為 TIME-WAIT sockets 持續佔用,導致 rmmod 無法成功,這時可透過給定的 drop-tcp-socket 核心模組來剔除特定的 TCP 連線。請詳細閱讀 kecho 以得知必要的設定和準備工作。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
user-echo-server: 執行於使用者層級的 TCP 伺服器

user-echo-serverkecho 的使用者層級的實作,可對照功能和比較效能,運用 epoll 系統呼叫,會傾聽 port 12345。

不管是 user-echo-server 抑或 kecho,都可搭配給定的 bench 程式來分析效能。請詳細閱讀 kecho 以得知必要的設定和準備工作。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
seHTTPd

seHTTPd 是個高效的 web 伺服器,涵蓋並行處理、I/O 事件模型、epoll, Reactor pattern,和 Web 伺服器在事件驅動架構的考量,可參見 高效 Web 伺服器開發

預先準備的套件: (eBPF 作為後續分析使用)

$ sudo apt install wget
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
$ echo "deb https://repo.iovisor.org/apt/$(lsb_release -cs) $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/iovisor.list
$ sudo apt-get update
$ sudo apt-get install bcc-tools linux-headers-$(uname -r)
$ sudo apt install apache2-utils

取得程式碼和編譯:

$ git clone https://github.com/sysprog21/sehttpd
$ cd sehttpd
$ make

預期可見 sehttpd 這個執行檔。接著透過內建的 test suite 來測試:

$ make check

對 seHTTPd 進行壓力測試

首先,我們可用「古典」的方法,透過 Apache Benching tool 對 seHTTPd 進行壓力測試。在一個終端機視窗執行以下命令:

$ ./sehttpd

切換到網頁瀏覽器,開啟網址 http://127.0.0.1:8081/ 應在網頁瀏覽器畫面中見到以下輸出:

Welcome!
If you see this page, the seHTTPd web server is successfully working.

然後在另一個終端機視窗執行以下命令:

$ ab -n 10000 -c 500 -k http://127.0.0.1:8081/

參考程式輸出: (數值若跟你的測試結果有顯著出入,實屬正常)

Server Software:        seHTTPd
Server Hostname:        127.0.0.1
Server Port:            8081

Document Path:          /
Document Length:        241 bytes

Concurrency Level:      500
Time taken for tests:   0.927 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      4180000 bytes
HTML transferred:       2410000 bytes
Requests per second:    10784.81 [#/sec] (mean)
Time per request:       46.361 [ms] (mean)
Time per request:       0.093 [ms] (mean, across all concurrent requests)
Transfer rate:          4402.39 [Kbytes/sec] received

留意到上述幾項:

  • -k 參數: 表示 "Enable the HTTP KeepAlive feature",也就是在一個 HTTP session 中執行多筆請求
  • -c 參數: 表示 concurrency,即同時要下達請求的數量
  • -n 參數: 表示壓力測試過程中,期望下達的請求總量

關於輸出結果,請詳閱 ab - Apache HTTP server benchmarking tool 說明。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
需要注意的是,ab 無法有效反映出多執行緒的特性 (ab 自身就消耗單核 100% 的運算量),因此我們才會在 khttpd 提供 htstress.c,後者提供 -t 選項,能夠依據測試環境的有效 CPU 個數進行分配。

ab - Apache HTTP server benchmarking tool 的實作從今日的 GNU/Linux 或 FreeBSD 來說,算是過時且未能反映系統特性,除了 htstress,尚可使用 wrk,該專案的訴求是

wrk is a modern HTTP benchmarking tool capable of generating significant load when run on a single multi-core CPU. It combines a multithreaded design with scalable event notification systems such as epoll and kqueue.

另一個可測試 HTTP 伺服器負載的工具是 httperf

例外處理

倘若你將 seHTTPd 執行後,不立刻關閉,隨即較長時間的等待和重新用上述 ab 多次測試 (變更 -n-c 參數的指定數值) 後,可能會遇到以下錯誤狀況 (部分)

  1. Segmentation fault;
  2. 顯示訊息 [ERROR] (src/http.c:253: errno: Resource temporarily unavailable) rc != 0

可用 $ grep -r log_err 搜尋原始程式碼,以得知現有的例外處理機制 (注意: 裡頭存在若干缺失,切勿「舉燭」)。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
eBPF 追蹤 HTTP 封包

研讀〈Linux 核心設計: 透過 eBPF 觀察作業系統行為 〉以理解核心動態追蹤機制,後者允許我們使用非侵入式的方式,不用去修改我們的作業系統核心內部,不用去修改我們的應用程式,也不用去修改我們的業務程式碼或者任何系統配置,就可快速高效地精確獲取我們想要的資訊。

seHTTPd 原始程式碼的 ebpf 目錄提供簡易的 HTTP 封包分析工具,就是建構在 eBPF 的基礎之上,並透過 IO Visor 提供的工具來運作。

概念示意圖:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

使用方式: (預先在另一個終端機視窗執行 $ ./sehttpd)

$ cd ebpf
$ sudo python http-parse-sample.py

注意: 這個工具預設監控 eth0 這個網路介面 (network interface)。倘若你的預設網路介面不是 eth0,你需要依據 ip 工具的輸出,決定監控哪個網路介面。舉例來說,在某台 GNU/Linux 機器上執行以下命令:

$ ip link

你會見到若干輸出,如果你的環境裡頭已執行 Docker,輸出數量會很可觀,但不用擔心,排除 lo, tun, virbr, docker, br-, veth 開頭的輸出,然後就剩下 enp5s0 這樣的網路介面 (端視你的網路硬體而有不同),於是可將上述命令改為:

$ sudo python http-parse-sample.py -i enp5s0

然後打開網頁瀏覽器,多次存取和刷新 http://127.0.0.1:8081/ 網址,然後你應可在上述執行 Python 程式的終端機見到類似以下的輸出:

TCP src port '51670' TCP dst port '8081'     
¢GET / HTTP/1.1
IP hdr length '20'
IP src '192.168.50.97' IP dst '61.70.212.51'
TCP src port '8081' TCP dst port '51670'
ÌHTTP/1.1 304 Not Modified

關於上述程式運作的概況,可參考 Appendix C

透過 eBPF 追蹤 fibdrv 核心模組的運作機制,可參見 0xff07 的共筆

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
電腦網路概論

預習 CS:APP 第 11 章: Network Programming,搭配閱讀:

網頁伺服器流程,參見下圖:

  • 基本的 Client-Server 運作概念:
    1. client 發送一個連線請求
    2. server 接收到請求後,再根據 server 本身的資源處理相對應請求
    3. server 回應(回傳相關資訊)
    4. client 獲得從 server 回傳的結果

  • 再來看到細部的運作流程,左半部分為 client,右半部分為 server
  1. 在 client 與 server 先透過 getaddrinfo() 啟用程序,回傳值為 struct addrinfo 的結構,裡面就含有連線所需要的資料,如:IP 位址、 port (通訊埠)、 server 名稱等等
  2. 再來 client 與 server 呼叫(call) socket() 建立連接,回傳值為 file descriptor ,注意只有建立連結但不會操作系統,也不會往網路上傳送任何內容

socket man page
socket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint.

  1. server 使用 bind() 函式將 socket 與特定的 IP 位址和 port 連接起來(在 kernel space 中進行)
  2. server 開啟監聽狀態,呼叫 listen() ,準備接受來自 client 的請求
  3. server 就可以使用 accept() 將 client 連接
  4. 而 client 使用 connect() 發送 Connection request 等待 server accept
  5. 成功取得連線後,client 與 server 就可以進行通訊,使用 read 、 write 的方式(rio, reliable I/O,一部分來自 Unix I/O 系統,可用於讀寫 rom 並處理一些底層 I/O 的操作)
  6. 當 client 結束連線,會發送結束連線的請求(EOF, End of file),server 獲得此訊息後結束對 client 的連線。
  7. 結束一個 client 的連線後,server 可以再接續下一個 client 的連線或是關閉整個 server

此流程只能用於單一的 server/client 的連線,依照需求適用於小型連線系統,如:路由器內部系統設定

  • 在 CS:APP 第 12 章 (並行程式設計)中,提到接收多個 client 的連線方式,與 khttpd 的方式相近,架構圖:

  • server 開啟監聽過程,當有任何一個 client 請求連線時,server 就會 fork 行程去處理到對應的 client ,所以在處理的過程中,子行程只要對應 client 就好,不會干涉到其他子行程的運作(Address space 獨立),能完成多 client 的連線需求。

以 server 的角度就是持續的接受連線的請求

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
kHTTPd: 執行在 Linux 核心模式的 Web 伺服器

取得 kHTTPd 原始程式碼並編譯:

$ git clone https://github.com/sysprog21/khttpd
$ cd khttpd
$ make

預期會見到執行檔 htstress 和核心模組 khttpd.ko。接著可進行測試:

$ make check

參考輸出:

0 requests
10000 requests
20000 requests
30000 requests
40000 requests
50000 requests
60000 requests
70000 requests
80000 requests
90000 requests

requests:      100000
good requests: 100000 [100%]
bad requests:  0 [0%]
socker errors: 0 [0%]
seconds:       4.722
requests/sec:  21177.090

這台電腦的實驗結果顯示,我們的 kHTTPd 每秒可處理超過 20K 個 HTTP 請求。上述實驗透過修改過的 htstress 工具得到,我們可拿來對 http://www.google.com 網址進行測試:

$ ./htstress -n 1000 -c 1 -t 4 http://www.google.com/

參考輸出:

requests:      1000
good requests: 1000 [100%]
bad requests:  0 [0%]
socker errors: 0 [0%]
seconds:       17.539
requests/sec:  57.015

kHTTPd 掛載時可指定 port 號碼: (預設是 port=8081)

$ sudo insmod khttpd.ko port=1999

除了用網頁瀏覽器開啟,也可用 wget 工具:

$ wget localhost:1999

參考 wget 執行輸出:

Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:1999... failed: Connection refused.
Connecting to localhost (localhost)|127.0.0.1|:1999... connected.
HTTP request sent, awaiting response... 200 OK
Length: 12 [text/plain]
Saving to: 'index.html'

得到的 index.html 內容就是 Hello World! 字串。

下方命令可追蹤 kHTTPd 傾聽的 port 狀況:

$ sudo netstat -apn | grep 8081

注意,在多次透過網頁瀏覽器存取 kHTTPd 所建立的連線後,可能在 module unload 時,看到 dmesg 輸出以下:

CPU: 37 PID: 78277 Comm: khttpd Tainted: G      D WC OE K  4.15.0-91-generic #92-Ubuntu
Hardware name: System manufacturer System Product Name/ROG STRIX X399-E GAMING, BIOS 0808 10/12/2018
RIP: 0010:0xffffffffc121b845                                                                                                                                  
RSP: 0018:ffffabfc9cf47d68 EFLAGS: 00010282
RAX: 0000000000000000 RBX: ffff8f1bc898a000 RCX: 0000000000000218
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffffffff84c5107f
RBP: ffffabfc9cf47dd8 R08: ffff8f14c7e34e00 R09: 0000000180400024
R10: ffff8f152697cfc0 R11: 000499a097a8e100 R12: ffff8f152697cfc0
R13: ffffabfc9a6afe10 R14: ffff8f152697cfc0 R15: ffff8f1d4a5f8000
FS:  0000000000000000(0000) GS:ffff8f154f540000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffffffc121b845 CR3: 000000138b00a000 CR4: 00000000003406e0
Call Trace:
  ? __schedule+0x256/0x880
  ? kthread+0x121/0x140
  ? kthread_create_worker_on_cpu+0x70/0x70
  ? ret_from_fork+0x22/0x40
Code:  Bad RIP value.
RIP: 0xffffffffc121b845 RSP: ffffabfc9cf47d68
CR2: ffffffffc121b845
---[ end trace 76d6d2ce81c97c71 ]---

htstress.c 流程

htstress.c 為 client,做為發送給 server 的測試,未傳入參數時可以得到參數的設定模式,如下

$./htstress
Usage: htstress [options] [http://]hostname[:port]/path
Options:
   -n, --number       total number of requests (0 for inifinite, Ctrl-C to abort)
   -c, --concurrency  number of concurrent connections
   -t, --threads      number of threads (set this to the number of CPU cores)
   -u, --udaddr       path to unix domain socket
   -h, --host         host to use for http request
   -d, --debug        debug HTTP response
   --help             display this message

對應在 script/test.sh 中的敘述:

./htstress -n 100000 -c 1 -t 4 http://localhost:8081/
  • -n : 表示對 server 請求連線的數量
  • -c : 表示總體對 server 的連線數量
  • -t : 表示使用多少執行緒

main 中主要建立與 server 的連線

  1. 設定參數 : 透過 getopt_long() 獲得輸入的參數,再透過 swtich 設定對應的變數
  2. 設定連線所需的資訊 : getaddrinfo 取得多個 addrinfo 結構,裡面含有 server 的 IP 位址
  3. 計算時間 : start_time() 紀錄時間,使用 gettimeofday() 計算運行時間
  4. 測試 server 連線 : 使用 pthread_create 創立參數所設定的執行數數量,執行 worker() 函式對應到每一個創建 client,發送連線請求給 server
  5. 印出測試結果

再來看到 worker() 函式,與 server 進行連線過程,分別要建立與 server 連線的 client 與 epoll 程序監聽

  • 建立 epoll_event 結構陣列儲存監聽資料,變數名稱為 evts[MAX_EVENT] (MAX_EVENT 為設定監聽事件數量的最大值)
struct epoll_event
{
  uint32_t events;	/* Epoll events */
  epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;
  • epoll_create (變數為 efd)建立總體對 server 的 concurrency(1) 連線

不過自從 Linux2.6.8 後 epoll_create 中 size 的引數是被忽略的,建立好後占用一個 fd,使用後必須呼叫 close() 關閉,否則會導致資源的浪費

  • socket 連線方式,定義於函式 init_conn(),並設定 epoll 程序
    • socket 連線定義於 struct econn ecs[concurrency], *ec 中,進行初始化將 efd(epoll fd) 與 socket(ecs) 傳入 init_conn()
    • 先透過 socket() 建立與 server 的連線,並返回 fd,傳入 ec->fd
    • fcntl() : file control,對 fd 更改特性,fctrl(ec->fd, F_SETFL, O_NONBLOCK) 將 socket 的 fd 更改為非阻塞式,相比於阻塞式的方式,不會因為讀取不到資料就會停著
    • connect() : 為系統呼叫,根據 socket 的 fd (ec->fd) 與 server 的 IP 地址連線,因為是 nonblocking 的型式,所以不會等待連線成功的時候才會返回,因此在未連線時會回傳一巨集 EAGAIN 表示未連線,所以將 connect() 在迴圈中執行到連線成功
    • epoll_ctl() : 將連線成功的 socket (ec->fd)加入在 epoll 監聽事件(efd)中,所使用到 EPOLL_CTL_ADD 巨集加入監聽事件,並將 efd 事件設定為可寫的狀態,使用 EPOLLOUT
static void init_conn(int efd, struct econn *ec)
{
    int ret;

    // 建立連線
    ec->fd = socket(sss.ss_family, SOCK_STREAM, 0);
    ...

    // 設定 fd 控制權為 nonblock 形式
    fcntl(ec->fd, F_SETFL, O_NONBLOCK);

    // sys call 連線
    do {
        ret = connect(ec->fd, (struct sockaddr *) &sss, sssln);
    } while (ret && errno == EAGAIN);
    ...
    
    // 設定 epoll fd 的事件狀態,並指向 socket
    struct epoll_event evt = {
        .events = EPOLLOUT, .data.ptr = ec,
    };
    
    // 加入已完成連線的 socket 加入 epoll 監聽程序中
    if (epoll_ctl(efd, EPOLL_CTL_ADD, ec->fd, &evt)) {
        ...
    }
}

連線的初始化完成後,繼續看 worker() 處理 I/O 事件的無限 for-loop

  • epoll 監聽 :

    • 進入無限的 for-loop 中處理所有的連線請求
      • 使用 epoll_wait 輪詢的方式將可用的 fd 儲存至 evts 陣列中
    • htstress.cevts.event 表示事件狀態的巨集:
      • EPOLLIN : 表示對應 fd 可讀
      • EPOLLOUT : 表示對應 fd 可寫
      • EPOLLERR : 表示對應 fd 發生錯誤
      • EPOLLHUP : 表示對應 fd 被結束連線
  • epoll 的錯誤處理,以 if (evts[n].events & EPOLLERR){ ... } 判斷事件是否為錯誤狀態

    • getsockopt() : 可以獲得 epoll 監聽 socket 的狀態,透過巨集 SO_ERROR 紀錄錯誤訊息(0 為沒有錯誤的產生),看到宣告方式 if (getsockopt(efd, SOL_SOCKET, SO_ERROR, (void *) &error, &errlen) == 0),讀取到 efd 的資料將檢查的結果寫入至 error 變數中
    • atomic_fetch_add() : 使用到 atomic 的操作方式,紀錄錯誤產生的數量,確保在記錄錯誤數量的時候不會被多執行緒干擾(每個 socket 都有機會互相干擾程序,所以要確保計數的正確性)
    • close() : 將有錯誤連線的 socket 連線關閉,避免系統的佔用

ISO/IEC 9899:2011 (P.283) : atomic_fetch function
These operations are atomic read-modify-write operations.

if (evts[n].events & EPOLLERR) {
    /* normally this should not happen */
    ...
    if (getsockopt(efd, SOL_SOCKET, SO_ERROR, (void *) &error, &errlen) == 0) {...}
    ...
    // 計數錯誤
    atomic_fetch_add(&socket_errors, 1);
    // 關閉有錯誤的 socket fd
    close(ec->fd);
    ...
    // 重新初始化連線
    init_conn(efd, ec);
}
  • client 傳送數據至 server :

    • 事件狀態為 EPOLLOUT(表示可寫) : 先確認是事件的狀態為可寫,並確保連線的狀態是可用的,使用 send() 函式開啟要傳送資料的 fd,再來傳送資料(包含傳送的資料與長度,以檔案的 offset 表示),傳送成功後會返回傳送資料的長度
    • 若傳送有問題時,紀錄錯誤訊息(使用 write(),注意到 write 的第一個引數為 fd,這裡使用 2參考文章解釋,0 表示 STDIN 標準輸入(鍵盤),1 表示 STDOUT 標準輸出(終端機視窗),2 表示 STDERR 標準錯誤輸出(將錯誤訊息輸出至終端機))
    • 確認資料是否有完整傳送至 server,將事件改為 EPOLLIN 可讀的狀態,等待 server 傳送資料
  • server 傳送數據至 client :

    • 事件狀態為 EPOLLIN,使用 recv() 得到從 server 傳送來的資料,從 socket 的 fd 獲得,將獲得的資料存入 buffer(inbuf) 中。
  • 關閉 client 與 server 連線:

    • 當處理完所有的通訊資料後(也就是 ret = 0) 時,使用 close() 關閉 client 的 fd(要關閉否則會占用資源),這裡要注意的是在建立 epoll 監聽與 socket 連線,同時都要有對應的 close() 關閉其 fd,不過在 htstress.c 中沒有看到對 epoll 的 fd 進行 close() 的敘述。

epoll_create(2) 提到:
When no longer required, the file descriptor returned by epoll_create() should be closed by using close(2). When all file descriptors referring to an epoll instance have been closed, the kernel destroys the instance and releases the associated resources for reuse.
(對應的 epoll_create() 要透過 close() 將 epoll fd 關閉,不過若 epoll 所監聽所有的 fd 已被關閉,核心就會直接釋放 epoll 的相關資源)

// client 傳送訊息,確認事件狀態為可寫
if (evts[n].events & EPOLLOUT) {
    ret = send(ec->fd, outbuf + ec->offs, outbufsize - ec->offs, 0);
    ...

// 將錯誤訊息存入
if (debug & HTTP_REQUEST_DEBUG)
    write(2, outbuf + ec->offs, outbufsize - ec->offs);
    ...
        
/* write done? schedule read */
if (ec->offs == outbufsize) {
    evts[n].events = EPOLLIN;
    evts[n].data.ptr = ec;
    ...
        
// 事件可讀狀態
if (evts[n].events & EPOLLIN) {
    ...
    // 獲得從 server 傳來的資料
    ret = recv(ec->fd, inbuf, sizeof(inbuf), 0);           
    ...

// 所有請求處理結束
if (!ret) {
    // 關閉 socket 連線
    close(ec->fd);
    ...
}

核心 API

許多 Linux 裝置驅動程式或子系統會透過 kernel threads(簡稱kthread),在背景執行提供特定服務,然後等待特定 events 的發生。等待的過程中,kthread 會進入 sleep 狀態,當 events 發生時,kthread 會被喚醒執行一些耗時的工作,如此一來,可防止 main thread 被 blocked。
使用示範: kernel-threads.c

kthread_run 巨集在 Linux v5.5 的定義 include/linux/kthread.h :

#define kthread_run(threadfn, data, namefmt, ...)		      \
({	 							      \
    struct task_struct *__k					      \
        = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
    if (!IS_ERR(__k))						      \
        wake_up_process(__k);					      \
    __k;							      \
})

可見到 kthread_create 成功時直接 wake_up_process,回傳值為 task_struct

下方命令可查閱系統上的 kthread:

$ ps -ef

預期可見:

root         2     0  0 Feb17 ?        00:00:01 [kthreadd]

PPID 為 2 的都屬於 kthread,而 $ ps auxf 可見樹狀結構。
參考輸出結果:

0:01 /usr/sbin/sshd -D
0:00  \_ sshd: jserv [priv]
0:05  |   \_ sshd: jserv@pts/11
0:03  |       \_ -bash
0:00  |           \_ ps auxf
0:00  |           \_ less

尋找剛才載入的 khttpd 核心模組:

$ ps -ef | grep khttpd

預期可見以下:

root     18147     2  0 14:13 ?        00:00:00 [khttpd]