OS Process & Thread (user/kernel) 筆記

Yovan
12 min readFeb 10, 2019

--

從學生時代的恐龍版本教科書上,開啟對 Operating System 世界的理解,像是 CPU scheduling、Memory management、I/O management、File system。透過這些模組的相互合作, 就是 OS 運作的開始 !

從現在開始,可以命令 OS 做事

Program

這個 "命令" 就是一連串又一連串的指令,也是現在常說的程式碼 (code),稱為 Program。不同 Language (e.g. Assembly、C++、Java …) 對於指令有不同的包裝,最後都會轉換成 machine code (硬體能夠理解的指令)。

Process

是 OS 進行資源分配最基本的單位 , 當 Program 在 OS 中執行 , 我們稱為 Process,OS 會給予執行所需要的資源,包含 CPU 的使用、Memory 的配置等等。不同的 Process 可以是 OS 採用同一個 Program 所建立的。

Program 如果比喻成設計圖, Process 就是參考設計圖建立出來的成品。

Process 主要包含 :

  • Code Section
  • Data Section
  • OS Resources (e.g. files)

*Code Section+Data Section = memory (text segment、data segment、heap segment)

OS 管理所有 Process 的方法,就是對每一個 Process 建立一個表格,稱為 PCB (Process Control Block),紀錄該 Process 相關狀態和資訊。 OS 在切換不同 Process 時,會先保存原本 Process 當下執行狀態,再切換到下一個 Process ,並恢復此 Process 最後停留的狀態,繼續執行。OS 就是透過 PCB 掌控 Process 的運行。

Process 切換的動作我們稱為 Context Switch,

Context Switch 發生的時機 :

- External Interrupt ( CPU 以外元件所發出 : I/O interrupt、 Timers)

- Internal Interrupt ( CPU本身所引發 : stack overflow、divided by zero …)

- Software Interrupt (e.g. System Call: user mode 換到 kernel mode)

Thread (Light Weight Process)

是 OS 進行 CPU Scheduling 運算最基本的單位,無法獨立存在,存在於 Process 中。

Thread主要包含 :

  • Stack
  • Register Set
  • Program Counter

Process 內 Thread 彼此之間共享:

  • Code Section
  • Data Section
  • OS Resources
這裡我所理解的 thread 以user-level thread為主

一個 Process 至少包含一個 Thread

一個 Process 可以同時擁有多個 Thread

每個 thread共享 Process 的 code、data

每個 thread 有自己的 stack、registers

*到這裡,在我的認知裡 Process 是沒有也不需要 Stack, Process 本身資源( OS 分配的)擁有者而 Thread 是執行者。

  • Process 是 OS 分配資源的對象
  • Thread 是 OS 分配 CPU 時間的對象

Thread 最讓人困惑的部分 User-level & Kernel-level

到目前為止 Process 和 Thread 之間的關係,是很好理解的,就如上面所描述的。但是以往再更深入理解 Thread 的時候,始終對於 user-level thread 和 kernel-level thread 的劃分只有理論上的理解,而沒有一個很好的實作參照,所以很難有一個具象的完整的概念。

Thread 可劃分為 :

  • User-level thread
  • Kernel-level thread

User-level thread

使用 User-level library 管理 Thread,包括處理 Thread Context Switch,也因此 OS 是察覺不到 Thread 存在的,因為只有在 User-level 中運行,所以不會使用到 System Call

Kernel-level thread

由 OS 管理 Thread,因此 OS 是察覺的到 Thread 存在,所以也有 Thread Context Switch 的發生,但是比 Process Context Switch 成本低,因為不需要切換 address space(記憶體空間),專門處理 System Call

OS 是察覺的到 Thread 存在,這是什麼意思呢 !!! 網路上看的很多敘述,包含我自己在寫這句話的時候,看似合理,其實有很大的矛盾,回想一下 ... Thread是 不能獨立存在 的必須 被包含 在 Process中 ! 那 ... Kernel-level thread是如何存在的呢 ? 後面會藉由 Linux 實作例子可以幫助理解這句話的意思。

User-level thread 和 Kernel-level thread 的對應模式:

  • Many-to-One :
  • One-to-One:
  • Many-to-Many:

因為 OS 知道 Kernel-level thread 的存在,有多少個 Kernel-level thread 就表示同時間有多少個可以平行執行(如果有多個 Processor 的話)。換句話說,只有一個 Kernel-level thread 的時候,如果此 Kernel-level thread 被 Block 的時候,整個 Process 都會被 Block。

  • Pthread library: 產生 user-level thread,對應模式需要看實作版本
  • Kthread library: 產生 kernel-level thread

Linux Kernel 2.6.34 Process & Thread 實作概要分析

Linux 並沒有一個明確的 Thread 或是 Process 的概念,取而代之為 Task (也可以稱做 light-weight process ) , task_structs 就是 Task 的資料結構體現, 也被稱做為 process descriptor (類似 PCB 的概念),紀錄了這個 Process 所有的 context。

接下來會以 Process 來稱呼這個 Task

/* task_struct 記錄著目前這 Process 的狀態 */
struct task_struct {
volatile long state; // -1 unrunnable, 0 runnable, >0 stopped};

每個 task_structs 都會有一個 kernel stack ,提供在 Kernel mode 時進行運算時,可存放臨時變數的地方。

/* task_struct 就有一個指向 kernel stack 的變數 */
struct task_struct {
void *stack; // point to kernel stack};

還有 Process 很重要的一塊,memory 的配置與使用,Process 都會擁有一個唯一的 mm_struct ,讓 Process 有抽象的獨立 memory (32 or 64 bits address space) 可以使用。Linux 中,如果共享 mm_struct ,則稱為 Thread。

/* task_struct 就有一個指向 mm_struct 的變數 */
struct task_struct {
struct mm_struct *mm // point to memory descriptor};

這裡說明一下,以 OS 的概念

* 如果產生出來的 task_struct 有自己的 mm_struct,稱為 Process,其實本身就是個 Main Thread (CPU scheduling的基本單位)

* 如果 產生出來的 task_struct 共用別人的 mm_struct,稱為 Thread

mm_struct 也稱做為 Memory descriptor, mm_struct 描述了屬於這 Process 的整個 virtual address space 。

/* mm_struct 有一個 Process(main thread) 的 user stack 起始位置 */
struct mm_struct {
unsigned long start_stack // user stack start address};

在 mm_struct 中,會有一個 start_stack (user stack 起始位置)提供在 User mode 運算的存放空間。如果是一個 Thread,因為共享 parent 的 mm_struct ,為了不影響到 mm_struct 裡的 stack 空間,會在heap 或 mmap 內找一個空間作為 Thread 的 stack。

最後不論哪種方式產生的 user stack,都會初始化 task_struct->thread->sp ,讓其存放 user stack 訊息。

/* task_struct 就有一個 thread_struct 的變數 */
struct task_struct {
struct thread_struct thread; // 存放 context switch 相關的資訊};------------------------------------------------------------------
/* thread_struct 保留了大部分的 CPU registers 的訊息,context switch 主要就是回復保存在 thread_struct 裡的資訊 */
struct thread_struct {
unsigned long sp0; // 存放 kernel stack base address
unsigned long sp; // 存放 kernel stack (current)top address
};

所以 . . .

建立 process/thread (task_struct) 會得到兩個 stack

user stack :

  • 建立在 address space (mm_struct),在 user mode 使用
  • process (mm_struct 的 stack) 或 thread (mm_struct 的 heap/mmap)

kernel stack :

  • 建立在 kernel address space,在 kernel mode 使用
  • task_struct 指向的 stack (thread_info)

user stack 和 kernel stack 是不是很像 OS 裡提到的 user thread 和 kernel thread 對應關係 ! 在 Linux 裡是 one-to-one 模式。

補充

kernel address space : 所有 kernel stack 共用的記憶體空間

thread_info: 放在 struct thread_union 裡與 context switch 有關聯

還有一個很重要的 kernel thread …

Linux 有一個獨特的 kernel thread(不要跟前面講的 Kernel thread 混淆了) !

kernel thread 是一個特別的 process(task_struct),只會在 kernel 運行,意思就是不會切換到 user mode 做 user mode 的事 。

因此不需要 mm_struct,如果 mm 是 NULL 則表示是 kernel thread,也因為沒有 mm 也就沒有 user stack。

struct task_struct {  struct mm_struct *mm = NULL;};

最後回顧一下 原本對 OS 部分疑惑

  1. Thread是不能獨立存在的必須被包含在Process中 ?
  • Yes , Thread 是需要使用別人的 mm_struct , 如果自己擁有獨立的 mm_struct 就叫做 Process 了。

2. OS 是察覺的到 Thread 存在,那 Kernel-level thread 是如何被察覺的呢 ?

  • 這關係到 Linux context switch ,會寫另外一篇來說明,簡單來說,前面有提到透過切換不同 stack 達到 user 和 kernel 的操作,從 user mode切換到 kernel mode 時,會把 user stack 訊息放到 kernel stack 裡。OS context switch 主要就是切換 kernel stack , 以 OS 視角來說 , 就只是切換到不同的 kernel stack。

--

--

Yovan

時間不會為誰而停留,就算暫停了全世界的鐘,也停不了這一秒鐘,所以有什麼理由,不好好把握每一刻! 希望現在開始透過紀錄,留下在軟體開發的路途上,每個走過的足跡! www.linkedin.com/in/yovan-li