【转载】linux进程及进程控制
Linux進程控制
?
程序是一組可執行的靜態指令集,而進程(process)是一個執行中的程序實例。利用分時技術,在Linux操作系統上同時可以運行多個進程。分時技術的基本原理是把CPU的運行時間劃分成一個個規定長度的時間片,讓每個進程在一個時間片內運行。當進程的時間片用完時系統就利用調度程序切換到另一個進程去運行。因此實際上對于具體單個CPU的機器來說某一個時刻只能運行一個進程。但由于每個進程運行的時間片很短(例如15個系統滴答=150ms),所以表面看起來好像所有進程在同時運行著。
對于Linux0.11內核來講,系統最多可由64個進程同時存在。除了第一個進程是"手工"建立以外,其余的都是進程使用系統調用fork創建的新進程,被創建的進程成為子進程(Child Process),創建者,則稱為父進程(parent process)。內核程序使用進程標識號(process ID,pid)來標識每個進程。進程由可執行的指令代碼,數據和堆棧區組成。進程中的代碼和數據部分分別對應一個可執行文件中的代碼段,數據段。每個進程只能執行自己的代碼和訪問自己的數據及堆棧區。進程之間相互之間的通信需要通過系統調用來進行。對于只有一個CPU的系統,在某一個時刻只能有一個進程正在運行。內核通過進程調度程序分時調度各個進程運行。
Linux系統中,一個進程可以在內核態(Kernal mode)或者用戶態(user mode)下執行,因此Linux內核堆棧和用戶堆棧是分開的。用戶堆棧用于進程在用戶態下臨時保存調用函數的參數,局部變量等數據。內核堆棧則含有內核程序執行函數調用時的信息。
?
?
1.1任務數據結構
內核程序通過進程表對進程進行管理,每個進程在進程表中占有一項。在Linux系統中,進程表項是一個task_struct任務結構指針。任務數據結構定義在頭文件include/linux/sched.h中。有些書上稱其為進程控制塊PCB(Process Control Block)或者進程描述符PD(Processor Descriptor)。其中保存著用于控制和管理進程的所有信息。主要包括進程當前運行的狀態信息,信號,進程號,父進程號,運行時間累計值,正在使用的文件和本任務的局部描述符以及任務狀態段信息。該結構每個字段的含義如下所示。
?
當一個進程在執行時,CPU的所有寄存器中的值,進程的狀態以及堆棧中的內容被稱為該進程的上下文。當內核需要切換(switch)至另一個進程時,它就需要保存當前進程的所有狀態,也即保存當前進程的上下文,以便在再次執行該進程時,能夠恢復到切換時的狀態執行下去。在Linux中,當前進程上下文均保存在進程的任務數據結構task_struct中。在發生中斷時,內核就在被中斷進程的上下文中,在內核狀態下執行中斷服務例程。但同時會保留所有需要用到的資源,以便中斷服務結束時能恢復被中斷進程的執行。
?
?
1.2 進程運行狀態
一個進程在其生存期內,可處于一組不同的狀態下,稱為進程狀態。見下圖2-6所示。進程狀態保存在進程任務結構的state字段中。當進程正在等待系統中的資源而處于等待狀態時,則稱其處于睡眠等待狀態。在Linux系統中,睡眠等待狀態被分為可中斷的和不可中斷的等待狀態。
?
運行狀態(TASK_RUNNING)
當進程正在被CPU執行,或已經準備就緒隨時可以由調度程序執行,則稱該進程為處于運行狀態(running)。進程可以在內核態運行,也可以在用戶態運行。當系統資源已經可用時,進程就被喚醒而進入準備運行狀態,該狀態稱為就緒態。這些狀態在內核中表示方法相同,都被稱為處于TASK_RUNNING狀態。
?
可中斷睡眠狀態(TASK_INTERRUPTIBLE)
當進程處于可中斷等待狀態時,系統不會調度該進程執行。當系統產生一個中斷或者釋放了進程正在等待的資源,或者進程收到一個信號,都可以喚醒進程轉換到就緒狀態(運行狀態)。
?
不可中斷睡眠狀態(TASK_UNINTERRUPTIBLE)
與可中斷睡眠狀態類似。但處于該狀態的進程只有被使用wake_up()函數明確喚醒時才能被轉換到可運行就緒狀態。
?
暫停狀態(TASK_STOPPED)
當進程收到信號SIGSTOP,SIGTSTP,SIGTTIN或SIGTTOU時就會進入暫停狀態。可向其發送SIGCONT信號讓進程轉換到可運行狀態。在Linux0.11中,還為實現對該狀態的轉換處理。處于該狀態的進程將被作為進程終止來處理。
?
僵死狀態(TASK_ZOMBIE)
當進程已停止運行,但其父進程還沒有詢問其狀態時,則稱該進程處于僵死狀態。
?
當一個進程的運行時間片用完,系統就會使用調度程序強制切換到其他的進程去執行。另外,如果進程在內核態執行時需要等待系統的某個資源,此時該進程就會調用sleep_on()或者sleep_on_interruptible()自愿放棄CPU使用權,而讓調度程序去執行其他程序。進程則進入睡眠狀態(TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE)。
只有當進程從"內核運行態"轉移到"睡眠狀態"時,內核才會進行進程切換操作。在內核態下運行的進程不能被其他進程搶占,而且一個進程不能改變另一個進程的狀態。為了避免進程切換時造成內核數據錯誤,內核在執行臨街區代碼時禁止一切中斷。
?
?
1.3 進程初始化
在boot/目錄中引導程序把內核從磁盤上加載到內存中,并讓系統進入保護模式下運行后,就開始執行系統初始化程序init/main.c。該程序首先確定如何分配使用系統物理內存,然后調用內核各部分的初始化函數分別對內存管理,中斷處理,塊設備和字符設備,進程管理以及硬盤和軟盤硬件進行初始化處理。在完成了這些操作之后,系統各部分已經處于可運行狀態。此后程序把自己"手工"移動到任務0(進程0)中運行,并使用fork()調用首次創建出進程1。在進程1種程序將繼續進行應用環境的初始化并執行shell登陸程序。而原進程0則會在系統空閑時被調度執行,此時任務0僅執行pause()系統調用,并又會調用調度函數。
"移動到任務0種執行"這個過程由宏move_to_user_mode(include/asm/system.h)完成。它把main.c程序執行流從內核態(特權級0)移動到了用戶態(特權級3)的任務0種繼續運行。在移動之前,系統在對調度程序的初始化過程(sched_init())中,首先對任務0的運行環境進行的設置。這包括人工預先設置好 任務0數據結構各字段的值(include/linux/shed.h),在全局描述符中添入任務0的任務狀態段(TSS)描述符和局部描述符表(LDT)的段描述符,并把它們分別加載到任務寄存器tr和局部描述符表寄存器ldtr中。
這里需要強調的是,內核初始化是一個特殊過程,內核初始化代碼也即是任務0的代碼。從任務0數據結構中設置的初始化數據可知,任務0的代碼段和數據段的基址是0,段限長是640KB。而內核代碼段和數據段的基地址時0,段限長是16MB,因此任務0的代碼段和數據段分別包含在內核代碼段和數據段中。內核初始化程序main.c也即是任務0中的代碼,只是在移動到任務0之前系統正以內核態特權級0運行著main.c程序。宏move_to_user_mode的功能就是把運行特權級內核態的0級變換到用戶態的3級,但是仍然繼續執行原來的代碼指令流。
在移動到任務0的過程中,宏move_to_user_mode使用了中斷返回指令造成特權級改變的方法。該方法的主要思想是在對站中構筑中斷返回指令需要的內容,把返回地址的段選擇符設置成任務0代碼段選擇符,其特權級為3。此后執行中斷返回指令iret時將導致系統CPU從特權級0跳轉到外層的特權級3上運行。參見下圖所示的特權級發生變化時中斷返回堆棧結構示意圖。
?
? ? ? ? ?宏move_to_user_mode首先往內核堆棧中壓入任務0數據段選擇符和內核堆棧指針。然后壓入標志寄存器內容。最后壓入任務0代碼段選擇符合執行中斷返回后需要執行的下一條指令的偏移位置。該偏移位置是iret后的一條指令處。
當執行iret指令時,CPU把返回地址送入CS:EIP中,同時探出對站中標志寄存器內容。由于CPU判斷出墓地代碼段的特權級是3,與當前內核態的0級不同。于是CPU會把堆棧中的堆棧段選擇符合堆棧指針彈出到SS:ESP中。由于特權級發生了變化,段寄存器DS,ES,FS和GS的值變得無效,此時CPU會把這些段寄存器清零。因此在執行了iret指令后需要重新加載這些段寄存器。此后,系統就開始特權級3運行在任務0的代碼上。所使用的用戶態堆棧還是原來在移動之前使用的堆棧。而其內核態堆棧則被指定為其任務數據解噢股所在頁面的頂端開始(PAGE_SIZE+(long)&init_task)。由于以后在創建新進程時,需要復制任務0的任務數據結構,包括其用戶堆棧指針,因此需要任務0的用戶態堆棧在創建任務1(進程1)之前保持"干凈"狀態。
?
?
1.4 創建新進程
Linux系統中創建新進程使用fork()系統調用。所有進程都是通過復制進程0而得到的,都是進程0的子進程。
在創建新進程的過程中,系統首先在任務數組中找出一個還沒有被任何進程使用的空項(空槽)。如果系統已經有64個進程在運行,則fork()系統調用會因為任務數組表中沒有可用空項而出錯返回。然后系統為新建進程在主內存區中申請一頁內存來存放其任務數據結構信息,并復制當前進程任務數據結構中所有內容作為新進程人物數據結構的模板。為了防止這個還未處理完成的新進程被調度函數執行,此時應該立刻將新進程狀態置為不可中斷的等待狀態(TASK_UNINTERRUPTIBLE)。
隨后對復制的任務數據結構進行修改。把當前進程設置為新進程的父進程,清除信號位圖并復位新進程各統計值,并設置初始運行時間片值為15個系統嘀嗒數(150ms)。接著根據當前進程設置任務狀態段(TSS)中各寄存器的值。由于創建進程時新進程返回值應為0,所以需要設置tss.eax=0。新建進程內核態堆棧指針tss.esp0被設置成新進程任務數據結構所在內存頁面的頂端,而堆棧段tss.ss0被設置成內核數據段選擇符。tss.ldt被設置為局部表描述符在GDT中所引值。如果當前進程使用了協處理器,把還需要協處理器的完整狀態保存到新進程的tss.i387結構中。
此后系統設置新任務的代碼和數據段基址,限長并輔之當前進程內存分頁管理的頁表。如果父進程中也有文件是打開的,則應將對應文件的打開次數增1。接著在GDT中設置新任務的TSS和LDT描述符項,其中基地址信息指向新進程人物結構中的tss和ldt。最后再將新任務設置成可運行狀態并返回新進程號。
?
1.5 進程調度
由前面描述可知,Linux進程是搶占式的。被搶占的進程仍然處于TASK_RUNNING狀態,只是暫時沒有被CPU運行。進程的搶占發生在進程處于用戶態執行階段,在內核執行時是不能被搶占的。
為了能讓進程有效地使用系統資源,又能使進程有較快的響應時間,就需要對進程的切換調度采用一定的調度策略。在Linux0.11中采用了基于優先級排隊的調度策略。
調度程序:
schedule()函數首先掃描任務數組。通過比較每個就緒態(TASK_RUNNING)任務的運行時間遞減滴答計數counter的值來確定當前哪個進程運行時間最少。哪一個的值大,就表示運行時間還不長,于是就選中該進程,并使用任務切換宏函數切換到該進程運行。
如果此時所有處于TASK_RUNNING狀態進程的時間片已經用完,系統就會根據每個進程的優先權值priority,對系統中所有進程(包括正在睡眠的進程)重新計算每個任務需要運行的時間片值counter。
計算公式是:? counter= counter/2 + priority
然后schedule()函數重新掃描人物數組中所有處于TASK_RUNNING狀態,重復上述過程,直到選擇出一個進程位置。最后調用switch_to()執行實際的進程切換操作。
如果此時沒有其他進程可運行,系統就會選擇進程0運行。對于linux0.11來說,進程0會調用pause()把自己置為可中斷的睡眠狀態并在此調用schedule()。不過在調度進程運行時,schedule()并不在意進程0處于什么狀態。只要系統空閑就調度進程0運行。
進程切換:
執行實際進程切換的任務由switch_to()宏定義的一段匯編代碼完成。在進行切換之前,switch_to()首先檢查要切換到的進程是否就是當前進程,如果是則什么也不做,直接退出。否則就首先把內核全局變量current置為新任務的指針,然后長跳轉到新任務的任務狀態段TSS組成的地指處,造成CPU執行任務切換操作。此時CPU會把其所有寄存器的轉改保存到當前人物寄存器TR中TSS段選擇符所指向的當前進程任務數據結構的tss結構中,然后把新任務狀態段選擇符所指向的新任務數據結構中tss結構中的寄存器信息恢復到CPU中,系統就正式開始運行新切換的任務了。這個過程可參考下圖
?
?
1.6 終止進程
當一個進程結束了運行或在半途中終止了運行,那么內核就需要釋放該進程所占用的系統資源。這包括進程運行時打開的文件,申請的內存等。
當一個用戶程序調用exit()系統調用時,就會執行內核函數do_exit()。該函數會首先釋放進程代碼段和數據段占用的內存頁面,關閉進程打開著的所有文件,對進程使用的當前工作目錄,根目錄和運行程序的i節點進行同步操作。如果進程有子進程,則讓init進程作為其所有子進程的父進程。如果進程是一個會話頭進程并且有控制終端,則釋放控制終端,并向屬于該會話的所有進程發送掛斷信號SIGHUP,這通常會終止該會話中的所有進程。然后把進程狀態置為僵死狀態TASK_ZOMBIE。并向其原父進程發送SIGCHILD信號,通知其某個子進程已經終止。最后do_exit()調用調度函數去執行其他進程。由此可見在進程終止時,它的task_struct任務數據結構仍然保留著。因為其父進程還需要使用其中的信息。
在子進程在執行期間,父進程通常使用wait()或waitpid()函數等待其某個子進程終止。當子進程被終止并處于僵死狀態時,父進程就會把子進程運行所使用的時間累加到自己進程中。最終釋放已終止子進程任務數據結構所占用的內存頁面,并置空子進程在任務數組中占用的指針項。
轉載于:https://www.cnblogs.com/mod109/p/6042073.html
總結
以上是生活随笔為你收集整理的【转载】linux进程及进程控制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RTP/RTCP协议介绍
- 下一篇: sublimeText OmniMark