# 2025q1 Homework3 (kxo) contributed by < `horseface1110` > ## 開發環境 ``` $ gcc --version gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 40 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 1 On-line CPU(s) list: 0 Vendor ID: GenuineIntel Model name: QEMU Virtual CPU version 2.5+ CPU family: 15 Model: 107 Thread(s) per core: 1 Core(s) per socket: 1 Socket(s): 1 Stepping: 1 BogoMIPS: 5199.99 Flags: fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx lm constant_tsc nopl xtopology cpuid tsc_known_freq pni ssse3 cx16 sse4_1 sse4_2 x2 apic popcnt aes hypervisor lahf_lm cpuid_fault pti Virtualization features: Hypervisor vendor: KVM Virtualization type: full Caches (sum of all): L1d: 32 KiB (1 instance) L1i: 32 KiB (1 instance) L2: 4 MiB (1 instance) L3: 16 MiB (1 instance) NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0 Vulnerabilities: Gather data sampling: Not affected Itlb multihit: KVM: Mitigation: VMX unsupported L1tf: Mitigation; PTE Inversion Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Meltdown: Mitigation; PTI Mmio stale data: Unknown: No mitigations Reg file data sampling: Not affected Retbleed: Not affected Spec rstack overflow: Not affected Spec store bypass: Vulnerable Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Spectre v2: Mitigation; Retpolines; STIBP disabled; RSB filling; PBRSB-eIBRS Not affected; BHI Retpoline Srbds: Not affected Tsx async abort: Not affected ``` ## 將棋盤的顯示移到使用者層級 ### 程式碼分析 | 檔案 | 說明 | 修改目標 | | --------------------- | ----------------------------------------- | -------------------------------------------- | | `main.c` | Linux kernel module 主入口。負責 device 註冊、初始化等 |把畫棋盤的部分改成將特定資訊傳到user_space| | `xo-user.c` | 使用者空間程式,負責與 `/dev/kxo` 溝通 | 改寫為**從 device 讀取棋盤狀態並繪圖** | ### `main.c`棋盤狀態壓縮傳遞重構 > commit [78bf879](https://github.com/sysprog21/kxo/commit/78bf8794c080f9610084b7763ef6e301deb577c9) #### 動機 原本的 `draw_board()` 會在 kernel 內產生完整的棋盤圖形,然後將其存入 `draw_buffer`,最終傳遞到 user space 端顯示。但這種方式傳遞的是渲染後的字串,每次都需將所有字元輸出,浪費空間且造成不必要的複製負擔。 事實上,棋盤的資訊很簡單──每格只需表達三種狀態 (`' '`, `'X'`, `'O'`),因此完全可以只傳遞棋盤的邏輯狀態,將渲染工作移到 user space。如此不僅減少 kernel/user 的資料傳輸量,也能讓 user 端客製化顯示格式。 #### 重構步驟 1. **位元壓縮設計** 由於棋盤為 9 格,每格 3 種狀態,使用 2 bit 即可編碼一格(00 空、01 X、10 O)。因此可以用一個 `uint32_t`(32 bit)來存全部棋盤。 ```c static uint32_t draw_board(char *table) { uint32_t position = 0; for(int i = 0; i < N_GRIDS; i++) { uint32_t val = 0; if(table[i] == ' ') val = 0; else if(table[i] == 'X') val = 1; else if(table[i] == 'O') val = 2; position |= (val << (i * 2)); } return position; } ``` 2. **介面修改** `drawboard_work_func` 原先只呼叫 `draw_board()` 渲染,現在改成呼叫新函式,並將回傳值存入 `position`,並傳給 `produce_board()`,將編碼後的 4 byte 放進 kfifo,供 user space 取用。 ```c mutex_lock(&producer_lock); uint32_t position = draw_board(table); mutex_unlock(&producer_lock); mutex_lock(&consumer_lock); produce_board(position); mutex_unlock(&consumer_lock); ``` 同時調整全域 `draw_buffer` 的大小為 4 bytes,僅存 `position`。 ```c static char draw_buffer[4]; ``` ```c static void produce_board(uint32_t position) { memcpy(draw_buffer, &position, sizeof(position)); unsigned int len = kfifo_in(&rx_fifo, draw_buffer, sizeof(draw_buffer)); // ... } ``` #### 小節 此時,當有interrupt進來時,就會把整個棋盤的資訊傳給user_space,順利將棋盤在user_space畫出來。但我發現這樣無法達成另一個目標:紀錄棋局的落子順序。所以我改用另一種方式:每次落子時傳player和落子位置 :::danger 注意書寫規範:中英文間用一個半形空白字元區隔 ::: ### 紀錄棋局的落子順序 #### 新的資料結構 > commit [5e115cb](https://github.com/sysprog21/kxo/commit/5e115cb880029a65d17375cd27b14318864b312d) 為了能夠紀錄落子資訊,我想把每次落子都分開來看。所以資訊改用1 byte傳輸,分別為: ``` position:4 player:1 win:3 ``` 有16格,所以需要4bit紀錄落子在哪裡;至於為甚麼win需要3 bit,因為我想湊到1 byte。如果沒有湊到1 byte,會不知道怎麼傳輸資料到user_space,buffer<s>好像</s> 沒辦法用自定義的struct為單位。 :::danger 注意書寫規範! 提供數學分析。不要說「好像」,要是 Apple 和 Google 工程師將「好像」掛在嘴邊,你敢用他們開發的產品嗎? ::: #### 每次落子就傳資料 > commit [0512022](https://github.com/sysprog21/kxo/commit/05120222c1962c9255ccef96cda2a99f788d240) 之前傳輸資料是發生在interrrupt後,`game_tasklet_func`會負責把資料傳到user_space裡面,但這樣沒辦法傳送每個落子紀錄,因為有可能在interrupt之前已經落了多子了。所以 - 移除`produce_board`中的kfifo_in - 由ai_one、ai_two下完棋後直接將畫圖工作放入workqueue(`queue_work(kxo_workqueue, &drawboard_work);`) #### 成功 - 成功之前,發線總是會有重複的落子資訊出現,以為是race condition,但最後發現是因為棋局結束時,kernel也會傳送最後一步到user。所以最後才新增了`win`的標記 <s> - fifo大小對比: - 修改前:![image](https://hackmd.io/_uploads/BkXewCtkxl.png) - 改完後:![image](https://hackmd.io/_uploads/BkxQURtkeg.png) </s> :::danger 注意書寫規範:文字訊息不要用圖片展現 :::