350 likes | 479 Views
作業系統. 第七章 行程相關實作. 第七章 行程相關實作. 資料結構 行程描述器 行程狀態 行程串列 就緒佇列 行程運作 行程間的溝通 摘要. 資料結構. 以作業系統的觀點來看行程,一個行程控制區塊就代表一個行程。 Linux 中與行程相關的資料結構 行程控制 區塊 行程狀態 行程串列 就緒佇列. 行程描述器. 描述器用來描述系統元件,如行程描述器。 描述器就是對應到實作的程式碼中的某個結構。 行程描述器是個儲存在核心位址空間中的龐大結構。
E N D
作業系統 第七章 行程相關實作
第七章 行程相關實作 • 資料結構 • 行程描述器 • 行程狀態 • 行程串列 • 就緒佇列 • 行程運作 • 行程間的溝通 • 摘要 /43
資料結構 • 以作業系統的觀點來看行程,一個行程控制區塊就代表一個行程。 • Linux 中與行程相關的資料結構 • 行程控制區塊 • 行程狀態 • 行程串列 • 就緒佇列 /43
行程描述器 • 描述器用來描述系統元件,如行程描述器。 • 描述器就是對應到實作的程式碼中的某個結構。 • 行程描述器是個儲存在核心位址空間中的龐大結構。 • Linux 中行程描述器稱為 task_struct。定義在 Linux 核心源碼的include/linux/sched.h 中。 /43
行程狀態 • 在 Linux 中,行程的狀態記錄在行程描述器中的 state 欄位: #define TASK_RUNNING 0 #define TASK_INTERRUPTIBLE 1 #define TASK_UNINTERRUPTIBLE 2 #define TASK_ZOMBIE 4 #define TASK_STOPPED 8 /43
行程狀態 (續) • TASK_RUNNING:行程正在執行或是等待被執行時。 • TASK_INTERRUPTIBLE:等待某些事件發生時,如等待信號的傳遞。 • TASK_UNINTERRUPTIBLE:與TASK_INTERRUPTIBLE 一樣,不同的是信號傳遞時並不會造成狀態的改變。 • TASK_STOPPED:收到 SIGSTOP、SIGTSTP、SIGTTIN 或SIGTTOU 信號時。 • TASK_ZOMBIE:當子行程結束時,父行程尚未呼叫類似 wait() 的系統呼叫來得知子行程的狀態並作適當處理,於是子行程就會處於這個狀態。 /43
行程串列 • Linux 使用鏈結串列將系統中所有的 PCB 串在一起。 • 行程描述器中的 next_task 與 prev_task 這兩個欄位,就是鏈結串列的指標。 • 使用for_each_task 巨集可以輕易地走訪整個行程串列。 /43
Linux 的行程串列 next_task prev_task prev_task next_task prev_task next_task init_task /43
就緒佇列 • 在就緒佇列中的所有行程,其狀態皆為TASK_RUNNING。 • 行程描述器中實作就緒佇列的 run_list 欄位: • 就緒佇列的開頭 /43
就緒佇列 (續) • 將一個行程加入就緒佇列: • add_to_runqueue() /43
第七章 行程相關實作 • 資料結構 • 行程運作 • 行程建立 • 內文切換 • 行程結束 • 行程間的溝通 • 摘要 /43
行程運作 • 在多行程系統中,行程必須能夠動態地被建立與刪除。 • Linux 中行程如何 • 建立 • 內文切換 • 刪除 /43
行程建立 • 與建立行程有關的系統呼叫 • fork() • vfork() • clone() • fork() 複製一份與父行程相同的位址空間 • copy-on-write 技術 • vfork() 允許父行程與子行程共用相同的位址空間 • 父行程呼叫 vfork() 後會被阻隔 /43
行程建立 (續) • clone() 可以透過一些參數來設定父行程與子行程間可以共用那一些系統資源。 • 在核心中 fork()、 vfork() 與 clone() 系統呼叫皆會觸發 do_fork()。 • 以 vfork() 為例: /43
行程建立 (續) • do_fork() 函式執行的內容大致可以分成 6 個部分,分別為: • 配置 task_struct 的空間。 • 複製父行程 task_struct 中所有的欄位。 • 改變子行程 task_struct 中不可繼承的欄位並作一些初始化的設定。 • 根據 clone_flags 判斷要複製那些父行程資源,並且初始化子行程的核心堆疊。 • 將子行程的 task_struct 加入系統的行程串列中。 • 將子行程喚醒。 /43
內文切換 • 內文切換時,最重要的就是將行程執行時的 CPU 暫存器值保存下來。 • 與硬體架構關係非常地密切,需要使用組合語言來輔助撰寫。 • Linux 中內文切換的工作是由 switch_to 這個巨集來執行。 /43
行程與 CPU 暫存器狀態的示意圖(1) 行程prev的行程描述器 ESP 0x1d001d00 stack esp:0x1f002100 0x1c100100 ESI data eip:0x1b01c000 text 行程prev EDI 0x1c001100 行程next的行程描述器 stack 0x1b00a100 EIP data esp:0x1a00c100 CPU暫存器 text 行程next eip:0x1a00a000 主記憶體 /43
行程與 CPU 暫存器狀態的示意圖(2) 行程prev的行程描述器 ESP 0x1d001d00 stack esp:0x1d001d00 data 0x1c100100 ESI eip:0x1b01c000 text 行程prev EDI 0x1c001100 行程next的行程描述器 stack 0x1b00a104 EIP data esp:0x1a00c100 CPU暫存器 text 行程next eip:0x1a00a000 主記憶體 /43
行程與 CPU 暫存器狀態的示意圖(3) 行程prev的行程描述器 ESP 0x1a00c100 stack esp:0x1d001d00 data 0x1c100100 ESI eip:0x1b01c000 text 行程prev EDI 0x1c001100 行程next的行程描述器 stack 0x1b00a108 EIP data esp:0x1a00c100 CPU暫存器 text 行程next eip:0x1a00a000 主記憶體 /43
行程與 CPU 暫存器狀態的示意圖(4) 行程prev的行程描述器 ESP 0x1a00c100 stack esp:0x1d001d00 data 0x1c100100 ESI eip:0x1d001d18 text 行程prev EDI 0x1c001100 行程next的行程描述器 stack 0x1b00a10c EIP data esp:0x1a00c100 CPU暫存器 text 行程next eip:0x1a00a000 主記憶體 /43
行程與 CPU 暫存器狀態的示意圖(5) 行程prev的行程描述器 ESP 0x1a00c100 stack esp:0x1d001d00 0x1c100100 ESI data eip:0x1d001d18 text 行程prev EDI 0x1c001100 行程next的行程描述器 stack 0x1a00a000 EIP data esp:0x1a00c100 CPU暫存器 text 行程next eip:0x1a00a000 主記憶體 /43
行程與 CPU 暫存器狀態的示意圖(6) 行程prev的行程描述器 ESP 0x1a00c100 stack esp:0x1d001d00 data 0x1c100100 ESI eip:0x1d001d18 text 行程prev EDI 0x1c001100 行程next的行程描述器 stack 0x1a00a00c EIP data esp:0x1a00c100 CPU暫存器 text 行程next eip:0x1a00a000 主記憶體 /43
行程結束 • 大部分的行程結束是指行程執行完最後一個指令。 • 需要將行程先前使用過的一些系統資源回收。 • 正常的情況下,行程執行到最後一個指令時會觸發 exit() 系統呼叫。 • 在核心中真正作處理的是 do_exit() 函式。 /43
行程結束 (續) • do_exit() 函式會依序執行下列動作: • 將行程描述器中的 flag 欄位設為 PF_EXITING,代表這個行程正在結束。 • 分別移除號誌佇列與計時器佇列中該行程曾經插入的元素。 • 將行程所用到的記憶體空間、開啟的檔案、檔案系統資源與信號佇列回收。 • 將行程描述器中的 state 與 exit_code 欄位分別設為 TASK_ZOMBIE 與相應的返回值。 • 更新父行程與子行程間的相互關係。 • 呼叫排程器 schedule() 選出下一個可以執行的行程。 /43
第七章 行程相關實作 • 資料結構 • 行程運作 • 行程間的溝通 • 旋轉鎖 • 號誌 • 訊息佇列 • 摘要 /43
行程間的溝通 • Linux 提供了一些行程的通訊機制,讓行程間進行溝通: • 管道 • 號誌 • 訊息 • 共享記憶體 • 插口 • Linux 核心中進行行程間的溝通時,為了保持重要核心資料結構的一致性,使用了一些同步機制,如: • 旋轉鎖 • 號誌 /43
旋轉鎖 • 最基本的同步機制之一,它就像門鎖一樣,當門鎖鎖上後,其他人就不能再進入這個房間。 • 在單處理器的環境下,有可能造成等待的行程一直忙碌等待,而將鎖鎖上的行程卻沒有機會進行內文切換,將鎖打開。 • 多處理器的環境下旋轉鎖相當有效率。 /43
旋轉鎖 (續) • 每個旋轉鎖以 spinlock_t 結構來代表,其中只有一個 lock 欄位: • lock 的值為 1 時,表示鎖是開著的。 • lock 的值小於等於 0 時,則表示鎖已被鎖上。 • spin_lock() 將 spinlock_t 結構中的 lock 欄位值減 1,如果 lock 的值小於 0,則使用迴圈一直去測試 lock 欄位值是否等於 0。 • spin_unlock() 將 lock 的欄位值設為 1。 • 當兩個行程同時讀取同一份資料時,使用旋轉鎖是比較沒有效率的: • 讀 / 寫的旋轉鎖。 /43
讀 / 寫旋轉鎖 行程 E 行程 C :讀取用 R R R W :寫入用 W 行程 B 行程 A 臨界區 W R W 行程 F 行程 D 臨界區 /43
號誌 • Linux 號誌可分為核心號誌與使用者號誌。 • Linux 核心號誌的型別為 struct semaphore。 • struct semaphore 的欄位 count: • 大於 0 時,代表現在可以使用此號誌。 • 小於或等於 0 時,代表這個號誌現在正忙碌中。 • 使用或釋出信號: • down() - 將號誌的 count 值減 1。 • up() - 將 count 值加 1。 /43
訊息佇列 • 使用者號誌、訊息與共用記憶體屬於 IPC 的範圍。 • 透過代號來使用 IPC 資源。 • semget()、msgget() 或 shmget() 分別取得信號、訊息或共用記憶體的 IPC 代號。 • Linux 中所有與 IPC 相關的函式都是透過 ipc() 系統呼叫來完成的。 • 訊息佇列: • msgget() - 取得訊息佇列的代號。 • msgsnd() - 傳送訊息。 • msgrcv() - 接受訊息。 /43
訊息佇列 (續) • Linux IPC 訊息佇列的資料結構: • struct ipc_ids - 每一種 IPC 資源各自擁有一個 ipc_ids 結構,訊息佇列為 msg_ids。 • struct msg_queue - 代表一個訊息佇列。 • struct msg_msg、struct msg_msgseg - struct msg_msg 用來代表一個訊息,每個訊息使用雙向鏈結串列連接在一起。訊息超過一個分頁(4 KB)時,使用其它的 struct msg_msgseg 來儲存。 • struct msg_sender、struct msg_receiver - 用來代表正在等待的傳送端與接收端行程。 • msgsnd() 在核心中是呼叫 sys_msgsnd(),大致可分為三個部分,分別為: • 初始化。 • 檢查訊息佇列是否有空間存放訊息。 • 檢查訊息佇列中的接收端行程串列是否有行程正等待訊息。 /43
IPC 訊息佇列的資料結構 msg_ids msg_msg … 7 ……… … m_list msg_msg entries[] … m_list … next … q_messages next msg_msgseg q_senders msg_receiver msg_sender next q_receivers r_list list msg_queue r_tsk tsk … r_msg /43
摘要 (1) • Linux 核心中的行程控制區塊是由一個 task_struct 結構來代表。 • Linux 核心中動態建立行程的系統呼叫 • fork() • vfork() • clone() • Linux 核心中處理內文切換的巨集: • switch_to /43
摘要 (2) • Linux 核心中的同步機制 • 旋轉鎖 / 讀寫旋轉鎖 • 號誌 • down() - 使用號誌。 • up() - 釋出號誌。 • Linux 提供 IPC 機制,允許使用者空間的行程進行同步或相互溝通。 • Linux 核心中的訊息佇列 • msgget() - 取得訊息佇列的代號。 • msgsnd() - 傳送訊息。 • msgrcv() - 接受訊息。 /43