contributed by <PigeonSir>
注意書寫規範!
ai 演算法 : mcts.c, negamax.c
kxo module : main.c
user-mode 觀看棋局 : xo-user.c
遊戲規則 & 勝利判定 : game.c
一開始編譯 kxo 時發生錯誤,其原因為 zobrist.c 中的 u128 未定義,暫時透過新增定義解決
但詢問 chatgpt 發現該操作存在風險,因為並非所有架構都支援 __uint128_t
,查看該函式的程式碼
說好的進度呢?
要可以同時觀看多場對局,代表要有很多個 AI1 和 AI2,並要透過鍵盤觸發 signal 來切換對局、暫停和停止,參考 coro 的程式碼發現 setjump/longjump 僅能在 user space 使用,所以我的計畫是在 kernel mode 中運用現有的架構延伸多棋局的功能,並且在 user space 運用 corutine 支援多使用者同時觀看不同的棋局。
timer_setup(&timer, timer_handler, 0)
→ 沒有啟動 timer(尚未進入排程)mod_timer(&timer, jiffies + delay)
啟動第一個 tick 並等待 delay 後觸發 timer_handler()tasklet_schedule(&game_tasklet)
轉交排程器來決定誰是下一個落子先以確保每個棋盤可以獨立運算為目標,不排程畫圖的相關任務,中間有經過一些修改,以下為修改過的流程
為了存放多場棋局的資料並且傳送特定棋局的參數至愈排程的函式,新增結構體 kxo_task
tasklet 和 workqueue 都無法直接傳遞額外參數。它們只能接受固定型別的 callback 函式:
實作時,需要在 kxo_task 中定義 tasklet_struct 和 work_struct 的結構體,並且在模組初始化階段定義執行的函式
在函式被執行後用 cotainer_of()
或是透過指標取得 kxo_task
棋盤資訊
按照上述策略修改程式碼後會觸發以下錯誤
觸發的時間點是在 ai_one_work_func 呼叫 get_cpu 並且使用 mutex 後,推測是因為調用了多場棋局但全部共用一個 mutex lock 導致的,所以試著先改成每個棋盤一個 mutex
改完之後就不再有該問題了,但是發現電腦會變很卡,而且每一場棋局都只有 ai O 在下棋
commmit 70162cf
結果沒什麼大不了,只是我在 game_tasklet_func 中決定排程時條件判斷的變數忘了改。
原本以為我已經排除了,結果只是因為我把每個棋盤的 mutex lock 分開後只在每個棋盤排程 ai O 所以沒有觸發等待,把 ai X 加入後該問題又跟著回來了,即便我只使用一個棋盤還是會發生,而且當我把兩個 ai 任務的 mutex_lock 都註解掉時,這個錯誤又不發生了,之後可以考慮不要使用 mutex,但目前先研究一下觸發錯誤的原因
觀察 get_cpu() 的程式碼
Keeping Kernel Code Preempt-Safe
commit f71ff80
目前的作法在任一個棋盤(cdev)被 open 就會全部的棋局都開始運算,接下來改成只有被開啟的棋局需要被排程。
先在 kxo_task
中新增 bool playing
來識別該棋局有無被開啟
在 kxo_open
中透過 inode
來得知現在被開啟的棋盤編號
最後在 timer_handler
中只排程有被開啟的棋局
前面只執行了多棋局的運算,接下來在核心層級把在畫圖加回來,並且要可以支援多棋局觀看
game_tasklet_func
每下一子就排程
draw_board_work_function
draw_board_work_function
搬運資料
draw_board()
把棋盤資料從 table
搬到 draw_buffer
,並加入棋盤格和換行produce_board()
把 draw_buffer
的資料寫進 kfifo
timer_handler
中若是棋局結束也會執行一遍kfifo_to_user()
傳遞 rx_fifo
內的資料給使用者層級Commit 2eab22c
在結構體內新增傳送棋盤資訊用的參數
剩下就是根據執行時的 id 維護對應的棋盤資訊來達成顯示多個棋局的功能
實測透過 cat /dev/kxoN 可以正確的顯示棋盤資訊,但明明沒新的落子卻一直畫新的圖,推估是因為 ai 的計算時間較長,同一個 tasklet 會排程一個 ai 和一個畫圖的 work_struct 進 workqueue,但兩者執行時間落差過大,導致一次 ai 下一個子的同時畫圖的任務已經執行超過一次了,雖然使用 xo-user 觀看棋局時會避免重複繪製棋盤,但這樣的溝通方式還是有改善空間,接下來透過簡化傳送的資料以及把畫圖移至使用者層級來改善。
原本的作法是紀錄並傳送整個棋盤,改為只傳送當前的落子位置,由使用者執行紀錄棋盤與畫圖,但是勝負判定還是要在核心模組內進行,所以核心模組還是需要透過 table 紀錄棋盤資訊
先把 produce_board 和 draw_board 移到 xo-user.c,並在 ai 運算完後直接把結果放到 kfifo 裡面,在一個 unsigned short 裡面紀錄換誰、棋盤編號和落子位置
win | turn | game ID | move |
---|---|---|---|
1 bit | 1 bit | 6 bit | 1 byte |
修改 main.c 和 xo-user.c 並觀察輸出,發現會重複傳送資料以及出現錯誤的 game id
實際印出 ai 計算完放到 kfifo 中的值發現跟預先規劃的配置不同
發現原因是我在把 ai 算出來的 move 轉成設定的資料格式並寫到 step 時沒有使用 WRITE_ONCE
修正之後就確定寫進 kfifo 的數值是正常的
但是在 xo.user.c 還是會重複讀取跟讀到亂數,經過觀察 dmesg 發現錯誤總是發生在讀取大於 0xFF 的數之後,推測原因是 kfifo_to_user 可以一次複製超過一個 byte 的資料到 user-space 但是他的 pointer 只會移動一個 byte ,所以改成把 unsigned short 拆成兩個 byte 分開傳送,就可以收到正確的資料
user space
kernel space