contributed by < linhoward0522
>
linux-headers
套件
linux-headers
套件已正確安裝於開發環境
fibdrv.ko
核心模組
fibdrv.ko
核心模組在 Linux
核心掛載後的行為
輸出
發現是沒有 disable Secure Boot in UEFI (BIOS) settings
接著會要求你設定一組密碼,之後 reboot
,選擇 change secure boot state
後輸入剛剛設定的密碼後選擇 Yes
這兩道命令的輸出都是 0
研讀 Linux 效能分析的提示 描述,在自己的實體電腦運作 GNU/Linux,做好必要的設定和準備工作 從中也該理解為何不希望在虛擬機器中進行實驗;
這邊一直解決不了,後來參考其他學員 willwillhi1
的做法
修改 /etc/default/grub
內的 GRUB_CMDLINE_LINUX_DEFAULT
,加入 isolcpus=7
來指定編號 7
的核心不被排程器賦予任務,修改完成後需 sudo update-grub
來更新設定,重開機後即生效
重新開機後檢查 CPU affinity
結果如下
CPU
中執行address space layout randomization
(ASLR)scaling_governor
為 performance
,準備以下 shell script
,檔名為 performance.sh
:Intel
處理器,關閉 turbo mode
fibdrv
核心模組include/linux/fs.h
fibdrv
屬於 character device
,所以資料結構為 struct cdev
include/linux/cdev.h
const struct file_operations
,存放關於此 character device
的相關操作
fib_sequence
用來計算費氏數列
fibdrv
設計為一個 character device
,可以藉由檔案操作介面進行操作(open
, read
, write
, mmap
等等),定義如下:
fib_read
用來取得費氏數列的計算結果
其中 offset
是用來控制要算到第幾個費氏數列fib_device_lseek
更改 offset
fib_write
目前沒有功能mutex
,用來控制使用此模組的行程數量,fib_open
一次只允許一個行程做使用
init_fib_dev
cdev_alloc
先配置一個 cdev structure
,接著需要使用 cdev_init
來初始化,最後用 cdev_add
才能被加入 Linux
核心
研讀上述費氏數列相關材料 (包含論文),摘錄關鍵手法,並思考 clz / ctz 一類的指令對 Fibonacci 數運算的幫助。請列出關鍵程式碼並解說
Variable-length array
允許執行時期再決定陣列佔用的空間,而不是編譯時期時決定。可能有以下問題
security implication
)因為 variable-length array
會生成更多的機器碼,更慢的代碼,而且相對而言更加地不可維護,所以 Linux kernel
專案從頭到尾都沒有使用過 VLA
is an array data structure whose length is determined at run time (instead of at compile time).
Linus Torvalds has expressed his displeasure in the past over VLA usage for arrays with predetermined small sizes because it generates lower quality assembly code.With the Linux kernel, the Linux kernel is effectively VLA-free.
改變實作將程式碼改成不用陣列來儲存計算出來的值
複習 C 語言 數值系統 和 bitwise operation,思考 Fibonacci 數快速計算演算法的實作中如何減少乘法運算的成本
Fast doubling
來實作,時間複雜度可降為 透過 Q-Matrix
以及 Fast Doubling
之數學推論,我們最後可得兩公式可供使用
透過這兩個公式可以讓我們計算費波那契數列的時間複雜度為
其中 Fast Doubling
文章當中有提及幾種作法
RECURSIVE (TOP-DOWN) APPROACH
n
越大,就會有更多子樹被重複執行retrieving data from array
array
來儲存已經被計算過的數值,可以先確認是否被計算過,若有則直接取值n
越大而增加記憶體的使用use a two-elements array
為了解決上述問題,我們使用 two-elements array
即不會隨著 K
越大而增加記憶體的使用
當我們要計算 時,如果我們只有 , ,要如何計算 ?
根據公式得知我們可以使用 , 來去計算出 ,
最後我們即可得
我們可以使用 two-elements array
來計算
n
是偶數,我們可以找到 ,
n
是奇數,我們可以找到 ,
10 | 5 | 2 | 1 | 0 | |
v | v | ||||
bottom-up
方法,我們可以用 , 計算出 , , ,然後用 , 計算出 , ,用 , 計算出 , , ,用 , 計算出 BIT-MASK
highest 1-bit
即可知道要迭代幾次bottom-up
的方法BIT-MASK
來改善上述實作
&
操作從最高位元做到最低位元許多現代處理器提供 clz / ctz 一類的指令,可搭配上述 Fast Doubling 手法運用:
使用 __builtin_clzll
改寫將上述程式碼改成
int __builtin_clzll (unsigned int x)
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.
Similar to __builtin_clz, except the argument type is unsigned long long.
fibdrv.c
增加
fib_write
目前沒有功能,於是用來輸出 fib 的執行時間
fib_write
後發現可以利用 size
來指定要使用哪種方法,將上面程式碼做修正
client_test.c
Makefile
,即可在命令列輸入 make test
來執行
先將先前載入過的模組釋放掉,再將 fibdrv.ko
模組載入,接著將行程固定在特定的 CPU 中並執行 client_test
,將結果存到 test
,最後將模組釋放掉後並使用 gnuplot
來畫出運行結果fast doubling
方法整體效率比 interative
方法還要好,但是使用了 clz
的 fast doubling
方法並沒有比較快,甚至還比 interative
方法還慢,有點不太合理
這邊目前找不出原因
根據與老師的一對一討論後,老師建議應當要考慮 cache
的影響,所以我在實驗過程中會計算兩次費氏數列,確保 cache
裡的資料已被更新,同時執行第二次費氏數列的同時才會紀錄時間,以降低 cache
的影響
使用 clock_gettime
相關 API ,首先在 client_test.c
中加入 #include <time.h>
clock_gettime
函數wall-clock time
或程式的 CPU time
,其所傳回的時間是用 timespec
這個結構所儲存的 :
其精準度最高可達 nanosecond
,若要查詢實際的精確度,可以使用 clock_getres
函數
CLOCK_MONOTONIC
: 單調遞增時間(monotonic time
),這個時間會非常穩定的持續遞增,不會因為系統時間改變而有變動,適合用於測量程式執行效能client_test.c
程式碼,使用引數來決定要測量哪種方法
The atoi, atol, and atoll functions convert the initial portion of the string pointed to by nptr to int, long int, and long long int representation, respectively.
Makefile
以上三種方法共同的現象是前幾個 F(n)
的計算中所花的時間上都會非常的高。
而且 fast doubling with clz
振動的幅度較大
學習指出針對大數運算的加速運算和縮減記憶體操作成本的舉措,紀錄你的認知和疑惑
將其定義在 bn.h
中
number
: 指向儲存的數值,之後會以 array
的形式來取用size
: 配置的記憶體大小,單位為 sizeof(unsigned int)
sign
: 判斷為正數或負數, 0 為正數、1 為負數由於大數沒辦法直接以數值的形式列出,所以我們使用字串陣列將大數數值轉換成十進位的 ASCII
表示法
這裡使用將對數轉換為 10 為底的方法,來估算大數所需的十進位位數
首先宣告一個長度為 len
,作用是計算出轉換成字串後所需要的總長度,並初始化為 '0'
的字串陣列
GFP_KERNEL
表示要求分配的記憶體是可交換的(swappable)
src
中的每一個整數,從最高位元到最低位元src->number[i]
中的每一個二進位,一樣從最高位元到最低位元d
,判斷 src->number[i]
中的該位元是否為 1,如果為 1,則 carry
設為 1,否則設為 0s[j]
的 ASCII
碼值減去 '0'
的 ASCII
值,就可以得到 s[j]
代表的數值,然後將該數值加上 carry
。如果相加的結果大於 9 ,則需要進位,進位後則要將該位的值減去 10接著跳過字串開頭的所有 '0'
,因為這些 '0'
不會影響轉換後的數值
如果 src->sign
為 1,則表示該大數為負數,則在字串開頭插入一個負號
此函式用來將 bn
數字向左移動
首先計算 bn
中最高位元的位數,為了在左位移時檢查是否需要增加 bn
數字的大小
bn
數字進行左移操作shift
位,再將 src->number[i - 1]
位置的數字右移 32-shift
位,並使用 |
合併,即可完成左移操作bn
數字最低位元的左移 shift
位加法與減法由於需要考慮數值的正負號,先由 bn_add
與 bn_sub
判斷結果的正負號,再使用函式bn_do_add
與 bn_do_add
進行無號整數的計算
bn_add
負責所有正負號的判斷,所以 bn_sub
只是改變 b 的正負號後,再直接交給 bn_add
判斷bn_cmp
負責比對兩個 bn
物件絕對值後的大小c
的大小for
迴圈後,對於 c
中的第 i
個位元,分別從 a
和 b
中取出第 i
個位元的值,將其相加後並加上 carry
的值,即可得 c
中第 i
個位元的值carry
右移 32 位元,以便下一次迴圈可以加上進位carry
用於保存上一位減法操作的借位a
與 b
,計算完成後再補上負號參考作業規範中迭代以及
fast doubling
的程式進行測試
調整 fibdrv.c
中 MAX_LENGTH
的數值
調整 client.c
中 offset
的數值
使用命令 make check
時會將 client.c
的結果輸出到 out
,並且跟 scripts/expected.txt
做比對,但是 expected.txt
從 以後的數值是錯誤的
先修改命令 make check
把第六行註解,並在第七行加上 && $(call pass)
,若是結果正確就會出現 Passed [-]
接著要用 verify.py
做測試,所以做一點修改
其中 exit(1)
是有錯誤即退出,所以若結果是錯誤的就不會在終端機出現 Passed [-]
因為我是先 fork
,再從自己的 repo
git clone
下來,所以只好參考 commit : a6b1320
在本地端做修改後才能正常 git commit
提交一份 pull request 以改進原本的檢查。
:notes: jserv
Linux
核心不傾向使用 VLA
Variable-length array
允許執行時期再決定陣列佔用的空間,而不是編譯時期時決定。可能有以下問題
security implication
)fast doubling
?Fast doubling
來實作,時間複雜度可降為
Linux
核心可作,但在 user space
無法?kmalloc
)kmalloc()
will return a memory chunk with size of power of 2 that matches or exceeds len and will return NULL upon failure.
The maximum size allocatable by kmalloc()
is 1024 pages, or 4MB on x86.
使用 valgrind
測量大數運算佔用的記憶體空間
GFP_KERNEL
Using GFP_KERNEL
means that kmalloc
can put the current process to sleep waiting for
a page when called in low-memory situations.
This is a normal allocation and might block. This is the flag to use in process context code when it is safe to sleep. The kernel will do whatever it has to in order to obtain the memory requested by the caller. This flag should be your first choice.