contributed by < eric88525
>
lsmod
的輸出結果有一欄名為 Used by
,這是 "each module's use count and a list of referring modules",但如何實作出來呢?模組間的相依性和實際使用次數 (reference counting) 在 Linux 核心如何追蹤呢?
搭配閱讀 The Linux driver implementer’s API guide » Driver Basics
fibdrv.c
存在著 DEFINE_MUTEX
, mutex_trylock
, mutex_init
, mutex_unlock
, mutex_destroy
等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題。嘗試撰寫使用 POSIX Thread 的程式碼來確認。研讀上述 Linux 效能分析的提示 描述,在自己的實體電腦運作 GNU/Linux,做好必要的設定和準備工作
硬體配置
研讀上述費氏數列相關材料 (包含論文),摘錄關鍵手法,並思考 clz / ctz 一類的指令對 Fibonacci 數運算的幫助。請列出關鍵程式碼並解說
推導可得知 \(F(K)\) 和 \(F(K+1)\) 可得出 \(F(2K)\), \(F(2K+1)\), \(F(2K+2)\)。
\[
\begin{split}
F(2k) &= F(k)[2F(k+1) - F(k)] \\
F(2k+1) &= F(k+1)^2+F(k)^2
\end{split}
\]
透過最後需要求出的數字 N,不斷除以二來得知要如何從 \(\begin{pmatrix}F_0 \\ F_1\end{pmatrix}\) 到 \(\begin{pmatrix}F_N \\ F_{N+1}\end{pmatrix}\)
如果將數字轉成二進制,最高位元會對應到前面狀態的 a 是奇數或偶數,最低位元會對應到後面狀態的 a 是奇數或偶數,例如求 \(F(10)\),10是奇數或偶數需要由最低位元來判斷,\(F(1)\) 的奇偶則是由 MSB 來判斷
__builtin_clz (counting leading zero)
後:
temp
來暫存 \(F(N+2) = F(N) + F(N+1)\) 的結果iteration 預設版本
vs fast doubling
vs 加上 clz 的 fast doubling
mode
來決定要使用哪種運算__asm__ volatile
楊制讓編譯器使用原本程式碼lsmod 的輸出結果有一欄名為 Used by,這是 “each module’s use count and a list of referring modules”,但如何實作出來呢?模組間的相依性和實際使用次數 (reference counting) 在 Linux 核心如何追蹤呢?
reference counting 在 linux kernel 中由 atomic_t 型態的變數來記錄,並提供 API 來讓我們初始化/增加/減少 reference counting。
需要為 atomic counting 原因為多個 cpu 可能會同時維護一個 reference count
ref:
注意到
fibdrv.c
存在著DEFINE_MUTEX
,mutex_trylock
,mutex_init
,mutex_unlock
,mutex_destroy
等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題。嘗試撰寫使用 POSIX Thread 的程式碼來確認。
為了檢驗多個 process 同時對 fib device 互動時的行為,撰寫 multiprocess 的測試檔案(改寫自 wiki)
建立三個 thread ,如果無法開啟 device 則會等待 0.5 秒後再次嘗試開啟,在對 device read 後等待 0.1 秒後才會進行下一次 read
process 0 進入 device 後上鎖,其他 process 無法進入 device 後開始等待直到能成功進入,因此執行順序為 process \(0 \rightarrow 1 \rightarrow 2\)
拔除 mutex 後執行順序會交互,但運算結果還是正確,這是因為 read 行為只用到 offset 來當 \(F(n)\) 的輸入,沒有共用任何變數
ratio
,並更改 fib_read
行為:
fib_open
成功後和 fib_release
都會把 ratio
歸零預期正常的執行結果為:
有 mutex lock 輸出結果如同預期
去除 mutex 後 ratio
的數值會隨著不同 process 的 read / open / release 行為更動,最後產生無法預期的結果
參考 KYG-yaya573142 的實做
在宣告實體方面, bn_init 可以宣告 size 位元的實體,並把數值全設定為0
為了靈活運用,新增從 int 初始化的方式
MAX(a->size, b->size) + 1
的空間來除存運算結果宣告位元長度 +1的空字串並填入數字
包含 指標交換
, free
, copy
dest 為要寫入的目標,透過 swap 來達到把結果左移的效果
嘗試研讀 bignum (fibdrv 分支) 的實作,理解其中的技巧並與你的實作進行效能比較,探討個中值得取鏡之處。
比較自己實做的 bignum 與教授提供提供的 bignum (my
為我的實做, ref
為教授的實做)
單就 iteration 版本來比較運算時間差異巨大,代表除存數字的資料結構本身有很大的問題。以十進制為單位的運算,相較於 apm_digit
用 uint_64_t
或是 uint_32_t
為,會進行更多次的進位和加法
大數資料結構比較:假設要做6位數的運算,以十進制的資料結構要做許多次的加法和進位計算,以 uint_64_t / uint_32_t 的則只要一次即可
bn
為大數的資料結構,裡面包含了
apm_digit
pointer 指向數據,根據系統不同可為 uint32_t
或是 uint64_t
apm_size (uint32_t)
的 size 和 alloc 用來紀錄 digits 長度和已經分配多少個 apm_digit
大小的空間c = a+b
cy
來紀錄進位,有進位則讓 c 的空間增大並儲存進位每當要進行加減法之前,都先用 BN_MIN_ALLOC
來確保 c 所分配的空間能存放運算結果,也運用到了 do...while
來避免展開後的 dangling else 發生
而 __n->alloc = ((__s + 3) & ~3U)) 這段相當於讓 n->alloc = \(s*4\)
在系統中 apm_digit 可為 4 Byte / 8 Byte ,指派 alloc 為 \(size*4\) 也符合 min alloc 的用意
而實際的加減法是由 apm_add
, apm_sub
執行
apm_add
會先做檢查來確保運算能正常進行讓 apm_digit *u 和 apm_digit *v 分別指向兩個變數的最低位元,從最低為原往高位元運算
在 15 16 兩行完成了
非常精簡的寫法,最後 return 進位,來讓呼叫他的 apm_add 決定是否擴大空間儲存進位
在這邊的 u 必大於 v,技巧跟 add 一樣只是回傳的是借位。可以用 (*w = ud - vd) > ud
來計算借位,是因為 unsigned int 如果不夠減,會從最大值往循環,例如
(uint_32_t) 10 - (uint_32_t) 11 = 4294967295
APM_TMP_ALLOC(csize)
展開成 xmalloc(size * APM_DIGIT_SIZE)
(APM_DIGIT_SIZE 可為 4 或 8,代表 apm_dight 占用多少 Bytes)apm_mul
來完成(usize+vsize)-(ul + vl)
,(ul + vl)
是去除 leading zero 的位元數vsize < KARATSUBA_MUL_THRESHOLD
時用 \(O(N^2)\) 複雜度的乘法,但當超過 KARATSUBA_MUL_THRESHOLD
就用 apm_mul_n
(karatsuba 快速乘法)。複雜度為 \(O(N)\) 的乘法,作法為依序讓 v 的單一位元和 u 相乘。
在 digit 乘法做了以下優化,用了 inline asm
Extended assembly 的格式為以下,得知 lo 和 hi 是 output operands
register 對應表
回來看程式碼,以下解釋 %0
和 %3
代表什麼意思
再來談到限制,=a
指定要用 rax 暫存器,=b
指定 rdx 暫存器
mulq 執行: 將輸入 s 乘上 rax 暫存器,存放結果到 R[%rdx]:R[%rax] 構成的 128bit oct-word
%rdx
和 %rax
暫存器 (都是64bit),%rdx
高位元 / %rax
低位元ref:
Karatsuba 為一種快速乘法方式,可將原先 \(n 位數 \times n位數\) ,所需 \(n^2\) 次的乘法次數減少為 \(3n^{\log{2}^{3}}\)
假要做 \(x=56\) 和 \(y=12\) 的乘法,可以先把 x 和 y 個別拆成 (0~u-1)位元 和 (u~2u)位元,\(u = max(digit(x),digit(y)) /2\) ,在這為1
\[
x = 56, \ a = 5, \ b = 6
\\
y = 12, \ c = 1, \ d =2 \\
\]
x, y 可寫成
\[
x = a * 10^{u} + b =56 \\
y = c * 10^{u} + d =12
\]
兩者相乘
\[
x * y = ac*10^{2u} + 10^{u}(ad+bc) + bd
\]
需要求出
\[
(ac) \ and \ (ad+bc) \ and \ (bd)
\]
Karatsuba 的做法為:
\(Step \ 1.\) 求出 \(a*c\) (mul times+1)
\(Step \ 2.\) 求出 \(b*d\) (mul times+1)
\(Step \ 3.\) 求出 \((a+b)(c+d)\) (mul times+1)
\(Step \ 4.\) 計算 step3 - step2 - step1 = ad + bc
\(Step \ 5.\) 整合結果 \(x * y = ac*10^{2u} + 10^{u}(ad+bc) + bd\)
目標為求出
\((2^{2N} + 2^N)u1*v1 + (2^N)(u1-U0)(v0-v1) + (2^N + 1)(u0*v0)\)
中項使用 (U1-U0)(V0-V1)
是希望避免進位導致多餘的計算
u1
u0
v1
v0
指向切分起點(這邊代表前面介紹算法的a, b, c, d 變數),進行遞迴算出\(u1 * v1\) 和 \(u0 * v0\)(U1-U0)(V0-V1)
運算結果(U1-U0)(V0-V1)
會不會產生負號dev/fibonacci ⇒ client.c (可能會是多個 process) / lseek
VFS 的 lseek 操作的用意?
圖/資料來源
virtual file system 是核心建立的中介層,提供抽象的方法 像是 open, stat, read, write, chmod 讓 user 和 kernel 互動。遵循 vfs 開發的檔案系統可以被掛載到 linux 核心內。
在 fibdrv.c
內的 file object
file_operations 結構定義在 <linux/fs.h>
內,有此結構的程式載入到 kernel 後可與 user space 透過 VFS 介面互動。
lseek 的用意是更改 process 的 file descriptor 指向的 open file table offset(讀寫位置),且每個被 open 的 file,他們的 file descriptor 都是獨立的互不干擾。