進程、輕量級進程(LWP)、線程
- 進程:程序執行體,有生命期,用來分配資源的實體
- 線程:分配CPU的實體。
- 用戶空間實現,一個線程阻塞,所有都阻塞。
- 內核實現,不會所用相關線程都阻塞。用LWP實現,用線程組表示這些線程邏輯上所屬的進程。
進程描述符
- 進程描述符(簡稱pd, process descriptors),結構體是:task_struct
- 數據較多,存放在kenerl的動態內存空間。
- pd的引用放在thread_info中,
- thread_info與內核棧,放在一個8K空間(它的地址8K對齊)。內核程序使用的棧空間很小。
- thread_info在底部,內核棧在頂部向下增長。
- 好處:多CPU時方便,每個CPU根據自己的棧指針就可以找到當前的pd (以後用current表示當前CPU運行的進程描述符)。
- esp(內核棧指針)低8位置零,就是thread_info地址。
- 每進程有自己的thread_info, (分配釋放函數: alloc_thread_info, free_thread_info)
- 描述符的內容
- 相關的ID (一個4元素數組)
- 進程ID (PID)
- PID按創建順序連續增長,到最大值後從最小值開始。
- 0號進程:交換進程(swapper)
- 有PID可用位圖,表示那一個PID可用,至少佔一個頁。
- 線程組ID(tgid),用LWP實現多線程支持
- 多進程時,進程id,就是線程組id, 也就是組長的pid(LWP)。 getpid() 取的是線程組的id(tgid), 也是組長的pid.
- 單線程時,pid = gid。所以getpid,也是真正的pid.
- 進程組ID(pgrp)。
- 回話的ID(session).
- 組ID,都是組長的PID。FIXME: 但pb也有各組長的PID
- 線程組長:tgid
- 進程組長:signal->pgrp ,
- 會話長:signal->session
- 管理ID數據結構——哈希表管理 (利用id找到所用相關的pd,方便)。
- 一個哈希表數組(pid_hash),存放四個哈希表, 每一個表代表一類id (pid, tgid, pgrp, session)
- 每個哈希表的由數組(索引爲哈希值)和二維鏈表(嵌入到進程描述符內的pids中)實現
- 第一維鏈表:哈希衝突鏈表。
- 第二維鏈表:要查找的值相同的鏈表, 叫per-PID list(同一組的所有線程,同一組的所有進程,同一會話的所有進程);
- 進程組ID(pgrp), 回話ID(session)在共享信號的數據結構裏。因爲同一進程內的所有LWP,這兩個ID都是一樣的
-
- 家族關係:由pd裏的鏈表(下級)和pd指針(上級)實現
- 關係:
- 親生父親:創建自己的進程,或是託孤進程(創建自己的進程死了)。
- 父親:自己死時要發信號告知的。一般是親生父親,有時是監控自己的進程 (調用ptrace)
- 孩子:
- 兄弟:
- 監控(自己起的名字,類似於監護。由於管理方式相同,也歸爲家族關係)
- 監控的進程列表:ptrace_children
- 被監控的其他進程:ptrace_list (類似於被監控的兄弟)
- 在鏈表裏爲了管理方便:
- 最大兒子的兄弟是父親
- 最小兒子的弟弟也是父親
- 父親保管最大兒子,和最小兒子
-
- 進程資源及資源限制:
- CPU相關:
- 內存相關:
- 進程地址空間
- 鎖住內存大小
- 進程頁數 (只有記錄,沒有限制)
- 堆大小,棧大小
- 資源相關:
- 文件:
- core dump大小
- 最大文件大小
- 打開文件個數
- 進程同步與通信
- 相關數據結構 和 處理流程
- pd->sigal->rlim 是一個表示進程資源使用情況以及限制的結構 的數組。
- 表示進程資源使用情況以及限制的結構:包含當前值,最大值兩個數值。
- 只有超級用戶才能增大資源限制。
- 一般用戶登陸時:
- kernel創建root進程,減少limit,
- 建一個 shell子進程,繼承limit.
- 把shell進程的用戶,改成登陸的那個用戶
-
- 進程狀態(state)
- 運行,TASK_RUNNING
- 阻塞
- 可中斷阻塞,TASK_INTERRUPTIBLE
- 不可中斷阻塞,TASK_UNINTERRUPTIBLE
- 可被硬件中斷,“釋放資源”事件,喚醒。
- 但不能被信號喚醒。可用於驅動程序中。
- 組織pb的結構:等待列隊: 每一類事件一個列隊,用內嵌鏈表實現(雖然沒列出內嵌鏈表節點)
- 列隊頭:
- 列隊節點:
- 獨佔標誌:表示該進程是否要獨佔資源 (不再喚醒別的進程)
- 指向pd的指針
- 用於喚醒進程的回調函數。(提供進程的執行機會,是否操作等待列隊由用戶決定)
- 停止
- 停止TASK_STOPPED
- 追蹤TASK_TRACED
- 該進程被一個調試進程監控以後,收到任何一個信號就進入該狀態
- 組織pb的結構:FIXME: 信號的等待列隊?
- 退出
- 退出_殭屍EXIT_ZOMBIE
- 進程終止,資源沒有被回收(父進程要用,沒有調wait系列函數)
- 退出_死亡EXIT_DEAD
- 進程終止,資源正在被回收(父進程要用,沒有調wait系列函數)。
- 一旦資源回收完成,進程描述符也就被回收了。
- 它防止該進程再次被wait.
- 組織pb的結構:不掛到隊列上,只在家族關係中,等待父進程收回資源
進
程控制 :
- 阻塞(current阻塞到某個列隊上):
- 基本流程
- 臨時生成一個列隊節點,初始化。
- 改變current的狀態,放入節點,掛到列隊上。
- 調度 (=====》至此,阻塞完成。 一旦被別的進程喚醒====》從調度函數中返回)
- 從等待列隊上摘除節點。
- 變化:
- 將掛列隊、調度、從列隊刪除三步拆開,便於靈活處理。
- 可中斷的、限時、獨佔的函數類似。只不過進程狀態、調度函數、獨佔標誌不同。
- 非獨佔的從列隊開始添加,獨佔的從末尾添加。(但一個列隊內既有獨佔的,又有非獨佔的等待進程,很少見)
- 喚醒:
- 基本流程
- 喚醒一個進程:調用節點裏的回調函數
- 喚醒的時候從列隊開頭依次喚醒,直到喚醒一個獨佔的後停止。
- 變化
- 是否只喚醒可中斷的進程. (_interruptible後綴)
- 喚醒的獨佔進程的數目(1個,多個(_nr後綴),所有(_all後綴))
- 喚醒後是否不檢查優先級,馬上給予CPU (有_sync的不檢查優先級)。
- 進程切換
- 切換pgd (全局頁目錄),此章不討論。
- 切換內核棧,硬件上下文
- 硬件上下文,就是CPU的寄存器。
- 一部分(大多數CPU寄存器(除了通用寄存器))在pd中保存(task_struct->thread, 類型是thread_struct),
- 一部分(通用寄存器)保存在內核棧中.
- 原來用硬件指令()保存CPU信息。後來改成軟件(一個個MOV指令)
- 容易控制,可以挑選信息保存,便於優化。不保存的做其他用(如:進程間傳遞)
- far jmp:跳至目標進程的TSSD。而linux是每個CPU一個TSS,不是每進程一個
- 對於一些寄存器(ds、es)可以檢查值。
- 與用硬件指令保存時間差不多。
- switch_to 宏
- 三個參數:
- prev: 要換走的進程,一般是當前進程
- next: 要換到的進程。
- last: 傳出參數。當前進程再次被換到時,最後一個佔用CPU的進程。(prev指向的進程 就是 next指向的進程 的last)
- 步驟:
- 棧切換, 完成後就是在新進程的上執行了:
- 保存prev(放在eax)
- eflags,ebp入內核棧;
- 保存並裝載新的esp (舊的esp放到prev->thread.esp,新的esp是next->thread.esp)
- 此時current就是新的esp所指的thread_info內的task指針
- 設置返回地址:
- prev進程以後得到執行時的__switch_to的返回地址: __switch_to後的第一條指令, 放入prev->thread.eip,
- 準備next進程的從__switch_to返回的地址: next->thread.eip入棧.
- 調用__switch_to ()函數,該函數動作如下:
- 更新CPU的相關信息(tss和gdt):
- 存next->thread.esp0(內核棧低)到本地TSS.esp0中。
- 所在CPU的全局段表裏的TLS段, 設成next進程的.
- 更新tss的I/O位圖.
- 更新CPU的寄存器(pd->thread (tss) 與 CPU寄存器交換數據):
- 保存FPU, MMX, XMM寄存器, 先不裝載以後需要時通過中斷裝載(TODO: )
- 保存prev的fs, gs寄存器. 裝載next的
- 裝載next的debug寄存器(debug寄存器一個8個, 進程切換時只需6個)
- 返回
- prev放入eax (prev就是新進程的last)
- ret
- ret返回的地址: (__switch_to之前被存入棧中, __switch_to ret時進入eip)
- 如果是next新進程, next->thread.eip是iret_from_fork.
- 如果next不是新進程:
- 彈出ebp, elfags
- 把eax放入last變量 (prev就是next進程的last)
- 任務狀態段(一個存CPU狀態的數組,tss_struct init_tss[])
- 每個CPU用段上的一個元素。(FIXME: 用於:用戶模式要進入內核模式時,設置相應寄存器)
- TSS上存內核棧地址。CPU上的程序從用戶模式轉到內核模式,設置esp。
- TSS存I/O端口許可位圖。用戶模式程序用到I/O時,檢查有無權限
- 所以,進程切換時,要保存的寄存器在pd->thread中。
- thread_struct不是thread_info。thread_info中只有少量的數據或指針, 用於通過esp快速定位數據
- 進程切換時,更新TSS上的信息。
- CPU控制單元再從TSS上取需要的信息。
- 即反應了CPU的當前進程情況,又不需要維護所有進程的狀態數據。
- TSS的描述符在GDT裏。
- TSSD:任務狀態段描述符 (其實應該叫任務狀態描述符,每個TSSD,表示一個CPU的狀態, FIXME: :具體以源碼爲準)
- CPU原始設計,每個進程一個TSS元素。
- linux設計,每個CPU一個TSS元素。
- cpu裏的tr寄存器,保存着自己的TSSD(即init_ttss[cpu_id]),不用總上gdt裏去取。
|