Try   HackMD

2023q1 Homework7 (ktcp)

contributed by < Korin777 >

開發環境

$ gcc --version 
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
$ 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):                  20
  On-line CPU(s) list:   0-19
Vendor ID:               GenuineIntel
  Model name:            12th Gen Intel(R) Core(TM) i7-12700H
    CPU family:          6
    Model:               154
    Thread(s) per core:  2
    Core(s) per socket:  14
    Socket(s):           1
    Stepping:            3
    CPU max MHz:         4700.0000
    CPU min MHz:         400.0000
    BogoMIPS:            5376.00
Caches (sum of all):     
  L1d:                   544 KiB (14 instances)
  L1i:                   704 KiB (14 instances)
  L2:                    11.5 MiB (8 instances)
  L3:                    24 MiB (1 instance)
NUMA:                    
  NUMA node(s):          1
  NUMA node0 CPU(s):     0-19

CMWQ

使用者可以創建 workqueue 共享系統上的 worker-pool , 並根據設定的 flag 來使用對應的 worker-pool , woker-pool 會在需要時喚醒或動態增加 worker 來處理 work item , idle worker 不會被立刻釋放以減少建立 worker 的成本

在 khttpd 引入 CMWQ

commit 0f89eac
在原本的實做中,每當有新的連線需要自己創建新的 kernel thread 來處理,參考 kecho 引入 CMWQ 讓 worker-pool 上的 worker 來處理這些連線

khttpd_wq = alloc_workqueue(KBUILD_MODNAME, WQ_UNBOUND, 0);

首先在 module 載入時建立 workqueue , WQ_UNBOUND 表示此為 unbound workqueue , 它會去使用 unbound worker-pool , 最後一個參數為每個 cpu 同時能處理的 work item 數量,這邊 0 代表預設的 256 個

  • normal and high priority woker-pool: 每個 cpu 都有, worker 會 bound 在那個 cpu
  • unbound worker-pool: worker 不會 bound 在特定的 cpu ,會犧牲 locality
struct khttpd {
    struct socket *sock;
    struct list_head list;
    struct work_struct khttpd_work;
};
struct http_service {
    bool is_stopped;
    struct list_head worker;
};
static struct work_struct *create_work(struct socket *sk)
{
    struct khttpd *work;

    if (!(work = kmalloc(sizeof(*work), GFP_KERNEL)))
        return NULL;

    work->sock = sk;

    // set up a work item pointing to that function
    INIT_WORK(&work->khttpd_work, http_server_worker);

    list_add(&work->list, &daemon.worker);

    return &work->khttpd_work;
}
static void free_work(void)
{
    struct khttpd *l, *tar;
    /* cppcheck-suppress uninitvar */

    list_for_each_entry_safe (tar, l, &daemon.worker, list) {
        kernel_sock_shutdown(tar->sock, SHUT_RDWR);
        flush_work(&tar->khttpd_work);
        sock_release(tar->sock);
        kfree(tar);
    }
}

每個連線會有各自的 struct khttpd 紀錄使用的 socket 及對應的 work item ,透過 INIT_WORK 來設定實際要處理的 function => http_server_worker
struct http_service 連接我們建立的 work item ,在卸載 module 時可以透過它去確保所有 work item 都執行完成並釋放資源

queue_work(khttpd_wq, work);

最後把創建好的 work item 插入 workqueue 讓對應的 worker-pool 中的 worker 執行

Original CMWQ
(requests/sec) 75470.673 136500.882

透過 ./htstress -n 100000 -c 1 -t 4 http://localhost:8081/ 測量,每秒能處理的 requests 顯著增加

提供目錄檔案存取功能

引入 timer 主動關閉逾期的連線

commit 7213049
原本想參考 sehttpd 來實做 timer ,後來發現 kernel api 也有 timer 可以使用,便試著用他來關閉逾期的連線

struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct hlist_node	entry;
	unsigned long		expires;
	void			(*function)(struct timer_list *);
	u32			flags;

#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
};

timer_list 結構體定義於 <linux/time.h>, timer 透過 list_head 的變形 hlist_head 連接, expires 為 timeout 的時間點,透過 function 來執行 timeout 後序該做的事

void timer_callback(struct timer_list *arg) {
    struct khttpd *worker = container_of(arg, struct khttpd, timer);
    kernel_sock_shutdown(worker->sock, SHUT_RDWR);
}

首先定義 timer 的 callback function ,在連線逾時後主動關閉連線,這裡將 timer_list 嵌入 khttpd 結構體來取的連線對應的 socket

timer_setup(&worker->timer, timer_callback, 0);

接著透過 timer_setup 來初始化 timer

mod_timer(&worker->timer, jiffies + msecs_to_jiffies(5000));

透過 mod_timer 在每次收到新的 request 時重製 timer 逾時的時間點

del_timer_sync(&worker->timer);

最後透過 del_timer_sync 在逾時或 client 關閉連線時將 timer 從 hlist_head 中移除

參考資料

Concurrency Managed Workqueue
蜗窝科技-CMWQ