2025q1 Homework3 (kxo)

contributed by < weiso131 >

作業書寫規範:

  • 無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利)
  • 文字訊息 (尤其是程式執行結果) 請避免用圖片來表示,否則不好搜尋和分類
  • 共筆書寫請考慮到日後協作,避免過多的個人色彩,用詞儘量中性
  • 不要在筆記內加入 [TOC] : 筆記左上方已有 Table of Contents (TOC) 功能,不需要畫蛇添足
  • 不要變更預設的 CSS 也不要加入任何佈景主題: 這是「開發紀錄」,用於評分和接受同儕的檢閱
  • 在筆記中貼入程式碼時,避免非必要的行號,也就是該手動將 c=cpp= 變更為 ccpp。行號只在後續討論明確需要行號時,才要出現,否則維持精簡的展現。可留意「你所不知道的 C 語言: linked list 和非連續記憶體」裡頭程式碼展現的方式
  • HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」
  • 留意科技詞彙的使用,請參見「資訊科技詞彙翻譯」及「詞彙對照表
  • 不要濫用 :::info, :::success, :::warning 等標示,儘量用清晰的文字書寫。:::danger 則僅限授課教師作為批注使用
  • 避免過多的中英文混用,已有明確翻譯詞彙者,例如「鏈結串列」(linked list) 和「佇列」(queue),就使用該中文詞彙,英文則留給變數名稱、人名,或者缺乏通用翻譯詞彙的場景
  • 在中文敘述中,使用全形標點符號,例如該用「,」,而非 ","。注意書名號的使用,即 ,非「小於」和「大於」符號
  • 避免使用不必要的 emoji 字元

注意項目的使用,亦即用 #####

鍵盤事件處理

觀察程式
發現 xo-user.c 藉由修改 kxo_state 這個 sysfs
來修改 attr_obj

暫停/重啟

原本的程式就能做到這個功能
然而,若是在某個使用者視窗暫停之後
其他視窗皆無法藉由 ctrl + P 繼續執行
若是將該視窗關閉,就再也無法讓其他視窗繼續執行

這是因為 read_attr 不同步
導致其他使用者嘗試對 /dev/kxo 進行 read
此時因為 attr_obj 的 display 被設成 1
kfifo 不會有新的物件加入
這會讓 read 陷入無盡的等待

此問題可以藉由在 user space 的迴圈中
不斷藉由讀取 kxo_state 更新 read_attr
來解決

commit 98edb06

儲存對亦資料

history.[h/c] 實作對應的 API

  • void history_init(void);
    初始化 history
  • void history_update(uint64_t move);
    用來紀錄棋局內擺放棋子的位置
  • void history_new_table(void);
    用來重新開啟一場棋局
  • void history_release(void);
    用來釋放 history 所佔的記憶體
    這必須放在 del_timer_sync 之後
    否則可能會因為觸發計時導致存取已經釋放的指標

commit ce4cd73

離開對亦並顯示棋局紀錄

對於顯示棋局紀錄其實有兩種方法

  1. 在 release 的時候根據 attr_obj.end 的狀態判斷使用者是否按下 ctrl + Q , 再用 print_string 利用將資料輸出到呼叫 release 的使用者

    commit 3333893

  2. 利用 ioctl 讓使用者在跳出迴圈後可以進行資料讀取,並由使用者處理輸出的工作

    commit 79422ee

第一種方法的好處就是容易實作,但它在有多個使用者的時候,必須等待前個使用者將資料全部輸出才能換下個使用者輸出,這在場次很多的時候問題會變得更明顯。
此外,若能夠讓使用者實際存取到資料,未來想在使用者端加入機器學習會變得更容易。

在使用 ctrl + q 離開對亦之後,因為 attr_obj.end 被設成 '1'
再次執行 xo-user 將不會進入迴圈,必須重新安裝核心模組
這不太方便
因此我在最後一個使用者進行 release 時,將 attr_obj.end 設成 '0' ,重新執行將不再需要重新安裝核心模組

commit 50e778c

將 draw_board 移至 user space

單純將 draw_board 結果換成回傳 table

commit dc4cef2

利用 bit 儲存對亦資料

提供數學分析

作業說明的快速檢查勝方出現的方法

作業說明中以 is_win 函式快速判斷棋盤的狀態,利用位元運算同時降低空間占用並提升效率。
觀察 is_winmove_masks 可發現其原理就是利用 4, 2, 1 去湊出 7 來代表連成一線,這樣每條線的狀態都占用 1 byte 來表示。
這個方法能做到同時表達棋盤位置資訊與快速計算是否有連線。

觀察 check_win 使用

check_win 可以利用 table 的資訊判斷是否有勝方出現,並回傳勝方的棋子圖案。
此方法的缺點是用到了大量迴圈,相較能用 bitwise 快速判斷的方法,明顯非常沒效率。
這個函式在 main.c, mcts.c, negamax.c 都有被反覆使用
mcts.cnegamax.c 光是在一步棋的預測就有可能多次呼叫 check_win ,若能增加其執行效率應該能有效提昇整個核心模組的效率。

把棋盤位置資訊與連線狀態分開儲存

用 2 個 bit 儲存連線狀態,可用 48 bit 儲存其中一方的連線狀態
32 bit 儲存棋盤的位置資訊(前 16 bits 存 O , 後 16 bits 存 X)
總佔用記憶體是 128 bits = 16 bytes , 與原本的 table 相同
回傳棋盤位置資訊給使用者時可以只回傳 32 bits

用 2 個 bit 儲存連線狀態的實作

此方法參考了 DETECT_NULL 的方法,把它從偵測 0000 變成偵測 00
將空棋盤初始化為 0x5555 , 也就是 0b0101010101010101
對於任意一條線, 0b01 代表差三個, 0b10 代表差兩個, 0b11 代表差一個
0b00 則代表連成一線

利用 bitwise 傳遞 table 資訊給 user space

單純將傳遞資料的部份改成 bitwise 雖然能減少複製到 kfifo 與 user space 的時間(bitwise 儲存方法只需 4 bytes ,原本的方法需要 16 bytes) ,然而,可能會因為將字元陣列轉換成 bitwise 表示方法而花費更多時間。
在這個修改之後,會將 main.c, mcts.c, negamax.c 儲存 table 的方法換成 bitwise 且能快速判斷勝負的版本,這次的修改主要是為了避免一次進行大幅度的修改,增加版本控制難度。

commit 37f2dd6

將棋盤位置資訊與連線狀態分開儲存的方法應用到 main.c

kxo_table.h 定義 struct table 儲存棋盤位置資訊與連線狀態
並將 main.c 儲存棋盤的方法從字元陣列換成 struct table
它可以利用 bitwise 操作
提取使用者端所需的棋盤格式與
降低判斷勝負所需的時間

雖然這次的修改免去了上次修改在儲存到 kfifo 時所需的轉換
也成功將 check_win 換成更高效的實作 is_win

然而,在提供資訊給 mctsnegamax 時仍須將 struct table 轉成字元陣列,而且這兩個算法內部仍使用 check_win

commit a937e43

修改 mcts.c

修改 negamax.c