# 2025q1 Homework3 (kxo) contributed by < `Charlie-Tsai1123` > :::danger 注意細節 ::: ## 問題修正 ### 出現亂碼 在執行 ```sudo ./xo-user``` 若按下 ctrl + P 會暫停繪製棋盤的畫面但會出現以下錯誤 <!-- <s> ![image](https://hackmd.io/_uploads/HJyHMlu0Jg.png) </s> :::danger 注意看作業規範:文字訊息不要用圖片展現 ::: --> ```bash O| | | ------- |X|X| ------- | | |O ------- O| | | ------- ���Stopping the kernel space tic-tac-toe game... ``` 我先找到 ctrl + P 的程式碼位子,在 xo-user.c 中 ```c int attr_fd = open(XO_DEVICE_ATTR_FILE, O_RDWR); ``` XO_DEVICE_ATTR_FILE 是用來更改遊戲的狀態 ```c 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("Stopping to display the chess board...\n"); break; ``` 從這段程式可以得知,buf[0] 是用來控制程式是否顯示棋盤 read 部份負責看現在的狀態 write 部份則更改現在狀態,從這部份我對修改這個錯誤沒有頭緒,所以轉向觀察輸出棋盤的程式 main.c 發現輸出棋盤的地方好像沒有加上 '\0' 這樣 printf 處理時不知道哪裡終結可能會出現亂碼,所以做了以下修改 ```main.c``` ```diff while (i < DRAWBUFFER_SIZE) {...} + draw_buffer[i - 1] = '\0'; ``` ```xo-user.c``` ```c - printf("%s", display_buf); + printf("%s\n", display_buf); ``` ![image](https://hackmd.io/_uploads/SyOYbM_C1x.png) ### 改善 ctrl + Q >commit [0e1d9b4](https://github.com/sysprog21/kxo/commit/0e1d9b45ab65b2564159dc783e48a5ee21f381f4) ## 畫面呈現在使用者層級 >commit [5377a9c](https://github.com/sysprog21/kxo/commit/5377a9c6b9c368ca054956e6b39a89ad4e225df4) ### 修改 ```draw_buffer``` 參考 rota1001 從原本的 ```DRAWBUFFER_SIZE``` 可以得知 ```draw_buffer``` 紀錄了整個畫面的資訊,也因此從 kernel mode 傳到 user mode 會用很多資源 (66 bytes 以 4x4 為例),那要如何減少?每格狀態只會有三種 ('O', 'X', ' ') 但三不是 2 的冪,所以要用 2 個 bit 表示 (4種狀態),所以傳遞資訊只需要 ```BOARD_SIZE``` $\times$ ```BOARD_SIZE``` $\times$ 2 bits (32 bits = 1 bytes 以 4x4 為例) 因此我將 ```draw_buffer``` 的型別改用 ```unsigned int``` ```diff - static char draw_buffer[DRAWBUFFER_SIZE]; + static unsigned int draw_buffer; ``` :::danger 提供更多數學分析 ::: 然後從 ```main.c``` ```draw_board``` 可以發現 ```draw_buffer``` 的更新是根據 ```table``` ,它紀錄了每個位子的符號 ```c draw_buffer[i++] = j & 1 ? '|' : table[k++]; ``` 所以可以思考該如何把 ```O```, ```X```, ``` ``` 用三個狀態表示,我先列出他們三個的 Asquii Code: | ```O``` | ```X``` | ``` ``` | | -------- | -------- | -------- | | 1==00==1111 | 1==01==1000 | 0==10==0000 | 可以發現黃色標起來的地方剛好可以用 2 bits 代表不同狀態,只需要把那一格的 ```char``` 向右移動 4 格再 and 0b11 = 3 ,因此依序把值填入 ```draw_buffer``` 中 ```c while (i < N_GRIDS) { draw_buffer |= ((table[k++] >> 4) & 3) << (i << 1); i++; } ``` >[!Note]明明第三跟第四個 bit 就可以表示三個狀態了為什麼要用第五跟第六 >| ```O``` | ```X``` | ``` ``` | >| -------- | -------- | -------- | >| 100==11==11 | 101==10==00 | 010==00==00 | > >因為等等在 user mode 的 draw_board 不想用到 if else ### 移動 ```draw_board``` 到 user mode ```c const char mapping[3] = {'O', 'X', ' '}; ``` 仔細觀察剛剛用來代表 ```O```, ```X```, ``` ``` 的值,剛剛好分別對應 0, 1, 2 ,所以可以創見一個大小為 3 的陣列分別就把 0, 1, 2 對應到 ```O```, ```X```, ``` ``` ```c static int draw_board(unsigned int display_buf) { const char mapping[3] = {'O', 'X', ' '}; for (int i = 0; i < BOARD_SIZE; i++) { putchar(mapping[display_buf & 3]); display_buf >>= 2; for (int j = 0; j < BOARD_SIZE - 1; j++) { putchar('|'); putchar(mapping[display_buf & 3]); display_buf >>= 2; } putchar('\n'); for (int j = 0; j < (BOARD_SIZE << 1) - 1; j++) { putchar('-'); } putchar('\n'); } return 0; } ``` >[to do] 測試是否真的效率較好 ## 引入 coroutine 首先我看了 <[並行程式設計:排程器原理](https://hackmd.io/@sysprog/concurrency-sched)> 中的 coro.c 程式碼,裡面使用了 setjmp 跟 longjmp 處理 coroutine 嘗試在 main.c 使用 setjmp.h 出現以下錯誤 ```bash /home/charlie-tsai/linux2025/hw/kxo/main.c:15:10: fatal error: setjmp.h: No such file or directory 15 | #include <setjmp.h> | ^~~~~~~~~~ compilation terminated. ``` 根據 《[The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/)》(lkmpg) 5.2 >One point to keep in mind is the difference between library functions and system calls. Library functions are higher level, run completely in user space and provide a more convenient interface for the programmer to the functions that do the real work — system calls. System calls run in kernel mode on the user’s behalf and are provided by the kernel itself. 因為 setjmp/longjmp 是 glibc 提供的函式,設計給 user space 使用,依賴 user space 的 stack ,因為 kernel 沒有使用 glibc 所以無法使用 想法: 將不同演算法下棋也可以運作在 user space 並用 coroutine 在 AI1, AI2, draw_board 間輪流執行 ### 讓 user space 可以使用 kernel space 函式 (mcts, negamax) >Commit [166d6e4](https://github.com/sysprog21/kxo/commit/166d6e40758cea1ba13fbb1644b1ded0cd366745) 首先要讓 user space 可以使用 mcts 跟 negamax 這兩個函式,先去翻了 lkmpg 並學習了 ioctl 的用法以及第六章提及的 [character device driver](https://sysprog21.github.io/lkmpg/#character-device-drivers) >[!Note] Device >device 分成兩種 character devices 跟 block devices >| character devices | block devices | >| -------- | -------- | >| input or output many or as few bytest they like | input output through fix-sized buffer | >||buffer for request 可以選擇順序(就像選擇更近的 sector 存取資料) <!-- #### 嘗試自己寫 kernel module 並讓 user space 使用 --> ==修改 file_operations== file_operations 定義了各種 device 的函式 ```diff static const struct file_operations kxo_fops = { .read = kxo_read, .llseek = no_llseek, .open = kxo_open, .release = kxo_release, .owner = THIS_MODULE, + .unlocked_ioctl = kxo_ioctl, }; ``` ==define magic number== magic number 可以辨別不同的 ioctl 指令來源,這裡想要讓 ```mcts``` ```negamax_predict``` 讓使用者使用,所以需要用到兩組 magic number ```c #define MY_IOCTL_MAGIC_MCTS 'O' #define MY_IOCTL_MCTS _IOWR(MY_IOCTL_MAGIC_MCTS, 1, unsigned int) #define MY_IOCTL_MAGIC_NEGAMAX 'X' #define MY_IOCTL_NEGAMAX _IOWR(MY_IOCTL_MAGIC_NEGAMAX, 1, unsigned int) ``` ==完成 kxo_ioctl== 進行四個步驟: 1. kernel space 讀取 user space 傳過來的資料 --> ```copy_from_user``` 2. 將收到的 unsigned int 轉成 char* --> ```int_to_table``` 3. run ```mcts``` 或 ```negamax_predict``` 4. 將 kernel space 產生的最佳位子傳回到 user space --> ```copy_to_user``` ```c switch (cmd) { case MY_IOCTL_MCTS: if (copy_from_user(&value, (unsigned int __user *) arg, sizeof(unsigned int))) return -EFAULT; int_to_table(value, user_table); value = mcts(user_table, 'O'); if (copy_to_user((unsigned int __user *) arg, &value, sizeof(unsigned int))) return -EFAULT; return 0; case MY_IOCTL_NEGAMAX: if (copy_from_user(&value, (unsigned int __user *) arg, sizeof(unsigned int))) return -EFAULT; int_to_table(value, user_table); value = negamax_predict(user_table, 'X').move; if (copy_to_user((unsigned int __user *) arg, &value, sizeof(unsigned int))) return -EFAULT; return 0; default: return -EINVAL; ``` ==修改 user space 程式== 用 ```ioctl``` 傳入棋盤並回傳最佳下棋位子,接著再更新 空棋盤會是 ```0b10101010101010101010101010101010``` 每一格都是 10 因為代表 ``` ``` (可以看上面 mapping 的方法) * mcts 只要清空那一格就好 (```00``` 代表 ```O```) : user_draw_buffer &= ~(0b11 << (move << 1)); * negamax 還要 or 01 (```01``` 代表 ```X```) : user_draw_buffer &= ~(0b11 << (move << 1)); user_draw_buffer |= (0b01 << (move << 1)); ```c void task_mtcs(int fd) { finish++; unsigned int move = user_draw_buffer; ioctl(fd, MY_IOCTL_MCTS, &move); if (move != -1) { user_draw_buffer &= ~(0b11 << (move << 1)); } } void task_negamax(int fd) { finish++; unsigned int move = user_draw_buffer; ioctl(fd, MY_IOCTL_NEGAMAX, &move); if (move != -1) { user_draw_buffer &= ~(0b11 << (move << 1)); user_draw_buffer |= (0b01 << (move << 1)); } } ``` 結果 上面是 kernel space 執行的,下面是 user space 執行的 ```bash |O|O| ------- | | | ------- |X| | ------- | | | ------- ======= ======= | | | ------- |X| | ------- | | |O ------- | | | ------- ``` >[to do] 目前犯了個錯誤因為 user space 沒辦法使用 check_win (因為在 kernel )所以我是讓 mcts 跟 negamax 總共呼叫 16 次再開始新的一局,之後想要寫在 user space 判斷棋盤勝利 ### 使用 coroutine 上面實做完後,目前是讓兩個 ai 更新完 table 後再執行 draw_board ```diff if (FD_ISSET(STDIN_FILENO, &readset)) { FD_CLR(STDIN_FILENO, &readset); listen_keyboard_handler(); } else if (read_attr && FD_ISSET(device_fd, &readset)) { FD_CLR(device_fd, &readset); printf("\033[H\033[J"); /* ASCII escape code to clear the screen */ read(device_fd, &display_buf, sizeof(display_buf)); + task_mtcs(device_fd); + task_negamax(device_fd); draw_board(display_buf, user_draw_buffer); } ``` 那現在想要將這兩個函式跟 draw_board 還有 keyboard handler 改成使用 coroutine ## 讀懂程式 ### mcts.c MCTS 演算法分成三步(參考[Monte Carlo Tree Search 解說](https://www.youtube.com/watch?v=J3I3WaJei_E)) 1. sellect 2. rollout && expand - rollout (還沒有更新過 n~i~ = 0) - expand (有更新過了 --> 往下試探新增新的節點) 3. backpropagate (往上更新結果 n~i~++ , w~i~++ if win) ![image](https://hackmd.io/_uploads/rJ6rwchygg.png) >n~i~ 是右邊的數字代表模擬了幾次, w~i~ 是贏得次數 :::danger 數學分析呢? ::: ### ```xo-user.c``` ```xo-user.c``` 用來控制並顯示來自 Linux kernel space 的圈圈叉叉(XO)遊戲,透過 /dev/kxo 與 /sys/class/kxo/kxo/kxo_state 與核心模組互動。 (/dev/kxo 內有棋盤的資訊、/sys/class/kxo/kxo/kxo_state 內有遊玩狀態的資訊) #### ```static bool status_check(void)``` ### ```main.c``` ## 閱讀 lkmpg