# 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
```