# 2020q1 Homework2 (fibdrv)
contributed by < `OscarShiang` >
###### tags: `linux2020`
## 測試環境
```shell
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.4 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.4 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
$ cat /proc/version
Linux version 5.3.0-40-generic (buildd@lcy01-amd64-024)
(gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1))
#32~18.04.1-Ubuntu SMP Mon Feb 3 14:05:59 UTC 2020
```
## 作業要求
- [ ] 研讀上述 ==Linux 效能分析的提示== 描述,在自己的實體電腦運作 GNU/Linux,做好必要的設定和準備工作 $\to$ 從中也該理解為何不希望在虛擬機器中進行實驗;
- [ ] 研讀上述費氏數列相關材料 (包含論文),摘錄關鍵手法,並思考 [clz / ctz](https://en.wikipedia.org/wiki/Find_first_set) 一類的指令對 Fibonacci 數運算的幫助。請列出關鍵程式碼並解說
- [ ] 複習 C 語言 [數值系統](https://hackmd.io/@sysprog/c-numerics) 和 [bitwise operation](https://hackmd.io/@sysprog/c-bitwise),思考 Fibonacci 數快速計算演算法的實作中如何減少乘法運算的成本;
- [ ] `lsmod` 的輸出結果有一欄名為 `Used by`,這是 "each module's use count and a list of referring modules",但如何實作出來呢?模組間的相依性和實際使用次數 ([reference counting](https://en.wikipedia.org/wiki/Reference_counting)) 在 Linux 核心如何追蹤呢?
- [ ] 注意到 `fibdrv.c` 存在著 `DEFINE_MUTEX`, `mutex_trylock`, `mutex_init`, `mutex_unlock`, `mutex_destroy` 等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題
- [ ] 修正 Fibonacci 數計算的正確性,改善 fibdrv 計算 Fibinacci 數列的執行效率,過程中需要量化執行時間,可在 Linux 核心模組和使用層級去測量
## 改善 Fibonacci 的計算效率
Fibonacci 數的定義如下:
$$
F_0 = 1,\ F_1 = 1 \\
F_n = F_{n - 1} + F_{n - 2}\ (n \geq 2)
$$
而在程式中的實作為
```cpp
int F(int n)
{
if (n == 0 || n == 1)
return 1;
return F(n - 1) + F(n - 2);
}
```
假設計算 F(6) 的時候,最後程式呼叫的結果就會變成下圖:
```graphviz
strict digraph G
{
1[label="F(6)"]
2[label="F(4)"]
3[label="F(5)"]
4[label="F(2)"]
5[label="F(3)"]
6[label="F(3)"]
7[label="F(4)"]
8[label="F(0)", style=filled]
9[label="F(1)", style=filled]
10[label="F(1)", style=filled]
11[label="F(2)"]
12[label="F(1)", style=filled]
13[label="F(2)"]
14[label="F(2)"]
15[label="F(3)"]
16[label="F(0)", style=filled]
17[label="F(1)", style=filled]
18[label="F(0)", style=filled]
19[label="F(1)", style=filled]
20[label="F(0)", style=filled]
21[label="F(1)", style=filled]
22[label="F(1)", style=filled]
23[label="F(2)", style=filled]
24[label="F(0)", style=filled]
25[label="F(1)", style=filled]
1 -> {2, 3}
2 -> {4, 5}
3 -> {6, 7}
4 -> {8, 9}
5 -> {10, 11}
6 -> {12, 13}
7 -> {14, 15}
11 -> {16, 17}
13 -> {18, 19}
14 -> {20, 21}
15 -> {22, 23}
23 -> {24, 25}
}
```
在該展開圖裡,F(4), F(3), F(2) 在遞迴的過程中就會被重複計算,很顯然這樣的計算效率非常的差。根據 Vorobev 等式,我們可以將 Fibonacci 數的計算方式整理成下列形式:
$$
\begin{bmatrix}
F_n \\
F_{n - 1}
\end{bmatrix} =
\begin{bmatrix}
1 & 1 \\
1 & 0
\end{bmatrix} \times
\begin{bmatrix}
F_{n - 1} \\
F_{n - 2}
\end{bmatrix}
$$
而 $F_{2n + 1}$ 與 $F_{2n}$ 則變成
$$
\begin{align}
\begin{bmatrix}
F_{2n + 1} \\
F_{2n}
\end{bmatrix}
&=
\begin{bmatrix}
1 & 1 \\
1 & 0
\end{bmatrix} ^ {2n} \times
\begin{bmatrix}
F_1 \\
F_0
\end{bmatrix} \\
&=
\begin{bmatrix}
1 & 1 \\
1 & 0
\end{bmatrix} ^ {n} \times
\begin{bmatrix}
1 & 1 \\
1 & 0
\end{bmatrix} ^ {n} \times
\begin{bmatrix}
F_1 \\
F_0
\end{bmatrix} \\
&=
\begin{bmatrix}
F_{n + 1} & F_{n} \\
F{n} & F_{n - 1}
\end{bmatrix} \times
\begin{bmatrix}
F_{n + 1} & F_{n} \\
F{n} & F_{n - 1}
\end{bmatrix}
\times
\begin{bmatrix}
1 \\
0
\end{bmatrix} \\
&=
\begin{bmatrix}
F_{n + 1}^2 + F_n^2 \\
F_n(2F_{n + 1} - F_n)
\end{bmatrix}
\end{align}
$$
所以 $F_{2n + 1}$ 與 $F_{2n}$ 就可以表示為
$$
F_{2n + 1} = F_{n + 1}^2 + F_n^2 \\
F_{2n} = F_n(2F_{n + 1} - F_n)
$$
只要我們有 $F_0 = 0,\ F_1 = 1,\ F_2 = 1$,這樣的整理可以避免逐項計算 Fibonacci 數所花費的時間:
```graphviz
strict digraph G
{
1[label="F(6)"]
2[label="F(3)"]
3[label="F(4)"]
4[label="F(1)" style="filled"]
5[label="F(2)" style="filled"]
6[label="F(2)" style="filled"]
7[label="F(3)"]
8[label="F(1)" style="filled"]
9[label="F(2)" style="filled"]
1->{3, 2}
2->{4, 5}
3->{6, 7}
7->{8, 9}
}
```
同時因為電腦數值系統的特性,可以將其轉換為利用數值的 bit 來進行判斷:
- 遇到 `0` : 計算 $F(2n + 1)$ 與 $F(2n)$
- 遇到 `1` : 計算 $F(2n + 1)$ 與 $F(2n)$ 後再計算 $F(2n + 2)$
### 使用 fibdrv 進行實驗
為了在 fibdrv 這個核心模組裡面測量使用原本的遞迴方式計算 fibonacci 與使用 Vorobev 等式計算 fibonacci 的時間差異,並進行分析
以下使用下列 fast doubling 的函式進行測試
```cpp
static long long fib_doubling(long long k)
{
if (k == 0)
return 0;
long long t0 = 1, t1 = 1, t3 = 1, t4 = 0;
long long i = 1;
while (i < k) {
if ((i << 1) <= k) {
t4 = t1 * t1 + t0 * t0;
t3 = t0 * (2 * t1 - t0);
t0 = t3;
t1 = t4;
i <<= 1;
} else {
t0 = t3;
t3 = t4;
t4 = t0 + t4;
i++;
}
}
return t3;
}
```
根據 [The Linux Kernel Documentation](https://www.kernel.org/doc/html/latest/core-api/timekeeping.html) 中所描述 ktime 的 API
我將 `fib_read` 這個函式改寫
```cpp
/* calculate the fibonacci number at given offset */
static ssize_t fib_read(struct file *file,
char *buf,
size_t size,
loff_t *offset)
{
// record the execution time by calling ktime_get()
ktime_t kt = ktime_get();
ssize_t result = fib_sequence(*offset);
duration = ktime_sub(ktime_get(), kt);
ktime_t kt2 = ktime_get();
fib_doubling(*offset);
kt2 = ktime_sub(ktime_get(), kt2);
printk(KERN_INFO "%lld %lld", ktime_to_ns(kt), ktime_to_ns(kt2);
return result;
}
```
在執行 User Space 的 `client` 程式後利用終端機指令 `dmesg` 查看 `fibdrv` 的輸出
```shell
...
[ 1192.078203] 276 98
[ 1192.078210] 243 100
[ 1192.078217] 259 104
[ 1192.078223] 244 129
[ 1192.078230] 274 139
[ 1192.078237] 230 123
[ 1192.078244] 297 116
[ 1192.078251] 296 144
[ 1192.078258] 295 151
[ 1192.078265] 282 147
[ 1192.078272] 290 138
[ 1192.078279] 273 144
[ 1192.078286] 309 124
[ 1192.078293] 341 151
[ 1192.078300] 285 149
[ 1192.078307] 272 151
```
將這些實驗的數據以 gnuplot 繪製成圖表
![](https://i.imgur.com/faJvHQS.png)
在圖表中可以看到,兩者之間的差異在第 30 項前的差異並不大,但在第 30 項後差異持續擴大
根據 [task switch](https://en.wikipedia.org/wiki/Context_switch) 的描述:
> In computing, a **context switch** is the process of storing the state of a [process](https://en.wikipedia.org/wiki/Process_(computing) "Process (computing)") or [thread](https://en.wikipedia.org/wiki/Thread_(computing) "Thread (computing)"), so that it can be restored and resume [execution](https://en.wikipedia.org/wiki/Execution_(computing) "Execution (computing)") at a later point.
所以在進行上述實驗的時候就會導致 tail recursive 的花費時間分佈有抖動的情況發生
為了避免這樣的情況,我將函式改到 User Space ,並以 `time.h` 中的 `struct timespec` 計時
```cpp
for (int n = 0; n <= 100; n++) {
long long fib;
struct timespec ts, ts1;
// test with adding version
clock_gettime(CLOCK_REALTIME, &ts);
fib = fib_adding(n);
clock_gettime(CLOCK_REALTIME, &ts1);
printf("time cost: %ld", ts1.tv_nsec - ts.tv_nsec);
// test with doubling version
clock_gettime(CLOCK_REALTIME, &ts);
fib = fib_doubling(n);
clock_gettime(CLOCK_REALTIME, &ts1);
printf(" %ld\n", ts1.tv_nsec - ts.tv_nsec);
}
```
並透過 `taskset` 將行程綁定在第 0 個 CPU 上執行
```shell
$ taskset 0x1 ./fib
```
最後將實驗結果繪製成下圖
![](https://i.imgur.com/8mxXYrm.png)
從這張圖中可以看到,時間花費數值跳動的情況已經改善許多
透過設定 `/boot/grub/grub.cfg` 更改 bootloader 的參數,將開機指令 `linux` 後面加上 `isolcpus=0` 將第 0 個 CPU 排除在開機程序之外
重新開機後,確認第 0 個 CPU 已經被排除成功
![](https://i.imgur.com/dmGOkGk.png)
利用 `taskset` 設定測試程式執行於第 0 個 CPU 上,測試結果如下
![](https://i.imgur.com/zk10pjZ.png)
可以發現在限定使用特定 CPU 來進行實驗時,實驗所造成的誤差與干擾與在完全不調整的情況下相差非常多
為了避免單一實驗之極端值所造成的實驗誤差,我以 Python 腳本將實驗的過程與資料的整理自動化
```python
#!/usr/bin/env python3
import os
import pandas as pd
import math
PATH = '/home/oscar/Desktop/statistic'
times = 10000
adding = pd.DataFrame(columns = range(times))
doubling = pd.DataFrame(columns = range(times))
# executing experiment
for i in range(times):
os.system('taskset 0x1 ./fib > ' + PATH + '/tmp/data' + str(i))
for i in range(times):
f = open(PATH + '/tmp/data' + str(i), 'r')
content = f.read()
raw = content.split('\n')
raw.remove('')
add = []
double = []
for j in range(len(raw)):
numbers = raw[j].split(' ')
add.append(int(numbers[0]))
double.append(int(numbers[1]))
adding[i] = add;
doubling[i] = double
print('data' + str(i) + ' completed')
print(adding)
print('-' * 30)
print(doubling)
# processing the data
mean_add, std_add = adding.mean(axis = 1), adding.std(axis = 1)
mean_double, std_double = doubling.mean(axis = 1), doubling.std(axis = 1)
add = []
double = []
print('processing adding data...')
for i in range(len(adding.index)):
sum = 0
cnt = 0
for j in adding.iloc[i]:
if abs(j - mean_add[i]) < 2 * std_add[i]:
sum = sum + j;
cnt = cnt + 1;
sum = sum / cnt;
add.append(sum);
print('processing doubling data...')
for i in range(len(doubling.index)):
sum = 0
cnt = 0
for j in doubling.iloc[i]:
if abs(j - mean_double[i]) < 2 * std_double[i]:
sum = sum + j;
cnt = cnt + 1;
sum = sum / cnt;
double.append(sum);
out = []
for i, j, k in zip(range(len(add)), add, double):
out.append('{} {} {}'.format(i, j, k))
# output the result
f = open(PATH + '/runtime_result', 'w')
print('file write: {}'.format(f.write('\n'.join(out))))
f.close()
```
重複實驗 $10^4$ 次後利用 95% 的信賴區間統計資料,得到下圖
![](https://i.imgur.com/MF0Iqxn.png)
### 使用硬體手段加速
但不是所有的整數都會將其記憶體的 32 個位子填滿,所有我們可以利用 clz 計算 leading zeros 的個數,讓我們可以直接從數字的有效位元開始計算,而不需要一直判斷不需要的 leading zeros
根據 [GCC 內建函式](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)的描述,在 GCC 中就有直接可以使用的 clz / ctz 功能,所以我們可以在程式中透過 macro 將 clz 引進程式中
```cpp
#define clz(s) __builtin_clz(s)
```
並利用 clz 先將 leading zeros 計算出來,作為 for 迴圈的中止點
根據 [GCC 內建函式](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)對於 `__builtin_clz` 回傳值的說明
> Returns the number of leading 0-bits in x, starting at the most significant bit position. **If x is 0, the result is undefined.**
所以為避免發生 undefined value 產生,在 `n = 0` 時直接回傳 `0`
因為 `n = 0` 的情況在計算的時候只會發生 2 次,所以利用 `unlikely` 巨集 (在 Linux 核心原始程式碼也存在) 提示 compiler 產生對 CPU 分支預測友善的程式碼。
將 `unlikely` 加入程式中
```cpp
#define unlikely __builtin_expect(!!(x), 0)
```
並將 `n = 0` 的判斷加入 `unlikely` 的標示
```cpp
long long fib_doubling_with_clz(int n)
{
if (unlikely(!n))
return 0;
long long a = 0, b = 1;
for (int i = 32 - clz(n); i >= 0; i--) {
long t1 = a * (2 * b - a);
long t2 = b * b + a * a;
a = t1;
b = t2;
if (n & (1 << i)) {
t1 = a + b;
a = b;
b = t1;
}
}
return a;
}
```
與前面的實驗相同,本程式一樣使用 `taskset` 綁定來進行測量,而實驗結果如下圖所示
![](https://i.imgur.com/hXXYu2h.png)
在上圖的實驗中,有 41 項 Fibonacci 數的計算是使用 clz 改善後的版本較佳,所以根據實驗結果可以知道 clz 的確能加速 fast doubling 的計算
透過重複實驗降低實驗誤差與極端值後結果呈現下圖
![](https://i.imgur.com/eWNx5FA.png)
## 計算第 92 項之後的 Fibonacci 數
在進行 `make check` 時,會發現測試在第 93 項時會發生錯誤
利用測試程式輸出計算結果後發現在第 92 項之後出現 Overflow 的問題
```
...
90 2880067194370816120
91 4660046610375530309
92 7540113804746346429
93 -6246583658587674878
94 1293530146158671551
95 -4953053512429003327
96 -3659523366270331776
97 -8612576878699335103
98 6174643828739884737
99 -2437933049959450366
100 3736710778780434371
```
因為上述的情況發生在使用 `long long` 時,表示使用內建的整數型態以無法符合我們的要求,所以需要改以自定義的 `struct` 來改進結構
在 `struct` 的設計與運算實作上我參考〈[Bignums Alrithmetic](http://dl.fefe.de/bignum.pdf)〉進行設計,使用 `usigned int *` 來存取一個動態長度的陣列
```cpp
typedef struct _bign {
unsigned int *d;
unsigned int size;
} bn;
```
:::warning
對照 quiz4,你可看到透過 small buffer optimization 手法實作的 vector,也能作為上述動態長度陣列用途,這也是隨堂測驗的規劃。
:notes: jserv
:::
接著依序將加法、減法與陣列的調整操作實作出來
加法的部分,我參考〈[Bignums Alrithmetic](http://dl.fefe.de/bignum.pdf)〉所提及的做法,利用 `uint64_t` 保留每個元素相加之後的進位。最後利用 15 ~ 20 行的 for 迴圈確認陣列的實際長度並將陣列的長度進行調整
```cpp=
void bn_add(bn *a, bn b)
{
uint32_t len = max(a->size, b.size) + 1;
uint32_t num[len];
memset(num, 0, sizeof(num));
uint64_t l = 0;
for (int i = 0; i < len; i++) {
uint32_t A = (i < a->size) ? a->d[i] : 0;
uint32_t B = (i < b.size) ? b.d[i] : 0;
l += (uint64_t)A + B;
num[i] = l;
l >>= 32;
}
for (int i = len - 1; i >= 0; i--) {
if (!num[i])
len--;
else
break;
}
// adjustment array size
if (len > a->size) {
a->d = realloc(a->d, len * sizeof(int));
a->size = len;
}
memcpy(a->d, num, sizeof(int) * len);
}
```
但因為在 GCC 裡沒有對應 64 bit 以上 `int` 型態位元轉換的實作,所以我參考 [How to convert a 128-bit integer to a decimal ascii string in C?](https://stackoverflow.com/questions/8023414/how-to-convert-a-128-bit-integer-to-a-decimal-ascii-string-in-c?fbclid=IwAR2dyjjTH34OKAb4-QKGHhZwNnAfKS2nkFcK0w3mYApZAQxxiT87e9bY4dM) 討論中,將大數轉換成十進位的字串形式,方便之後輸出運算的結果
```cpp=
char *bn_todec(bn a) {
char str[8 * sizeof(int) * a.size / 3 + 2];
unsigned int n[a.size + 1];
memset(str, '0', sizeof(str) - 1);
str[sizeof(str) - 1] = '\0';
memcpy(n, a.d, sizeof(int) * a.size);
for (int i = 0; i < 8 * sizeof(int) * a.size; i ++) {
int carry;
carry = (n[a.size - 1] >= 0x80000000);
for (int j = a.size - 1; j >= 0; j--)
n[j] = ((n[j] << 1) & 0xffffffff) + ((j - 1) >= 0 ? (n[j - 1] >= 0x80000000) : 0);
for (int j = sizeof(str) - 2; j >= 0; j--) {
str[j] += str[j] - '0' + carry;
carry = (str[j] > '9');
if (carry)
str[j] -= 10;
}
}
// search for numbers
int i = 0;
while (i < sizeof(str) - 2 && str[i] == '0')
i++;
// passing string back
char *p = malloc(sizeof(str) - i);
memcpy(p, str + i, sizeof(str) - i);
return p;
}
```
利用 bitwise 的運算將十進位的每位數字算出來後存進 `str` ,最後利用 27 行的迴圈計算 `str` 中未使用的數量,最後利用 `malloc` 取得一塊記憶體空間,並將 `str` 中有效的元素利用 `memcpy` 將十進位的字串指標 `p` 回傳回去
因為使用 fast doubling 計算 fibonacci 數的做法有使用到乘法,所以我使用 Karatsuba algorithm 來實作大數的乘法
因為 Karatsuba 用到 bitwise 的左、右位移,所以依需求實作出大數位元的位移
```cpp
void bn_rshift(bn *out, int wid)
{
int data = wid >> 5; // devide by 32
wid &= 31; // mod 31
// move int element across array
if (data) {
int tmp[data];
memcpy(tmp, &out->d[data], sizeof(int) * (out->size - data));
memset(out->d, 0, sizeof(int) * out->size);
memcpy(out->d, tmp, sizeof(int) * (out->size - data));
out->size -= data;
out->d = realloc(out->d, sizeof(int) * out->size);
}
if (wid) {
// move bit in the same array
if (out->size == 1)
out->d[0] >>= wid;
// mvoe bit across array
else {
out->d[0] >>= wid;
int mask = set_mask(wid);
for (int i = 1; i < out->size; i++) {
int part = out->d[i] & mask;
if (part)
out->d[i - 1] |= (part << (32 - wid));
out->d[i] >>= wid;
}
}
}
// clean up the redundant array
for (int i = out->size - 1; i >= 1; i--) {
if (!out->d[i])
out->size--;
else
break;
}
out->d = realloc(out->d, out->size * sizeof(int));
}
```
右位移在實作上先處理超過 32 位元的位移,利用 `memcpy` 將整個陣列進行位移,當處理完超過 32 位元以上的位移後,處理小於 32 位元的位移,利用 mask 擷取跨陣列元素位移的位元,並將其利用 `|=` 放在對應的位置上,完成整個大數的位移。
```cpp
void bn_lshift(bn *out, int wid)
{
int data = wid / 32;
wid %= 32;
if (wid) {
for (int i = out->size - 1; i >= 0; i--) {
int zeros = clz(out->d[i]);
if (wid <= zeros) {
out->d[i] <<= wid;
} else {
// for fast lhs element -> realloc new
if (i == out->size - 1) {
out->d = realloc(out->d, (out->size + 1) * sizeof(int));
memset(out->d + out->size, 0, sizeof(int));
++out->size;
}
out->d[i] <<= zeros;
int mask = set_mask(wid - zeros) << (32 - wid + zeros);
int tmp = (mask & out->d[i]) >> (32 - wid + zeros);
out->d[i + 1] |= tmp;
out->d[i] <<= (wid - zeros);
}
}
}
if (data) {
uint32_t tmp[out->size];
memcpy(tmp, out->d, out->size * sizeof(int));
out->d = realloc(out->d, (out->size + data) * sizeof(int));
memset(out->d, 0, (out->size + data) * sizeof(int));
memcpy(out->d + data, tmp, out->size * sizeof(int));
out->size += data;
}
}
```
左位移在實作上則與右位移有些不同:左位移首先處理小於 32 位元的位移,並利用 `clz` 檢查是否會導致 `msb` 溢位的問題產生,若會產生問題,則調整陣列大小,並將 `msb` 移動到新的陣列元素上
接著再進行超過 32 位元的位移,一樣利用 `memcpy` 進行陣列元素長度的移動,最後將陣列大小寫入回 `out->size` 完成位移
完成左右位移的建構之後就可以實作出 Karatsuba 的乘法了
最後將 `bn` 的結構引進測試程式中,並改寫 fibonacci 的計算函式,測試結果如下:
```
...
n = 87, 679891637638612258
n = 88, 1100087778366101931
n = 89, 1779979416004714189
n = 90, 2880067194370816120
n = 91, 4660046610375530309
n = 92, 7540113804746346429
n = 93, 12200160415121876738
n = 94, 19740274219868223167
n = 95, 31940434634990099905
n = 96, 51680708854858323072
n = 97, 83621143489848422977
n = 98, 135301852344706746049
n = 99, 218922995834555169026
n = 100, 354224848179261915075
```
與 [The first 300 Fibonacci numbers](http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibtable.html) 的資料比對後確認計算結果無誤,並將其引進 `fibdrv` 中
將 `fib_sequence` 進行改寫
```cpp
static char *fib_sequence(int k)
{
bn out;
bn_init(&out);
fib_doubling(&out, k);
char *str = bn_todec(out);
bn_free(&out);
return str;
}
```
因為最後的計算結果會以字串的方式回傳回來,所以將 `fib_read` 改寫為使用 `linux/uaccess.h` 所提供的 `copy_to_user` 函式
```cpp
static ssize_t fib_read(struct file *file,
char *buf,
size_t size,
loff_t *offset)
{
char *str = fib_sequence(*offset);
copy_to_user(buf, str, strlen(str));
return 0;
}
```
但因為在 [The Linux Kernel API](https://www.kernel.org/doc/htmldocs/kernel-api/mm.html) 中並沒有使用者層級常用的 `malloc`, `free`, `memcpy` 等函式,所以將 `bign.c` 加上 macro 改寫原先使用 `stdlib.h` 所進行的記憶體管理
:::warning
可比照 [memory.h](https://github.com/sysprog21/bignum/blob/master/memory.h),對記憶體操作函式包裝,你可以提供兩種版本,一個是對應到 `<stdlib.h>`,另一個則是 kmalloc, kzalloc 一類 Linux 核心函式。
:notes: jserv
:::
> 好的,已新增 `memory.h` 來包裝記憶體管理函式
> [name= Oscar][color= orange]
在新增 `memory.h` 後,嘗試將 `bign.c`, `bign.c` 與 `memory.h` 在編譯過程中與 `fibdrv.ko` 連結起來,卻得到下列結果
```
...
ERROR: "bn_copy" [/home/oscar/linux2020/fibdrv/fibdrv.ko] undefined!
ERROR: "bn_free" [/home/oscar/linux2020/fibdrv/fibdrv.ko] undefined!
ERROR: "bn_assign" [/home/oscar/linux2020/fibdrv/fibdrv.ko] undefined!
ERROR: "bn_init" [/home/oscar/linux2020/fibdrv/fibdrv.ko] undefined!
WARNING: modpost: missing MODULE_LICENSE() in /home/oscar/linux2020/fibdrv/fibdrv.o
```
直接將 `bign.c` 中的所有函式定義直接加入 `fibdrv.c` 中進行編譯,但在執行 `client` 程式時,在計算 Fibonacci 數時電腦當機。
利用 Valgrind 觀察使用者層級的測試程式,推測是記憶體管理問題導致問題產生
## 參考資料
1. [sysprog21/fibdrv](https://github.com/sysprog21/fibdrv)
2. [Calculating Fibonacci Numbers by Fast Doubling](https://chunminchang.github.io/blog/post/calculating-fibonacci-numbers-by-fast-doubling)
3. [Other Built-in Functions Provided by GCC](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)
4. [Bignums Alrithmetic](http://dl.fefe.de/bignum.pdf)
5. [How to convert a 128-bit integer to a decimal ascii string in C?](https://stackoverflow.com/questions/8023414/how-to-convert-a-128-bit-integer-to-a-decimal-ascii-string-in-c?fbclid=IwAR2dyjjTH34OKAb4-QKGHhZwNnAfKS2nkFcK0w3mYApZAQxxiT87e9bY4dM)