--- tags: linux2023 --- # 第 7 週課堂問答簡記 ## yanjiew1 [Timsort 研究與對 Linux 核心貢獻嘗試](https://hackmd.io/@yanjiew/linux2023q1-timsort) ## DokiDokiPB [quiz4](https://hackmd.io/@DokiDokiPB/linux2023q1-quiz4) > 留意 LLRB 和典型 rbtree 的落差,見[關於紅黑樹](https://hackmd.io/@yanjiew/rbtree01) ## qwe661234 [fibdrv](https://hackmd.io/@qwe661234/linux2022q1-homework3) ## zeddyuu [fibdrv](https://hackmd.io/@zeddyuu/linux2023q1-fivdrv) [quiz4](https://hackmd.io/@zeddyuu/q4) ## fennecJ >l_offt 在 linux 中的定義是什麼 type? 在 [fibdrv.c](https://github.com/qwe661234/fibdrv/blob/master/fibdrv.c#L231) 使用到 `l_offt` 作為參數型態,可能發生什麼潛在的問題 `src`: linux v6.2.8 `l_offt`: defined in `include/linux/types.h` L46 ```c=45 #if defined(__GNUC__) typedef __kernel_loff_t loff_t; #endif ``` `__kernel_loff_t`: defined in `include/uapi/asm-generic/posix_types.h` L88 ```c=88 typedef long long __kernel_loff_t; ``` long long 的 range 為 `-9223372036854775808` ~ `9223372036854775807` ,loff_t 單位為 byte , $\approx ± 8388608$ TiB >kernel module 計算完 big num 之後是怎麼把資料傳到 user space 的 ? > 用 `__copy_to_user` 這個 kernel API 。 >那這個 API 最後面參數的 size 範圍有多大。 最後一個參數的型別為 unsigned long ,在 LP64 的機器上是 64bit 的寬度。 >對,linux 在處理這個議題的時候是尊重使用者電腦的架構決定寬度,若在 32 位元的機器上 unsigned long 的寬度則會是 32 位元。 ## chun61205 針對正整數在相鄰敘述進行 mod 10 和 div 10 操作,如何減少運算成本? ```c static void string_number_add(char *b, char *a, char *res, size_t size) { int carry = 0; for (int i = 0; i < size; i++) { int temp = (b[i] - '0') + (a[i] - '0') + carry; carry = temp / 10; temp = temp % 10; res[i] = temp + '0'; } } ``` ### 利用除法原理將 mod 10 和 div 10 合併 根據除法原理: $f = g \times Q + r$ * $f$: 被除數 * $g$: 除數 * $Q$: 商 * $r$: 餘數 可以將 mod 10 和 div 10 合併,以此來減少除法的數量。 ```c carry = temp / 10; temp = temp - carry * 10; ``` ### 利用 bitwise operation 來去除除法運算 這裡參考了 [Hacker's Delight](http://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf) 來實作除法。 #### 探討精確度 這裡採用 bitwise operation 來實作除法,因為 $10$ 有包含 $5$ 這個因數,無法完全用 $2$ 的次方項來表示,因此結果會是不準確的。然而,觀察上面的程式碼後可以發現, `temp` 不可能會大於 `19` ,因此只需要考慮 `19`~`0` 的情況即可。 我們的目標是,得到 `temp / 10` 且直到小數點後第一位都是精準的。 這時,我們可以提出一個猜想,假設我們我們目標的最大數是 `n` , `l` 則是比 `n` 還要小的非負整數。那麼假設 $n=ab$ ($a$ 是十位數 b 是個位數),且 $l=cd$ ($c$ 是十位數,$d$ 是個位數),則只要以 $n$ 算出來的除數在 $n$ 除以該除數後在經確度內,則 $l$ 除與該除數仍然會在經確度內。證明如下: 假設 $x$ 是除數, $$ a.b\leq\frac{n}{x}\leq a.b9\\ \Rightarrow\frac{n}{a.b9}\leq x\leq\frac{n}{a.b} $$ 1. 這裡可以很明顯的看出 $x$ 的右邊是 $10$ ,因此一定在精確度內。 2. $x$ 的左邊: $$ c.d\leq l\times\frac{a.b9}{n}\leq c.d9\\ \Rightarrow c.d\leq cd\times\frac{a.b9}{ab}\leq c.d9\\ $$ 1. $cd\times\frac{a.b9}{ab}$ 的左邊顯然成立 2. $cd\times\frac{a.b9}{ab}$ 的右邊: $$ c.d + \frac{0.09cd}{ab}\leq c.d + 0.09 $$ 因為 $ab > cd$ 因此上述式子依然成立。 因此,當初我的猜想也成立,接下來只需要針對 `19` 來做討論即可。 $$ 1.9\leq \frac{19}{x}\leq 1.99\\ \Rightarrow 9.55\leq x\leq10 $$ 接下來只需要找到這之中可以用除法來表示的除數即可。 #### 找到除數 方法如下,透過 bitwise operation 得到的算式必定是 $\frac{an}{2^N}$ 其中 $N$ 為非負整數, $a$ 正整數。因此只需要透過查看 $2^N$ 再配對適合的 $a$ 即可。 其中, $2^N=128, a=13,\frac{128}{13}\approx9.84$ 便是一個可以使用的解。 #### 實作除法 接著,嘗試透過 bitwise operation 來配對數字。 ```c unsigned d2, d1, d0, q, r; d0 = q & 0b1; d1 = q & 0b11; d2 = q & 0b111; q = ((((temp >> 3) + (temp >> 1) + temp) << 3) + d0 + d1 + d2) >> 7; r = temp - (((q << 2) + q) << 1); ``` 關於這段程式碼,我的思路如下: 1. 先湊出 $13$: 觀察 $13$ 後可以發現, $13=8+4+1=2^3+2^2+2^0$ ,因此,首先要做的便是,使用 bitwise operation 得到 $\frac{13temp}{8}$ $\frac{temp}{8}+\frac{temp}{2}+temp$ ```c (temp >> 3) + (temp >> 1) + temp ``` 2. 這時會發生一個問題,也就是在 right shift 後,會有部分 bits 被忽略,因此必須將它們另外考慮進來。 ```c d0 = q & 0b1; d1 = q & 0b11; d2 = q & 0b111; ``` 3. 合併步驟 1, 2 ```c ((((temp >> 3) + (temp >> 1) + temp) << 3) + d0 + d1 + d2) ``` 4. 湊出 $128$ ,也就是右移 $7$ bits ```c q = ((((temp >> 3) + (temp >> 1) + temp) << 3) + d0 + d1 + d2) >> 7; ``` 5. mod 10 就是 `temp` 減去 div 10 的結果乘與 $10$ ```c r = temp - (((q << 2) + q) << 1); ``` 其中 `(((q << 2) + q) << 1)` 就是 ($q\times 4 + q)\times2$ 也就是 $10\times q$ 測試結果如下: ```c #include <stdint.h> #include <stdio.h> int main() { for(int n = 0; n <= 19; n++){ unsigned d2, d1, d0, q, r; d0 = q & 0b1; d1 = q & 0b11; d2 = q & 0b111; q = ((((n >> 3) + (n >> 1) + n) << 3) + d0 + d1 + d2) >> 7; r = n - (((q << 2) + q) << 1); printf("q: %d r: %d\n", q, r); } return 0; } ``` ```shell q: 0 r: 0 q: 0 r: 1 q: 0 r: 2 q: 0 r: 3 q: 0 r: 4 q: 0 r: 5 q: 0 r: 6 q: 0 r: 7 q: 0 r: 8 q: 0 r: 9 q: 1 r: 0 q: 1 r: 1 q: 1 r: 2 q: 1 r: 3 q: 1 r: 4 q: 1 r: 5 q: 1 r: 6 q: 1 r: 7 q: 1 r: 8 q: 1 r: 9 ``` 結果正確。 可包裝為以下函式: ```c #include <stdint.h> void divmod10(uint32_t in, uint32_t *div, uint32_t *mod) { uint32_t x = (in | 1) - (in >> 2); /* div = in/10 ==> div = 0.75*in/8 */ uint32_t q = (x >> 4) + x; x = q; q = (q >> 8) + x; q = (q >> 8) + x; q = (q >> 8) + x; q = (q >> 8) + x; *div = (q >> 3); *mod = in - ((q & ~0x7) + (*div << 1)); } ``` 使用案例: ```c unsigned div, mod; divmod10(193, &div, &mod); ``` 延伸閱讀: * [Modulus without Division, a tutorial](http://homepage.cs.uiowa.edu/~dwjones/bcd/mod.shtml)