# 2025q1 Homework3 (kxo) contributed by < `Jordymalone` > :::danger 注意細節! > 收到 ::: 把 AI 當作 payload,開發核心模組讓大家去擴充 學習要點: 1. 怎麼去開發個核心模組 2. 怎麼去改善核心模組 3. 怎麼去測量性能 4. 怎麼去改進整體的運作 5. 怎麼去降低非必要的 crash 參閱以下教材: - [ ] 《The Linux Kernel Module Programming Guide》([LKMPG](https://sysprog21.github.io/lkmpg/)) - 至少讀到 15.3 節 - [ ] [Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module) - [ ] [Introduction to Linux kernel driver programming](https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Introduction-to-Linux-Kernel-Driver-Programming-Michael-Opdenacker-Bootlin-.pdf) ## ksort > 參閱 [Linux 核心模組](https://hackmd.io/@sysprog/linux2025-kxo/%2F%40sysprog%2Flinux2025-kxo-b) 先從老師的 github 上 pull ksort ```shell $ git clone https://github.com/sysprog21/ksort ``` ```shell $ make cc -Wall -Werror -o user user.c cc -Wall -Werror -o test_xoro test_xoro.c make -C /lib/modules/6.11.0-19-generic/build M=/home/jordan/jserv/ksort modules make[1]: Entering directory '/usr/src/linux-headers-6.11.0-19-generic' CC [M] /home/jordan/jserv/ksort/sort_mod.o CC [M] /home/jordan/jserv/ksort/sort_impl.o LD [M] /home/jordan/jserv/ksort/sort.o CC [M] /home/jordan/jserv/ksort/xoro_mod.o LD [M] /home/jordan/jserv/ksort/xoro.o MODPOST /home/jordan/jserv/ksort/Module.symvers CC [M] /home/jordan/jserv/ksort/sort.mod.o LD [M] /home/jordan/jserv/ksort/sort.ko CC [M] /home/jordan/jserv/ksort/xoro.mod.o LD [M] /home/jordan/jserv/ksort/xoro.ko make[1]: Leaving directory '/usr/src/linux-headers-6.11.0-19-generic' ``` 失敗: ```shell $ make check make insmod make[1]: Entering directory '/home/jordan/jserv/ksort' make -C /lib/modules/6.11.0-19-generic/build M=/home/jordan/jserv/ksort modules make[2]: Entering directory '/usr/src/linux-headers-6.11.0-19-generic' make[2]: Leaving directory '/usr/src/linux-headers-6.11.0-19-generic' sudo insmod sort.ko insmod: ERROR: could not insert module sort.ko: Invalid module format make[1]: *** [Makefile:31: insmod] Error 1 make[1]: Leaving directory '/home/jordan/jserv/ksort' make: *** [Makefile:39: check] Error 2 ``` * 編譯並測試 預期會看到 2 次綠色的 Passed [-] 字樣 > 但我遇到 insmod 問題,解決辦法要從 BIOS 那邊做修改 ```shell $ make check Git hooks are installed successfully. cc -Wall -Werror -o user user.c cc -Wall -Werror -o test_xoro test_xoro.c make -C /lib/modules/6.11.0-19-generic/build M=/home/neat/MJ/ksort modules make[1]: Entering directory '/usr/src/linux-headers-6.11.0-19-generic' warning: the compiler differs from the one used to build the kernel The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 You are using: gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 CC [M] /home/neat/MJ/ksort/sort_mod.o CC [M] /home/neat/MJ/ksort/sort_impl.o LD [M] /home/neat/MJ/ksort/sort.o CC [M] /home/neat/MJ/ksort/xoro_mod.o LD [M] /home/neat/MJ/ksort/xoro.o MODPOST /home/neat/MJ/ksort/Module.symvers CC [M] /home/neat/MJ/ksort/sort.mod.o LD [M] /home/neat/MJ/ksort/sort.ko BTF [M] /home/neat/MJ/ksort/sort.ko Skipping BTF generation for /home/neat/MJ/ksort/sort.ko due to unavailability of vmlinux CC [M] /home/neat/MJ/ksort/xoro.mod.o LD [M] /home/neat/MJ/ksort/xoro.ko BTF [M] /home/neat/MJ/ksort/xoro.ko Skipping BTF generation for /home/neat/MJ/ksort/xoro.ko due to unavailability of vmlinux make[1]: Leaving directory '/usr/src/linux-headers-6.11.0-19-generic' make insmod make[1]: Entering directory '/home/neat/MJ/ksort' make -C /lib/modules/6.11.0-19-generic/build M=/home/neat/MJ/ksort modules make[2]: Entering directory '/usr/src/linux-headers-6.11.0-19-generic' warning: the compiler differs from the one used to build the kernel The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 You are using: gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 make[2]: Leaving directory '/usr/src/linux-headers-6.11.0-19-generic' sudo insmod sort.ko sudo insmod xoro.ko make[1]: Leaving directory '/home/neat/MJ/ksort' sudo ./user Sorting succeeded! sudo ./test_xoro n_bytes=0 n_bytes_read=0 value=0000000000000000 n_bytes=1 n_bytes_read=1 value=000000000000005f n_bytes=2 n_bytes_read=2 value=000000000000ed8e n_bytes=3 n_bytes_read=3 value=0000000000747c6a n_bytes=4 n_bytes_read=4 value=00000000c61be1dd n_bytes=5 n_bytes_read=5 value=000000f069befa4a n_bytes=6 n_bytes_read=6 value=0000ebab275f98f0 n_bytes=7 n_bytes_read=7 value=0042569408ac9f4b n_bytes=8 n_bytes_read=8 value=16b355d705d7d6d7 n_bytes=9 n_bytes_read=8 value=0b7f14b4fc108855 make rmmod make[1]: Entering directory '/home/neat/MJ/ksort' make[1]: Leaving directory '/home/neat/MJ/ksort' ``` Makefile 配置 > 待理解 Makefile ```Makefile obj-m += sort.o sort-objs := \ sort_mod.o \ sort_impl.o obj-m += xoro.o xoro-objs := \ xoro_mod.o CFLAGS = -Wall -Werror GIT_HOOKS := .git/hooks/applied KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(GIT_HOOKS) user test_xoro $(MAKE) -C $(KDIR) M=$(PWD) modules $(GIT_HOOKS): @scripts/install-git-hooks @echo user: user.c $(CC) $(CFLAGS) -o $@ $^ test_xoro: test_xoro.c $(CC) $(CFLAGS) -o $@ $^ insmod: all rmmod sudo insmod sort.ko sudo insmod xoro.ko rmmod: @sudo rmmod sort 2>/dev/null || echo @sudo rmmod xoro 2>/dev/null || echo check: all $(MAKE) insmod sudo ./user sudo ./test_xoro $(MAKE) rmmod clean: $(MAKE) -C $(KDIR) M=$(PWD) clean $(RM) user test_xoro ``` * 觀察產生的 sort.ko 核心模組 ```shell $ modinfo sort.ko filename: /home/neat/MJ/ksort/sort.ko version: 0.1 description: Concurrent sorting driver author: National Cheng Kung University, Taiwan license: Dual MIT/GPL srcversion: 1329BC1219644B610C36E35 depends: retpoline: Y name: sort vermagic: 6.11.0-19-generic SMP preempt mod_unload modversions ``` 透過 insmod 將模組載入核心後才會有下面的裝置檔案 `/dev/sort` ```shell $ sudo insmod sort.ko $ ls -l /dev/sort crw------- 1 root root 509, 0 Mar 24 18:46 /dev/sort ``` 開頭的 c 代表 character device,代表 /dev/sort 是個字元裝置檔,權限設定為 `crw` > 字元裝置檔? > 可以把他想成一個統一介面,供使用者透過檔案操作來去存取硬體或虛擬設備 `/dev` 目錄主要用來存放裝置檔案,這些檔案代表了各種系統中的硬體或是虛擬設備 ### 對照 `sort_mod.c` 尋找彼此關係 ```shell $ cat /sys/class/sort/sort/dev 509:0 ``` 是裝置檔的主要 (509) 與次要號碼(0),用來供內核和驅動程式確認設備身份。 * 主要號碼 - 傳統上,主要號碼用來識別管理該裝置的驅動程式。也就是說,當系統收到對某個裝置檔案的操作時,會根據主要號碼判斷該請求應該交給哪個驅動程式來處理。 * 次要號碼 - 用來讓驅動程式進一步區分同一主要號碼下的不同裝置。當一個驅動程式管理多個設備時,次要號碼就會幫助確定具體是那一個設備在被操作。 > 參閱 [LDD3](https://lwn.net/Kernel/LDD3/) ```c int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); ``` > 參閱 [blog](https://meetonfriday.com/posts/edcd30e6/) > 待補解釋 * printk * alloc_chrdev_region * cdev * device_create * `dev_t` - Within the kernel, the dev_t type (defined in <linux/types.h>) is used to hold device numbers—both the major and minor parts ```c unsigned long copy_from_user(void *dest, const void *src, unsigned long count) { memcpy((void *)dest, (void *)src, count); return 0; } ``` ```shell $ journalctl -k | grep sort Mar 24 18:46:21 NEAT-LAB kernel: sort: loaded ``` `sort_main.c` 中的 common swaptype 是透過什麼函式去判斷要用哪個交換類型的? `#define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS)` 是什麼? ```c void sort_main(void *sort_buffer, size_t size, size_t es, cmp_t cmp) { /* The allocation must be dynamic so that the pointer can be reliably freed * within the work function. */ struct qsort *q = kmalloc(sizeof(struct qsort), GFP_KERNEL); struct common common = { .swaptype = ((char *) sort_buffer - (char *) 0) % sizeof(long) || es % sizeof(long) ? 2 : es == sizeof(long) ? 0 : 1, .es = es, .cmp = cmp, }; init_qsort(q, sort_buffer, size, &common); queue_work(workqueue, &q->w); /* Ensure completion of all work before proceeding, as reliance on objects * allocated on the stack necessitates this. If not, there is a risk of * the work item referencing a pointer that has ceased to exist. * 等待工作佇列中的所有任務都完成執行 */ drain_workqueue(workqueue); } ``` ### ksort 核心模組內部 #### 使用者層級 (user-level) 的程式如何與 ksort 互動? > ksort 設計為一個 character device,可理解是個能夠循序存取檔案,透過定義相關的函式,並利用存取檔案的系統呼叫以存取 (即 open, read, write, mmap 等等)。因此,使用者層級 (user-level 或 userspace) 的程式可透過 read 系統呼叫來得到輸出。 在 `user.c` 中,使用者透過呼叫 `open(KSORT_DEV, O_RDWR)`,`KSORT_DEV` 就是 `/dev/sort` 這一步會叫系統找出 /dev/sort 對應的驅動程式。 以 ksort 為例,是先建立一個隨機數字排序的 buffer ```c int fd = open(KSORT_DEV, O_RDWR); ... int *inbuf = malloc(size); ... ssize_t r_sz = read(fd, inbuf, size); ``` 透過 read 系統呼叫排序好的結果,這邊的 read 對應到 `sort_mod.c` 中實作的`sort_read()` ```c len = copy_from_user(sort_buffer, buf, size); ... sort_main(sort_buffer, size / es, es, num_compare); len = copy_to_user(buf, sort_buffer, size); ... ``` * `copy_from_user()` 中的 buf 是使用者層級的 buffer,而 sort_buffer 則是在核心層級宣告的 buffer,所以這邊是先將使用者層級的內容複製到核心層級 * `sort_main()` 對核心層級的 buffer 做排序,也就是排序法的主要函式 * `copy_to_user()` 將排序好的結果複製到使用者層級的 buffer 中 以此來達到互動的效果。 ### LVFS 介面 > 待補解釋 LVFS 定義是? 這些 driver 是? * Character Device Driver; * Block Device Driver; * Network Device Driver; 至於前面提到的系統呼叫 `read` 是怎麼對應到 `sort_read` 這個操作呢? 可以看到 `sort_mod.c` 中,有定義以下操作 ```c static const struct file_operations fops = { .read = sort_read, .write = sort_write, .open = sort_open, .release = sort_release, .owner = THIS_MODULE, }; ``` > 待研讀 [Linux 核心設計: 檔案系統概念及實作手法](https://hackmd.io/@sysprog/linux-file-system) 以 ksort 為例,在使用裝置前我們定義了以上的 file operation,並將其註冊到 kernel 中。 所以,使用者層級的程式若呼叫到 read 系統呼叫,便會透過 VFS 將 read 對應到 `sort_read`> > 作業要求有提到 `client.c` 在哪啊?是 `user.c` 嗎? > 使用者模式 (user-mode) 的位址空間配置一個 buffer 空間時,核心裝置驅動不能直接寫入該地址,依據研究指出,從核心模式複製資料到使用者層級的時間成本是每個位元組達 22ns,因此效能分析時要考慮 copy_to_user 函式的時間開銷,特別留意到資料大小成長後造成的量測誤差。 執行 `user.c` 程式碼來觀察結果 ```shell $ sudo ./user Sorting succeeded! ``` ### 核心模式的時間測量 LWN 是什麼? 跟計時有關的功能有兩種: 1. timer: 安排「在某個時間點做某件事情」。 2. timeouts: 用來作為逾時的通知,提醒有東西遲到了,可以把網咖的 "時間快到搂" 這句經典台詞換成 "時間已經超過搂"。另外有個簡單的例子是 qtest 裡面的 SIGALRM 的使用。這對於時間的精準度要求不高。 ```c static void sigalrm_handler(int sig) { trigger_exception( "Time limit exceeded. Either you are in an infinite loop, or your " "code is too inefficient"); } static void q_init() { fail_count = 0; INIT_LIST_HEAD(&chain.head); signal(SIGSEGV, sigsegv_handler); signal(SIGALRM, sigalrm_handler); } ``` 早期是用 jiffies 來計算開機以來的計時器中斷發生的次數。 這個計時器受限於計時器中斷觸發和實質能處理的的頻率,而這個頻率有其極限。關於中斷,可參見〈[Linux 核心設計: 中斷處理和現代架構考量](https://hackmd.io/@sysprog/linux-interrupt)〉。 hrtimer 是在 Linux 2.6.16 開始有的新的計時機制,裡面使用 `ktime_t` 這個新的資料結構來進行計時。這個結構體的定義會隨架構有所不同。 * DEFINE_KTIME(name); * ktime_t timespec_to_ktime(struct timespec tspec); * ktime_t timeval_to_ktime(struct timeval tval); * struct timespec ktime_to_timespec(ktime_t kt); * struct timeval ktime_to_timeval(ktime_t kt); * clock_t ktime_to_clock_t(ktime_t kt); * u64 ktime_to_ns(ktime_t kt); > 參閱 [ktime 相關 API](https://www.kernel.org/doc/html/latest/core-api/timekeeping.html) 將相關操作修改成作業要求中的程式碼,用來輸出上一次 fib (?) 的執行時間 > 老師的程式碼示範要做點修改,無法直接套用 * `ktime_t ktime_get` - Useful for reliable timestamps and measuring short time intervals accurately. Starts at system boot time but stops during suspend. > 在相關 API 中沒看到 ktime_sub 的身影 * `loff_t *offset` - 這是一個指向「檔案偏移量」 (file offset) 的指標。 ```c #if defined(__GNUC__) && !defined(__STRICT_ANSI__) typedef __kernel_loff_t loff_t; #endif typedef long long __kernel_loff_t; ``` ```c static ssize_t sort_read(struct file *file, char *buf, size_t size, loff_t *offset) { return (ssize_t) sort_time_proxy(*offset); } // 用 typedef 去定義 loff_t static long long sort_time_proxy(long long k); ``` ### Linux 效能分析 #### 查看行程的 CPU affinity > taskset - set or retrieve a process's CPU affinity > CPU affinity represented as a bit mask ```shell $ taskset -p PID pid 1's current affinity mask: ffffffffffffffff // 對 bitmask 表示法不易掌握,可加上 -c 參數,讓 taskset 直接輸出 CPU 核的列表 $ taskset -cp 1 ``` 輸出的 affinity mask 是一個十六進位的 bitmask,將其轉換為二進位格式之後,若位元值為 1 則代表該行程可在這個位元對應的 CPU 核中執行,若位元值為 0 則代表該行程不允許在這個位元對應的 CPU 核中執行。 在上面這個例子中十六進位的 ff 轉成二進位的格式會是 11111111,這 8 個 1 分別代表該行程可以在第 0 到第 7 個 CPU 核中執行,最低(LSB; 最右邊)的位元代表第 0 個 CPU 核,次低的代表第 1 個,以次類推。 ```shell $ lscpu ... CPU(s): 20 On-line CPU(s) list: 0-19 ... NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0-19 ... ``` #### 將行程固定在特定的 CPU 中執行 ```shell $ taskset -p COREMASK PID ``` 其中 COREMASK 就是指定的十六進位 core mask,PID 則為行程的 ID。除此之外,亦可使用 -c 參數以 CPU 的核心列表來指定 ```shell $ taskset -cp CORELIST PID ``` 其中 CORELIST 為 CPU 核列表,以逗點分隔各個核的編號或是使用連字號指定連續的區間,例如: 0,2,5,7-10。 ```shell $ taskset -cp 0-18 2766744 pid 2766744's current affinity list: 0-19 pid 2766744's new affinity list: 0-18 ``` #### 限定 CPU 給特定的程式使用 taskset 可指定行程所使用的 CPU 核,但不代表其他的行程不會使用這些被指定的 CPU 核,如果你不想讓其他的行程干擾你要執行的程式,讓指定的核心只能被自己設定的行程使用,可以使用 isolcpus 這個 Linux 核心起始參數,這個參數可以讓特定的 CPU 核在開機時就被保留下來。 設定的方式有兩種,一個是在開機時使用 boot loader 所提供的自訂開機參數功能,手動加入 isolcpus=cpu_id 參數,或是直接加在 GRUB 的設定檔中,這樣 Linux 的排程器在預設的狀況下就不會將任何一般行程放在這個被保留的 CPU 核中執行,只有那些被 taskset 特別指定的行程可使用這些 CPU 核。 ```c isolcpus=0,1 ``` 透過這個設定可以把第 0 個與第 1 個 CPU 核保留下來,並透過 taskset 指派要執行的程式。 #### 排除干擾效能分析的因素 ASLR 是? (Address space layout randomization) :::danger 參照課堂討論紀錄 > 好! ::: ## Simrupt 因 kxo 衍生自 [simrupt](https://github.com/sysprog21/simrupt) 故先從此專案開始理解。 > 參閱 Linux Kernel Development ## kxo > 遊戲主本運作於 Linux 核心內運作,讓二個不同的井字遊戲 (也稱為 XO Game,本核心模組因此得名) 人工智慧演算法,藉由 workqueue,執行在「不同的 CPU」並模擬二者的對弈,並允許使用者層級的程式藉由開啟 /dev/kxo 來設定二個人工智慧程式的對弈並存取彼此的棋步。 發現要在 `main.c` 前面新增 `<linux/vmalloc.h>`,才可以正常 make > 已有同學修正 PR [#7](https://github.com/sysprog21/kxo/pull/7) `xo-user.c`: `FILE *fp = fopen(XO_STATUS_FILE, "r");` 中的 r 是甚麼意思? `strcspn` 是? `read_buf[strcspn(read_buf, "\n")] = 0;` `tcsetattr.c`: ```c /* Set the state of FD to *TERMIOS_P. */ int tcsetattr (int fd, int optional_actions, const struct termios *termios_p) { struct termios myt; if (optional_actions & TCSASOFT) { myt = *termios_p; myt.c_cflag |= CIGNORE; termios_p = &myt; optional_actions &= ~TCSASOFT; } switch (optional_actions) { case TCSANOW: return __ioctl (fd, TIOCSETA, termios_p); case TCSADRAIN: return __ioctl (fd, TIOCSETAW, termios_p); default: return __ioctl (fd, TIOCSETAF, termios_p); } } libc_hidden_def (tcsetattr) ``` `main.c`: * snprintf * sscanf :::danger 說好的進度呢? ::: ### 縮減使用者和核心層級的通訊成本 可以知道當此 kxo module 載入進系統後,其 file_operations 對應到的操作如下: ```c static const struct file_operations kxo_fops = { #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) .owner = THIS_MODULE, #endif .read = kxo_read, .llseek = no_llseek, .open = kxo_open, .release = kxo_release, }; ``` `kxo_read`: ```c static ssize_t kxo_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { ... do { ret = kfifo_to_user(&rx_fifo, buf, count, &read); if (unlikely(ret < 0)) break; if (read) break; if (file->f_flags & O_NONBLOCK) { ret = -EAGAIN; break; } ret = wait_event_interruptible(rx_wait, kfifo_len(&rx_fifo)); } while (ret == 0); ... } ``` :::info `unlikely` 是什麼? ```c if (unlikely(!access_ok(buf, count))) return -EFAULT; ``` ::: 可以看到這個函式中關鍵的部分,`ret = kfifo_to_user(&rx_fifo, buf, count, &read);`,我們透過這個函式來將核心中 rx_fifo 的資料複製給 user space。 那是什麼函式準備 rx_fifo 這個 buffer ? ```c static int __init kxo_init(void) { dev_t dev_id; int ret; if (kfifo_alloc(&rx_fifo, PAGE_SIZE, GFP_KERNEL) < 0) return -ENOMEM; ... } ``` 可以知道再載入並初始化 kxo 這個模組時,會使用 `kfifo_alloc` 動態地去 allocate 一個新的 kfifo buffer 用來存放資料。而這個資料就是我們的 chess board。 ```c /* Insert the whole chess board into the kfifo buffer */ static void produce_board(void) { unsigned int len = kfifo_in(&rx_fifo, draw_buffer, sizeof(draw_buffer)); if (unlikely(len < sizeof(draw_buffer))) pr_warn_ratelimited("%s: %zu bytes dropped\n", __func__, sizeof(draw_buffer) - len); pr_debug("kxo: %s: in %u/%u bytes\n", __func__, len, kfifo_len(&rx_fifo)); } ``` 而 `produce_board` 這個函式會透過 `kfifo_in` 將 chess board 寫入 kfifo buffer,可以注意到 `kfifo_in` 有個參數是 `draw_buffer`,是透過下面這個函式 `draw_board` 將更新的棋盤寫入 buffer 裏面。 :::info 為什麼我們核心已經有 rx_fifo 了,還要有個 draw_buffer ? ::: ```c #define BOARD_SIZE 4 #define N_GRIDS (BOARD_SIZE * BOARD_SIZE) /* Draw the board into draw_buffer */ static int draw_board(char *table) ``` 那主要在做這件寫入的事情是誰在做的?Kernel thread 他透過函式 `drawboard_work_func` 來處理這件事情, ```c /* Workqueue handler: executed by a kernel thread */ static void drawboard_work_func(struct work_struct *w) { ... read_lock(&attr_obj.lock); if (attr_obj.display == '0') { read_unlock(&attr_obj.lock); return; } read_unlock(&attr_obj.lock); mutex_lock(&producer_lock); draw_board(table); mutex_unlock(&producer_lock); /* Store data to the kfifo buffer */ mutex_lock(&consumer_lock); produce_board(); mutex_unlock(&consumer_lock); wake_up_interruptible(&rx_wait); } ``` :::info 那 `drawboard_work_func` 與 kernel thread 是什麼關係? 這部分代表什麼? ```c WARN_ON_ONCE(in_softirq()); WARN_ON_ONCE(in_interrupt()); #define WARN_ON_ONCE(condition) WARN_ON(condition) ``` ::: 我們可以透過此命令來去檢視 kxo 模組 attr_obj 的設定: ```bash $ cat /sys/class/kxo/kxo/kxo_state 1 1 0 display / resume / end ``` :::info `attr_obj.resume` 似乎沒有用到? ::: 可以知道 kxo 模組在 `kxo_init` 函式中預設 display 是 1。 所以 `drawboard_work_func` 會根據 display 的設定決定要不要繼續做後面的事情,若設定為 1,就會去做 `draw_board` 和 `produce_board` 的動作 :::info `produce_board` 這個函式取名感覺可以修改? 畢竟他的動作是把 draw_buffer 的資料插入 rx_fifo 裡面 ::: 所以流程我認為是: ``` attr_obj.display (1) -> draw_board -> produce_board -> kfifo_in() -> rx_fifo buffer rx_fifo -> kfifo_to_user -> user space buffer ``` 而使用者可以透過 `Ctrl + P` 來修改 display 的值 `xo-user`: ```c static void listen_keyboard_handler(void) { ... case 16: /* Ctrl-P */ read(attr_fd, buf, 6); buf[0] = (buf[0] - '0') ? '0' : '1'; read_attr ^= 1; write(attr_fd, buf, 6); if (!read_attr) printf("\n\nStopping to display the chess board...\n"); break; ... } ``` 我要如何測試效能? 我使用 `timeout -s SIGINT 5s taskset -c 0 perf stat ./xo-user` 這個命令來去檢測 ```bash Performance counter stats for './xo-user': 0.42 msec task-clock # 0.000 CPUs utilized 1 context-switches # 2.384 K/sec 0 cpu-migrations # 0.000 /sec 57 page-faults # 135.909 K/sec 1,315,019 cpu_core/cycles/ # 3.135 GHz 1,069,527 cpu_core/instructions/ # 0.8136 ins per cycle 190,000 cpu_core/branches/ # 453.030 M/sec 6,408 cpu_core/branch-misses/ TopdownL1 (cpu_core) # 29.2 % tma_backend_bound # 9.5 % tma_bad_speculation # 45.8 % tma_frontend_bound # 15.6 % tma_retiring 4.967719478 seconds time elapsed 0.000741000 seconds user 0.000000000 seconds sys ```