Try   HackMD

2024q1 Homework6 (integration)

contributed by < HotMercury >

注意細節!要更新 HackMD 筆記的標題。

閱讀 linux 核心模組教材

Linux 核心模組運作原理
ubuntu linux 預設開啟 EFI_SECURE_BOOT_SIG_ENFORCE 當使用第三方 kernel module 時,所以有幾種方法可以解決。

  • Disable Secure Boot in UEFI (BIOS) settings
  • An alternative way is to disable Secure Boot using mokutil
$ sudo apt install mokutil
$ sudo mokutil --sb-state
$ sudo mokutil --disable-validation

這裡嘗試理解 UEFI Secure Boot
What is UEFI Secure Boot?
UEFI is unified extensiblt firmware interface 更多細節可以參考 man page,UEFI secure boot,用來避免在 boot process 時載入惡意程式,大部分的 x86 hardware 都有 microsoft key 但我們還是可以透過 disable or 更改 sign key,所以 SB 並不是微軟帝國為了防止其他 OS 的手段。

改進漢語表達。

kernel module 前置作業
Install the header files for the kernel

sudo apt-get update 
apt-cache search linux-headers-`uname -r`
sudo apt-get install kmod linux-headers-`uname -r`
dpkg -L linux-headers-`uname -r` | grep "/lib/modules/.*/build"

The Linux Kernel Module Programming Guide 提供安裝的 example,我認為是可以改成以下方式比較容易跟著做,我想嘗試貢獻。

- sudo apt-get install kmod linux-headers-5.4.0-80-generic
+ sudo apt-get install kmod linux-headers-`uname -r`

小試身手 hello module

  • MODULE_LICENSE

They are defined within include/linux/module.h

This exists for several reasons

  1. So modinfo can show license info for users wanting to vet their setup is free
  2. So the community can ignore bug reports including proprietary modules
  3. So vendors can do likewise based on their own policies
  • init
  • exit
  • makefile

如果直接在 make file 裡面使用 PWD := &(CURDIR) 可能會有錯誤

In the sudoers security policy, env_reset is enabled by default, which restricts environment variables.

make -C /lib/modules/`uname -r`/build M=`pwd` modules

掛載

sudo insmod hello.ko

hello.ko insmod 流程

hello.ko

strace -o strace.log insmod hello.ko

fibdrv

make check 後會有以下的 warning,跟據 ISO C90 forbids variable length array 使用 dynamic allocate,因為在 kernel 中所以使用 kmalloc

warning: ISO C90 forbids variable length array ‘f’ [-Wvla]
31 |  long long f[k + 2];
static long long fib_sequence(long long k)
{
    /* FIXME: C99 variable-length array (VLA) is not allowed in Linux kernel. */
    // long long f[k + 2];
    long long *f = kmalloc((k + 2) * sizeof(long long), GFP_KERNEL);
    if (!f) {
        pr_info(KERN_ALERT "Failed to allocate memory\n");
        return -ENOMEM;
    }
    f[0] = 0;
    f[1] = 1;

    for (int i = 2; i <= k; i++) {
        f[i] = f[i - 1] + f[i - 2];
    }
    long long result = f[k];
    kfree(f);
    return result;
}

macro 展開

kernel module 會用到的 macro,而這些 macro 是怎麼讓 kernel 知道的,以及 insmod 對應的 linux 內部操作有什麼關西?

  • MODULE_LICENSE
  • MODULE_AUTHOR
  • MODULE_DESCRIPTION

接下來的 code 接來自 linux v6.8 且假設我的 code 是 MODULE_AUTHOR("Jay")
include/linux/module.h

#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)

展開成 MODULE_INFO(author, "Jay")

/* Generic info of form tag = "info" */
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)

展開成 __MODULE_INFO(author, author, "jay") 為什麼需要傳入兩個 tag 是為了統一界面?

你做實驗,看能否通過編譯。

__section : GCC extension determine the storage location of the object tagged with it.
__stringify : #define __stringify_1(x...) #x
include/linux/moduleparam.h

#define __MODULE_INFO(tag, name, info)    \
    static const char __UNIQUE_ID(name)[] \
    __used __section(".modinfo") __aligned(1) \
    = __MODULE_INFO_PREFIX __stringify(tag) "=" info 

探討 __UNIQUE_ID(author),所以展開會變成 __UNIQUE_ID_author0
__COUNTER__ 根據 The C Preprocessor This macro expands to sequential integral values starting from 0

Care must be taken to ensure that __COUNTER__ is not expanded prior to inclusion of precompiled headers which use it. Otherwise, the precompiled headers will not be used

include/linux/compiler.h

#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)

include/linux/compiler_type.h

#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)

所以最後的展開為以下

static const char __UNIQUE_ID_author0[] \
  __used __attribute__((section(".modinfo"), unused, aligned(1))) \
  = "author=Jay"

接下來透過 objdump 觀察

objdump - display information from object files

$ objdump -s fibdrv.ko | head -130

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 6f723d4a 6179006c 6963656e  author=Jay.licen
 0040 73653d44 75616c20 4d49542f 47504c00  se=Dual MIT/GPL.
 0050 73726376 65727369 6f6e3d38 38353230  srcversion=88520
 0060 37394236 44374236 32354643 46303130  79B6D7B625FCF010
 0070 44370064 6570656e 64733d00 72657470  D7.depends=.retp
 0080 6f6c696e 653d5900 6e616d65 3d666962  oline=Y.name=fib
 0090 64727600 7665726d 61676963 3d352e31  drv.vermagic=5.1
 00a0 352e302d 3130312d 67656e65 72696320  5.0-101-generic 
 00b0 534d5020 6d6f645f 756e6c6f 6164206d  SMP mod_unload m
 00c0 6f647665 7273696f 6e732000           odversions . 

insmod 流程

閱讀《Linux Device Driver 3/e》 Initialization and Shutdown
initialization functions 通常會以 static 的方式,因為通常只被用來註冊使用。

The module loader drops the initialization function
after the module is loaded, making its memory available for other uses.

避免過多的中英語詞彙交錯

建構 Linux 核心模組所用到的巨集 kernel module 的 macro 會增加 specail section ?? 因此可以找到對應的 module initialization function.
至於 module function 會被如何呼叫

Modules can register many different types of facilities, including different kinds of devices, filesystems, cryptographic transforms, and more. For each facility, there is a specific kernel function that accomplishes this registration. The arguments passed to the kernel registration functions are usually pointers to data structures describing the new facility and the name of the facility being registered. The data structure usually contains point

CMWQ

閱讀 workqueue
非同步的執行上下文通常會使用 workqueue API,所以可以想像成一個代工廠,將任務交給工廠,如果沒有任務,工人就可以去放假。

原本設計的 multi-thread 是一個 core 一個 thread,而 single thread 則是共用一個,每個 workqueue 會維護一個 work pool,以上不管是 MT or ST 都沒有足夠好的 concurrency,因此接下來要探討 Concurrency Managed Workqueue

這裡提到 default 32k PID 是什麼意思

The kernel grew a lot of MT wq users over the years and with the number of CPU cores continuously rising, some systems saturated the default 32k PID space just booting up.

Concurrency Managed Workqueue
goal

  • 相容原本的 workqueue API
  • per-CPU 維護統一的 work pools 且共享於所有的 wq,提供彈性的 concurrency 機制,減少資源浪費
  • 隱藏使用者一些機制

Design
要使用 wq 時會先初始化 work item,指向 function 並放入 work queue。
接下來搭配 simrupt.c 來理解 wq API

simrupt.c 導讀

是一個 kernel module device driver 用來做 deffered work

  • simrupt_init
    首先用 kfifo_alloc 分配一個 fifo buffer,以下為 fifo 展開的樣子(type 為初始化參數)。
struct {
    union {
        struct __kfifo kfifo;
        type *type;
        const datatype *const_type;
        char (*rectype)[0];
        type *ptr;
        typr *ptr_const;
    }
    type buf[0]
}

接著透過 alloc_chrdev_region 註冊 Character devicescdev_init 初始化 struct cdev 以及對應的 operations。

  • alloc_workqueue alloc a wq

@name : wq 的名字
@flags : 可以設定 wq 的模式

  • WQ_UNBOUND
/* Create the workqueue */
simrupt_workqueue = alloc_workqueue("simruptd", WQ_UNBOUND, WQ_MAX_ACTIVE);
if (!simrupt_workqueue) {
    vfree(fast_buf.buf);
    device_destroy(simrupt_class, dev_id);
    class_destroy(simrupt_class);
    ret = -ENOMEM;
    goto error_cdev;
}