# 2023q1 Homework3 (quiz3) contributed by < [zeddyuu](https://github.com/zeddyuu) > # 測驗 `1` --- # 測驗 `2` --- # 測驗 `3` ## LFSR 的原理以及 Linux kernel 中的應用案例 LFSR 指的是給定前一狀態的輸出,再將此輸出通過一個線性函數當作下次輸入的移位暫存器,且由於暫存器的狀態是有限的,最終會是一個重複的循環。 在 bash 中下以下指令可以用 commit 訊息關鍵字去搜尋 commit histroy ```shell git log --grep='LFSR' ``` 可以找到 [dec0fb3](https://github.com/torvalds/linux/commit/dec0fb3946c478e7bb6165a1b7a03092ceefd419) 在此 commit message 中包含關鍵字 `LFSR` 在 Linux kernel 中 LFSR 相關的應用的案例可以在 [linux/crypto/jitterentropy.c](https://github.com/torvalds/linux/blob/master/crypto/jitterentropy.c) 中的 `jent_lfsr_time` 函式中發現 Fibonacci LSFR 的應用。 ## 解釋上述程式碼運作原理 `log2_64()` 函式的運作原理是利用位移 64 位元的數值,並且在位移後用分支來確認數值在於哪兩個 2 的冪之間,再利用 `log_table_256` 將剩餘的高位 8 個位元(數值在0~255以內)找出對應在 2 的哪個冪加上去即完成函式找出 `log2` 的功能。 舉例來說像是 $260_{2} = 100000100$ 進行計算,經過一系列分支最後只會跑右移 8 位元,高位仍然有值,故此值會在 $2^{8}$ ~ $2^{16}$ 之間,之後利用 `log_table_256` 去找出剩餘高位位元數值對應到哪個哪一個 2 的冪,也就是查表中 1 對應的 2 的冪為 0,因為確定此值在 $2^{8}$ ~ $2^{16}$ 故將 8 + 0 = 8,計算完成。 比較特別的是 `log_table_256` 的宣告方式 ```c static const char log_table_256[256] = { #define _(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, _(4), _(5), _(5), _(6), _(6), _(6), _(6), _(7), _(7), _(7), _(7), _(7), _(7), _(7), _(7), #undef _ }; ``` 其實可以寫成 ```c #define _(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n static const char log_table_256[256] = { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, _(4), _(5), _(5), _(6), _(6), _(6), _(6), _(7), _(7), _(7), _(7), _(7), _(7), _(7), _(7), #undef _ }; ``` 這樣就可以看出是透過巨集展開來達到產生 log table,以前總覺得巨集只能出現在 code 最前方定義,但在這邊學到巨集可以出現在程式碼中任何地方,並且對應到 [你所不知道的 C 語言](https://hackmd.io/@sysprog/c-prog/%2Fs%2FS1maxCXMl#_Generic-C11) 中提到的巨集實際的作用為產生程式碼,後先經過 preprocessor 後再交由 compiler 編譯程式。 --- # 測驗 `4` ```c int ceil_log2(uint32_t x) { uint32_t r, shift; x--; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; return r | shift | (x >> 1) + 1; } ``` ## 解釋上述程式碼運作原理 此題的原理是每次檢查 x 有沒有超過 2 的冪個位元數,檢查過後再根據結果用 OR 操作做 bit set 去將 shift 的第 $log_2$ 位元做 set,代表此值一定大於此時 2 的冪,例如 $6_{2} = 0110$,檢查是否大於 $1111$,沒有代表此時的值不超過 $2^{4}$,再檢查是否大於 $0011$,有就把 shift 做 OR 操作 set bit 成 $0010$,代表此時的值必定大於等於 $2^{2}$,且可用於右移 x 繼續檢查是否大於 $0001$,沒有所以結束,因為要取 ceil 所以將結果 + 1 完成計算,另外一開始會減 1 是為了處理 x 剛好是 2 的冪 的情況。 故程式碼還缺少一次檢查是否大於 $2^{0} = 1$ ```c r |= shift shift = (x > 0x1) << 0; r |= shift; ``` 可以再簡化為 ```c r | shift | (x >> 1) ``` :::success TODO 改進程式碼,使其得以處理 x = 0 的狀況,並仍是 branchless 在 Linux 核心原始程式碼找出 ceil 和 log2 的組合 (類似的形式),並解說應用案例,例如在 Linux 核心排程器就有 :::