進程、輕量級進程(LWP)、線程

進程、輕量級進程(LWP)、線程

進程、輕量級進程(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相關:
        •     佔用CPU總時間
        •     用戶的最大進程數
      •    內存相關:
        •     進程地址空間
        •     鎖住內存大小
        •     進程頁數 (只有記錄,沒有限制)
        •     堆大小,棧大小
      •    資源相關:
        •     文件:
          •      core dump大小
          •      最大文件大小
          •      打開文件個數
        •     進程同步與通信
          •      鎖數目,
          •      懸掛信號數據
          •      在消息列隊中佔的大小
      •    相關數據結構 和 處理流程
        •     pd->sigal->rlim 是一個表示進程資源使用情況以及限制的結構 的數組。
        •     表示進程資源使用情況以及限制的結構:包含當前值,最大值兩個數值。
      •    只有超級用戶才能增大資源限制。
      •    一般用戶登陸時:
        •     kernel創建root進程,減少limit,
        •     建一個 shell子進程,繼承limit.
        •     把shell進程的用戶,改成登陸的那個用戶
  •  
    •   進程狀態(state)
      •    運行,TASK_RUNNING
        •     組織pd的結構:就緒進程鏈:
          •      一個CPU一組鏈表,每個鏈表表示一種優先級。
      •    阻塞
        •     可中斷阻塞,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裏去取。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章