contribute by < arthurchang09
>
下方組合語言可以順利完成 alignment
的部分,相當於 memchr_opt
第一個 while
迴圈,
目前已將 alignment 與 DETEC_CHAR
巨集的部分改寫成以下 inline assembly 。
效能與正確性測試程式如下
編譯
初步在虛擬機 (CPU i7-10700K) userspace 之效能如下圖
雖然效能不及 default-memchr
,但已比 memchr-opt
運行速度更快。
預計採用 fibdrv
方式寫成 Linux character device driver,使用 copy_from_user
將字串從 userspace
複製到 kernel 放入 kmalloc
要到的記憶體空間,最後將結果使用 copy_to_user
回傳到 userspace
,並將執行時間用 return 的方式回傳。
inline assembly 效能卻較差,試著在優化
將 alignment 的部分稍作修改,省一次 jmp
。
考慮到之後會在 LKML 提交圖表,標示方式應該統一:
glibc-memchr-x86
: glibc 裡頭 sysdep/x86 的 memchr
實作linux-memchr-x86
: 原本 Linux 核心的 x86 memchr
實作memchr-x86-opt
: 上述針對 Linux 核心 x86 memchr
的效能改進實作回顧 inline assmebly 程式碼,用 gcc -S test.c
觀察組合語言,發現每一段 inline assmebly 前都會將原本 register 的植存回記憶體,我的實作又有數個分段的 inline assembly ,因此最佳的方式只要一塊 inline assembly 。
經過數次對比測試,發現去掉下列部分組合語言:
去掉開頭 alignment 的組合語言在不開啟編譯器優化下效能沒差。但先去掉以節省將 register 存回 memory 的操作。
在虛擬機中 Linux 核心之效能表現不錯。和 glibc-memchr-x86
實作比較上,效能也有些許勝出,如下圖所示:
以上測試皆在虛擬機上,轉移到實體電腦測試(CPU i5-7200U)。
參照 std::find() and memchr() Optimizations 一文,使用其效能分析的手法,對上述 memchr-x86-opt
和相關實作進行評比。
接著參考 std::find() and memchr() Optimizations , 其採用 128 KiB
的 char
buffer 儲存字串。會選擇這個大小是因為會有更好的 throughput ,參見 GNU 註解 ,我先在虛擬機是跑裡面的 script
得到以下輸出:
筆電雙系統
每次執行執會稍有不同,但可看出大約在 速度就達到接近頂峰,因此選擇這個大小。
此文作者程式碼主要是測試較為破碎的字串,輸入資料擷取部分如下:
測試程式會用一次 find()
,和 memchr
功能相近的程式,去找每一行的 \n
的位址,再用一次 find()
找到 |
。以上方的資料為例,第一次尋找會找到 W,w,WW,WWW,WY|Y,y,A
,第二次會找到 |Y,y,A
。接著移向下一行再尋找,直到 buffer ,即暫存所有資料的 array 走到盡頭,接著會再繼續從檔案讀取下一波 131072
byte 資料。因此,估計在不開優化的情形下 linux-memchr-x86
的表現不會太差。
以下為改寫成 C 之測試程式碼
採用 command line 的 time
測量整個程式的執行時間,不開啟編譯器優化
memchr-x86-opt | linux-memchr-x86 | glibc-memchr-x86 | |
---|---|---|---|
real time | 11.33825 | 11.72425 | 13.076 |
user time | 11.0325 | 11.406 | 12.726 |
sys time | 0.2935 | 0.32425 | 0.344 |
由上方表格可看出在短字串下 linux-memchr-x86
表現比 glibc-memchr-x86
好
接下來測試開啟編譯器優化,由於先前 inline assembly 的 label 是採用文字,造成開啟 -O3
優化時會出現錯誤,因此在此修改 label ,改用數字表示,且在 jump
instruction 後的 label 要加上 f
或 b
表示會往下方或上方的 label 進行 jump ,使編譯器 unroll 時不會發生錯誤,如下:
memchr-x86-opt | linux-memchr-x86 | glibc-memchr-x86 | |
---|---|---|---|
real time | 8.34025 | 11.1175 | 8.4965 |
user time | 8.01525 | 10.82 | 8.23925 |
sys time | 0.312 | 0.291 | 0.257 |
開啟 -O3
後, 三者的速度都有所加快。其中 glibc-memchr-x86
之速度加快很多,但仍較 memchr-x86-opt
慢。
接著回去看組合語言,兩者皆是將函式直接放入 int main()
中,因此 funtion call 的成本兩者應該是相近的,其中並未使用 loop unrolling 的方式優化。因此推測是 inline assmebly 的部分帶來的加速效果。因此我使用原本測驗一的程式碼再跑一次測試,並使用 perf
比較是否是組合語言達成這個效果,如下:
memchr-x86-opt(assembly)
memchr-x86-opt(original)
glibc-memchr-x86
linux-memchr-x86
整理如下:
memchr-x86-opt(assembly) | memchr-x86-opt(original) | glibc-memchr-x86 | linux-memchr-x86 | |
---|---|---|---|---|
time(s) | 8.25079 ± 0.00243 | 8.77154 ± 0.00486 | 8.50679 ± 0.00295 | 11.2574 ± 0.0114 |
instructions | 447,8938,8415 | 448,9144,0365 | 444,6003,3863 | 309,2942,6350 |
cache-misses | 282,0384 | 342,8490 | 335,5993 | 1548,9334 |
insn per cycle | 2.18 | 2.06 | 2.10 | 1.10 |
相較於測驗一原始程式,可以看出我修改的 inline assembly 在 IPC(instruction per cycle) 以及 cache-misses 有所領先。
以上的測試主要是針對較短字串,若目標字元在整個字串相對靠後的位置,表現應該會有所變化。接下來我對輸入資料做修改,在 |
之前至少有 200 個單字,後面有 1 ~ 50 個單字,每個單字平均長度以 5 個字元計算,包括逗號,至少需比較 1199 個字元。
資料產生的方式為以下 word_gen.py
,從 /usr/share/dict/words
中隨機選取單詞組合
範例字串
開啟 -O3
優化 結果
memchr-x86-opt | linux-memchr-x86 | glibc-memchr-x86 | |
---|---|---|---|
real time | 1m 5.57875 | 4m 20.38075 | 1m 16.8215 |
user time | 45.76925 | 3m 57.2797 | 55.55025 |
sys time | 19.73975 | 22.98525 | 21.2315 |
未開啟優化 :
memchr-x86-opt | linux-memchr-x86 | glibc-memchr-x86 | |
---|---|---|---|
real time | 1m 30.063 | 4m 20.9525 | 2m 28.592 |
user time | 1m 8.37275 | 3m 57.7035 | 2m 6.033 |
sys time | 21.73575 | 23.12075 | 22.484 |
由以上表格可看出在不開優化下 linux-memchr-x86
在長字串的搜尋就沒有比 glibc-memchr-x86
快。
由於 time
這個命令可能會將 ELF loader 和 VMA 的成本都算進去,因此改用 struct timespec
來測量時間這 3000 次 iteration 總共花的時間。
結果如下:
開啟 -O3
memchr-x86-opt | linux-memchr-x86 | glibc-memchr-x86 | |
---|---|---|---|
時間(s) | 65.403 | 259.202 | 65.284 |
未開啟優化
memchr-x86-opt | linux-memchr-x86 | glibc-memchr-x86 | |
---|---|---|---|
時間(s) | 89.296 | 259.409 | 147.488 |
意外的是開啟 -O3
時 glibc-memchr-x86
這次的測試成績居然較 memchr-x86-opt
佳,大約可以快約 0.1 ~ 0.8 秒,因此我用同樣的方式重新測量了一次時間,結果 glibc-memchr-x86
時間又回到之前採用 time
命令的長度,反而 memchr-x86-opt
的執行時間沒有太大改變,維持 65 秒上下,不太清楚為何有這 10 秒的差距。
memchr-x86-opt | linux-memchr-x86 | glibc-memchr-x86 | |
---|---|---|---|
時間(s) | 65.398 | 259.214 | 76.24 |
接著我在程式碼不同的位置放上 clock_gettime
測量不同區段的執行時間。首先,放在上方程式碼 17 行 for
迴圈前後,將每次跑迴圈的時間累加起來 。接著是在 while
迴圈前後,將每次檔案處理完的時間加起來,比較這其中的差距。
逐次 for
迴圈時間總和
memchr-x86-opt | glibc-memchr-x86 | |
---|---|---|
時間(s) | 45.5391 | 44.93 |
逐次 while
迴圈時間總和
memchr-x86-opt | glibc-memchr-x86 | |
---|---|---|
時間(s) | 69.74045 | 76.209 |
明顯觀察到 for
迴圈的執行時間 glibc-memchr-x86
較快,而 while
迴圈中則變慢,且 while
迴圈的測量數據中, memchr-x86-opt
竟然較測量 3000 次 iteration 長,明顯測量上有問題。
接著我測量跑完單一檔案所花的時間,也就是 while
迴圈前後的位置放上 clock_gettime
,但只執行一次就跳出最外層的 for
迴圈,結果如下:
memchr-x86-opt | glibc-memchr-x86 | |
---|---|---|
時間(s) | 0.02173 | 0.02484 |
上方表格的數字乘 3000 就和使用 time
以及測量 3000 iteration 的時間相近,
跟老師討論後,認為時間的差距有可能是因為 CPU0 存在一些特定的裝置驅動或 kthread,造成測量的時間有誤,因此重新限定 CPU 給特定的程式使用。我的 CPU 為 2 核 4 緒,因此將 CPU2 和 CPU 3 獨立出來。
進入 /etc/default/grub
看到以下這行
改成
在筆電的 BIOS 翻了一圈並沒有相關選項,因此無法關閉。推測可能是因為筆電的 BIOS 限制較嚴格,加上我的筆電 CPU 其實只有兩個實體核,故無法關閉。
為了去除可能的誤差,我按照老師的提示,運用統計手法去除極端值,並開啟 -O2
優化。
首先,測量一次檔案的時間,總共取 1000 次,並且取 95% 信賴區間。
memchr-x86-opt | linux-memchr-x86 | glibc-memchr-x86 | |
---|---|---|---|
時間範圍(s) | 0.02190 ~ 0.02201 | 0.08536 ~ 0.08713 | 0.02256 ~0.02275 |
可看出在 -O2
優化下, memchr-x86-opt
和 glibc-memchr-x86
效能是相差無幾,兩者的核心概念是相同,只是實作方式稍有不同。我所新增的組合語言也是基於 -O3
的組合語言改寫,因此效能差不多是在意料之中。
相較於 linux-memchr-x86
, memchr-x86-opt
的執行時間是 前者的 ,可見 SWAR 、 word-wide comparing 是十分有效的方法。
UML 之核心版本為 5.15,memchr
反組譯的結果如下:
簡單觀察組合語言, lea
將資料讀入 register , cmp
將讀入的資料和字元做比較,其中 %sil
是一個 8-bits register ,若相同,則 Zero Flag 設起,會跳到 req
回傳值。因此這部分的組合語言為 byte-wise comparing 。
以下組合語言輸出命令如下
gcc -S asm.c
gcc -S -O2 asm.c
並使用 diff
命令比較組合語言差異
if (!TOO_SMALL(length))
中的部分,多了也就是 unsigned long *asrc = (unsigned long *) src;
這段程式碼的組合語言。
但開啟 -O2
優化後,這部分被優化掉,有沒有 上述這段程式碼不影響組合語言。
-O2
優化時也沒有差異。我直接 git clone
Linux 在 Github 上的專案。
git blame arch/x86/lib/string_32.c
memchr 基本上由 Andi Kleen 和 Paolo Ciarrocchi 變更 memchr
這段程式碼,主要內容還是由 Andi Kleen 撰寫。
memchr
的改進描述,可參見 arm64: Better optimised memchr()
Although we implement our own assembly version of
memchr()
, it turns out to be barely any better than what GCC can generate for the generic C version (and would go wrong if thesize_t
argument were ever large enough to be interpreted as negative). Unfortunately we can't import the tuned implementation from the Arm optimized-routines library, since that has some Advanced SIMD parts which are not really viable for general kernel library code. What we can do, however, is pep things up with some relatively straightforward word-at-a-time logic for larger calls.Adding some timing to optimized-routines'
memchr()
test for a simple benchmark, overall this version comes in around half as fast as the SIMD code, but still nearly 4x faster than our existing implementation.
commit 9e51cafd783b22018fb15bfb06d65f69349223a9
思考中…
The original assembly version of memchr()
is implemented with the byte-wise comparing technic, which is not fully using whole 64-bits registers in x86-64 CPU. We use word-wide comparing so that 8 characters can be compared at the same time on x86-64 CPU. First we align the input and then use word-wise comparing to find the first 64-bit word that content the target. Secondly, we compare every byte in the word and get the output.
We create two files to measure the performance. The first file contains on average 10 characters ahead the target character. The second file contains at least 1000 characters ahead the target character. It turns out that "memchr()" is slightly better in the first test and nearly 4x faster than the orginal implementation in the second test.
memchr-opt-x86
可能的添加方式以下節錄 /include/linux/string.h
以及 /lib/string.c
可看到若無 def __HAVE_ARCH_MEMCHR
,則會使用 string.h
中的 memchr
。
追溯 __HAVE_ARCH_MEMCHR
,發現在 arch
目錄下各個架構 CPU ,如 arm 、 arm64 、 powerpc 、 x86 等的 string.h
都有,其中 x86 的部分只有在 string_32.h
、 string_32.c
中有 define。
接著觀察 x86 這兩個檔案,擷取如下:
string_32.h
中 define __HAVE_ARCH_MEMCHR
,並在 string_32.c
中判斷是否已 define ,並且實作程式碼。
接著再看 string_64.h
,其內容也類似上述描述,都是 #define __HAVE_ARCH_XXXX
接著 function 的宣告,只是沒有統一的 string_64.c
,而是各個 .s
file 的組合語言檔案。因此,memchr-opt-x86
要加入且暫時不管 x86-32
的話,應該是在 string_64.h
做出類似前述的 #defin
,並且在 /arch/x86/lib
建立一個對應的 .c
檔。
之後會嘗試在 UML 上修改並測試。
增加下面這兩行
並將我的 memchr-opt-x86
新增成 string_64.c
,如下:
compile 成 UML 並使用命令 objdump -xd linux | grep -A12 "<memchr>"
卻沒有找到 memchr
之組合語言,使用命令 objdump -xd linux | grep memchr
,部分輸出如下:
顯示成功移除掉原本的實作,但我的程式並沒有成功放入。推測是沒有進行編譯並放入核心,因此要需要對 Makefile
做修改。
arch/x86/lib
增加此行後,按照前述反組譯仍沒有出現,我繼續尋找解法。我追蹤 string_32.o
,發現在 arch/x86/um/Makefile
中有一段如下
在第 23 行有 ../lib/string_32.o
,其所在的 ifeq
是判斷 CPU 是否為 32 bits ,則下面的 else 必為 64 bits ,因此我在第 31 行最後面增加 ../lib/string_64.o
,再編譯一次 UML 。
首先輸入命令 objdump -xd linux | grep memchr
,擷取部分輸出如下:
接著 objdump -xd linux | grep -A53 "<memchr>"
注意到 60036d4b
到 60036d6c
是我寫的組合語言,至此我的程式成功進入 UML 中。
unsigned long
或 long
的 macro執行 grep -r "sizeof(unsigned long)" *
或 grep -r "sizeof(long)" *
找到以下
在尋找 macro 前,發現 kernel 中有些程式碼直接實作 unalign 而非使用 macro ,如 /lib/string.c
中的 strscpy
在 include/linux/align.h
有 alignment 的 macro 。
對比原本的 unalign
可發現實作方式基本一樣,只是多個 == 0
,因此可以直接使用。
改成如下
參考 建構 User-Mode Linux 的實驗環境
編譯完 linux
執行檔,進行 UML 的設定時,執行 UML.sh
,其內容如下
出現以下錯誤
於是決定直接執行 ./linux
並比較
原版
增加 string_64.c
版本
似乎是要印出 Linux version 5.15.0 ...
時出現 Aborted (core dumped)
,經過一番查找,發現是我自己寫的 memchr
中組合語言有問題,用原本測驗題的程式碼不會 Aborted (core dumped)
,經過測試,發現是迴圈的條件判斷出問題,即這段指令 jae 1b\n\t
,修改成 ja 1b\n\t
即沒有問題。
在 Userspace 進行正確性驗證,將測試字串放入 memchr-opt-x86
及 glibc 採用 SSE2 指令的 memchr
並比較字串內容,測試到字串長度為 10 萬皆相同。
修改後的結果如下:
5/20 加入初始版註解
準備核心模組時,輸入下列命令
出現以下
改成下列命令即可。
在 Linux 核心程式碼最上層建立一個名為 tests
的目錄,對 建構 User-Mode Linux 的實驗環境 範例的 hello.c
稍作修改。
hello.c
內容:
Makefile
如下
編譯上述核心模組並複製到 rootfs 中
啟動 UML 並掛載
在增加 string_64.c
的 UML 輸出結果如下
原始版本如下
第三行的結果為用 ktime
得到的時間,因為是 UML ,其值不代表實際時間,但可看出有我的 memchr
的確較快,也證實程式碼確實有進入核心。
考慮以下讓程式碼更精簡的修改:
記得要用 checkpath 檢查,搭配 –strict
選項
memchr()
放入核心並重新 compile這是在虛擬機重新編譯核心,劃分的硬碟空間建議只少 50 GB,CPU Core至少分配 4 個。
安裝必要套件
下載 Linux Kernel 程式碼
解壓縮並進入到此目錄
複製原本的 .config
產生 所需 .config
選擇 load
再選擇 save
接著執行下列命令或動作避免編譯過程產生錯誤
並將 .config
中 CONFIG_DEBUG_INFO_BTF
改成如下:
開始編譯, -j 4
表示動用四個 CPU Core 加速編譯,此過程耗費時間最長。
跑完會有以下輸出
編譯 modules
安裝 module
安裝核心
更新 grub
接著重新開機,從 UML 的安裝可知 memchr()
和開機有關,若核心有成功更新成 5.17.9
且能成功開機,則更新後的 memchr()
應該是沒有大問題。
輸入命令檢查核心版本
得到以下輸出
核心版本成功更新。
接著一樣撰寫 kernel module 觀察 memchr-opt-x86
是否真的編譯入核心。
Makefile
執行輸入下列命令將核心模組放入核心
得到以下結果
可見核心的 memchr 和 memchr-opt-x86
執行時間相近,可以說成功放入 Linux Kernel。
今天發布 5.18 版,可能要再編譯一次核心並測試
經過重複上述步驟,在 5.18 Linux kernel 成功開機,利用上述的 kernel module 的結果也相似。
第一個 commit message 內容
第二個 commit message 內容
第一個 commit 跑命令 ./scripts/checkpatch.pl -strict
出現以下訊息
WARNING
的部分確定是要新增檔案,因此可以不理會。 CHECK
的部分建議在 .h
中避免 extern
prototypes ,我翻查 Linux Kernel 中其他 string.h
相關的 memchr()
prototype ,皆是 extern
prototype 。
按照老師的指示增加了 cover letter
第一份
第二份
第二份的收件人應該是 Jeff Dike (5/28 更正), email 為 jdike@linux.intel.com
。第一份的收件人應為 Andi Kleen 。雖然他不在前述的名單上,之前用 git blame
查詢到他是相關檔案的維護者,他的 email 為 ak@linux.intel.com
。
安裝 git-email
設定 .gitconfig
由於我的 gmail 有設定雙重驗證,所以要透過 git 送 email 要開啟應用程式密碼,在手機開啟 Google帳戶 -> 安全性 -> 應用程式密碼 ,在此設定密碼。
接著輸入命令
要輸入密碼時,輸入前面設定的密碼,抑或是在 .gitconfig
設定。
傳輸完成後在我的兩個信箱皆有收到信。
開發者有三點回復:
d = c
好像是 pointer 和值的比較,可能有問題0xFEFEFEFEFEFEFEFF
) constb (0x8080808080808080
) 的用途是什麼?jne
指令是否要改為 jnz
,後者似乎更合適。經過一番檢視,我的答案如下,目前尚未回覆給開發者:
d
的宣告方式,這應是 unsigned char
而非 unsigned char*
,一開始的 unsigned char* src
讓開發者以為 d
是 pointer
,實際上不是。(可能要指出 C 語言規格書的說明?)0x0101010101010101
,只是為了減少步驟寫成加法。因此 CF flag 不需要處理。 指令寫成 jnz
會更有助於理解,但其作用和 jne
相同,皆是檢查 Zero Flag ,會做修改。Andi Kleen 則是要求指出在 Kernel 中具體的效益,目前在研究中
另一位開發者 David Laight 也有提出幾點意見:
mask |= mask << i
%rdi
?read-write
的 register 要用 +r
,不要重複寫add
and jnz
.我經過測試,發現應該是要將 length
變成 8 的倍數,因此將 IS_ALIGN()
巨集改成 length & LBLOCKSIZE
。 if
條件判斷中將 for
迴圈去掉,將 inline assembly 做一些調整並改寫了一小部分。經過 UML 測試能成功啟動。 但對於 David Laight 最後兩點還是不知如何達成。
我去掉了 alignment 的部分,並且新增一個變數 dst
,為執行 word-wise comparison 的上界。因此在組合語言中,不必對 length
做減法,只需比較 src
和 dst
之大小關係,省下一個指令,等於每跑一次迴圈,減少執行一個指令。
David Laight 將我的程式碼改寫,整題而言較為簡潔易懂,沒有組合語言。也解決 target character 為負時會出錯的問題。我認文可以當最終版本
在程式碼中加入
發現執行 UML 時會卡住,因此改寫成
接著先執行先前編譯好的 UML 執行檔
擷取部分輸出
可看到 call memchr()
時,字串的長度並不長,大約只有不到 200 個字元。根據先前的測試,優化過的 memchr()
的效能優化並不明顯。
以上是將化的 memchr()
重新編u4並放入 kernel ,開機後的結果。同樣最多只有 200 多的字元。
目前 arch/x86
目錄中,針對 x86-64 調整過的實作是 memcpy, memset, memmove,但其他的 mem/str 系列函式仍採用 generic 實作。也許可查閱 arch/x86/lib/string_32.c
以確認在 Linux 核心使用的狀況。
我接著在 /lib/string.c
的 memchr
增加程式碼
得到的結果類似先前的測試,顯示 generic 實作似乎沒有被使用。目前看起來 memchr()
的使用狀況看我之前的觀察類似,透過 #define __HAVE_ARCH_MEMCHR
決定使用哪種 memchr()
。由於前述尋找 memchr()
皆是在虛擬機上運行。接下來會嘗試在雙系統重新編譯 kernel 並且測試使否有所改變。
嘗試繼續尋找,在 /drivers/misc/lkdtm/heap.c
第 207 行找到
這裡可以看到要搜尋 512 byte
/lib/string.c
的 memchr()
與 memchr_inv
跟老師討論後決定改成貢獻 lib/string.c
中的 memchr()
,並且和其功能相似但作用相反的涵式 memchr_inv()
進行 macro 上的整合。
memchr_inv()
功能為找出第一個和不標字元不相同之字元位址,也就是 memchr()
相反的功能。
在 /lib/string.c
的實作如下
這裡可看到在字串長度小於 16 字元時,直接呼叫 check_bytes8()
尋找第一個不符合的字元。
接著使用一連串的 #if defined
來建立 mask 。如果是 64-bits CPU ,其 BITS_PER_LONG
會是 64 ,以及有 ARCH_HAS_FAST_MULTIPLIER
,直接用乘法建立,像我的 CPU 屬於此類。若是 32-bits 且有 ARCH_HAS_FAST_MULTIPLIER
,會先建立 32-bits mask 再用 |
建成 64-bits mask。若沒有 ARCH_HAS_FAST_MULTIPLIER
則都採用 |
建立。
值得注意的是無論是 32-bits 或是 64-bits , memchr_inv()
一律採用 64-bits 的 mask 。然而在 32-bits CPU , u64
會需要使用兩個 register ,這樣執行效能是否和採用 unsigned int
效能一樣需要近一步的測試。
使用 complier explorer 並下 -m32
查看 32 bit 和 64 bits 的差別。在 32 bits 下都會用到較多的指令,如:
32 bits
64 bits
接下來 prefix
變數是用來處理 alignment ,將 unalign 的字元先處理掉。
接著 while
迴圈對長度為 8 bytes
的字串進行比較,若有不同則呼叫 check_bytes8()
尋找第一個不同的字元的位址。
最後再處理長度不到 8 bytes
的字串。
memchr
和 memchr_inv
共同處並合併兩者最明顯的共同之處為皆是要產生一個 word 長度的 mask ,因此可以寫成一個巨集,如下:
巨集名稱 CREATE_MASK
可能還要修改,目前先暫訂如此進行簡單測試。
由於 memchr_inv()
無論是 32-bit 或是 64-bit 皆是採用 u64
的型態進行處理,因此在 memchr()
也打算用同樣的方式,才能使用共同的巨集。
memchr()
變成如下
使用以下命令編譯成 x86-32 進行測試
成功通過之前使用的正確性測試
接著在 UML 測試修改過的 /lib/string.c
能否通過 UML 開機測試。
依照之前重新編譯 kernel 的方式將上方修改過的 memchr()
以及 memchr_inv()
,成功重新開機。
然而,重新編譯只編譯 64-bit 的 kernel , 32-bit kernel 並沒有測試到。由於 Ubuntu 已經沒有 32 bit 的映像檔,因此我選用 Linux Mint 32-bit 來對kernel 進行重新編譯。
首先去官網下載 32-bit 版本。使用 Virtual Box 安裝時選擇 Ubuntu 32-bit 即可。
從 lscpu
可看到
對比 64-bit Ubuntu
這應該是 32-bit 的 Linux 沒錯。
接著重複之前的重新編譯 kernel 的步驟,並註解掉 /arch/x86/include/asm/string_32.h
中的 memchr()
,以免使用這塊的 memchr()
而非 /lib/string.c
中的版本。
另外像之前的測試一樣加入 printk("str %u\n", length)
觀察是否真的執行到 memchr()
以及字串的長度。
重新編譯好後, Linux Mint 成功重新開機,輸入命令 dmesg
也觀察到 memch()
的輸出,和 64 bit 版本差不多。至此可以確定 32-bit Linux 下 memchr()
能正確運行。
memchr()
透過git blame 可發現 mmemchr()
是 Linus Torvalds 撰寫的,而 memchr_inv
是 Akinobu Mita 所撰寫。
patch 1/2:
patch 2/2:
製作 patch 時,出現以下 CHECK
按照指示修該產生以下訊息
第一種情況為優先權問題,假設有一 macro 如下:
用以下方法呼叫
展開如下
然而,考慮到優先程度,實際上會用以下方式運行
而非
第二種情況是擔心 mask
可能是 fuction 的回傳值,在 macro 中重複使用會造成多次呼叫 function 。
然而,MEMCHR_MASK_GEN
這個 macro 只接受變數輸入,是將 mask
中的值展開成 8 bytes ,不可能牽涉到優先權以及 function 呼叫,因此我認為不須理會以上的 CHECK 。
輸入下列命令後
因此寄給的對象可能是 memchr_inv()
的作者 Akinobu Mita akinobu.mita@gmail.com
以及 Andy Shevchenko andy@kernel.org
根據老師的提示,有些處理器架構,如:ARM ,並沒有對 unaligned data 進行處理時會有效率問題,因此要針對這一點做處理,也是開發者們在乎的問題。