# 2019q1 Homework2 (fibdrv)
contributed by < `F74021200` >
## 自我檢查:
### 1.檔案 fibdrv.c 裡頭的 MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_VERSION 等巨集做了什麼事,可以讓核心知曉呢? insmod 這命令背後,對應 Linux 核心內部有什麼操作呢?請舉出相關 Linux 核心原始碼並解讀
從 [include/linux/module.h](https://elixir.bootlin.com/linux/v5.0/source/include/linux/module.h#L199) 中能找到 MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_VERSION 等巨集的定義,如下:
MODULE_LICENSE:
```click=199
#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)
```
MODULE_AUTHOR:
```click=205
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
```
MODULE_DESCRIPTION:
```click=208
#define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)
```
MODULE_VERSION:
```click=236
#if defined(MODULE) || !defined(CONFIG_SYSFS)
#define MODULE_VERSION(_version) MODULE_INFO(version, _version)
#else
#define MODULE_VERSION(_version) \
static struct module_version_attribute ___modver_attr = { \
.mattr = { \
.attr = { \
.name = "version", \
.mode = S_IRUGO, \
}, \
.show = __modver_version_show, \
}, \
.module_name = KBUILD_MODNAME, \
.version = _version, \
}; \
static const struct module_version_attribute \
__used __attribute__ ((__section__ ("__modver"))) \
* __moduleparam_const __modver_attr = &___modver_attr
#endif
```
:::warning
應該思考 `sysfs` 在這裡的運用狀況,以及一個 Linux 核心模組的掛載,如何反映到 `sysfs` 的呈現
:notes: jserv
:::
在 [Patrick Mochel 對於 sysfs 的報告](https://mirrors.edge.kernel.org/pub/linux/kernel/people/mochel/doc/papers/ols-2005/mochel.pdf) 第一頁 introduction 提到: sysfs is a mechanism for representing kernel objects, their attributes, and their relationships with each other. 並在第五頁的 module 中提到: The module directory contains subdirectories for each module that is loaded into the kernel.The name of each directory is the name of the module—both the name of the module object file and the internal name of the module. 也就是說, /sys/module 這個目錄下會有以已載入 module 名稱命名的子目錄;在 [sysfs(5) -man page](http://man7.org/linux/man-pages/man5/sysfs.5.html) 中有提到,在 /sys/module/"module-name" 目錄中會有一些相關檔案,這些檔案分別紀錄了此 module 的一些資料,例如,傳入的參數值;另外,在 [/kernel/module.c -1703行](https://elixir.bootlin.com/linux/latest/source/kernel/module.c#L1721) 中, module_add_modinfo_attrs() 裡,第19行有 sysfs 相關的 function , sysfs_create_file();
```click=
static int module_add_modinfo_attrs(struct module *mod)
{
struct module_attribute *attr;
struct module_attribute *temp_attr;
int error = 0;
int i;
mod->modinfo_attrs = kzalloc((sizeof(struct module_attribute) *
(ARRAY_SIZE(modinfo_attrs) + 1)),
GFP_KERNEL);
if (!mod->modinfo_attrs)
return -ENOMEM;
temp_attr = mod->modinfo_attrs;
for (i = 0; (attr = modinfo_attrs[i]) && !error; i++) {
if (!attr->test || attr->test(mod)) {
memcpy(temp_attr, attr, sizeof(*temp_attr));
sysfs_attr_init(&temp_attr->attr);
error = sysfs_create_file(&mod->mkobj.kobj,
&temp_attr->attr);
++temp_attr;
}
}
return error;
}
```
此 sysfs_create_file() 的第二個參數為 &temp_attr->attr , temp_attr 是一個 pointer to struct module_attribute
[struct module_attribute](https://elixir.bootlin.com/linux/latest/source/include/linux/module.h#L52) :
```click=
struct module_attribute {
struct attribute attr;
ssize_t (*show)(struct module_attribute *, struct module_kobject *,
char *);
ssize_t (*store)(struct module_attribute *, struct module_kobject *,
const char *, size_t count);
void (*setup)(struct module *, const char *);
int (*test)(struct module *);
void (*free)(struct module *);
};
```
在第一行有個宣告 struct attribute attr ,
[struct attribute](https://elixir.bootlin.com/linux/latest/source/include/linux/sysfs.h#L30) ( 定義於 /include/linux/sysfs.h ) :
```click=
struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
```
另外,此三個巨集都使用到了 MODULE_INFO() ,在 [include/linux/module.h](https://elixir.bootlin.com/linux/v5.0/source/include/linux/module.h#L199) 中,對 MODULE_INFO() 的定義為:
```click=
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)
```
其中, __MODULE_INFO() 的定義在 Linux-v5.0 的 [include/linux/moduleparam.h](https://elixir.bootlin.com/linux/v5.0/source/include/linux/moduleparam.h#L21) 中:
```click=21
#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
#define __MODULE_PARM_TYPE(name, _type) \
__MODULE_INFO(parmtype, name##type, #name ":" _type)
/* One for each parameter, describing how to use it. Some files do
multiple of these per line, so can't just use MODULE_INFO. */
#define MODULE_PARM_DESC(_parm, desc) \
__MODULE_INFO(parm, _parm, #_parm ":" desc)
```
[`__UNIQUE_ID()`](https://elixir.bootlin.com/linux/latest/source/include/linux/compiler-gcc.h#L71) : used to generate unique identifiers.
```click=
#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)
```
[`__COUNTER__`](https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html)
[`__PASTE()`](https://elixir.bootlin.com/linux/latest/source/include/linux/compiler_types.h#L54) :
```click
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
```
[`__used`](https://elixir.bootlin.com/linux/latest/source/include/linux/compiler_attributes.h#L248) :
```click
#define __used __attribute__((__used__))
```
> [used](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes) : means that the variable must "be emitted" even if it appears that the variable is not referenced.( 但不知為什麼前後有加雙底線 )
:::warning
對照 gcc 的手冊關於 [Extensions to the C Language Family](https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html),不難發現雙底線 (underscore) 開頭的標注法,是 C extension,而 `__attribute__` 緊跟著屬性類型,用兩個括號區隔,也是慣例。這些在 [OpenMP](https://www.openmp.org/) 也能見到類似的用法
:notes: jserv
:::
>[Alternate Keywords](https://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html#Alternate-Keywords)
[`__attribute__`](https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html#Attribute-Syntax) : 在使用 GNU Compiler 時,能夠附加一些屬性到變數上, GNU Compiler 在編譯時,會依據這些屬性於記憶體中配置變數。
[section("section_name")](https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Variable-Attributes.html) : Compiler 會將變數置於 "section_name" 的 section 中。
[aligned(n)](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes) : 於記憶體中,以 n bytes 對齊。
[unused](https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Variable-Attributes.html) : 宣告的變數可能不會被使用到,告知 compiler 不要於上述情況時輸出警告訊息。
[__stringify()](https://elixir.bootlin.com/linux/v4.2/source/tools/lib/lockdep/uinclude/linux/stringify.h#L5) : 用於命名 identifier 。
```clike
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
```
`__UNIQU_ID_##name##No. = "#tag = info"`
tag = name = license
info = _license
`__UNIQU_ID_##license##No. = "#license = _license"`
```clike
#define __MODULE_INFO(tag, name, info) \
static const char __UNIQUE_ID(name)[] \
__used __attribute__((section(".modinfo"), unused, aligned(1))) \
= __stringify(tag) "=" info
```
上列程式碼的作用為:宣告一個字串,利用輸入的參數為此變數命名以及產生字串內容,並要求 compiler 將此變數置於 ".modinfo" 這個 section 中。(使用命令 readelf -a fibdrv.ko 時,能看到 ".modinfo" 是其中一個 section ,推測:compiler 可能是將此字串置於 fibdrv.ko 檔案的 ".modinfo" section 。詳細運作仍在尋找中)
### 2.當我們透過 insmod 去載入一個核心模組時,為何 module_init 所設定的函式得以執行呢?Linux 核心做了什麼事呢?
由 [include/linux/module.h](https://elixir.bootlin.com/linux/latest/source/include/linux/module.h#L87) 第 80 行註解知:作為 module_init() 的參數被傳入的 function 會在 kernel 開機時期或 mudule 載入時期被執行;第 83 行則提到: module_init() 會在 module 載入時期被呼叫。
[`module_init()`](https://elixir.bootlin.com/linux/latest/source/include/linux/module.h#L87) :
```click=
#define module_init(x) __initcall(x);
```
[`__initcall()`]() :
```click=
#define __initcall(fn) device_initcall(fn)
```
[`device_initcall()`](https://elixir.bootlin.com/linux/latest/source/include/linux/init.h#L226)
```click=
#define device_initcall(fn) __define_initcall(fn, 6)
```
[`__define_initcall()`](https://elixir.bootlin.com/linux/latest/source/include/linux/init.h#L197)
```click=
#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
```
[`___define_initcall()`](https://elixir.bootlin.com/linux/latest/source/include/linux/init.h#L185)
```click=
#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#define ___define_initcall(fn, id, __sec) \
__ADDRESSABLE(fn) \
asm(".section \"" #__sec ".init\", \"a\" \n" \
"__initcall_" #fn #id ": \n" \
".long " #fn " - . \n" \
".previous \n");
#else
#define ___define_initcall(fn, id, __sec) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(#__sec ".init"))) = fn;
#endif
```
[`__ADDRESSABLE()`](https://elixir.bootlin.com/linux/latest/source/include/linux/compiler.h#L294) :
```click=
#define __ADDRESSABLE(sym) \
static void * __section(".discard.addressable") __used \
__PASTE(__addressable_##sym, __LINE__) = (void *)&sym;
```
[asm -gcc inline assembly](https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html)
由以上程式碼,可知 module_init 所設定的函式會被放置在與 ".init" 相關的 section(section() 中的傳入參數為字串,但不知為什麼會出現 #__sec)
在 [/include/linux/init.h -line 168](https://elixir.bootlin.com/linux/latest/source/include/linux/init.h#L168) 對於 initcalls 的註解:
> initcalls are now grouped by functionality into separate subsections. Ordering inside the subsections is determined by link order.
> For backwards compatibility, initcall() puts the call in the device init subsection.
> The `id` arg to __define_initcall() is needed so that multiple initcalls can point at the same handler without causing duplicate-symbol build errors.
> Initcalls are run by placing pointers in initcall sections that the kernel iterates at runtime. The linker can do dead code / data elimination and remove that completely, so the initcall sections have to be marked as KEEP() in the linker script.
上述第八行提到: Linux kernel 會置放指標於 initcall section ,利用此方式來執行 initcall 。
於此推測: Linux kernel 會將 module_init 所設定的函式置於 fibdrv.ko 檔案中與 ".init" 相關的 section , linux kernel 會藉由指標知道函式的程式碼,並執行函式程式碼 (確切運作仍在尋找中)。
:::warning
你需要知道 linker 和 loader 個別的作用,這在 [你所不知道的 C 語言](http://hackfoldr.org/dykc/) 系列講座曾探討過。
ELF 只是「執行檔案」的格式,至於如何被載入及執行,取決於其 loader 的實作
:notes: jserv
:::
### 3.試著執行 $ readelf -a fibdrv.ko, 觀察裡頭的資訊和原始程式碼及 modinfo 的關聯,搭配上述提問,解釋像 fibdrv.ko 這樣的 ELF 執行檔案是如何「植入」到 Linux 核心
ELF 是一種 [object file format](https://en.wikipedia.org/wiki/Object_file#Object_file_formats) , 有些 libraries 和 exacutable object file 會以 ELF 檔的形式存在; ELF 檔案被分成很多的 "section" ,這些 section 分別被用來存放特定的資料,例如, [.text](https://lwn.net/Articles/531148/) 存放 executable code 。 在使用命令 readelf -a fibdrv.ko 時,在 Section Headers 中有一個 section 名為 ".modinfo" 。
由 modinfo 的 macro 作用,可知 module 的 license,author,description,version 等資訊被加入 fibdrv.ko 這個 ELF 檔,
## 關於 [client.c](https://github.com/sysprog21/fibdrv/blob/master/client.c)
由 [read(2) - Linux man page](https://linux.die.net/man/2/read) 中, 若 read 成功,會回傳讀進的 byte 數,而 read 的其中一個輸入參數為 count ,即所要讀取的 byte 數,因此,讀進的 byte 數應該會小於等於 count ,但是在 client.c 中,計算出的 fibonacci number 是 read() 的回傳值,但是 client.c 中,read() 的 count 參數為 1 ,然而, fibonacci number 會大於 1 ,此不符合 read() system call ;另外,在 [fibdrv.c](https://github.com/sysprog21/fibdrv/blob/master/fibdrv.c) 中, fib_read() 會回傳 fibonacci number ,但我不知 fib_read() 的 caller 是誰;因為 client.c 中的 read() 回傳值與 fib_read() 的回傳值相同,因此推測, client.c 中的 read() 被 fib_read() 所取代;還在尋找詳細運作的程式碼。
由於在 c 語言中,一個變數最大只能擁有 64bits 因此若要表示更大的數,需要用兩個以上的變數表示一個數,而在這種情況下作加法運算時,若使用 "+" operator ,會發生 overflow ,因此實作一個 64-bit 的全加器。
原本程式回傳一個 64bits 的數字,但當計算的 fibonacci number 大於 64bits 所能表示的數字時,需要回傳兩個以上的 64bits 數字,原本預計用一個 struct 包含這些數字,並回傳此 struct 的位址,但是當 fib_sequence 執行結束時,此 struct 所佔用的空間也會被釋放,因此改使用 read 時傳入的 buf 來儲存計算結果,但在嘗試對 buf 賦予值時, 使用 make check 會在 sudo ./client > out 後出現 Killed ,
以上問題似乎與 fib_read 的參數 buf 之賦值有關,參閱了[此份](https://www.apriorit.com/dev-blog/195-simple-driver-for-linux-os)的第七項 Using Memory Allocated in User Mode 後,便可成功編譯。其中提到: The user allocates a special buffer in the user-mode address space. And the other action that the read function must perform is to copy the information to this buffer.The address to which a pointer from that space points and the address in the kernel address space may have different values. That's why we cannot simply dereference the pointer. 此句的意思應該是:在 user-mode 的位址空間配置了一個 buffer 位址,這個位址若是在 kernel 的位址空間,也就是同樣位址但不同位址空間時,位址可能會有不同的值。(以上不知是否理解正確)
因此需要透過 copy_to_user() 這個 function ,將想傳給 user-mode 的 client 字串 copy 到 fib_read() 的 buf 參數後, client 端便可接收到此字串。
In file included from /home/tinin/sysprog19/fibdrv/fibdrv.c:1:0:
./arch/x86/include/asm/uaccess.h:30:27: error: unknown type name ‘mm_segment_t’; did you mean ‘apm_event_t’?
static inline void set_fs(mm_segment_t fs)
^~~~~~~~~~~~
apm_event_t