# Linux 核心專題: 亂數產生器研究
> 執行人: ShamrockLee
> [期末專題講解影片](https://youtu.be/dZ66QkoA5YI)
> [簡報](https://docs.google.com/presentation/d/18_HDogktJwnkModepk0hZDRlmVGEcARfH4GRRdwAujo/edit?usp=sharing)
:::info
勘誤:
* 範圍: 簡報、期末專題解說影片的「 PRNG 介紹」
```diff
-虛擬亂數產生器是一種能產生性質類似亂數的數列的遞迴函數
+虛擬亂數產生器是一種能產生性質類似亂數的數列的演算法
```
解釋:
* 如果直接以遞迴函數作為 PRNG 來產生數列,那只要知道比該遞迴函數的輸入還要多的項,就能預測往後的數值,密碼學上不安全。
* PRNG 內部的狀態 (state) 變化能夠由遞迴函數描述,而輸出值是由內部狀態透過令一個函數決定。
$$
\begin{cases} state_{i+1} = f(state_i) \\ output_i = g(state_i) \end{cases}
$$
* $state_0$ 由亂數種子 (seed) 決定。
:::
:::success
:question: 提問清單
* Tausworthe generator 一度使用於 Linux 核心 (`lib/random32.c`) 作為 PRNG,後續 Linux 改版時,基於什麼考量變更 PRNG 呢?
> 根據 Willy Tarreau 所貢獻的 commit [`c51f8f88d705`](https://github.com/torvalds/linux/commit/c51f8f88d705e06bd696d7510aff22b33eb8e638) 當中的 commit message ,利用 Tausworthe generator (LFSR) 實作的 PRNG 能讓攻擊者從一段已知的數列推測其他部份的值,導致運用於 SSL 等需要 CSPRNG 的場合時不安全。
> 該 commit 解決的方式,是將 prandom ( Linux PRNG )改以基於 SipHash 的演算法實作。[name=ShamrockLee]
:::
## 任務簡述
[第 3 週測驗題](https://hackmd.io/@sysprog/linux2023-quiz3)第三題介紹到 Linear feedback shift register (LFSR),這也是 Linux 核心亂數產生器 (RNG) 的基礎之一。本任務探討 [Linux RNG](https://www.amossys.fr/fr/ressources/blog-technique/linux-csprng-architecture/) 的原理、實作機制,以及應用場景。
相關素材:
* [初探 Linux kernel 亂數產生器 – random generator](https://szlin.me/2016/10/05/%E5%88%9D%E6%8E%A2-linux-kernel-%E4%BA%82%E6%95%B8%E7%94%A2%E7%94%9F%E5%99%A8-random-generator/)
* [The Raspberry Pi’s Hardware Random Number Generator](http://scruss.com/blog/2013/06/07/well-that-was-unexpected-the-raspberry-pis-hardware-random-number-generator/)
## 重做[第 3 週測驗題](https://hackmd.io/@sysprog/linux2023-quiz3)第三題,並彙整其他學員的成果
### 線性回饋移位暫存器原理與 Linux 核心當中的實作與應用
線性回饋移位暫存器,正如其名,是指遞迴關係能以線性函數(具疊加性、一次齊次性的函數)表達、主要以一串位移暫存器組成的電子電路,或上述電路所代表的演算法。
Fibonacci LFSR (又稱 Maximum-length LFSR )由一列相接的移位暫存器組成,邏輯電平隨時脈朝右方傳遞,並透過對事先選定的幾個暫存器值做抑或運算決定最左方的數值。其值構成的序列稱為 maximum-length sequence ( MLS 、 M-sequence )。
以軟體實作相應的 PRNG 時,能定義 `lfsr` 函式,其輸入值為指向無號整數的指標,該整數的各個位元即對應到電字電路各個移位暫存器的邏輯電平。以最高位對應上述最左端的暫存器,並以位元右移模擬移位暫存器的行為。
![Fibonacci 線性回饋移位暫存器](https://upload.wikimedia.org/wikipedia/commons/2/28/LFSR-F16.svg)
(圖片取自 Wikipedia,以 CC0 授權。原圖為 SVG 檔。之後有時間的話,打算以 Graphviz 重製。)
上圖展示一個 Fibonacci 線性回饋移位暫存器,以第 11, 13, 14, 16 位元的值做 XOR 運算後指派到第 1 位元,而第 k 位元的值指派到第 k + 1 位元($k \ge 1$)。
(目前未能理解 $x^{16} + x^{14} + x^{13} + x^{11} + 1$ 與上述運算的關聯。)
> 多項式的乘方跟 64-bit 整數第幾位數正好相反。第 0 位是 $x^{64}$ ,第 1 位是 $x^{63}$ 故式子中 `(*up) >> 3` 是代表 $x^{64-3}$ 為 $x^{61}$ 以此類推。
> `lib/random32.c` 內有說明。 PRNG 用 LFSR 去產生。
以下測驗 `3` 中的程式碼,便是利用 `((*up) >> (64 - k)) & 1` 取得 $2^{64 - k}$ 位的值以做 XOR 運算,接著利用 `((*up) >> 1) | (new_bit << 63)` 得到將運算結果指派到 $2^{63}$ 位,其他位右移一位的值,並指派回 `*up` 。
```c
/* Implementation of LFSR (linear feedback shift register)
* on uint64_t using irreducible polynomial x^64 + x^61 + x^34 + x^9 + 1
* (On 32 bit we could use x^32 + x^22 + x^2 + x^1 + 1)
*/
static void lfsr(uint64_t *up)
{
uint64_t new_bit =
((*up) ^ ((*up) >> 3) ^ ((*up) >> 30) ^ ((*up) >> 55)) & 1u;
/* don't have to map '+1' in polynomial */
*up = ((*up) >> 1) | (new_bit << 63);
/* shift *up by 1 to RIGHT and insert new_bit at "empty" position */
}
```
以線性回饋移位暫存器的原理實作的 PRNG 又稱為 Tausworthe 產生器( Tausworthe generator ),或許和 Robert C. Tausworthe 於 1965 年在期刊 *Mathematics of Computation* 上發表的論文 *Random Numbers Generated by Linear Recurrence Modulo Two* 有關。這個別稱在 Linux 核心專案中也有出現。
利用
```bash
git log --grep='\(LFSR\|[Ll]inear [Ff]eedback [Ss]hift [Rr]egister\|[Tt]ausworthe\)'
```
能列出 Linux 核心原始碼中,所有 commit message 提到 LFSR 的 commits 。截至 `v6.4-rc6` ( commit `858fd168a95c` ),在 `master` 分支上共有[ 23 個 commits ](https://gist.github.com/ShamrockLee/14bdd8b71c75aedd4a3a508fd642da4b#file-git_log_lk_grep_lfsr_till_6-4-rc6-log)含有此關鍵字。再以關鍵字搜尋程式碼,能幫助我們了解 LFSR 實作於 Liunx 核心的過程。
Linux 核心專案於 `2.6.2-rc2` 開始採用 Git 版本控制系統,對應的 commit `1474855d0878` 也是專案中最早的 commit 。而在這之前, Fibonacci LFSR 實作就已出現在 `drivers/net/ewrk3.c` 的函數 `get_hw_addr` 中,作為 EtherWORKS 乙太網路卡 driver 的一部分。
另外在 `net/core/utils.c` 當中, `net-random` 函數時做了 taus88 演算法,是一種 maximally equidistributed combined Tausworthe generator ,是由 Pierre L'Ecuyer 所設計的演算法,週期大約為 $2^{88}$ 。
當時雖然已有 `include/linux/random.h` ,但直到 commit `aaa248f6c9c8` (發布於 `2.6.19-rc3` ), `net_random` 才獨立成 `lib/random32.c` 當中的 `random32` ,並宣告於 `include/linux/random.h` 中。
`random32` 經過一系列改進,包含 reseeding 與升級演算法到 taus113 ( commit `a98814cef879` )。
Fibonacci LFSR 或 Galoise LFSR 仍被當做一種簡單的 PRNG ,在 driver 、 profiler 等處實作。
### 整數 $\log_2$ 在 Linux 核心當中的實作
整數 $\log_2$ 函數在 Linux 核心當中以 `ilog2` 命名。
Linux 核心專案中, `include/linux/ilog2.h` 提供 `__ilog2_u32` 、 `__ilog2_u64` 函數,及 `log2` 、 `const_log2` 巨集。前二者事實上就只是回傳 `fls(n) - 1` (或 `fls64(n) - 1`)。
巨集 `log2(n)` 針對輸入型別選擇要呼叫的函數。巨集 `const_log2(n)` 主要在提供類似 C++ `constexpr` 在編譯時求值的支援。實作方式是藉由 GCC extension `__builtin_constant_p(n)` 偵測輸入值是否在編譯階段已知;若是則傳入一連串條件運算子,從左至右一一確認每個位元是否為零;若否則調用 `log2(n)` 巨集。
Linux 核心針對不同的處理器指令提供不同的 `fls` 實作,針對有支援 GCC extension `__builtin_clz(n)` 的環境,直接使用 `32 - __builtin_clz(n)` 或 `64 - __buitlin_clz(n)` 。針對其他指令集,則使用 inline assembly 實作。另外有 `include/asm-generic/bitops/fls.h` ,使用以下 C 語言實作,透過二分搜尋來找到變數左方位元皆為零的區域大小。
```c
/**
* fls - find last (most-significant) bit set
* @x: the word to search
*
* This is defined the same way as ffs.
* Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32.
*/
static __always_inline int fls(unsigned int x)
{
int r = 32;
if (!x)
return 0;
if (!(x & 0xffff0000u)) {
x <<= 16;
r -= 16;
}
if (!(x & 0xff000000u)) {
x <<= 8;
r -= 8;
}
if (!(x & 0xf0000000u)) {
x <<= 4;
r -= 4;
}
if (!(x & 0xc0000000u)) {
x <<= 2;
r -= 2;
}
if (!(x & 0x80000000u)) {
x <<= 1;
r -= 1;
}
return r;
}
```
#### 無分支、不使用 extension 的 `fls` 實作嘗試
Linux 核心專案未提供,不使用編譯器 extension 且平台無關的 `fls` 實作。我嘗試改寫上述平台無關的實作,以
```c
x = x << (!(x & 0xffff0000u)) << 4
```
或
```c
static int x1 = x << (!(x & 0xffff0000u)) << 4
```
取代
```c
if (!(x & 0xffff0000u)) {
x <<= 16;
}
```
試圖避開(顯式)分支,但以 `gcc -O2 -std=c99 -S` 編譯後,卻發現編譯產生的組合語言,分支和原本的實作一樣多。
我另外在 [quiz2](https://hackmd.io/@ShamrockLee/sysprog2023q1-quiz2#%E5%BB%B6%E4%BC%B8%E5%95%8F%E9%A1%8C%EF%BC%9A%E7%84%A1%E5%88%86%E6%94%AF%E7%9A%84-floor_log2_u32-%E8%88%87-ceil_log2_u32-%E5%AF%A6%E4%BD%9C%EF%BC%88%E5%90%AB%E4%BE%8B%E5%A4%96%E8%99%95%E7%90%86%EF%BC%89) 中實作了,但指令數量從 Linux 核心實作的 24 個,上升到 40 個。尚未測試過效能,但以指令數量的成長來看,可能會造成效能損失。即使如此,在有 constant time 要求或其他需要避免分支的場合,還是有潛在用途。
:::warning
對照〈[回顧 bitops 並改進](https://hackmd.io/@sysprog/ByS9w_lHh)〉
:notes: jserv
:::
上述無分支的 `fls` 實作,其原理為
1. 將最高非零位元以外的位元設為零
2. 以 bit masks 做二分搜尋,算出非零位元的索引
另外,將第一部的「最高非零位元」改為「最低非零位元」,就能實作 `fls` 。
〈[回顧 bitops 並改進](https://hackmd.io/@sysprog/ByS9w_lHh)〉制定 `gen_ffs` 與 `gen_fls` 巨集界面,並透過 `_Pragma("GCC unroll 6")` 運算子在巨集內展開迴圈( unroll loop ),不增加 branch 就能相容不同整數型態寬度的輸入值。(選用 6 是因為 $2^6 = 64$ 。〈回顧 bitops 並改進〉文中也提到,若未來要適用於 128-bit 的輸入值,能上調為 7 。)
上述技巧僅適用於 GCC 編譯器。考慮到 Clang 也支援 loop unrolling ,能定義巨集
```c
#ifdef __clang__
#define unroll_ffls_branchless _Pragma("clang loop unroll_count(6)")
#elif __GNUC__ >= 8
#define unroll_ffls_branchless _Pragma("GCC unroll 6")
#else
#define unroll_ffls_branchless
#endif
```
並將 `unroll_ffls_branchless` 加在巨集定義的迴圈之前,這樣無論以 GCC 與 Clang 編譯,迴圈都能正確展開。類似的作法出現在 Linux 核心 [`i40e_xsk` driver](https://github.com/torvalds/linux/blob/v6.4-rc7/drivers/net/ethernet/intel/i40e/i40e_xsk.h) 、 [`ice_xsk` driver](https://github.com/torvalds/linux/blob/v6.4-rc7/drivers/net/ethernet/intel/ice/ice_xsk.h) 當中。
利用前述迴圈展開技巧,能將 `fls` 、 `ffs` 實作所需的步驟拆解為三個巨集
* `__preserve_msb_branchless` 「保留非零最高位元」的無分支實作
* `__preserve_lsb_branchless` 「保留非零最低位元」的無分支實作
* `__locate_bit_branchless` 「定位非零位元索引」的無分支實作
對照 `gen_fls` 與 `gen_ffs` 界面,能分別實作 `gen_fls_branchless` 與 `gen_ffs_branchless` 巨集。其界面相容於,能如同 `gen_f{f,l}s` 一般套用到相關 `clz` 、 `ctz` 等函數實作。
〈回顧 bitops 並改進〉對 Linux 核心當中現有實作分析後,發現「索引從 1 開始」與「索引從 0 零開始」兩種風格,並以「回傳 0 」處理「索引從 0 開始」風格的例外(輸入值為 0 的情形)。這樣一來,「從 0 開始」在輸入值為 0 、為 1 時會對應到同樣的值,而有資訊損失。因此我針對「從 1 開始」實作運算步驟,並在 `gen_fls_branchless` 與 `gen_ffs_branchless` 內處理「從 0 開始」的例外。
「從 1 開始」特別考慮輸入值為零的情形,拆解的三個巨集都要具備處理此種例外的步驟。上述例外處理主要透過 `!!` 將數值對應到 0 或 1 (編譯成組合語言後會是 `TEST` 指令),或使用 bit mask 。
以下是實作的程式碼:
`__preserve_msb_branchless`:
```c
/* Preserve the most significant bit set of X and unset others.
* Return zero when non set.
*/
#define __preserve_msb_branchless(X, type) \
do { \
type __is_nonzero = !!(X); \
(X) = (X) >> 1; \
unroll_ffls_branchless for (size_t _i_exp = 1; \
_i_exp < (sizeof(X) << 3); _i_exp <<= 1) \
{ \
(X) |= (X) >> _i_exp; \
} \
(X) += __is_nonzero; \
} while (0)
```
不考慮整數類型寬度與 0 輸入值處理的話,這段程式的行為就是
1. 將非零最高位以下的位元都設為 1 ,本身設為 0
```c
x = x >> 1;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
// ...
```
2. 加一後就留下目標位元
```c
x += 1;
```
例外處理的部份,則是考慮當輸入值為零時,步驟 1 做完之後 x 會是 0 ,和 輸入值是 1 的情形無法分別,故須在進行步驟 1 之前先儲存 `!!x` ,以決定步驟 2 是否要加一。
`__preserve_lsb_branchless`:
```c
/* Preserve the least significant bit set of X and unset others.
* Return zero when non set.
*/
#define __preserve_lsb_branchless(X, type) \
do { \
unroll_ffls_branchless for (size_t _i_exp = 1; \
_i_exp < (sizeof(X) << 3); _i_exp <<= 1) \
{ \
(X) |= (X) << _i_exp; \
} \
type _x_upperfilled = (X); \
(X) = ~(X) + (!!_x_upperfilled); \
(X) &= _x_upperfilled; \
} while (0)
```
仿照 `__preserve_msb_branchless` 的思路,這次把最低非零位元右方的位元都設為 1 ,這樣為 0 的位元剛好就都在目標位元左方。
這時套用 bitwise NOT 翻轉所有位元,就呈現類似 `__preserve_msb_branchless` 第一部進行完之後的情形,接著只要加 1 就好。
`__locate_bit_branchless`:
```c
/* Take in an unsigned integer with at most one bit set, and then
* output the 1-based digit of that bit. Return 0 if no bits are set.
*
* Bits higher than (2**64 - 1) will be truncated.
*
* Determine the result bit value through binary search with the following
* masks:
*
* 0b01010101'01010101'01010101'01010101'01010101'01010101'01010101'01010101u
* 0b01100110'01100110'01100110'01100110'01100110'01100110'01100110'01100110u
* 0b01111000'01111000'01111000'01111000'01111000'01111000'01111000'01111000u
* 0b01111111'10000000'01111111'10000000'01111111'10000000'01111111'10000000u
* 0b01111111'11111111'10000000'00000000'01111111'11111111'10000000'00000000u
* 0b01111111'11111111'11111111'11111111'10000000'00000000'00000000'00000000u
* 0b10000000'00000000'00000000'00000000'00000000'00000000'00000000'00000000u
*/
#define __locate_bit_branchless(x_pow2, in_type, out_type) \
((out_type) (((!!((x_pow2) & ((in_type) (((in_type) ~(in_type) 0) & \
0x5555555555555555u)))) \
<< 0) + \
((!!((x_pow2) & ((in_type) (((in_type) ~(in_type) 0) & \
0x6666666666666666u)))) \
<< 1) + \
((!!((x_pow2) & ((in_type) (((in_type) ~(in_type) 0) & \
0x7878787878787878u)))) \
<< 2) + \
((!!((x_pow2) & ((in_type) (((in_type) ~(in_type) 0) & \
0x7f807f807f807f80u)))) \
<< 3) + \
((!!((x_pow2) & ((in_type) (((in_type) ~(in_type) 0) & \
0x7fff80007fff8000u)))) \
<< 4) + \
((!!((x_pow2) & ((in_type) (((in_type) ~(in_type) 0) & \
0x7fffffff80000000u)))) \
<< 5) + \
((!!((x_pow2) & ((in_type) (((in_type) ~(in_type) 0) & \
0x8000000000000000u)))) \
<< 6)))
```
`gen_fls_branchless`:
```c
/* Branchless version of gen_fls */
#define gen_fls_branchless(in_type, out_type, start_from, fn_name) \
static __always_inline out_type fn_name(in_type x) \
{ \
__preserve_msb_branchless(x, in_type); \
out_type _ret = __locate_bit_branchless(x, in_type, out_type); \
return _ret - (out_type) ((!(start_from)) & (!(_ret))); \
}
}
```
`gen_ffs_branchless`:
```c
/* Branchless version of gen_ffs */
#define gen_ffs_branchless(in_type, out_type, start_from, fn_name) \
static __always_inline out_type fn_name(in_type x) \
{ \
__preserve_lsb_branchless(x, in_type); \
out_type _ret = __locate_bit_branchless(x, in_type, out_type); \
return _ret - (out_type) ((!(start_from)) & (!(_ret))); \
}
```
2023-07-11 更新:
根據 Linux 核心官方文件 [Minimal requirements to compile the Kernel](https://www.kernel.org/doc/html/latest/process/changes.html) , Linux 核心專案支援以 5.1 版或之後的 GCC 編譯。這代表 Linux 核心內的程式,以受到支援的編譯器版本編譯,產生的目的碼都必須有正確的行為。
`gen_f{f,l}s_bratchless` 的價值在於產生沒有分支的 `ffs` 、 `fls` 實作,但前述實作用到的 `_Pragma("GCC unroll 6")` 功能,在 GCC 8才加入。原本設想以 conditional directives 定義巨集 `unroll_ffls_branchless` 以避免在未支援此 directive 的編譯器上編譯時產生的警告訊息或錯誤,但這也意謂在上述較低版本的編譯器上,迴圈可能根本沒展開,而導致出現分支。這對於 `gen_f{f,l}s` 可能只是效能下降,但對於 `gen_f{f,l}s_branchless` 來說是行為不正確,必須改善。
以下是改善後的實作:
```c
/* Preserve the most significant bit set of X and unset others.
* Return zero when non set.
*/
#define __preserve_msb_branchless(X, type) \
do { \
type _is_nonzero = !!(X); \
(X) >>= 1; \
(X) |= (X) >> 1; \
(X) |= (X) >> 2; \
(X) |= (X) >> 4; \
(X) |= (X) >> ((sizeof(type) > 1) << 3); \
(X) |= (X) >> ((sizeof(type) > 2) << 4); \
(X) |= (X) >> ((sizeof(type) > 4) << 5); \
(X) += _is_nonzero; \
} while (0)
```
```c
/* Preserve the least significant bit set of X and unset others.
* Return zero when non set.
*/
#define __preserve_lsb_branchless(X, type) \
do { \
(X) |= (X) << 1; \
(X) |= (X) << 2; \
(X) |= (X) << 4; \
(X) |= (X) << ((sizeof(type) > 1) << 3); \
(X) |= (X) << ((sizeof(type) > 2) << 4); \
(X) |= (X) << ((sizeof(type) > 4) << 5); \
type _x_upperfilled = (X); \
(X) = ~(X) + (!!_x_upperfilled); \
(X) &= _x_upperfilled; \
} while (0)
```
為了不依賴迴圈展開就能相容不同整數類型寬度,我們能手動展開至符合 64 位元整數類型的執行次數,再將每次執行欲移動的位元數與類型寬度比較,再用這項布林值決定是否歸零移動的位元數。由於比較的部份在編譯時期已知,能期待編譯器將之最佳化而不產生分支。
### `lab0-c` 中 [`log2_lshift16.h`](https://github.com/sysprog21/lab0-c/blob/master/log2_lshift16.h) 的原理與效能改善
#### 原理
`lab0-c` 當中的 `log2_lshift16.h` 儲存有預先計算好的 $\log_2$ 值對應關係。其結構是由條件控制所建立的 search tree 。其輸入值比數學上的值向左偏移 16 位元,輸出值比數學上的值向左偏移 3 位元,以整數表示定點數( fixed-point number )。
由輸出值皆為負數看來,輸入值對應到定點數 $(0, 1]$ 的範圍。
#### 效能改善
藉由 $\log$ 的數學特性,能將輸入值限縮在更小的範圍,強制其小數點後第一位(整數的 $2^{15}$ 位)為 1 ,否則先左移再查詢。這樣就能降低樹高,減少查詢時間。搭配指令集對 `__builtin_clz64` 的支援,預期能達到更高的效能,並減少檔案大小。
<!--
### TODO
1. 解釋 [Linear feedback shift register](https://en.wikipedia.org/wiki/Linear-feedback_shift_register) (LFSR) 的運作原理,搭配在 [Linux 核心原始程式碼](https://github.com/torvalds/linux)中,用 `git log` 和 `grep` 找出 LFSR 的應用案例
2. 解釋給定程式碼運作的原理
3. 在 Linux 核心原始程式碼中找到 $\log_2(x)$ 的實作 (整數),要涵蓋不同的整數型態寬度,以及是否能運用微處理器提供的指令來加速。也從 Linux 核心找出整數 $\log_2(x)$ 的應用案例並解說。
4. `lab0-c` 提供類似的實作 [log2_lshift16.h](https://github.com/sysprog21/lab0-c/blob/master/log2_lshift16.h),解釋其原理並嘗試改進其效能
-->
## Linux RNG 架構與文獻探討
### 隨機事件收集
* Linux 核心從多重來源蒐集隨機事件並估計其「熵」
* 硬體隨機數產生器( HWRNG )
* 鍵盤、滑鼠輸入(計算週期數)
* 中斷事件(計算週期數)
* Linus Jitter Dance
### 隨機事件混合
* LFSR
### TODO: 研讀 Linux RNG 相關文獻並記錄自己的認知和疑惑
至少要涵蓋以下:
* [random.c - Inside the Linux Kernel's RNG](https://www.zx2c4.com/projects/linux-rng-5.17-5.18/inside-linux-kernel-rng-presentation-sept-13-2022.pdf) / [演講錄影](https://t.co/kewXWerNmI)
* [Linux RNG architecture](https://www.amossys.fr/fr/ressources/blog-technique/linux-csprng-architecture/)
* [Documentation and Analysis of the Linux Random Number Generator](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/LinuxRNG/LinuxRNG_EN.pdf)
儘量紀錄理解狀況、彙整文件,並紀錄問題。藉由上述認知回覆下方問題:
* Linux RNG 如何達到 [CSPRNG](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) 要求?以真實應用場景來舉例說明
* LFSR 在 Linux RNG 的實作有何影響?以 Linux 核心原始程式碼搭配解說
* Linux 的 `/dev/random` 和 `/dev/urandom` 對應到內部哪些實作?如何運用硬體 RNG 或者相關機制 (例如 Raspberry Linux 的 [/dev/hwrng](https://www.arch13.com/raspberry-pi-random-number-generation/)) 呢?如何確保其「夠亂」呢?
## TODO: 撰寫 Linux 核心模擬測試 RNG 框架並提供可能的 `hwrng` 實作
對 Linux RNG 有足夠認知後,撰寫 Linux 核心模組來測試,並搭配 Linux 核心原始程式碼和〈[Documentation and Analysis of the Linux Random Number Generator](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/Studies/LinuxRNG/LinuxRNG_EN.pdf)〉,探討如何運用硬體特性作為有效的 entropy source,從而實作 hwrng。