contributed by < eric88525
>
$ sudo insmod khttpd.ko port=1999
這命令是如何讓 port=1999
傳遞到核心,作為核心模組初始化的參數呢?
過程中也會參照到 你所不知道的 C 語言:連結器和執行檔資訊
htstress.c
用到 epoll 系統呼叫,其作用為何?這樣的 HTTP 效能分析工具原理為何?kecho
已使用 CMWQ,請陳述其優勢和用法*
workqueue() functions are deprecated and scheduled for removal",請參閱 Linux 核心的 git log (不要用 Google 搜尋!),揣摩 Linux 核心開發者的考量user-echo-server
運作原理,特別是 epoll 系統呼叫的使用bench
原理,能否比較 kecho
和 user-echo-server
表現?佐以製圖drop-tcp-socket
核心模組運作原理。TIME-WAIT
sockets 又是什麼?參照 Linux 核心模組掛載機制,解釋
$ sudo insmod khttpd.ko port=1999
這命令是如何讓port=1999
傳遞到核心,作為核心模組初始化的參數呢?
內容參考 KYG-yaya573142
如果要在 insmod 時候初始化 module parameters,可以在 insmod 最後加入多個 變數名稱=值
來初始化。
使用的前提是在 module 需要先註冊好這些變數,而 module_param 是定義在 moduleparam.h
內的 macro
根據註解
展開後會執行:
param_check_##type
檢查變數型態module_param_cb
註冊變數__MODULE_PARM_TYPE
註冊核心模組資訊,如同註冊MODULE_LICENSE
、MODULE_DESCRIPTION
一樣__always_unused
為告訴編譯器這段不會有其他程式用到,抑制相關警告訊息kernel_param
的 structkernel_param 的結構如下
〈Linux 核心模組運作原理〉有說明
__MODULE_INFO
展開後
註冊 port 變數 module_param(port, ushort, S_IRUGO)
展開為
執行 objdump -s khttpd.ko
後,可以在 .modinfo 區塊看到 parmtype=port:ushort
當我們實際執行 sudo strace insmod khttpd.ko port=1999 apple=131
,可以看到使用到 finit_module
system call,而我們傳入的多個變數字串,中間用一個空格分開
實際的執行由 load_module 完成
args = next_arg(args, ¶m, &val)
將字串分析,存到 param 和 valret = parse_one(param, val, doing, params, num,..)
將初始化的數值,指派到 const struct kernel_param *params
內for loop 會去檢查此變數是否存在,如果存在則指派數值
參照 CS:APP 第 11 章,給定的 kHTTPd 和書中的 web 伺服器有哪些流程是一致?又有什麼是你認為 kHTTPd 可改進的部分?
課程介紹的 web server
以下為書中示範的 server 程式碼,可以看到都是在 user space 中執行
khttp 則是一個 kernel module,有些函式跟 user space 用法不同
user | kernel |
---|---|
socket | sock_create |
bind | kernel_bind |
listen | kernel_listen |
accept | kernel_accept |
負責 socket 建立 / bind / listen,類似 open_listenfd
負責接收連線( kernel_accept ),執行 http_server_worker
命名方面 daemon 在電腦術語中代表後台程式
In multitasking computer operating systems, a daemon is a computer program that runs as a background process, rather than being under the direct control of an interactive user
執行主要任務
參考 KYG-yaya573142 的報告,由於 request->request_url 為 char [128]
,因此要先計算串接後字串長度避免 core dump。
應描述針對 kHTTPd 的缺失,並擬定改進方案。上述的記憶體管理只是冰山一角,還有好多要改進!
htstress.c 用到 epoll 系統呼叫,其作用為何?這樣的 HTTP 效能分析工具原理為何?
在閱讀多方教材後,整理出 epoll 筆記
epoll 的好處在於:
用在 htstress.c 程式能大幅減少輪詢時間,減少 IO 時間
main
worker
epoll_wait
,當有 fd 在 ready list 時,檢查每一個fd 的狀態,執行對應動作
EPOLLOUT
: The associated file is available for write(2) operations.EPOLLIN
: The associated file is available for read(2) operations.給定的 kecho 已使用 CMWQ,請陳述其優勢和用法
先定義一些名詞
傳統的 multi thread 和 single thread 的缺點
CMWQ 提出了 worker pool 的概念,讓任務發佈者跟執行者不一定要相同 cpu
概念如下圖所示
執行 work 的單位叫做 worker,worker 的集合形成 worker_pool。
worker_pool 分兩種
我們可透過以下命令來查看 cpu 的 worker pool
第一行中的 kworker/0:0H
格式為 kworker/{cpu}:{id}{'H' 表示高優先級別}
work 存在於 workque 內的格式為 work_struct
,除了任務本身還記載了其他資訊
在第 71 行使用到了 alloc_workqueue
來分配一個 workque,此程式 (kecho) 預設 workque 為 unbound。前面提到的 unbound workque 可以透過 flag = WQ_UNBOUND
來設定
在 kecho_mod.c
,我們已經建立好了名為 kecho_wq
的 workque,接下來要把任務加入到 workque 內執行,步驟為
我們自定義的函式 create_work
,把 accept 後的 socket 轉成 struct kecho
最後回傳 &kecho->kecho_work
,在 160 行的 queue_work
加入 work queue 中來執行。
要執行的 work,socket 資訊需先由 container_of
巨集來取得
Concurrency Managed Workqueue (cmwq)
Linux 中的 workqueue 機制 [一]
Linux 中的 workqueue 機制 [二]
講解 Linux Workqueue 原理
核心文件 Concurrency Managed Workqueue (cmwq) 提到 “The original create_*workqueue() functions are deprecated and scheduled for removal”,請參閱 Linux 核心的 git log (不要用 Google 搜尋!),揣摩 Linux 核心開發者的考量
檢視了 workqueue.h 的 commit 紀錄,找到相關 commit
commit log
This patch makes changes to make new workqueue features available to
its users.
- Now that workqueue is more featureful, there should be a public
workqueue creation function which takes paramters to control them.
Rename _create_workqueue() to alloc_workqueue() and make 0
max_active mean WQ_DFL_ACTIVE. In the long run, all
create_workqueue*() will be converted over to alloc_workqueue().- To further unify access interface, rename keventd_wq to system_wq
and export it.- Add system_long_wq and system_nrt_wq. The former is to host long
running works separately (so that flush_scheduled_work() dosen't
take so long) and the latter guarantees any queued work item is
never executed in parallel by multiple CPUs. These will be used by
future patches to update workqueue users.
本次 commit 更動的地方只有重新命名而已
從說明得知隨著 workqueue 功能越來越多,應該有個 public creation function 給一般的 user 使用。
因此重新命名為 alloc_workqueue()
,而不是以 __ 開頭的 __create_workqueue
命名。
解釋 user-echo-server 運作原理,特別是 epoll 系統呼叫的使用
epoll 詳細原理和用法在這篇 epoll 筆記
以下為 user-echo-server 的程式碼
listener
epoll_fd
cllist
EPOLLIN
和 EPOLLET
事件, EPOLLIN
對應的是有資料可讀取(有新的連線),EPOLLET
指的是希望用 edge-trigger 觸發epoll_wait
來檢查有沒有 event 發生,事件的來源有兩種
handle_message_from_client
來處理
是否理解 bench 原理,能否比較 kecho 和 user-echo-server 表現?佐以製圖
bench 程式目的: 建立 MAX_THREAD 個連線來向 server 發送訊息,並把每個連線所用的時間存在 time_res
陣列內,每個連線都會測試 BENCH_COUNT 次後取平均
pthread_cond_broadcast(&worker_wait);
pthread_cond_wait(&worker_wait, &worker_lock)
pthread_mutex_lock
bench_worker 流程如下:
pthread_cond_broadcast
來釋放 worker_waitpthread_cond_broadcast
來解鎖res_lock
來確保寫入不受干擾在移除 print 之前的測試
移除 print 後少了許多干擾,測試結果平滑了許多
kecho 由於是在 kernel 層面,且有加入 cmwq 來加速,執行時間差異巨大
解釋 drop-tcp-socket 核心模組運作原理。TIME-WAIT sockets 又是什麼?
以下為關閉 TCP 連線的狀態圖
狀態 | 動作 |
---|---|
FIN-WAIT-1 | 主動端呼叫 close() 終止連線,向被動端發送 FIN 封包並等待 ACK |
CLOSE-WAIT | 被動端接收到FIN 後發送 ACK 回應,進入 CLOSE_WAIT 狀態。此時只能發送而不能接收,直到把剩下的訊息發送完才關閉連線 |
FIN-WAIT-2 | 等待對方的 FIN 封包,此時可接收資料,不能發送資料 |
LAST-ACK | 剩餘資料傳送完後向主動端發送 FIN |
CLOSED | 真正關閉連線 |
TIME_WAIT | 主動端接收到 LAST_ACK 發送來的 FIN 後,發送 ACK 來告知被動端可以 CLOSE。但ACK 不一定會成功送達,如果傳送失敗被動端會再次發送一次FIN ,TIME_WAIT 是為了確保對方再次發送 FIN 時能接收的到。TIME_WAIT 等待時間為 2個 MSL (Maximum Segment Lifetime),MSL 為 TCP 所允許最大 segment 存活的時間。 |
從上圖可知,TIME_WAIT 只有在主動要求關閉連線的一方才會出現。
如果 server 作為主動關閉方,當關閉多個連線後會有許多 TIME_WAIT 累積在 server 內,當 server 有最大連線數量時這些 TIME_WAIT 會佔據一部分的連線數量,進而影響效能。
在執行 user-echo-server (port=12345) 後建立 3 個連線並關閉,輸入以下命令就能看到正在 TIME_WAIT 的 server port 和 user port
module 使用方式為對 /proc/net/drop_tcp_sock 寫入,特別的是路徑位於 /proc/net
在 module 初始化 drop_tcp_init 時, 呼叫register_pernet_subsys(&droptcp_pernet_ops)
註冊了 network device 在 network namespace /proc/net
底下
節錄自 Linux Kernel Networking: Implementation and Theory By Rami Rosen
p.416
A network namespace is logically another copy of the network stack, with its own network devices, routing tables,neighbouring tables, netfilter tables, network sockets, network procfs entries, network sysfs entries, and other network resources. A practical feature of network namespaces is that network applications running in a given namespace (let’s say ns1) will first look for configuration files under /etc/netns/ns1, and only afterward under /etc. So, for example, if you created a namespace called ns1 and you have created /etc/netns/ns1/hosts, every userspace application that tries to access the hosts file will first access /etc/netns/ns1/hosts and only then (if the entry being looked for does not exist) will it read /etc/hosts. This feature is implemented using bind mounts and is available only for network namespaces created with the ip netns add command.
network namespace object 是由 net structure 定義
結構內的屬性描述了此 namespace 在哪以及名稱等相關資訊
如要建立的 network device 有特定的資料,則需傳入 struct pernet_operations 來初始化,此結構定義了
在初始化 network device 時呼叫 proc_create_data
,在 /proc/net 下建立了名為 drop_tcp_sock
的 procfs 實體,指定其 file_operations (open/write/release 行為) 為 droptcp_proc_fops
,並傳入 network device 的 private data
一個 namespace 下可有許多 device,id 可用來識別 namespace 下的特定 device
network device 的私有資料可透過 void *net_generic(const struct net *net, unsigned int id)
來取得,此函式回傳指向 net->gen[id] 的指標。
藉由以下命令來驗證 /proc/net/drop_tcp_sock
存在
在建立 /proc/net/drop_tcp_sock
實體時,有指定其 file operation 為 droptcp_proc_fops,因此在 open、write、release 時會執行指定的函式
droptcp_proc_open
droptcp_proc_write
droptcp_proc_release
droptcp_process
來釋放 socket負責釋放 tcp socket,由 droptcp_proc_release
呼叫
參考資料
TIME_WAIT and its design implications for protocols and scalable client server systems
Linux Kernel Networking Implementation and Theory