---
tags: 作業
---
# 2019q1 Homework2 (fibdrv)
contributed by < `ab37695543xs` >
## 開發環境
```
$ lscpu
架構: x86_64
CPU 作業模式: 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
每核心執行緒數: 2
每通訊端核心數: 4
Socket(s): 1
NUMA 節點: 1
供應商識別號: GenuineIntel
CPU 家族: 6
型號: 142
Model name: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
製程: 10
CPU MHz: 2099.944
CPU max MHz: 3400.0000
CPU min MHz: 400.0000
BogoMIPS: 3600.00
虛擬: VT-x
L1d 快取: 32K
L1i 快取: 32K
L2 快取: 256K
L3 快取: 6144K
NUMA node0 CPU(s): 0-7
```
```
$ uname -r
4.18.0-16-generic
```
```
$ gcc --version
gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0
```
```
$ busybox | head -1
BusyBox v1.27.2 (Ubuntu 1:1.27.2-2ubuntu3.1) multi-call binary.
```
## 作業目標
- [ ] 撰寫可適用於使用者和核心層級的程式
- [ ] 自動測試機制
- [ ] 透過工具進行效能分析
## 自我檢查
### 1. 檔案 `fibdrv.c` 裡頭的 `MODULE_LICENSE`,`MODULE_AUTHOR`,`MODULE_DESCRIPTION`,`MODULE_VERSION` 等巨集做了什麼事,可以讓核心知曉呢?`insmod` 這命令背後,對應 Linux 核心內部有什麼操作呢?請舉出相關 Linux 核心原始碼並解讀
#### [linux/module.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h)
* 單純以宣告時的註解來看
* `LICENSE`:提供不同的敘述像 `GPL`、`Dual BSD/GPL` 等等來說明給予何種授權
* `AUTHOR`:最好以姓名和電子郵件來說明作者資訊
* `DESCRIPTION`:簡單說明模組的功能
* `VERSION`:以格式 `[<epoch:>]<version>[-<extra-version>]` 表示,例如 `1:2.0`
* `epoch`:可以想像為大更新,沒提及的話預設為 0,數值愈大愈新版
* `version`:可以使用英數,以 `.` 區隔,一樣依數值排序
* `extra-version`:前面加上 `-`,主要為額外說明或區別
* 以宣告來看,可以猜想是傳入資訊給 `MODULE_INFO` 並記錄
```cpp
#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)
```
```cpp
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)
```
#### [linux/moduleparam.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/moduleparam.h#L21)
追溯下去發現又多了好幾個 macro
```cpp
#define __MODULE_INFO(tag, name, info) \
static const char __UNIQUE_ID(name)[] \
__used __attribute__((section(".modinfo"), unused, aligned(1))) \
= __stringify(tag) "=" info
```
#### [linux/compiler.h](https://elixir.bootlin.com/linux/v4.18.16/source/includelinux/compiler.h#L166)
先觀察第一個 macro `__UNIQUE_ID`
```cpp
# define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __LINE__)
```
#### [linux/compiler_types.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/compiler_types.h#L53)
這邊兩個井字號的 [`a##b`](https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html#Concatenation) 實際上是做文字連接,將兩邊的 token 合併為一個
```cpp
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
```
所以可以簡化為 `__UNIQUE_ID(prefix) = ((__UNIQUE_ID_ + prefix) + __LINE__)`
#### [linux/compiler-gcc.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/compiler-gcc.h#L183)
* GNU 提供 [attribute](https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html#Attribute-Syntax) 功能讓 [function](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes)、[variable](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes) 等等能夠添加屬性
* `attribute(used)` 表示即使物件未參照到,仍要將其放入 ELF[^[1]^](http://sp1.wikidot.com/elfobjfile)[^[2]^](http://www.study-area.org/cyril/opentools/opentools/x909.html),通常用於 inline
* 變數的部份有提到如果使用 `used`,需要一併使用 `static`
```cpp
#define __used __attribute__((__used__))
```
#### [linux/moduleparam.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/moduleparam.h#L21)
* 與 `__used` 一樣,用來修飾前面的 `__UNIQUE_ID`
* `section` 編譯器一般會將 code 放在 ELF 的 `.text` 區段,這樣可以指定存放的區段
* `unused` 表示不一定會用到,即使沒使用到編譯也不會跳警告
* `aligned` 指定最小的 alignment (byte),數值必須為 2 的次方
```cpp
__attribute__((section(".modinfo"), unused, aligned(1)))
```
#### [linux/stringify.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/stringify.h#L10)
* 最後的 macro `__stringify`,這邊的 [`#x`](https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing) 實際上是將傳入的引數轉成 const string
* 這邊引數的 `...` 是 [variadic macros](https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html),表示可以有多個引數
* 不寫成一個 macro 主要是為了讓引數也能用 macro,參考 [argument prescan](https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html)
* 考慮先 `#define foo bar`
* `__stringify(foo)` 等同 `__stringify(bar)`,得到 `bar`
* 如果只用 `#x`,會直接轉 macro,得到 `foo`
```cpp
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
```
* 所以可以簡化為 `tag "=" info`,整個在這邊會是一個字串
* 歸納來看 `__MODULE_INFO` 可以視為 `const char <name>[] = "<tag> = <info>"`
考慮 `MODULE_AUTHOR(CSIE)`,實際流程如下
1. `MODULE_INFO(author, CSIE)`
2. `__MODULE_INFO(author, author, CSIE)`
3. `const author[] = "author = CSIE"`
4. 寫入 ELF 的 `.modinfo` 區段
`insmod` 的實際操作併入第二問一起解釋
### 2. 當我們透過 `insmod` 去載入一個核心模組時,為何 `module_init` 所設定的函式得以執行呢?Linux 核心做了什麼事呢?
#### [linux/module.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h#L86)
作為模組初始化的進入點,依照模組的種類,會使用不同的 `module_init()`,一種是給 builtin,執行於 kernel boot;另一種是給 module,執行於 module insertion,下面都會再詳述
```cpp
#ifndef MODULE
...
#define module_init(x) __initcall(x);
...
#else
...
#define module_init(initfn) ...
```
#### 第一種 builtin module(靜態編譯)
#### [linux/init.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/init.h#L206)
第一種 `module_init(x)` 的 `__initcall(x)` 會等同 `__define_inicall(x, 6)`
```cpp
#define device_initcall(fn) __define_initcall(fn, 6)
...
#define __initcall(fn) device_initcall(fn)
```
* 等同宣告一個型態為 `initcall_t` 的函式指標 `__inicall_fn6`,函式回傳值為 int
* 引數 `id` 使得複數的 initcall 可以指向同一個函式 handler,而不會造成重複 symbol 的問題
* 使用 `__used` 強制放入 ELF
* `__attribute__((__section__(".initcall" #id ".init")))` 表示放入 ELF 名為 `.initcall6.init` 的區段
* 將函式指標指向引數 `fn`
```cpp
typedef int (*initcall_t)(void);
...
#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn;
```
在 kernal boost 過程中會執行 `do_one_initcall`,前面的 `id` 在此便代表執行的優先順序
```cpp
extern int do_one_initcall(initcall_t fn);
```
由於這部份非本次重點,有興趣可以詳見 [linux内核驱动之 module_init 解析](https://blog.csdn.net/Richard_LiuJH/article/details/45669207),只是要注意該 `vmlinux.lds.S` 是 arm 架構的,Ubuntu 的可以參考 [Where is vmlinux](https://superuser.com/questions/62575/where-is-vmlinux-on-my-ubuntu-installation)
#### 第二種 external module(動態編譯)
#### [linux/module.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h#L129)
* 透過 [`insmod`](http://www.skrenta.com/rt/man/insmod.1.html) 添加的模組肯定是需要 module insertion,所以適用第二種
* 觀察 inline 函式[^[1]^](http://www.greenend.org.uk/rjk/tech/inline.html)[^[2]^](http://gnitnaw.github.io/程式語言/2016/06/14/C_inline.html),主要作為編譯器最佳化的方式,直接在呼叫函式的地方改寫,減少呼叫的 overhead
* GNU 和 C99 的 static inline 規則是一樣的
```cpp
#define module_init(initfn) \
static inline initcall_t __maybe_unused __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
```
#### [linux/compiler-gcc.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/compiler-gcc.h#L147)
`__maybe_unused` 和前面 `__used` 類似,參考 [Linux 2007 的 commit](https://github.com/torvalds/linux/commit/0d7ebbbc6eaa5539f78ab20ed6ff1725a4e332ef),表示可能不會被參照,不要產生警告
```cpp
#define __maybe_unused __attribute__((unused))
```
* `inline __inittest()` 可以簡化為單純傳回引數 `initfn`
* 用途是為了檢查傳入的 `initfn` 是否對應 `initcall_t` 格式,否則會在編譯時出錯,可參考 [the advantage of inittest](https://stackoverflow.com/questions/26118635/what-is-the-advantage-of-inittest-in-kernel)
#### [linux/module.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h#L129)
* inline 後面定義了一個函式 `int init_module(void)`,宣告是可被外部參照的
* [function attribute](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes) 提到 `alias` 用於定義 `alias` 後面函式的別名,所以後面的函式一定要存在
* `#initfn` 用來將 `initfn` 轉為字串
```cpp
extern void init_module(void);
...
int init_module(void) __attribute__((alias(#initfn)));
```
可以簡化為呼叫 `init_module()` 會等同執行 `initfn` 而不需額外定義內容,所以 inline 的檢查相當重要
==後續 insmod 何時呼叫此 init_module?==
#### [busybox/modutils/insmod.c](https://elixir.bootlin.com/busybox/1.27.2/source/modutils/insmod.c#L49)
* `insmod` 會呼叫 `bb_init_module()`,並傳入模組名稱與後綴的選項
* `filename` 用 `*++argv` 取得第二個引數,對應到 `$ insmod fibdrv`
```cpp=
int insmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int insmod_main(int argc UNUSED_PARAM, char **argv)
{
char *filename;
int rc;
filename = *++argv;
...
rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0));
if (rc)
bb_error_msg("can't insert '%s': %s", filename, moderror(rc));
return rc;
}
```
#### [busybox/modutils/modutils.c](https://elixir.bootlin.com/busybox/1.27.2/source/modutils/modutils.c#L194)
註解提到在初始化模組時有兩種 syscall,會優先嘗試使用 file descriptor 的 `finit_module()`,如果不行才用一般的 `init_module()`,詳細可參考 [Michael Kerrisk 的說明](https://lwn.net/Articles/519010/)
* 首先看下半部的 `init_module()`
* `image_size` 為模組的配置大小,`INT_MAX - 4095` 可以改寫為 2147483647 (2^31^-1) - 4096 + 1
* 試著執行 `$ getconf PAGE_SIZE`,可以發現 4096 剛好等於 page size,關於數字來源可以參考 [page size of Linux (x86) 4KB](https://stackoverflow.com/questions/11543748/why-is-the-page-size-of-linux-x86-4-kb-how-is-that-calculated)
==為何要這樣初始化?==
* 接著經由三步驟
* 開啟 ELF 檔
* 使用 `mmap()` 將檔案內容放入記憶體
* 呼叫 `init_module()` 此 syscall
* 引入 `finit_module` 的考量
* 因為從 image 獲取 fd 與 module loading 是分開的,使得 OS 失去驗證原始檔案信賴度的能力
* 與其透過 `NULL` 繼續延用 `init_module()`,透過新增一個 syscall 來傳遞 fd 反而更好
```cpp=
int FAST_FUNC bb_init_module(const char *filename, const char *options)
{
size_t image_size;
char *image;
int rc;
bool mmaped;
if (!options)
options = "";
...
/*
* First we try finit_module if available. Some kernels are configured
* to only allow loading of modules off of secure storage (like a read-
* only rootfs) which needs the finit_module call. If it fails, we fall
* back to normal module loading to support compressed modules.
*/
# ifdef __NR_finit_module
{
int fd = open(filename, O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
rc = finit_module(fd, options, 0) != 0;
close(fd);
if (rc == 0)
return rc;
}
}
# endif
image_size = INT_MAX - 4095;
mmaped = 0;
image = try_to_mmap_module(filename, &image_size);
if (image) {
mmaped = 1;
} else {
errno = ENOMEM; /* may be changed by e.g. open errors below */
image = xmalloc_open_zipped_read_close(filename, &image_size);
if (!image)
return -errno;
}
errno = 0;
init_module(image, image_size, options);
rc = errno;
if (mmaped)
munmap(image, image_size);
else
free(image);
return rc;
}
```
原始碼上面定義了 [`finit_module()
`](https://linux.die.net/man/2/finit_module) 與 [`init_module()`](http://man7.org/linux/man-pages/man2/init_module.2.html),由於使用到 [`syscall()`](http://man7.org/linux/man-pages/man2/syscall.2.html),所以需要權限才能執行,也就是 `sudo insmod`
```cpp
#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)
#if defined(__NR_finit_module)
# define finit_module(fd, uargs, flags) syscall(__NR_finit_module, fd, uargs, flags)
#endif
```
#### [sys/syscall.h](https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/sys/syscall.h.html)
* `asm/unistd.h` 定義了 system call `__NR_<name>` 所對應的編號,[一說](https://stackoverflow.com/questions/8306202/what-does-nr-stand-for-in-system-call-number-that-is-usually-used-as-suffix) NR 表示 numeric reference
* 而 `bits/syscall.h` 主要是為了兼容舊系統使用 `SYS_<name>`,另外將 `__NR` 轉換
```cpp
/* This file should list the numbers of the system calls the system knows.
But instead of duplicating this we use the information available
from the kernel sources. */
#include <asm/unistd.h>
#ifndef _LIBC
/* The Linux kernel header file defines macros `__NR_<name>', but some
programs expect the traditional form `SYS_<name>'. So in building libc
we scan the kernel's list and produce <bits/syscall.h> with macros for
all the `SYS_' names. */
# include <bits/syscall.h>
```
#### [asm/unistd_64.h](https://code.woboq.org/userspace/include/asm/unistd_64.h.html#317)
* 這數字表示對應 system call table 裡的 syscall `__x64_sys_finit_module`
* 可參考 [x86 64 Linux system call table](http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/),或是查看最新版本的 [`arch/x86/entry/syscalls/syscall_64.tbl`](https://elixir.bootlin.com/linux/v4.18.16/source/arch/x86/entry/syscalls/syscall_64.tbl)
```cpp
#define __NR_finit_module 313
```
#### [bits/syscall.h](https://code.woboq.org/gcc/include/bits/syscall.h.html)
轉換只是重新定義
```cpp
#ifdef __NR_finit_module
# define SYS_finit_module __NR_finit_module
#endif
```
可以簡化為呼叫 `finit_module()` 等同呼叫 `syscall(__x64_sys_finit_module, fd, uargs, flags)`
#### [linux/syscalls.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/syscalls.h#L566)
* 這邊宣告了許多 syscall handler 的原型,實作定義在 `kernel/module.c`
* `asmlinkage` 是 gcc 的標籤,表示函式的參數要從 stack 拿而非 register (x64 常見),可以參考[什麼是 "asmlinkage"?](http://www.jollen.org/blog/2006/10/_asmlinkage.html)
* `__user` 表示從用戶空間取得
```cpp
/* kernel/module.c */
asmlinkage long sys_finit_module(int fd, const char __user *uargs, int flags);
```
* 原始碼上面也定義了 syscall 對應的 socket
* 也提到實作是透過 `__do_sys_*()` 的函式來執行
* [Linux 系统调用 (syscall) 原理](http://gityuan.com/2016/05/21/syscall/)
* [Linux 系统调用之 SYSCALL_DEFINE](https://blog.csdn.net/hxmhyp/article/details/22699669)
```cpp=
```
考慮 `insmod fibdrv.ko` 實際流程如下
* `bb_init_module("fibdrv.ko", parse_cmdline_module_options())`
* `finit_module(image, image_size, options)`
* `syscall(__x64_sys_finit_module, image, image_size, options)`
??
[arch/x86/kernel/vmlinux.lds.S](https://elixir.bootlin.com/linux/v4.18.16/source/arch/x86/kernel/vmlinux.lds.S)
[linux 模块机制浅析](https://blog.csdn.net/xiayulewa/article/details/45769017)
```
finit_module(3, "", 0) = 0
```
#### [kernel/module.c](https://elixir.bootlin.com/linux/v4.18.16/source/kernel/module.c#L3435)
查看 init_module 的實作
```cpp
static noinline int do_init_module(struct module *mod)
{
...
/* Start the module */
if (mod->init != NULL)
ret = do_one_initcall(mod->init);
if (ret < 0) {
goto fail_free_freeinit;
}
if (ret > 0) {
pr_warn("%s: '%s'->init suspiciously returned %d, it should "
"follow 0/-E convention\n"
"%s: loading module anyway...\n",
__func__, mod->name, ret, __func__);
dump_stack();
}
...
}
```
#### [kernel/module.c](https://elixir.bootlin.com/linux/v4.18.16/source/kernel/module.c#L3835)
```cpp
SYSCALL_DEFINE3(init_module, void __user *, umod,
unsigned long, len, const char __user *, uargs)
{
int err;
struct load_info info = { };
err = may_init_module();
if (err)
return err;
pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
umod, len, uargs);
err = copy_module_from_user(umod, len, &info);
if (err)
return err;
return load_module(&info, uargs, 0);
}
```
```cpp
static int load_module(struct load_info *info, const char __user *uargs,
int flags)
{
...
}
```
### 試著執行 `$ readelf -a fibdrv.ko`, 觀察裡頭的資訊和原始程式碼及 `modinfo` 的關聯,搭配上述提問,解釋像 `fibdrv.ko` 這樣的 ELF 執行檔案是如何「植入」到 Linux 核心
#### `$ modinfo readelf -a fibdrv.ko`
```=
filename: /home/ab37695543xs/hw2/fibdrv/fibdrv.ko
version: 0.1
description: Fibonacci engine driver
author: National Cheng Kung University, Taiwan
license: Dual MIT/GPL
srcversion: 24DC5FB7E7608AF16B0CC1F
depends:
retpoline: Y
name: fibdrv
vermagic: 4.18.0-16-generic SMP mod_unload
```
第 2 到 5 行對應到了 `fibdrv.c` 的 `MODULE_LICENSE`、`MODULE_AUTHOR` 等輸出
```cpp
MODULE_LICENSE("Dual MIT/GPL");
MODULE_AUTHOR("National Cheng Kung University, Taiwan");
MODULE_DESCRIPTION("Fibonacci engine driver");
MODULE_VERSION("0.1");
```
其餘則是定義在編譯產生出的 `fibdrv.mod.c`,`MODULE_INFO` 的功能與上面大同小異,只是多了一個 `tag` 引數要傳入
```cpp
MODULE_INFO(vermagic, VERMAGIC_STRING);
MODULE_INFO(name, KBUILD_MODNAME);
...
#ifdef RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif
...
MODULE_INFO(srcversion, "24DC5FB7E7608AF16B0CC1F");
```
* 定義了一個字串 `__module_depends = "depends="`
* `__used` 搭配 `static` 同前面所述,強迫變數放入 ELF
* `__attribute__((section(".modinfo")))` 表示放入 ELF 名為 `.modinfo` 的區段,與前面追溯下來的目的地相同
```cpp
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
```
* 定義了一個結構 `__this_module = {...}`
* `__visible` 讓此結構能被外部參照,詳見下面
* `__attribute__((section(".gnu.linkonce.this_module")))` 表示放入 ELF 名為 `.gnu.linkonce.this_module` 的區段
* `.name` 使用 `KBUILD_MODNAME` 取得模組名稱
* `.init` 設定 [`init_module`](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h#L132) 為初始化的函式
* `.exit` 只有在編譯 `unload` 時才會定義,設定 `cleanup_module` 為卸載時的函式
```cpp
__visible struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
```
#### [linux/compiler-gcc.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/compiler-gcc.h#L275)
* [`externally_visible`](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes) 主要是讓物件在當前的編譯個體,也能被外部使用
* 和 gcc 最佳化的指令 [`-fwhole-program`](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Optimize-Options) 相反,這會將除了 `main` 和 `externally_visible` 的部份都做較激進的最佳化
```cpp
#define __visible __attribute__((externally_visible))
```
#### 使用 [objdump](https://sourceware.org/binutils/docs/binutils/objdump.html) 檢查 object file
* 使用 `-x` 列出所有標頭資訊
* section 即區段部份可以看到最後一個為 `.modinfo`
* 查看 symbol table,可以看到上面 `MODULE_INFO` 的部份都已轉成 `__UNIQUE_ID` 的型式放在 `.modinfo` 區段
* 上半部訊息定義在 `fibdrv.c`,下半部則定義在編譯出的 `fibdrv.mod.c`
```
$ objdump -x fibdrv.ko
區段:
Idx Name Size VMA LMA File off Algn
0 .note.gnu.build-id 00000024 0000000000000000 0000000000000000 00000040 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
...
8 .modinfo 000000ec 0000000000000000 0000000000000000 00000560 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
SYMBOL TABLE:
...
0000000000000000 l O .modinfo 000000000000000c __UNIQUE_ID_version26
000000000000000c l O .modinfo 0000000000000024 __UNIQUE_ID_description25
0000000000000030 l O .modinfo 000000000000002e __UNIQUE_ID_author24
000000000000005e l O .modinfo 0000000000000015 __UNIQUE_ID_license23
0000000000000000 l df *ABS* 0000000000000000 fibdrv.mod.c
0000000000000078 l O .modinfo 0000000000000023 __UNIQUE_ID_srcversion16
00000000000000a0 l O .modinfo 0000000000000009 __module_depends
00000000000000a9 l O .modinfo 000000000000000c __UNIQUE_ID_retpoline15
00000000000000b5 l O .modinfo 000000000000000c __UNIQUE_ID_name14
00000000000000c1 l O .modinfo 000000000000002b __UNIQUE_ID_vermagic13
```
使用 `-s` 查看所有內容,可以發現資訊都正確地紀錄在 `.modinfo`
```
$ objdump -s fibdrv.ko
...
Contents of section .modinfo:
0000 76657273 696f6e3d 302e3100 64657363 version=0.1.desc
0010 72697074 696f6e3d 4669626f 6e616363 ription=Fibonacc
0020 6920656e 67696e65 20647269 76657200 i engine driver.
0030 61757468 6f723d4e 6174696f 6e616c20 author=National
0040 4368656e 67204b75 6e672055 6e697665 Cheng Kung Unive
0050 72736974 792c2054 61697761 6e006c69 rsity, Taiwan.li
0060 63656e73 653d4475 616c204d 49542f47 cense=Dual MIT/G
0070 504c0000 00000000 73726376 65727369 PL......srcversi
0080 6f6e3d32 34444335 46423745 37363038 on=24DC5FB7E7608
0090 41463136 42304343 31460000 00000000 AF16B0CC1F......
00a0 64657065 6e64733d 00726574 706f6c69 depends=.retpoli
00b0 6e653d59 006e616d 653d6669 62647276 ne=Y.name=fibdrv
00c0 00766572 6d616769 633d342e 31382e30 .vermagic=4.18.0
00d0 2d31362d 67656e65 72696320 534d5020 -16-generic SMP
00e0 6d6f645f 756e6c6f 61642000 mod_unload .
```
放入區段的過程與 linker 與 linker script 有關,可以詳見
* [GNU ld 的 linker script 簡介](https://www.slideshare.net/zzz00072/gnu-ldlinker-script)
* [Linker Script 初探](http://wen00072.github.io/blog/2014/03/14/study-on-the-linker-script/)
* [史丹佛大學的 ld.info](http://www.slac.stanford.edu/comp/unix/package/rtems/doc/html/ld/ld.info.Scripts.html)
* [預設 linker script](https://gist.github.com/csukuangfj/c4bd4f406912850efcbedd2367ac5f33) (`$ ld --verbose`)
* [Linux 二進位檔中的 section](https://alittleresearcher.blogspot.com/2015/02/linux-section.html)
### 這個 `fibdrv` 名稱取自 Fibonacci driver 的簡稱,儘管在這裡顯然是為了展示和教學用途而存在,但針對若干關鍵的應用場景,特別去撰寫 Linux 核心模組,仍有其意義,請找出 Linux 核心的案例並解讀。提示: 可參閱 [Random numbers from CPU execution time jitter](https://lwn.net/Articles/642166/)
### 查閱 [ktime 相關的 API](https://www.kernel.org/doc/html/latest/core-api/timekeeping.html),並找出使用案例 (需要有核心模組和簡化的程式碼來解說)
### [clock_gettime](https://linux.die.net/man/2/clock_gettime) 和 [High Resolution TImers (HRT)](https://elinux.org/High_Resolution_Timers) 的關聯為何?請參閱 POSIX 文件並搭配程式碼解說
### `fibdrv` 如何透過 [Linux Virtual File System](https://www.win.tue.nl/~aeb/linux/lk/lk-8.html) 介面,讓計算出來的 Fibonacci 數列得以讓 userspace (使用者層級) 程式 (本例就是 `client.c` 程式) 得以存取呢?解釋原理,並撰寫或找出相似的 Linux 核心模組範例
### 注意到 `fibdrv.c` 存在著 `DEFINE_MUTEX`, `mutex_trylock`, `mutex_init`, `mutex_unlock`, `mutex_destroy` 等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題
### 許多現代處理器提供了 [clz / ctz](https://en.wikipedia.org/wiki/Find_first_set) 一類的指令,你知道如何透過演算法的調整,去加速 [費氏數列](https://hackmd.io/s/BJPZlyDSV) 運算嗎?請列出關鍵程式碼並解說