owned this note
owned this note
Published
Linked with GitHub
# 2019q1 Homework2 (fibdrv)
contributed by < `Shengyuu` >
## Enviroment
```shell
4.15.0-46-generic
```
## 自我檢查清單
### 1. 檔案 fibdrv.c 裡頭的 MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_VERSION 等巨集做了什麼事,可以讓核心知曉呢? insmod 這命令背後,對應 Linux 核心內部有什麼操作呢?請舉出相關 Linux 核心原始碼並解讀
這幾個巨集並非用在 Kernel 的本身,而是在告訴其他人這個 kernel module 的基本資訊,包括作者、版權、描述、版本。我們可以在 linux/module.h 裡面找到這些巨集的定義
```c
#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
#define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)
```
從上述程式碼可看到這幾個巨集都使用了 MODLUE_INFO 這個巨集,而 MODULE_INFO 也是定義在 linux/modlue.h 的
```c
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)
```
MODULE_INFO 的定義右使用了巨集 __MODULE_INFO,在找一下會發現它被定義在 /linux/moduleparam.h 中,我們再來看看這個巨集又做了什麼事
```c
#ifdef MODULE
#define __MODULE_INFO(tag, name, info) \
static const char __UNIQUE_ID(name)[] \
__used __attribute__((section(".modinfo"), unused, aligned(1))) \
= __stringify(tag) "=" info
#else /* !MODULE */
/* This struct is here for syntactic coherency, it is not used */
#define __MODULE_INFO(tag, name, info) \
struct __UNIQUE_ID(name) {}
#endif
```
先不看中間 `__used` 和 `__attribute__` 的修飾,其實這個巨集就是在宣告一個字元陣列並給予特定字串,可簡化成以下形式比較好理解
```c
static const char __UNIQUE_ID(name)[]
= __stringify(tag) "=" info
```
## 什麼是 `__attribute__` 、 `section`
在詳細探討這個聚集之前要先來了解什麼是 `__attribute__` 以及 `section`
- `__attribute__` 主要用於改變所聲明或定義的函數或資料的特徵,它有很多用於改變不同特徵的子向,例如程式碼中的 `section` 、`unused` 、`aligned` 等等
- **`unused` :** 告訴 GCC 這個變數可能不會被用到,GCC 就不會對此提出 warming
```c
#include<stdio.h>
int main()
{
static int a = 1;
static int b __attribute__((unused)) = 2;
return 0;
}
```
```
$ gcc -o att att.c -Wall
att.c: In function ‘main’:
att.c:5:14: warning: unused variable ‘a’ [-Wunused-variable]
static int a = 1;
^
At top level:
att.c:5:14: warning: ‘a’ defined but not used [-Wunused-variable]
```
- **`used`:** 給予 static variable `used` 會強迫該變數被 emitted 即使這個變數沒有被 referenced
>used
This attribute, attached to a variable with static storage, means that the variable must be emitted even if it appears that the variable is not referenced.
節錄 [Common Variable Attributes](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes)
>
參考 [cjwind同學](https://hackmd.io/s/SkW25-2UV) 做的實驗
```c
#include<stdio.h>
static int a = 3;
int main()
{
return 0;
}
```
用 nm 指令可以列出目標文件(.o)的符號清單
```
$ gcc -O1 -c used.c -Wall
$ nm used.o
0000000000000000 T main
```
將 static int 變數宣告的地方加上 `__attribute((used))` 之後再用 `nm` 觀察一次
```c
#include<stdio.h>
static int a = 3;
int main()
{
return 0;
}
```
```
$ gcc -O1 -c used.c -Wall
$ nm used.o
0000000000000000 d a
0000000000000000 T main
```
- **`section`:** 可以在 object file 上加上一個 section,要注意的是 section 只能設定在 global variable 上
>Sometimes you need additional sections, or you need certain particular variables to appear in special sections.
節錄 [Common Variable Attributes](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes)
>
來個小範例
```c
#include<stdio.h>
int a __attribute__((section ("test_test")));
int main()
{
return 0;
}
```
```
$ gcc -O1 -c section.c -Wall
$ readelf -a used.o
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000006 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 00000046
0000000000000000 0000000000000000 WA 0 0 1
[ 3] .bss NOBITS 0000000000000000 00000046
0000000000000000 0000000000000000 WA 0 0 1
[ 4] test_test PROGBITS 0000000000000000 00000048
0000000000000004 0000000000000000 WA 0 0 4
```
- **aligned:** 可用來設定變數或 struct 以多少 bytes 對齊,對齊的長度必須是 2 的整數次方
>The aligned attribute specifies a minimum alignment for the variable or structure field, measured in bytes. When specified, alignment must be an integer constant power of 2.
節錄 [Common Variable Attributes](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes)
>
小範例
```c
#include<stdio.h>
struct p
{
int a;
char b;
char c;
int d;
} pp;
struct q
{
int a;
char b;
char c;
int d;
}__attribute__((aligned(1024))) qq;
int main()
{
printf("pp = %d\n qq = %d \n", sizeof(pp), sizeof(qq));
return 0;
}
```
```
$ gcc -o aligned aligned.c -Wall
$ ./aligned
pp = 12
qq = 1024
```
> `__attribute__` 語法說明參考[Attribute Syntax](https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html)
對變數使用 `__attribute__` 參考[Variable Attribute](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes)
> [color=#77FF00]
## 深入__module_info
再來詳細探討該巨集對這個字元陣列做了什麼
- `__used` 定義在 [/ linux / compiler_attributes.h ](https://github.com/torvalds/linux/blob/64c0133eb88a3b0c11c42580a520fe78b71b3932/include/linux/compiler_attributes.h)
```c
#define __used __attribute__((__used__))
```
這個巨集的作用就是將變數或函式的屬性設成 `__used__`,好讓目標檔會產生該變數或寒是的 symbol
- `__stringnify(tag)__` 定義在[/ linux / stringify.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/stringify.h)
```c
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __LINUX_STRINGIFY_H
#define __LINUX_STRINGIFY_H
/* Indirect stringification. Doing two levels allows the parameter to be a
* macro itself. For example, compile with -DFOO=bar, __stringify(FOO)
* converts to "bar".
*/
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
#endif /* !__LINUX_STRINGIFY_H */
```
>這裡用兩層 macro 是為了讓 macro 可以使用變數傳入,可以 參考 [cjwind同學](https://hackmd.io/s/SkW25-2UV)詳細的解說
>
了解完這些 macro 的定義後我們可以發現 `__module_info` 這個巨集會在目標檔(.o)中 .modinfo 的 section 內加入一個字串,而這個字串就是 `<tag>=<info>` ,也就是 license、author 等資訊
### 2. 當我們透過 insmod 去載入一個核心模組時,為何 module_init 所設定的函式得以執行呢?Linux 核心做了什麼事呢?
我們先來看看 module_init 在 /linux/module.h 內的定義
```c
#ifndef MODULE
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
..........
.......
...
#else /* MODULE */
/*
* In most cases loadable modules do not need custom
* initcall levels. There are still some valid cases where
* a driver may be needed early if built in, and does not
* matter when built as a loadable module. Like bus
* snooping debug drivers.
*/
#define module_init(initfn) \
static inline initcall_t __maybe_unused __inittest(void) \
{ return initfn; } \
int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));
```
從標頭檔的定義中我們可以看到 module_init 的宣告分為有 define module 和沒有 define module 兩個版本。前者代表這個 device driver 是 Loadable kernel module(LKM); 而後者代表 driver 是 Built-in kernel modules
**參考 [Difference between Linux Loadable and built-in modules](https://stackoverflow.com/questions/22929065/difference-between-linux-loadable-and-built-in-modules)**
## 剖析 module_init (LKM)
再來詳細看看這次作業用到的 module_init (LKM) 做了什麼
```c
#define module_init(initfn) \
static inline initcall_t __maybe_unused __inittest(void) \
{ return initfn; } \
int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));
```
- 首先來看看 `initcall_t` ,它是一個被定義在 [/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h) 內的一個 **pointer to function**
```c
typedef int (*initcall_t)(void);
```
再回頭看看 `static inline ...` ,看起來是在檢查傳入的函式 `initfn` 的回傳型態有沒有和 `initcall_t` 定義的一樣。試著把 `fibdrv.c` 裡頭 `init_fib_dev` 的回傳型態改為 `double` 看看會發生什麼事
```c
static double __init init_fib_dev(void)
double rc = 0;
```
```
$ make
/home/yao/Linux_Class/fibdrv/fibdrv.c: In function ‘__inittest’:
/home/yao/Linux_Class/fibdrv/fibdrv.c:171:168: error: return from incompatible pointer type [-Werror=incompatible-pointer-types]
module_init(init_fib_dev);
```
從 error 的提示看出傳入 `module_init` 的函式回傳的 type 一定要符合 `initcall_t` 定義的型態否則就會發生 error
- macro 中的第二段其實就是將 `initfn` 取個別名,這樣使用 `init_module` 系統呼叫的時候就會呼叫到我們定義的 initial function
>之後補完整
:::warning
在做這部份的時候有個疑惑
就是在 `fibdrv.c` 中倒數第二行使用 `module_init` macro 的時候,傳入的函式並沒有加括號
```c
module_init(init_fib_dev);
```
這樣的寫法應該會得到 `init_fib_dev` 函式的記憶體位置,如此一來 macro 中函式 `inittest` return 的值和 `init_fib_dev` 這個函式回傳的值好像就沒有什麼關係了
做個小測試:
```c
#include<stdio.h>
#define module_init(initfn) \
initcall_t inittest(void) \
{return initfn;} \
printf("%d",inittest());
typedef int (*initcall_t)(void);
static double fun(void)
{
return 3.0;
}
int main()
{
module_init(fun);
return 0;
}
```
```
$ gcc -o init init.c
init.c:6:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘initcall_t {aka int (*)(void)}’ [-Wformat=]
printf("%d",inittest());
$ ./init
92538538
```
從結果可以看出來,即使 `inittest` 函式回傳型態是 double ,編譯一樣不會有 error ,而且 return 的值真的是 `fun` 的記憶體位置
後來又是著按照 [afcidk](https://hackmd.io/s/r1ginnn8N) 同學的方法,去看 `fibdrv.c` 經過預處理過後的樣子
```c
static inline __attribute__((unused)) __attribute__((no_instrument_function)) __attribute__((gnu_inline)) initcall_t __attribute__((unused)) __inittest(void) { return init_fib_dev; } int init_module(void) __attribute__((alias("init_fib_dev")));;
```
確定 `init_fib_dev` 後面沒有接著括號
但是如果直接更改 `fibdrv.c` 中的 `init_fib_dev` 函式的回傳型態又會發生 error
:::
### 3. 試著執行 $ readelf -a fibdrv.ko, 觀察裡頭的資訊和原始程式碼及 modinfo 的關聯,搭配上述提問,解釋像 fibdrv.ko 這樣的 ELF 執行檔案是如何「植入」到 Linux 核心
### 4. 這個 fibdrv 名稱取自 Fibonacci driver 的簡稱,儘管在這裡顯然是為了展示和教學用途而存在,但針對若干關鍵的應用場景,特別去撰寫 Linux 核心模組,仍有其意義,請找出 Linux 核心的案例並解讀。提示: 可參閱 Random numbers from CPU execution time jitter
### 5. 查閱 ktime 相關的 API,並找出使用案例 (需要有核心模組和簡化的程式碼來解說)
### 6. clock_gettime 和 High Resolution TImers (HRT) 的關聯為何?請參閱 POSIX 文件並搭配程式碼解說
### 7. fibdrv 如何透過 Linux Virtual File System 介面,讓計算出來的 Fibonacci 數列得以讓 userspace (使用者層級) 程式 (本例就是 client.c 程式) 得以存取呢?解釋原理,並撰寫或找出相似的 Linux 核心模組範例
### 8. 注意到 fibdrv.c 存在著 DEFINE_MUTEX, mutex_trylock, mutex_init, mutex_unlock, mutex_destroy 等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題
### 9. 許多現代處理器提供了 clz / ctz 一類的指令,你知道如何透過演算法的調整,去加速 費氏數列 運算嗎?請列出關鍵程式碼並解說
## 測試給定的 fibdrv 模組
- 在最後編譯並測試輸入```$ make check``` 產生了
```
insmod: ERROR: could not insert module fibdrv.ko: Required key not available
```
>爬文的過程中找到這篇文章[Linux 内核模块](https://jin-yang.github.io/post/kernel-modules.html)問題出在 Modlue signature verification ,如果 UEFI Secure Boot 啟動,那麼內核所有模塊都必須使用 UEFI Secure key 簽名,去 BIOS 關閉 UEFI Secure Boot 才節決這個文題
>
:::warning
還不是很了結文中提到的 Modlue signature verification 之後有時間再回來看一下
:::
- 解決上面的問題之後再輸入一次```$ make check```還是有錯誤
```
insmod: error inserting 'fibdrv.ko': -1 Invalid module format
```
>這次的問題是因為 make 時使用的核心版本和執行中的核心版本不一致
>
查看系統核心版本
```
$ uname -r
4.15.0-46-generic
```
:::danger
尊重台灣科技前輩的篳路藍縷,使用台灣慣用術語
:notes: jserv
:::
查看 make 使用的核心版本
```
$ modinfo fibdrv.ko
4.15.0-45-generic SMP mod_unload
```
>輸入以下命令重新編譯 module 後問題就解決了
```shell
$ make clean
$ make all
$ make check
```
## 參考資料
[解析Linux 內核可裝載模塊的版本檢查機制](https://www.ibm.com/developerworks/cn/linux/l-cn-kernelmodules/index.html)
[C/C++ 的預處理定義: #, #@, ##](https://blog.xuite.net/jesonchung/scienceview/93554778-C%2FC%2B%2B+%E7%9A%84%E9%A0%90%E8%99%95%E7%90%86%E5%AE%9A%E7%BE%A9+%3A+%23+%2C++%23%40+%2C+%23%23)
###### tags: `Linux 核心設計 2019`