contributed by < OscarShiang
>
linux2020
MathEx
如何解析給定字串,從而分離出變數和對應數學表示法呢?MathEx
中,nop
一類使用者自訂的函數如何實作呢?MathEx
對於負數的除法結果不正確、乘法和加法的 overflow 沒有做處理,且缺乏對減法的 underflow 處理機制呢?你預計如何克服?expression.[ch]
裡頭 vec
相關的程式碼,將 quiz4 使用到的技巧予以應用,提出對空間效率更好的實作在 Linux 的核心中為了計算 load average 的數值,使用到了定點數的概念,首先看到 include/linux/sched.h
中相關的 macro :
可以看到在計算時會使用到的精度 FSHIFT
與在使用 Exponential smoothing 會用到的常數 EXP_1
, EXP_5
與 EXP_15
而在實作的部分在 kernel/sched/proc.c
中可以看到在 calc_global_load
函式中
為了分別計算 1, 5, 15 秒的 load average,在實作上對 avenrun
三個元素進行運算,利用 calc_load
計算數值
而 calc_load
的函式如下:
可以看到數值使用是 unsigned long
來實作,而為了維持定點數計算的正確性,在結束乘法運算後需要將數值向右 shift 才會是計算的結果
當 load average 被算出來之後,接著就是讓其可以正確地顯示出來。
在 fs/proc/loadavg.c
的實作中
LOAD_INT
與 LOAD_FRAC
的定義如下
LOAD_INT
的實作就是將小數部分捨棄,就可以直接使用。
而 LOAD_FRAC
的話,首先將 x
與 FIXED_1-1
進行 AND 運算,因為 FIXED_1 = 1 << FSHIFT
也就是 ,減 1 後就變成 所以透過遮罩的方式就可以將 x
的小數部分取出。
接著將其乘以 100 再使用 LOAD_INT
就可以取出小數部分的前兩位了
在 Linux Programmer's Manual
的 time(7)
有提到
Real time is defined as time measured from some fixed point, either from a standard point in the past (see the description of the Epoch and calendar time below), or from some point (e.g., the start) in the life of a process (elapsed time).
在 test_simple.c
中可以看到,解析字串的函式是 expr_create
,該函式中首先利用 expr_next_token
尋找 token 在字串的位置。
expr_next_token
最主要的功能如下:
#
標注的註解時忽略整行內容\n
時向後尋找其他內容最後將 token 所在的 index 回傳給 n
。
回到 expr_create
函式後,依據 token 的類型(數字、運算子、變數等等),分別存入不同的 vec 之中。因為 vec
的 push
與 pop
都是處理 vec 尾端的元素,所以可以知道在 vec 是作為 stack 使用
在遇到 '('
與 '{'
等等字元時,就會把儲存在 os
與 as
中的運算子與參數進行包裝,利用迴圈的方式重複這樣的過程,讓 result
在傳入 expr_eval
時可以用遞迴的方式將傳入的表示式算出數值。
在分析完所有的字元後,將 es
這個 vec 的元素 pop 到 result
中,並將 result
回傳,結束字串的解析。
在 MathEx/test-simple.c
中有示範使用 user_funcs
陣列包裝函式並在使用 expr_create
時一併傳入。而 struct expr_func
需要的 field data 有二個:
而在測試程式的實作如下:
上述實作中,在填入完函式的名稱與指標後,第三個參數 0
是為了讓編譯器可以將後面的資料初始化為 0。而最後一個 field data 全部都是 0 的元素是用來標記結尾的。
在 expr_create
的分析中,如下列的程式碼:
就會去比對傳入的函式的列表,如果與輸入的字串吻合的話,就會將函式的指標與參數一同 compile 成 bound_func
,讓 expr_eval
可以呼叫對應的函式來進行計算。
為了讓 livepatch 能夠找到對應的函式進行替換,我在 main.c
中加入 user_func_fib
作為 MathEx 的自定義函式。
在使用 user_func_fib
分析完參數後,將參數傳入 kfib
函式進行運算
在 livepatch_calc.c
中新增 livepatch_fib
函式作為 kfib
的替代函式
掛載 livepatch_calc
檢查函式是否被替換:
從 fromfixed
指令檢查計算結果,發現因為 livepatch_fib
未按照 fixed-point 的格式計算而產生錯誤
接著著手修改 livepatch_fib
以符合 fixed-point 的格式
目前的 fixed-point 所採取的作法是與 floating-point 類似的計算方式,就是將 32 bits 寬度的數值拆成三個部分: exponent 用以表示次方, mantissa 用以表示尾數以及 sign 來表示正負號
因為在 exponent 的表示方法是以 10 為底數,所以不需要像 IEEE 754 所規範的 32 位元浮點數一樣使用 8 個位元來表示。而在這樣的表示法中可以表示的最大數值為 1342177270000000
,最小的數值為 -1342177280000000
但是在數值較大時會產生精度的問題,像是現行的表示法可以表示 1342177270
,但卻不能表示 1342177271
,因為 1342177271
已經超過 mantissa 所能表示的大小,但卻不到 exponent 可以表示的範圍,就會導致 的情況產生
參考 include/linux/sched.h
中所使用的定點數,並設計以下結構:
也就是使用 32 bit 記錄 「整數」 部分,以及另外 32 bit 記錄 「小數」 部分。
除了擴充可記錄的數值範圍外,更減少在存取整數部分時的計算步驟:在 include/linux/sched.h
中所使用的結構在存取整數部分時需要做位移,但在此結構中只需取出 inte
的 field member 即可;
而在進行加減運算時,不需要考慮 exponent 不同的議題,直接運算即可。在進行乘除時,則可以利用 data
的 field member 來做位移修正。
將字串轉換為 fixed-point 的實作如下:
因為在計算時可能會碰到 NaN
, INF
等情況發生,所以需要定義這些特殊值的表示方法:
INF
: 調用 fraction
全為 1 的情況記錄NaN
: 最小的數值來表示 NaN
將 main.c
中,為了符合新的 fixed-point 的實作,將 user_func_fib
進行修改:
因為在新的 fixed-point 的定義下,整數部分實際上能運算到的最大範圍還是 ,所以在這樣的實作下 fib
實際能正確計算出的最大數值是 fib(75)
。
因為更改了 fixed-point 的實作,所以為了能檢驗運算的結果,需要一個新的 fixed-point 轉換的程式:
使用定義在 <stdint.h>
中的 UINT32_MAX
將小數部分轉換為浮點數形式,並印出對應的浮點數數值。
接著修改 scripts/test.sh
的轉換機制,將 fromfixed
改成執行編譯好的 scripts/eval
:
執行 make check
檢查結果:
從上面的結果可以看到完成 fixed-point 的轉換。