Try   HackMD

2025q1 Homework3 (kxo)

contributed by <Andrushika>

在 user level 繪製棋盤

commit d203aed
xo-user.c 中加入以下程式碼:

static int draw_board(const char *table)
{
    printf("\n\n");
    for (int i = 0; i < BOARD_SIZE; i++) {
        for (int j = 0; j < BOARD_SIZE; j++) {
            printf(" %c ", table[i * BOARD_SIZE + j]);
            if (j < BOARD_SIZE - 1)
                printf("|");
        }
        printf("\n");
        if (i < BOARD_SIZE - 1) {
            for (int j = 0; j < BOARD_SIZE * 4 - 1; j++) {
                printf("-");
            }
            printf("\n");
        }
    }
    printf("\n");
}

此處有對原本的 draw_board 做了一些修改,稍微提高了程式的可讀性;但因為用到了乘法運算,效能可能沒有原本來得好,是可以改進的地方。

這樣一來,在 kernel level 中就無須在乎棋盤的繪製方式,只要專注於將 data 本身利用 kfifo 傳輸給 user level 就好。
接下來修改 main.c 中原本的 produce_board,可以直接使用 table 的內容,並更名為更貼近實際行為的 send_board_data

/* send chess board data into the kfifo buffer */
static void send_board_data(void)
{
    unsigned int len = kfifo_in(&rx_fifo, table, N_GRIDS);
    if (unlikely(len < N_GRIDS) && printk_ratelimit())
        pr_warn("%s: %zu bytes dropped\n", __func__, N_GRIDS - len);

    pr_debug("kxo: %s: in %u/%u bytes\n", __func__, len, kfifo_len(&rx_fifo));
}

而在 drawboard_work_functimer_handler 中,不再需要 draw_board

static void drawboard_work_func(struct work_struct *w)
{
    int cpu;

    /* This code runs from a kernel thread, so softirqs and hard-irqs must
     * be enabled.
     */
    WARN_ON_ONCE(in_softirq());
    WARN_ON_ONCE(in_interrupt());

    /* Pretend to simulate access to per-CPU data, disabling preemption
     * during the pr_info().
     */
    cpu = get_cpu();
    pr_info("kxo: [CPU#%d] %s\n", cpu, __func__);
    put_cpu();

    read_lock(&attr_obj.lock);
    if (attr_obj.display == '0') {
        read_unlock(&attr_obj.lock);
        return;
    }
    read_unlock(&attr_obj.lock);

    /* Store data to the kfifo buffer */
    mutex_lock(&producer_lock);
    mutex_lock(&consumer_lock);
    send_board_data();
    mutex_unlock(&producer_lock);
    mutex_unlock(&consumer_lock);

    wake_up_interruptible(&rx_wait);
}

按下 ctrl-q 離開時顯示對局歷史

在實作時遇到一個問題:無論進行了幾局遊戲,games_count 始終為 0(即 history 停留在第一局遊戲)。
因此,在 track_moves 加上以下程式碼進行偵錯:

static void track_moves(const char *board)
{
    printf("track_moves called. Current move_count: %d, game_count: %d\n", move_count, game_count);

    bool reset_detected = is_board_reset(board); 
    printf("is_board_reset returned: %s\n", reset_detected ? "true" : "false");
    
    /* Check if the board has been reset */
    if (reset_detected) {
        printf("Reset detected!\n");
        if (move_count > 0) {
            printf("Saving game %d with %d moves.\n", game_count + 1, move_count);
            save_current_game();
            printf("After save_current_game, game_count is now: %d\n", game_count);
        }else{
            printf("Reset detected, but move_count is 0, not saving.\n");
        }
        move_count = 0;
        printf("move_count reset to 0.\n");
        memcpy(prev_board, board, N_GRIDS);
        return;
    }
    
    /* Track new moves */
    for (int i = 0; i < N_GRIDS; i++) {
        if (board[i] != ' ' && prev_board[i] == ' ') {
            if (move_count < N_GRIDS) {
                current_moves[move_count].pos = i;
                current_moves[move_count].player = board[i];
                move_count++;
            }
        }
    }
    
    /* Update previous board state */
    memcpy(prev_board, board, N_GRIDS);
}

之後發現,即使棋局已經分出勝負並重置,is_board_reset 都未被觸發,導致不同棋局的 moves 仍會繼續被寫在同一條 game 紀錄中:

track_moves called. Current move_count: 0, game_count: 0
track_moves called. Current move_count: 1, game_count: 0
track_moves called. Current move_count: 1, game_count: 0
track_moves called. Current move_count: 2, game_count: 0
track_moves called. Current move_count: 3, game_count: 0
track_moves called. Current move_count: 3, game_count: 0
track_moves called. Current move_count: 4, game_count: 0
track_moves called. Current move_count: 5, game_count: 0
track_moves called. Current move_count: 5, game_count: 0
track_moves called. Current move_count: 6, game_count: 0
track_moves called. Current move_count: 6, game_count: 0
track_moves called. Current move_count: 6, game_count: 0
track_moves called. Current move_count: 6, game_count: 0
track_moves called. Current move_count: 7, game_count: 0
track_moves called. Current move_count: 8, game_count: 0
track_moves called. Current move_count: 8, game_count: 0
track_moves called. Current move_count: 9, game_count: 0
track_moves called. Current move_count: 10, game_count: 0
is_board_reset returned: false

原本的 is_board_reset 是這樣實作的:

static bool is_board_empty(const char *board)
{
    return memcmp(board, empty_board, N_GRIDS) == 0;
}

static bool is_board_reset(const char *board)
{
    if (!is_board_empty(board))
        return false;

    if (is_board_empty(prev_board))
        return false;
    
    return true;
}

此處使用了檢查棋盤是否為 empty 的方式進行判斷;然而,因為每次的畫面更新都是在 AI 決策完才被觸發,所以不會有 is_board_reset 條件成立的時候。

降低核心和使用者層級溝通的成本