# 2023q1 Homework3 (fibdrv)
contributed by < [`chiwawachiwawa`](https://github.com/Chiwawachiwawa) >
## 作業環境
```
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
CPU family: 6
Model: 158
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
Stepping: 9
CPU max MHz: 3800.0000
CPU min MHz: 800.0000
BogoMIPS: 5599.85
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mc
a cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss
ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art
arch_perfmon pebs bts rep_good nopl xtopology nonstop_
tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cp
l vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1
sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsav
e avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault
epb invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow
vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust
bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap cl
flushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm
ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp
md_clear flush_l1d arch_capabilities
Virtualization features:
Virtualization: VT-x
Caches (sum of all):
L1d: 128 KiB (4 instances)
L1i: 128 KiB (4 instances)
L2: 1 MiB (4 instances)
L3: 6 MiB (1 instance)
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0-7
Vulnerabilities:
Itlb multihit: KVM: Mitigation: VMX disabled
L1tf: Mitigation; PTE Inversion; VMX conditional cache flushe
s, SMT vulnerable
Mds: Mitigation; Clear CPU buffers; SMT vulnerable
Meltdown: Mitigation; PTI
Mmio stale data: Mitigation; Clear CPU buffers; SMT vulnerable
Retbleed: Mitigation; IBRS
Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl
Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer
sanitization
Spectre v2: Mitigation; IBRS, IBPB conditional, RSB filling, PBRSB-
eIBRS Not affected
Srbds: Mitigation; Microcode
Tsx async abort: Not affected
```
## 遇到的問題
我在執行
```
make check
```
會遇到一個錯誤
這邊需要進行一個步驟,如果我們是利用 windows 筆電進行重灌 ubuntu ,會因為我們在 bios 預設 secure boot ,會導致錯誤,這邊關閉就好。
## 研讀上述 Linux 效能分析的提示 描述,在自己的實體電腦運作 GNU/Linux,做好必要的設定和準備工作,從中也該理解為何不希望在虛擬機器中進行實驗
### perf
我是依據這篇文章來使用的( [from ncku csie](http://wiki.csie.ncku.edu.tw/embedded/perf-tutorial) )
這個工具可以來獲取我們機器上的一些數據,並且依照我們給定的權限,來盡可能地算出最直準確的值,
其中會有一些重要的數據需要讀取,例如:
### 為何我們需要在實體機器上面實行實驗,並且測量:
因為我們再進行效能測量時,會需要最直觀的 baer metal 數據,但是如果今天我們使用"虛擬的"機器來進行測量,我們需要對一些維持這個環境所需的程式,這樣會導致我們的硬體,例如 cpu 無法發揮他百分之百的性能,這樣我們透過 PERF 所獲得的數據,必定是不標準的。
我在嘗試使用 ktime 時遇到幾個問題,我操考了其他同學再測量時效時都指定了特定的 CPU 因此我也在實體機器上面嘗試設定成功了,但是我在虛擬機( virtual box )上面無法成功配置。
## 研讀上述費氏數列相關材料 (包含論文),摘錄關鍵手法,並思考 clz / ctz 一類的指令對 Fibonacci 數運算的幫助。請列出關鍵程式碼並解說
## F(92)以後的數值錯誤的原因
我在進行 `make check` 之後遇到跟題目所述一樣的情況,進行到 f(92) 會 fail,
因此要進行改善
```
n96111150@n96111150:~/fibdrv$ make check
make -C /lib/modules/5.19.0-32-generic/build M=/home/n96111150/fibdrv modules
make[1]: Entering directory '/usr/src/linux-headers-5.19.0-32-generic'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
You are using: gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
make[1]: Leaving directory '/usr/src/linux-headers-5.19.0-32-generic'
make unload
make[1]: Entering directory '/home/n96111150/fibdrv'
sudo rmmod fibdrv || true >/dev/null
[sudo] password for n96111150:
rmmod: ERROR: Module fibdrv is not currently loaded
make[1]: Leaving directory '/home/n96111150/fibdrv'
make load
make[1]: Entering directory '/home/n96111150/fibdrv'
sudo insmod fibdrv.ko
make[1]: Leaving directory '/home/n96111150/fibdrv'
sudo ./client > out
make unload
make[1]: Entering directory '/home/n96111150/fibdrv'
sudo rmmod fibdrv || true >/dev/null
make[1]: Leaving directory '/home/n96111150/fibdrv'
Passed [-]
f(93) fail
input: 7540113804746346429
expected: 12200160415121876738
```
#### 修改後的版本
閱讀相關的文章之後,
我的思路是先做出一個版本(不需要使用 big_num ):
1.先讓這段程式可以使用 fast_doubling 來動
其中我們要盡量使用 clz 指令來進行加速,
```c
static long long fib_sequence(int k)
{
if (k < 2)
return k;
long long first_element = 0;
long long second_element = 1;
long long cover_bits;
for(int i = 31 - __builtin_clz(k); i>=0; --i){
long long temp_1 = first_element * (2 * second_element - first_element);
long long temp_2 = first_element * first_element + second_element * second_element;
long long process_bits = 1UL<<i;
cover_bits = -!!(k&(process_bits));
first_element = (temp_1 & ~cover_bits) + (temp_2 & cover_bits);
second_element = (temp_1 & cover_bits) + temp_2;
}
return first_element;
}
```
程式碼說明:
#### 下一步,使程式可以進行到 f(92)以後
更改完畢之後,我要來處理進一步的優化,
我們來閱讀作業下面關於大數運算的部份,以及可以使 f(92)以後不出現 fail
首先我們要先來找出問題所在。
可以看到我們使用long long 來進行數值的 return,
這樣會導致在 fib(92) 之後產生 overflow,
為了解決這個問題,我要來閱讀另外一個專案 big_num ,並且詳細的內容要來研讀。
### 研讀 big_num 相關檔案
### 依照範例開始改良 bn 的基本操作
### 結構
```c
typedef struct _bn {
unsigned int *digits;
unsigned int size;
int sign;
} bn;
```
### bn_clz
```c
static int bn_clz(const bn *insert)
{
int count = 0;
for (int i = insert->size - 1; i >= 0; i--) {
if (insert->digits[i]) {
count += __builtin_clz(insert->digits[i]);
return count;
} else {
count += 32;
}
}
return count;
}
```
### bn_alloc
```c
bn *bn_alloc(size_t size)
{
bn *new = (bn *) kmalloc(sizeof(bn), GFP_KERNEL);
new->digits = kmalloc(sizeof(int) * size, GFP_KERNEL);
memset(new->digits, 0, sizeof(int) * size);
new->size = size;
new->sign = 0;
return new;
}
```
### bn_free
```c
int bn_free(bn *insert)
{
if (insert == NULL)
return -1;
kfree(insert->digits);
kfree(insert);
return 0;
}
```
### bn_resize
```c
static int bn_resize(bn *insert, size_t size)
{
if (!insert)//if insert is not a ptr
return -1;
if (size == insert->size)
return 0;
if (size == 0)
return bn_free(insert);
insert->digits = krealloc(insert->digits, sizeof(int) * size, GFP_KERNEL);
if (!insert->digits) { // realloc fails
return -1;
}
if (size > insert->size)
memset(insert->digits + insert->size, 0, sizeof(int) * (size - insert->size));
insert->size = size;
return 0;
}
```
### bn_copy
```c
int bn_copy(bn *dest, bn *insert)
{
if (bn_resize(dest, insert->size) < 0)
return -1;
dest->sign = insert->sign;
memcpy(dest->digits, insert->digits, insert->size * sizeof(int));
return 0;
}
```
### bn_bn_to_str
```c
char *bn_to_string(bn insert)
{
// log10(x) = log2(x) / log2(10) ~= log2(x) / 3.322
size_t len = (8 * sizeof(int) * insert.size) / 3 + 2 + insert.sign;
char *s = kmalloc(len, GFP_KERNEL);
char *p = s;
memset(s, '0', len - 1);
s[len - 1] = '\0';
for (int i = insert.size - 1; i >= 0; i--) {
for (unsigned int d = 1U << 31; d; d >>= 1) {
/* binary -> decimal string */
int carry = !!(d & insert.digits[i]);
for (int j = len - 2; j >= 0; j--) {
s[j] += s[j] - '0' + carry; // double it
carry = (s[j] > '9');
if (carry)
s[j] -= 10;
}
}
}
// skip leading zero
while (p[0] == '0' && p[1] != '\0') {
p++;
}
if (insert.sign)
*(--p) = '-';
memmove(s, p, strlen(p) + 1);
return s;
}
```
### bn_cmp
```c
int bn_cmp(const bn *a, const bn *b)
{
if (a->size > b->size) {
return 1;
} else if (a->size < b->size) {
return -1;
} else {
for (int i = a->size - 1; i >= 0; i--) {
if (a->digits[i] > b->digits[i])
return 1;
if (a->digits[i] < b->digits[i])
return -1;
}
return 0;
}
}
```
然後依據範例給的原始程式碼來思考如何優化。
#### 遇到問題
`fatal error: stdio.h: No such file or directory`
我更新了所有能更新的東西,卻還是無法使用,我目前往降級的方向去思考
最優解:直接重灌(老師的話要聽...不要亂用 root)
後來是我犯蠢了,我不該在 kernel 運用 stdio 的,因為 linux/ 底下壓根沒有它...
#### 思路
並且我留下了以下幾個操作
經歷了一陣學習之後,
我做出了以下幾個範例:
這邊我參考了[費波那契數](https://zh.wikipedia.org/zh-tw/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0),這邊我還需要詳加閱讀。
#### 結構
### 各式演算法實現(講解近日補上)
將寫好的 bn.h include 進入我們的程式。
我參考的是這幾篇:
[費氏數列分析](https://hackmd.io/@sysprog/fibonacci-number)
Vorobev 等式
重點為:
$f(2n) = 2 \times f(n+1) \times f(n) - (f(n))^2$
$f(2n+1)=(f(n+1))^2 + (f(n))^2$
Vorobev 等式怎麼證?
我們在 n 上作歸納。當 n := 1 和 2 的 case 很容易成立。假設 n:= n+1 的 case 也成立(以下我們把fib簡寫為f):
```
f (n+1+k) = f n * f k + f (n+1) * f(k+1)
```
我們來證明 n:=n+2
```
f (n+2+k)
= { f 之定義 }
f (n+k) + f (n+k+1)
= { (Vor) & (Vor') }
f (n-1) * f k + f n * f (k+1) +
f n * f k + f (n+1) * f(k+1)
= { f (n+1) = f n + f (n-1) }
f (n+1) * f k + f n * f (k+1) + f (n+1) * f (k+1)
= { f (n+2) = f (n+1) + f n }
f (n+1) * f k + f (n+2) * f (k+1) .
```
並且延伸閱讀提及的文章
[費氏數怎麼算?](https://scm.iis.sinica.edu.tw/ncs/2019/06/how-to-compute-fibonacci/)
普通公式:
```
fib n = (((1+√5)/2)^n - ((1-√5)/2)^n) /√5
```
[論文](https://ir.library.oregonstate.edu/downloads/t435gg51w)
[關於費式數列的那些事](https://yodalee.blogspot.com/2019/02/fibonacci.html)
#### 將 bn 帶入 sequence 之中
```c
void fib_og_bn(bn *target, unsigned int n){
bn_resize(target, 1);
if (n < 2){
target->digits[0] = n;
return;
}
bn *fst_ele = bn_alloc(1);
bn *sec_ele = bn_alloc(1);
target->digits[0] = 1;
for (unsigned int i = 1; i < n; i++){
bn_swap(sec_ele, target);
bn_add(fst_ele, sec_ele, target);
bn_swap(fst_ele, sec_ele);
}
bn_free(fst_ele);
bn_free(sec_ele);
}
```
#### 將 fdouble 與 bn 結合再一起
```c
void fib_fdbbn(bn *target, unsigned int n){
bn_resize(target, 1);
if (n < 2){
target->digits[0] = n;
return;
}
bn *f_k = target;
bn *f_k_next = bn_alloc(1);
f_k->digits[0] = 0;
f_k_next->digits[0] = 1;
bn *k = bn_alloc(1);
bn *k_next = bn_alloc(1);
for (unsigned int i = 1U << 31; i; i >>= 1){
bn_copy(k, k_next);
bn_lshift(k, 1, k);
bn_sub(k, f_k, k);
bn_mult(k, f_k, k);
bn_mult(f_k, f_k, f_k);
bn_mult(f_k_next, f_k_next, f_k_next);
/* f(k)*f(k) + f(k+1)*f(k+1) */
bn_copy(k_next,f_k);
bn_add(k_next, f_k_next, k_next);
if(n & i){
bn_copy(f_k, k_next);
bn_copy(f_k_next, k);
bn_add(f_k_next, k_next, f_k_next);
}
else{
bn_copy(f_k, k);
bn_copy(f_k_next, k_next);
}
}
bn_free(f_k_next);
bn_free(k);
bn_free(k_next);
}
```
#### 將 fdouble 與 clz 結合
```c
static long long fib_sequence_dbc(int k)
{
if (k < 2)
return k;
long long first_element = 0;
long long second_element = 1;
long long cover_bits;
for(int i = 31 - __builtin_clz(k); i>=0; --i){
long long temp_1 = first_element * (2 * second_element - first_element);
long long temp_2 = first_element * first_element + second_element * second_element;
long long process_bits = 1UL<<i;
cover_bits = -!!(k&(process_bits));
first_element = (temp_1 & ~cover_bits) + (temp_2 & cover_bits);
second_element = (temp_1 & cover_bits) + temp_2;
}
return first_element;
}
```
### 效能測試,演算法改寫
參考了作業說明上的程式碼,
由於因為作品中沒有用到 read 還有 write , 因此我們可以用來測量消耗的時間。
改寫 fib_read
```c
static ssize_t fib_read(struct file *file,
char *buf,
size_t size,
loff_t *offset)
{
bn *fib = bn_alloc(1);
ktime_t k1 = ktime_get();
fib_og_bn(fib, *offset);
ktime_t k2 = ktime_sub(ktime_get(), k1);
char *p = bn_to_string(*fib);
size_t len = strlen(p) + 1;
copy_to_user(buf, p, len);
bn_free(fib);
return ktime_to_ns(k2);
}
```
改寫 fib_write
```c
static ssize_t fib_write(struct file *file,
const char *buf,
size_t size,
loff_t *offset)
{
bn *fib = bn_alloc(1);
ktime_t k1 = ktime_get();
fib_fdbbn(fib, *offset);
ktime_t k2 = ktime_sub(ktime_get(), k1);
char *p = bn_to_string(*fib);
size_t len = strlen(p) + 1;
copy_to_user(buf, p, len);
bn_free(fib);
kfree(p);
return ktime_to_ns(k2);
}
```
### fib_sequence_og vs fib_sequence_fd
![](https://i.imgur.com/oYITWw6.png)
我們可以看到使用了 fast doubleing 在 n > 20 之後有顯著的速度提升,而使用了非遞迴但也非 fast doubleing
### fib_fd_nclz(遞迴) vs fib_fb_clz
![](https://i.imgur.com/bQ2k507.png)
這個實驗是利用未使用大數運算,並且使用了遞迴(也就是常人最直觀的算法)可以看到,使用了 fastdoubling 並且使用了 clz 硬體指令之後,效能差了100倍(而且這樣還是在 n = 93的情況下,如果數字加大,差距會變得更加顯著)
### fib_fdbbn vs fib_og_bn
![](https://i.imgur.com/p09VMFX.png)
我發現在一些奇怪的現象(為何會有一些況使得我們的 ns 突然暴漲?)
### fib_fd vs fib_fdbbn
![](https://i.imgur.com/qs1Lt2k.png)
## 學習指出針對大數運算的加速運算和縮減記憶體操作成本的舉措,紀錄你的認知和疑惑
### user space vs kernel space
### 探討時間暴漲的問題
我們可以看到,在 fast-doubleing-bignumber 時間雖然成長平穩但是使用的時間有時會暴漲,想理解這個問題我需要確認一下 client.c 這個檔案。
我發現問題似乎是出在 copy_to_user 這個東西上面。
#### copy to user