Kernel


thread 可以被認為是使用者程式當中的一個執行流程,Multi-thread process當中還可以包含更多的執行流程!

而在 Unix* 作業系統底下還存在一種特殊的 thread 稱之為 kernel thread(核心執行緒),

kernel thread 未必只有執行核心的某種功能,亦可能與某個 User program 有關係,

然而 kernel thread 與一般的 process 比較起來具有較輕鬆的環境切換代價,通常稱之為 Context switch,

發生在 process 調用 scheduler 進行排程的時候,要把當前 process 所用到的 CPU 資訊給儲存起來,

包含了 Register(暫存器)等等。

因為 thread 屬於簡單的 execution flow 所以每當要進行 Context switch 時,要儲存的資料會比較少,因此負擔會輕一些!

kernel thread 的特性如下:

  • 運作在 Kernel space & Kernel Mode
  • 不與 User 進行互動,因此不需要終端機設備
  • 於系統啟動時期建立,存活到系統關閉為止

在傳統的 Unix 作業系統,偶爾會將一些關鍵的任務委派給執行中的 process ,比如像是 Page swapping、服務網路連線、Disk cache等等之類的。

不過現在的作業系統大多都會把這些任務放在背景去執行,避免使用線性的方式而得到不好的效能,

特別是 kernel thread 只運作在 kernel space 之中,因此這些工作最好都交給 kernel thread 來做最適當不過了!

kernel_thread()函式可以用來建立新的 kernel thread,函式存在 linux/arch/i386/kernel/process.c 之中:

int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
struct pt_regs regs;
memset(&regs, 0, sizeof(regs));

regs.ebx = (unsigned long) fn;
regs.edx = (unsigned long) arg;

regs.xds = __USER_DS;
regs.xes = __USER_DS;
regs.xfs = __KERNEL_PERCPU;
regs.orig_eax = -1;
regs.eip = (unsigned long) kernel_thread_helper;
regs.xcs = __KERNEL_CS | get_kernel_rpl();
regs.eflags = X86_EFLAGS_IF | X86_EFLAGS_SF | X86_EFLAGS_PF | 0×2;

/* Ok, create the new process.. */
return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
}
EXPORT_SYMBOL(kernel_thread);

可以看到 kernel_thread() 函式的參數包括:

  • fn:要被執行的核心函式位置
  • arg:該函式的引數
  • 第三個是一組 Clone flags

而在 kernel_thread() 之中可以看到調用了 do_fork() 建立新的process,然而該參數的意義如下:

  • CLONE_VM:避免浪費時間在複製呼叫這個函式的行程的 Page 對映表
  • CLONE_UNTRACED:確保新的 kernel thread 不會被任何行程追蹤
  • pregs 指向了 &regs 這個結構的位址,當中包含了相應於 Kernel Mode Stack 裡面的位址

一些典型的例子,例如 Swapper, init process 都是屬於 kernel thread,雖然在Linux 使用 kernel thread 地方很有限,但是卻是用來執行幾個重要的核心功能。

Ref: Understanding the Linux Kernel 3e

所謂的 Kernel Control Path 指的是一連串的指令,核心用來處理 System Call, Exception, Interrupt

一件例外發生後,比如CPU執行某個 process 發生了 Page Fault 了,

那麼接下來則是一段KCP,用來調回適當的Page,好讓該 process 可以繼續被執行,

原則上,最簡單的情況是,CPU會循序的執行 KCP 從第一個指令,執行到最後一個,

不過 KCP 在以下幾種情況是可以被其他 KCP 插入的:

  1. 當 User-mode 的 process 請求 System call,而未必該 System call 的需求條件滿足了,因此會調用 Scheduler 選擇一個新的 process 來執行,發生了 Context switch,原本的 KCP 被中斷了。
  2. 當系統允許 Kernel Preemption 功能,而且中斷事件發生,並且具有較高優先權的 process 可執行
  3. 若 KCP 是可被中斷的,而且發生了硬體中斷事件,則CPU會轉而處理中斷事件的KCP,等待處理完以後,才回來繼續處理原先的KCP
  4. 執行KCP時,CPU偵測到Exception

每個 Exception 或 Interrupt 事件發生都會引起一個 KCP,

然而Interrupt handler(中斷事件處理器)可以被另外一個 Interrupt handler 中斷,所以KCP可以是巢狀執行的。

在OS中,一個很重要的Topic:System Call,中文翻成『系統呼叫』。

用意是User mode application 透過System call,間接提昇權限去處理敏感的操作!

Linux是一個開放的系統,理所當然地可以修改或者新增 System call。

那要如何新增一個System call呢?

一般來說我都把Linux的Kernel source code下載到 /usr/src 目錄下,以前在Fedora 3 就這樣放了!

而kernel source code可以從 官網http://www.kernel.org 上找到,不過很不幸地,大部分的主流版本像是Ubuntu或是Fedora等等,以我的經驗,拿從官網下載回來的Kernel source編譯完後,並不能保證一定能夠成功開機,因此最好的方式還是利用套件管理程式去取得該 Distribution 的 Kernel Source 比較保險。

接著假設下載回來的Kernel Source存放在 /usr/src 下的 linux 資料夾中,

  1. 編輯 linux/arch/i386/System_table.S
      .....................................
      
      .long sys_tee    /*315*/    <=這是原本的
      .long sys_mycall    /*316*/    <=這是我加上去的
  2. 編輯 linux/include/asm/unistd.h
      .....................................
      
      #define __NR_tee 315   <=這是原本的
      #define __NR_mycall 316   <=這是我加上去的
      #ifdef __KERNEL__
      #define __NR_syscalls 317   <=這是原本的,因為我又多加了一個所以總數再加一
  3. 編輯 linux/include/linux/syscalls.h
      .....................................
      
      asmlinkage long sys_set_robust_list(struct robust_list_head __user *head,
              size_t len);
      
      asmlinkage long sys_mycall(char *)    <=這是我加上去的
      #endif
  4. 接下來要修改對應的Makefile,新增的 System call 檔案為 mycall.c,編譯完的object檔案為mycall.o
      .....................................
      obj-y = mycall.o ipc.o ..........
  5. 編輯 linux/usr/include/asm/unistd.h
      .....................................
      
      #define __NR_tee 315   <=這是原本的
      #define __NR_mycall 316   <=這是我加上去的

接著要撰寫的是 mycall.c 的內容:
#include <linux/linkage.h> <=務必要引入此 header
#include <linux/kernel.h>

asmlinkage long(char *a){
printk(“%s\n”,a);
return 0;
}

然後重新編譯 Kernel 即大功告成!