# 2019q1 Homework2 (kcalc) contributed by < `johnnylord` > ###### tags: `sysprog2019` ### Reviewed by `afcidk` * 可以實驗一下最後面提到的 "每當空間不夠就 realloc 兩倍的空間" 這個作法帶來的效能提升 * Commit message 裏面可以多一點描述,像是在 [065a42](https://github.com/johnnylord/fibdrv/commit/065a42f6885260bdde0cd5119ca56d0abf16e1c0) 中可以提一下為什麼要再寫一個有 thread 版本的 client 程式 ## 實驗環境 ```shell $ uname -a Linux johnnylord 4.18.0-16-generic #17~18.04.1-Ubuntu SMP Tue Feb 12 13:35:51 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux ``` ## 自我檢查清單 ### 在給定的 `calc.c` 檔案中,和 fibdrv 一樣有 character device,但註冊用的 kernel API 不同 (`register_chrdev` vs. `alloc_chrdev_region`),能否解釋其差異和適用場合呢? 系統中的 device,都有代表其 driver 的 major number,和代表 device 本身的 minor number。所以在註冊一個 device 的時候 major number 和 minor number 是兩項很重要的資訊。 `alloc_chrdev_region` 可以由開發者指定 minor number 的基底和範圍,但動態的幫開發者選取 device 的 major number。而 `register_chrdev` 則是可以讓開發者指定 device 的 major number,或是讓系統自動分配,但是 minor number 的基點和範圍是固定的。 | API | Major number | Minor number | | --- | --- | --- | | `alloc_chrdev_region` | 系統自動分配 | 開發者自行指定基底和範圍 | | `register_chrdev` | 可自動分配也可開發者指定 | 固定基底和範圍 | `alloc_chrdev_region` 比較適合使用在當開發的 driver 要給別人使用時使用,而 `register_chrdev` 比較適合本地開發時使用或是這個 driver 本身就要榜定某個 major number。大部分情況我們應該使用 `alloc_chrdev_region`,這樣的作法使的 driver 更具可攜性。 :::success 以下連結是 linux 預定義的一些 major number 和 minor number 給不同的 device。 https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt ::: ### 在 `scripts/test.sh` 檔案中,有一道命令為 `sudo chmod 0666`,這個作用為何?對於我們測試有何幫助?能否對 fibdrv 建立的 `/dev/fibonacci` device file 也套用類似修改呢?另外,請解釋 device file 在核心及使用者層級的功能 每個 file 在系統中都有其可以存取的權限,最主要可以區分為 `user`, `group`, `other` 這種不同類別的權限。 以下查看 `chmod` 的 man page ```shell $ man chmod ``` > This manual page documents the GNU version of chmod. **chmod changes the file mode bits** of each given file according to mode, which can be either a ==symbolic representation== of changes to make, or an ==octal number representing== the bit pattern for the new mode bits. 這邊用第一份作業 `fibdrv` 的 `/dev/fibnocci` 這個裝置檔案當解說 ```shell $ ls -l /dev/fibonacci crw------- 1 root root 236, 0 三 17 11:08 /dev/fibonacci ``` 輸出的第一行(column),可以這樣去詮釋 ```shell TYPE USER GROUP OTHER c rw- --- --- TYPE: 檔案的類型,這邊表示 char device USER: 擁有這個檔案的 owner 的權限,這邊代表有 read/write 權力 GROUP: 同在同一個 group,對於這個檔案的權限 OTHER: 不是此檔案的 owner 也不再同一個 group,對於檔案的權限 ``` 而第二,三行則表示此檔案的 user(owner), 和 group。因此上述 `/dev/fibnocci` 只有 `root` 有權限去對它做存取,所以在執行 `client.c` 這個程式時,必須提高權限。 ```shell $ sudo ./client ``` 如果使用 `chmod 0666 /dev/fibnocci` 則便會開放給 group, other 讀寫的權限。 ``` $ chmod 666 /dev/fibnocci $ ls -l /dev/fibonacci crw-rw-rw- 1 root root 236, 0 三 17 11:08 /dev/fibonacci ``` 如此一來,執行 `client` 就不用加 `sudo` 了。 ### 是否知曉 MathEx 如何解析給定字串,從而分離出變數和對應數學表示法呢? 由於作業說明已有充分說明如何 evaluate 一個 expression(`struct expr *`),這邊就不做說明。 這邊我用 `gdb` 做 code tracing。測試檔為 `test-simple.c`,這邊專注於分析 `x = 40, add(2, x)` 是如何被解析。以及中間相關資料結構的變化。 :::danger 已使用 GDB 追蹤 code 和儲存資料的變化,但沒有完全理解細節,待補上@@ 在 `expr_create` 中這三個資料結構會動態的做變化,解析的 token 會被放進以下資料節夠中 * vec_expr_t es * vec_str_t os * vec_arg_t as ::: ### 在 `MathEx` 原始程式碼的 `expression.[ch]` 裡頭 vec 相關的程式碼,主要做什麼事?有沒有發現類似 list 使用到的技巧呢? 有點類似於 C++ 的 template 的概念,為某一資料型態做出 vector。 ```cpp #define vec(T) \ struct { \ T *buf; \ int len; \ int cap; \ } ``` 其中 `T *buf` 是存放此 vector 所要儲存的目標資料的起始位置,`len` 為這個 vector 的長度,`cap`(我比較傾向於 capability)表示此 vector 的可以容納幾個 vector item 的空間。所以以下條件 ==$len \le cap$== 永遠成立。 ![](https://i.imgur.com/G5U7Xhz.png) `expression.h` 為 `vec` 相關的資料結構定義了以下的操作(巨集) * **vec_nth** * **vec_peek** * **vec_push** * **vec_pop** * **vec_free** * **vec_foreach** 以上這些巨集與 linux kernel 中為 linked-list 所定義的一系列操作(巨集) 有異曲同工的意思。 它們都隱藏了背後的實做,讓撰寫程式碼時更為簡潔,更專注在邏輯上。 以下可以看到 `vec_push` 的實做 ```cpp #define vec_unpack(v) \ (char **) &(v)->buf, &(v)->len, &(v)->cap, sizeof(*(v)->buf) #define vec_push(v, val) \ vec_expand(vec_unpack(v)) ? -1 : ((v)->buf[(v)->len++] = (val), 0) /* Simple expandable vector implementation */ static inline int vec_expand(char **buf, int *length, int *cap, int memsz) { if (*length + 1 > *cap) { void *ptr; int n = (*cap == 0) ? 1 : *cap << 1; ptr = realloc(*buf, n * memsz); if (ptr == NULL) { return -1; /* allocation failed */ } *buf = (char *) ptr; *cap = n; } return 0; } ``` 在實做 `vec_push` 的時候,要考慮到許多議題,例如記憶體不足的問題,vector 本身資料結構的更新維護。但是我們都用巨集定義將這些細節隱藏了起來。 :::success 這邊 vector 的作法,每當空間不夠就 realloc 兩倍的空間,這樣預先分配多的記憶體,可以減少之後每次需要增加記憶體的次數。 如果是 linked list 則每次增加元素,就要做一次記憶體分配,會脫慢速度。 :::