# kxo Code Write-Up ## What is cdev? ## Code Tracing ### [main.c](https://github.com/sysprog21/kxo/blob/main/main.c) 重要的變數包含: - #### [kxo_init](https://github.com/sysprog21/kxo/blob/main/main.c#L450-L537) kxo 模組第一個被呼叫的函式,呼叫時機是在使用者使用 `sudo insmod` 時。流程圖如下: ```graphviz digraph g { rankdir=TD node [shape = record style="filled"]; overlap=false; // splines = ortho // start node kxo_init [label="kxo_init" shape=ellipse fillcolor=mediumturquoise] // function nodes node [shape = record style="filled" fillcolor="springgreen1"] kfifo_alloc alloc_chrdev_region cdev_init cdev_add kobject_put class_create device_create device_create_file vmalloc vfree device_destroy class_destroy alloc_workqueue negamax_init mcts_init init_attr_lock init_timer init_open_cnt // macro nodes node [shape = record style="filled" fillcolor="orange"] MAJOR // goto node subgraph cluster_goto { node [shape = record style="filled" fillcolor="grey"] label = "Error"; labelloc = "t"; labeljust = "l"; style = "filled, rounded"; fillcolor = pink; color = black; goto [label="{<f0>error_cdev | <f1>error_region | <f2>error_alloc}"]; } // if nodes node [shape=diamond style="filled" fillcolor="darkslategray1"] if_kfifo_failed [label="ret \< 0"] if_chrdev_region_failed [label="ret == 0"] if_cdev_add_failed [label="ret == 0"] if_class_create_failed [label="IS_ERR(kxo_class)"] if_device_create_file_failed [label="ret \< 0"] if_vmalloc_failed [label="!fast_buf.buf"] if_alloc_workqueue_failed [label="!kxo_workqueue"] // exits nodes out [shape=record fillcolor=springgreen1] ret [shape=ellipse fillcolor=mediumturquoise] /* link */ edge[weight=10] kxo_init -> kfifo_alloc kfifo_alloc -> if_kfifo_failed if_kfifo_failed -> alloc_chrdev_region [taillabel="false" labeldistance=2 labelangle=300] alloc_chrdev_region -> if_chrdev_region_failed [shape=diamond] if_chrdev_region_failed -> MAJOR [taillabel="true" label=" dev_id" labeldistance=2 labelangle=70] MAJOR -> cdev_init [label=" major"] cdev_init -> cdev_add cdev_add -> if_cdev_add_failed if_cdev_add_failed -> class_create [taillabel="true" labeldistance=2 labelangle=70] class_create -> if_class_create_failed if_class_create_failed -> device_create [taillabel="false" labeldistance=2 labelangle=70] device_create -> device_create_file device_create_file -> if_device_create_file_failed if_device_create_file_failed -> vmalloc [taillabel="false" labeldistance=2 labelangle=290] vmalloc -> if_vmalloc_failed if_vmalloc_failed -> alloc_workqueue [taillabel="false" labeldistance=2 labelangle=70] alloc_workqueue -> if_alloc_workqueue_failed if_alloc_workqueue_failed -> negamax_init [taillabel="false" labeldistance=2 labelangle=305] negamax_init -> mcts_init mcts_init -> init_attr_lock init_attr_lock -> init_timer init_timer -> init_open_cnt init_open_cnt -> ret /* link */ edge[weight=3] goto:f2 -> out edge[weight=2] if_kfifo_failed -> ret [taillabel="true" label=" -ENOMEM" labeldistance=2] if_chrdev_region_failed -> goto:f2 [taillabel="false" label=" ret" labeldistance=5] if_cdev_add_failed -> kobject_put [taillabel="false" label=" &kxo_cdev.kobj" labeldistance=2] kobject_put -> goto:f1 if_class_create_failed -> goto:f0 [taillabel="true" labeldistance=2 label=" PTR_ERR(kxo_class)"] out -> ret if_device_create_file_failed -> goto:f0 [taillabel="true" labeldistance=2] if_vmalloc_failed -> device_destroy [taillabel="true" labeldistance=2] device_destroy -> class_destroy class_destroy -> goto:f0 if_alloc_workqueue_failed -> vfree [taillabel="true" labeldistance=3 labelangle=30] vfree -> device_destroy } ``` 參考以下連結: - [linux fs char_dev.c](https://github.com/torvalds/linux/blob/master/fs/char_dev.c) - [linux drivers class.c](https://github.com/torvalds/linux/blob/master/drivers/base/class.c) - [linux drivers core.c](https://github.com/torvalds/linux/blob/master/drivers/base/core.c) - [linux kernel workqueue.c](https://github.com/torvalds/linux/blob/master/kernel/workqueue.c) 1. 呼叫 `kfifo_alloc` 配置 `fifo` 記憶體 - 返回值 < 0 : 失敗, 結束並返回 `-ENOMEM`,第一個離開點 - 其他 : do nothing 2. 呼叫 `alloc_chrdev_region`。我們不在意 `major` number 所以交給內核處理,如果在意則要使用 `register_chrdev_region`。 ```c /** * alloc_chrdev_region() - register a range of char device numbers * @dev: output parameter for first assigned number * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: the name of the associated device or driver * * Allocates a range of char device numbers. The major number will be * chosen dynamically, and returned (along with the first minor number) * in @dev. Returns zero or a negative error code. */ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) ``` 此處配置名為 `kxo` 的裝置數量為 `NR_KMLDRV` (1)。 - 返回值 0 : 正確,獲取主裝置號 - 其他 : `goto error_alloc` 3. 呼叫 `cdev_init` 初始化 `kxo_cdev` 結構體。 ```c /** * cdev_init() - initialize a cdev structure * @cdev: the structure to initialize * @fops: the file_operations for this device * * Initializes @cdev, remembering @fops, making it ready to add to the * system with cdev_add(). */ void cdev_init(struct cdev *cdev, const struct file_operations *fops) ``` 4. 呼叫 `cdev_add` 嘗試添加 char device,以 `dev_id` 作為第一個設備號,連續增加 `NR_KMLDRV` 個同類型設備。 ```c /** * cdev_add() - add a char device to the system * @p: the cdev structure for the device * @dev: the first device number for which this device is responsible * @count: the number of consecutive minor numbers corresponding to this * device * * cdev_add() adds the device represented by @p to the system, making it * live immediately. A negative error code is returned on failure. */ int cdev_add(struct cdev *p, dev_t dev, unsigned count) ``` - 返回值 0 : do nothing - 其他 : 先用 `kobject_put` 將 `kxo_cdev.kobj` 參考記數減去 1,再 goto error region 5. 呼叫 `class_create` - class 能讓用戶方便管理同類型的裝置 - 會在 `sys/class/<class-name>` 下建立目錄 ``` /** * class_create - create a struct class structure * @name: pointer to a string for the name of this class. * * This is used to create a struct class pointer that can then be used * in calls to device_create(). * * Returns &struct class pointer on success, or ERR_PTR() on error. * * Note, the pointer created here is to be destroyed when finished by * making a call to class_destroy(). */ struct class *class_create(const char *name) ``` - 返回值是無效指標 (`IS_ERR`) : 先獲取錯誤碼後,`goto error_cdev` 6. 呼叫 `device_create` 建立 `kxo_dev` 該函式負責註冊設備到 sysfs 中,並在 /dev 建立設備節點。 此處沒有檢查返回值,是將檢查步驟與以下合併。 7. 呼叫 `device_create_file` 在對應 `/sys/class/<class-name>` 建立文件。 - 返回值 < 0 : `goto error_cdev` - 其他 : do nothing 8. 呼叫 `vmalloc`,大小為一個頁表 (4K)。 在 Linux 核心中可以透過 `kmalloc` 或 `vmalloc` 配置記憶體。在老師的[共筆](https://hackmd.io/@sysprog/linux-memory#kmalloc-vs-vmalloc)中也有談到。 >[!Note] 但建議是 128 KiB 以上才使用 kmalloc? - 返回值為空指標 : `device_destroy` -> `class_destroy` -> `goto error_cdev`。 - 其他 9. 呼叫 `alloc_workqueue` 建立 `kxo_workqueue` workqueue 允許不同的 `work_struct` 實例並行,但對於同一個 `work_struct` 實例同時間僅會由最多一個 CPU 運行。 注意目前的 `WQ_MAX_ACTIVE` (512) 是偏好值。 - 返回值為空指標: 釋放以配置的虛擬記憶體 (`vfree`) -> `device_destroy` -> `class_destroy` -> `goto error_cdev` - 其他: do nothing 10. 初始化演算法 `negamax` 和 `mcts` 11. 初始化棋盤 `table` 和相關變數 由於建立字符設備的步驟是層層推進的,這裡使用 `goto` 語法,只要將 `error` 的釋放處理和前面初始化的順序相反,便可以優雅達成釋放與 char device 相關資源也是程式的第二個離開點。 ```c out: return ret; error_cdev: cdev_del(&kxo_cdev); error_region: unregister_chrdev_region(dev_id, NR_KMLDRV); error_alloc: kfifo_free(&rx_fifo); goto out; ``` >[!Note] 或許能將所有釋放資源部分移動到 goto? ### [kxo_state_show](https://github.com/sysprog21/kxo/blob/b98a4c957524ff5b241d7703c377775e33281c0d/main.c#L46C1-L55C2) & [kxo_state_store](https://github.com/sysprog21/kxo/blob/b98a4c957524ff5b241d7703c377775e33281c0d/main.c#L57C1-L67C2) 這兩個函式是透過: ```c static DEVICE_ATTR_RW(kxo_state); ``` 被註冊到: ```c struct device_attribute dev_attr_kxo_state ``` 後者也是包含在 `DEVICE_ATTR_RW` 中。RW 指的是擁有者的權限,該設備權限可被寫為 0644。 ### game.[ch] ### mcts.[ch] ### negamax.[ch] ### util.h ### xo-user.c ### xoroshiro.[ch] ### zobrist.[ch]