# 2019q1 Homework2 (fibdrv) contributed by < `fakepaper56` > ## 自我檢查清單 #### 檔案 fibdrv.c 裡頭的 `MODULE_LICENSE`, `MODULE_AUTHOR`, `MODULE_DESCRIPTION`, `MODULE_VERSION` 等巨集做了什麼事,可以讓核心知曉呢? insmod 這命令背後,對應 Linux 核心內部有什麼操作呢?請舉出相關 Linux 核心原始碼並解讀 1. 我們可以發現 `MODULE_LICENSE` , `MODULE_AUTHOR` 等巨集都是定義 `module.h` 中,同時他們都是被丟入 `__MODULE_INFO` 這個巨集中。而 `__MODULE_INFO` 定義 `moduleparam.h` 裡。 ``` clike= /* module.h */ #define MODULE_LICENSE(_license) MODULE_INFO(license, _license) #define MODULE_AUTHOR(_author) MODULE_INFO(author, _author) #define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description) #define MODULE_VERSION(_version) MODULE_INFO(version, _version) /* moduleparam.h */ #define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info) ``` 所以 `MODULE_LICENSE("GPL")` 我們可以轉換成 `__MODULE_INFO(license, license, "GPL")` 。 而 `__MODULE_INFO` 的巨集是定義如下: ``` clike= #define __MODULE_INFO(tag, name, info) \ static const char __UNIQUE_ID(name)[] \ __used __attribute__((section(".modinfo"), unused, aligned(1))) \ = __stringify(tag) "=" info ``` 我們先解析 `__UNIQUE_ID` 與 `__stringify` 這兩個巨集 可以看出來 `__UNIQUE_ID` 是做出 `__UNIQUE_ID_<perfix><line>` 的字串。 而 `__stringify` 跟它名子一樣,就是做字串化。 ``` clike= #define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __LINE__) /* compiler_types.h */ #define ___PASTE(a,b) a##b #define __PASTE(a,b) ___PASTE(a,b) /* stringfy.h */ #define __stringify_1(x...) #x #define __stringify(x...) __stringify_1(x) ``` 而最後,我們的`MODULE_LICENSE("GPL")`會被展開成如下: ``` clike= static const char __UNIQUE_ID_licienseXX[] __used __attribute__ ((section(".modinfo"),unused,aligned(1))) = "license" "=" "GPL" ``` `"license" "=" "GPL"` 這部份是利用C語言字串的特性,可視為一般的 `"license=GPL"` 。 那為什麼核心可以知道這些內容呢?他利用__attribute__,把這些描述內容放入`.text` 區的 `.modinfo` 區段而不是 `.data` 區。 2. #### 注意到 `fibdrv.c` 存在著 `DEFINE_MUTEX`, `mutex_trylock`, `mutex_init`, `mutex_unlock`, `mutex_destroy` 等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題 下面這支程式,我在 userspace 使用pthread做的程式,這兩個執行緒同時加了@n一萬次1,也同時減少了一萬次1。照理來說,我們預期結果@n為0。 ```clike= #include <stdio.h> #include <pthread.h> void *add(void *p) { int *ip = (int *)p; for(int i = 0; i < 10000; ++i) { int tmp = *ip + 1; *ip = tmp; } } void *sub(void *p) { int *ip = (int *)p; for (int i = 0; i < 10000; ++i){ int tmp = *ip - 1; *ip = tmp; } } int main() { int n = 0; pthread_t t1; // thread to execute add(). pthread_t t2; // thread to execute sub(). pthread_create(&t1, NULL, add, &n); pthread_create(&t2, NULL, sub, &n); /* wait for t1 t2 */ pthread_join(t1, NULL); pthread_join(t2, NULL); printf("%d\n",n); return 0; } ``` 執行結果如下,看起有不小的誤差。想樣一個場景,add() 剛執行完 tmp = *(ip) + 1 的時候我們跑完了一個完整的 sub(),這時候才執行 *ip = tmp 。這樣的話,中間插入的那個sub() 就跟白做了一樣。 ``` paper56@paper56-X510UNR:~$ ./a.out 127 paper56@paper56-X510UNR:~$ ./a.out -2234 paper56@paper56-X510UNR:~$ ./a.out 5194 paper56@paper56-X510UNR:~$ ./a.out -2734 ``` 核心如果沒有使用lock這種機制的話,如果執行多個行程卻同時讀寫一個資源的時候,也容易發生上面出現的狀況。 #### 許多現代處理器提供了 clz / ctz 一類的指令,你知道如何透過演算法的調整,去加速 費氏數列 運算嗎?請列出關鍵程式碼並解說 以我利用[fast doubling](https://www.nayuki.io/page/fast-fibonacci-algorithms)實做出的程式而言,首要做的事情:就是找到參數的 [MSB](https://zh.wikipedia.org/wiki/%E6%9C%80%E9%AB%98%E6%9C%89%E6%95%88%E4%BD%8D) :::warning 用語要精準,注意是 MSB 到 LSB,還是反向,避免「左右」這種含糊詞彙 :notes: jserv ::: 如果我們是用一個一個找的話,程式碼如下(以下程式碼,都沒考慮 `x==0` 的時候): ``` clike t = 1 << 31; while(x & t == 0) t >>= 1; ``` 但是如果善用 __builtin_clz() 這個 gcc 提供的 extension,可以使用硬體來減少上面程式用的 branch 的數量: ``` clike t = 1 << (31 - __buitin_clz(x)); ```