# 2025q1 Homework3 (kxo) contributed by < `fcu-D0812998` > ## 實作 coroutine > Commit [b0e3d58](https://github.com/sysprog21/kxo/commit/b0e3d588f553f686f1b4f94735c98fdbbd5e3661) 參考資料 : [並行程式設計: 排程器原理](https://hackmd.io/@sysprog/concurrency-sched) 移除原本的 select-based I/O 模式,將鍵盤事件與顯示棋盤重寫成 coroutine-based task switching 。 * 將 `listen_keyboard_handler()` 和 `draw_board()` 重寫為 coroutine-based task switching 。 * 在函式內加入: ```c TASK_SCHEDULE(); while (!end_attr) { ... TASK_YIELD(); } ``` * 使用 `TASK_SCHEDULE()` 初始化 coroutine 狀態,`TASK_YIELD()` 讓出控制權。 在原本使用 select-based I/O 的話,需要用多層的 if-else 條件式去做判斷,且當需求變多時, select 會越寫越複雜,在維護的時候會相當困難。這樣做更改的話,好處是 : * 輕量 : 一個 coroutine 只是一組類似暫存器的暫存上下文。 * 便宜:在 user-space 直接 `setjmp() + longjmp()` 就可以很快就切換,不用進 kernel。 * 結構化:每個 coroutine 自己有獨立的邏輯,結合 `TASK_SCHEDULE() / TASK_YIELD()` ,整個流程清楚且可控。 ## 把下棋畫面呈現的部分全部在使用者層級 > Commit [fbb00df](https://github.com/sysprog21/kxo/commit/fbb00dfe54b73b2dccc67554c1bb35781dc434f8) :::danger 說好的進度呢? ::: 首先要先了解, kernal 端如何呈現棋盤畫面,看到原版 main.c 也就是 kernal 端中有 `draw_board` 的函式,它的用途是將棋盤的狀態也就是 table 這個陣列裡的值轉成 ASCII 畫面,也就是 draw_buffer 。 ```c static int draw_board(char *table) ....... ``` * 所以目前就是要把 draw_board 這個函式搬去 user space ,讓 kernel 專心處理遊戲決策跟勝負判斷,不做 UI。 * 搬完後,因為原本每次 user space 從 kernel 端 read 時,會讀取 `DRAWBUFFER_SIZE` 大小的資料(包括了 table 、格線符號、換行符號),總共為 66 bytes,現在我們已經在 coroutime 中把顯示棋盤的部分放在 user space了,所以 kernel 端只需傳入 table 就好也就是棋盤的狀態,總共 4*4 = 16 bytes。 * 在 `produce_board` ,它的用途是把目前 kernel 端中棋盤的狀態放入 kfifo 緩衝區, 讓 user space 可以用 `read()` 把這份資料讀走顯示,我們將原本的 draw_buffer 改成只傳 table 就可以了。 ```diff - kfifo_in(&rx_fifo, draw_buffer, sizeof(draw_buffer)); + kfifo_in(&rx_fifo, table, sizeof(table)); ``` :::danger 注意書寫規範,中英文以一個半形空白字元區隔 ::: ## 降低核心和使用者層級之間的通訊成本 > Commit 在 ASCII 編碼中,需要 8bits 才能塞的下 `O` (79)跟 `X` (88),但我的棋盤其實只要呈現 `O` 跟 `X` 還有空白三個,那我的想法是把它轉換成 2Bits 就好。 ``` 空白 = 00 O = 01 X = 10 ``` :::danger 提供更多數學分析 ::: 棋盤大小為 4 * 4 = 16 原本設計的大小為 : 16 * 8 = 128 bits 改良設計的大小為 : 16 * 2 = 32 bits 節省了 96 bits 的空間