1. 程式人生 > >第一次作業:基於Linux 2.6 的源碼 分析進程模型

第一次作業:基於Linux 2.6 的源碼 分析進程模型

中斷 mic 選中 下一條 繼續 配置 AI 高級 stopped

1.前言

本文的內容是基於Linux 2.6的源碼,深入分析進程模型。

  • 什麽是進程
  • 操作系統是怎麽組織進程的
  • 進程狀態如何轉換
  • 進程是如何調度的
  • 談談自己對該操作系統進程模型的看法

2.什麽是進程

進程是處於執行期的程序以及它所包含的所有資源的總稱,包括虛擬處理器,虛擬空間,寄存器,堆棧,全局數據段等。

Windows10進程如圖所示

技術分享圖片

3.操作系統是如何組織進程的

一、描述進程——PCB

進程信息被放在一個叫做進程控制塊的結構中,可以理解為進程屬性的集合,稱之為:PCB,在Linux下,PCB是一個叫做task_struct的結構體,這個結構體裏面存放了進程的有關信息。

task_struct結構體的內容分類:

①標識符(PID):描述本進程的唯一的標識符,用來區別其他進程

獲取pid的方法有很多,最推薦的一種就是通過系統調用getpid()來獲取進程的pid,

②狀態:任務狀態,退出碼,退出信號等

狀態分類:R運行狀態(runing) S睡眠狀態(sleeping) D磁盤休眠狀態(Disk sleeping) T停止狀態(stopped) X死亡狀態(dead) Z僵屍狀態(zombie)

③優先級:相對於其他進程的優先級

PRI:進程可執行的優先級,值越小優先級越高

NI:代表nice值,表示進程可被執行的優先級的修正數值

④程序計數器:程序中即將被執行的下一條指令的地址

⑤內存指針:包括程序代碼和進程相關數據的指針,還有和其他進程共享的內存塊的指針

⑥上下文數據:進程執行時處理器的寄存器中的數據

⑦I/O狀態信息:包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表

⑧記賬信息:可能包括處理器的時間總和,使用的時鐘數總和,時間限制,記賬號等

⑨其他信息

二、組織進程

因為進程需要不斷的關閉和開啟,和數據結構中鏈表的結構很相似,所以運行在系統的進程都以task_struct鏈表的形式存在內核裏面,此時我們就把進程組織起來了。

三、查看進程

現在進程已經被描述和組織起來,那麽有些方法可以讓我們查看進程呢?

①進程信息可以被 /proc 文件查看; 比如:要查看PID(進程的標識符)為1的進程,就需要查看 /proc/1 這個文件夾

②通過系統調用獲取進程標識符:

#include<stdio.h>  
#include<sys/types.h>  
#include<unistd.h>  
int main(){  
      printf("pid:%d\n",getpid());  
      printf("ppid:%d\n",getppid());  
      return 0;  
 }  

getppid()函數是獲取父進程的標識符,getpid()函數是獲取子進程的標識符

四、創建進程

通過系統調用創建進程,也就是使用fork()函數

#include<stdio.h>  
#include<sys/types.h>  
#include<unistd.h>  
int main(){  
       pid_t ret = fork();  
     printf("hello proc:%d,ret = %d\n",getpid(),ret);  
       return 0;  
  }  

五、進程的狀態:

R運行狀態(running):表明進程要麽在運行中,要麽在運行隊列裏面

S睡眠狀態(sleeping):意味著進程在等待事件的完成,這個睡眠也叫做可中斷睡眠

D磁盤休眠狀態(Disk sleep):這個狀態的睡眠通常在等待I/O的結束,也叫做不可中斷睡眠

T停止狀態(stopped):可以發送SIGSTOP信號給進程,讓進程停止。也可以發送SIGCONT信號讓進程繼續運行。

X死亡狀態(dead):這個狀態只是一個返回狀態,你不會在任務列表裏面看到這個狀態

Z僵屍狀態(zombie):子進程退出,父進程沒有讀取子進程的退出碼,子進程進入Z狀態

僵屍進程:

#include<stdio.h>  
#include<sys/types.h>  
#include<unistd.h>  
#include<stdlib.h>  
int main(){  
      pid_t id = fork();  
      if(id<0){  
          perror("fork failed!\n");  
          return 1;  
      }  
      else if (id>0){//這個循環裏面,我們讓父進程sleep30s  
          printf("father pid:%d\n",getpid());  
          sleep(30);  
      }  
      else {//這個循環裏面,我們讓子進程sleep5s,之後退出  
          printf("child pid:%d\n",getpid());  
          sleep(5);  
          exit(EXIT_SUCCESS);  
      }  
      return 0;  
}  

僵屍進程的危害:

①因為進程的狀態必須被維護,退出狀態也屬於進程的基本信息,所以就保存在PCB中,在Z狀態中,子進程不退出,PCB就要一直被維護

②子進程進入僵屍狀態之後會一直占用內存,造成內存的資源浪費

③內存的泄漏

4.進程狀態如何轉換

1. Linux進程狀態有:

TASK_RUNNING : 就緒態或者運行態,進程就緒可以運行,但是不一定正在占有CPU,對應進程狀態的R

TASK_INTERRUPTIBLE:睡眠態,但是進程處於淺度睡眠,可以響應信號,一般是進程主動sleep進入的狀態,對應進程狀態S

TASK_UNINTERRUPTIBLE:睡眠態,深度睡眠,不響應信號,典型場景是進程獲取信號量阻塞,對應進程狀態D

TASK_ZOMBIE:僵屍態,進程已退出或者結束,但是父進程還不知道,沒有回收時的狀態,對應進程狀態Z

TASK_STOPED:停止,調試狀態,對應進程狀態T

技術分享圖片

2. 進程調度時機:

進程調度會引起進程狀態轉換,由上圖可知如下情況會觸發調度,進程終止或進程睡眠時主動exit或sleep釋放CPU;淺度睡眠的進程被CFS調度選中喚醒,深度睡眠進程由於信號量,鎖等的釋放而被喚醒;進程收到信號量等;還有一種最常見的中斷,異常。

5.進程是如何調度的

Linux進程調度的目標

    1.高效性:高效意味著在相同的時間下要完成更多的任務。調度程序會被頻繁的執行,所以調度程序要盡可能的高效;

    2.加強交互性能:在系統相當的負載下,也要保證系統的響應時間;

    3.保證公平和避免饑渴;

    4.SMP調度:調度程序必須支持多處理系統;

    5.軟實時調度:系統必須有效的調用實時進程,但不保證一定滿足其要求;

Linux進程優先級

進程提供了兩種優先級,一種是普通的進程優先級,第二個是實時優先級。前者適用SCHED_NORMAL調度策略,後者可選SCHED_FIFO或SCHED_RR調度策略。任何時候,實時進程的優先級都高於普通進程,實時進程只會被更高級的實時進程搶占,同級實時進程之間是按照FIFO(一次機會做完)或者RR(多次輪轉)規則調度的。

實時進程的調度

  實時進程,只有靜態優先級,因為內核不會再根據休眠等因素對其靜態優先級做調整,其範圍在0~MAX_RT_PRIO-1間。默認MAX_RT_PRIO配置為100,也即,默認的實時優先級範圍是0~99。而nice值,影響的是優先級在MAX_RT_PRIO~MAX_RT_PRIO+40範圍內的進程。

  不同與普通進程,系統調度時,實時優先級高的進程總是先於優先級低的進程執行。知道實時優先級高的實時進程無法執行。實時進程總是被認為處於活動狀態。如果有數個 優先級相同的實時進程,那麽系統就會按照進程出現在隊列上的順序選擇進程。假設當前CPU運行的實時進程A的優先級為a,而此時有個優先級為b的實時進程B進入可運行狀態,那麽只要b<a,系統將中斷A的執行,而優先執行B,直到B無法執行(無論A,B為何種實時進程)。

  不同調度策略的實時進程只有在相同優先級時才有可比性:

  1. 對於FIFO的進程,意味著只有當前進程執行完畢才會輪到其他進程執行。由此可見相當霸道。

  2. 對於RR的進程。一旦時間片消耗完畢,則會將該進程置於隊列的末尾,然後運行其他相同優先級的進程,如果沒有其他相同優先級的進程,則該進程會繼續執行。

  ,對於實時進程,高優先級的進程就是大爺。它執行到沒法執行了,才輪到低優先級的進程執行。等級制度相當森嚴啊。

非實時進程調度

Linux對普通的進程,根據動態優先級進行調度。而動態優先級是由靜態優先級(static_prio)調整而來。Linux下,靜態優先級是用戶不可見的,隱藏在內核中。而內核提供給用戶一個可以影響靜態優先級的接口,那就是nice值,兩者關系如下:

  static_prio=MAX_RT_PRIO +nice+ 20

  nice值的範圍是-20~19,因而靜態優先級範圍在100~139之間。nice數值越大就使得static_prio越大,最終進程優先級就越低。

  ps -el 命令執行結果:NI列顯示的每個進程的nice值,PRI是進程的優先級(如果是實時進程就是靜態優先級,如果是非實時進程,就是動態優先級)  

  而進程的時間片就是完全依賴 static_prio 定制的,見下圖,摘自《深入理解linux內核》,

  技術分享圖片

  我們前面也說了,系統調度時,還會考慮其他因素,因而會計算出一個叫進程動態優先級的東西,根據此來實施調度。因為,不僅要考慮靜態優先級,也要考慮進程的屬性。例如如果進程屬於交互式進程,那麽可以適當的調高它的優先級,使得界面反應地更加迅速,從而使用戶得到更好的體驗。Linux2.6 在這方面有了較大的提高。Linux2.6認為,交互式進程可以從平均睡眠時間這樣一個measurement進行判斷。進程過去的睡眠時間越多,則越有可能屬於交互式進程。則系統調度時,會給該進程更多的獎勵(bonus),以便該進程有更多的機會能夠執行。獎勵(bonus)從0到10不等。

  系統會嚴格按照動態優先級高低的順序安排進程執行。動態優先級高的進程進入非運行狀態,或者時間片消耗完畢才會輪到動態優先級較低的進程執行。動態優先級的計算主要考慮兩個因素:靜態優先級,進程的平均睡眠時間也即bonus。計算公式如下,

dynamic_prio = max (100, min (static_prio - bonus + 5, 139))

  在調度時,Linux2.6 使用了一個小小的trick,就是算法中經典的空間換時間的思想,使得計算最優進程能夠在O(1)的時間內完成。

Linux進程狀態機

技術分享圖片

6.談談自己對操作系統進程模型的看法

進程是操作系統提供的最古老的也是最重要的抽象概念之一,它們將一個單獨的CPU變換成多個虛擬CPU。沒有進程的抽象,現代計算將不復存在。所以進程非常的重要 ,所以要好好地學習進程,以更進一步的學習操作系統的後面的知識。

第一次作業:基於Linux 2.6 的源碼 分析進程模型