關(guān)于進(jìn)程的那些事
????????最近學(xué)習(xí)了Linux-C應(yīng)用開發(fā)中的進(jìn)程。這篇文章記錄下我個人對進(jìn)程相關(guān)的一些概念的理解和總結(jié)。
·進(jìn)程和程序的對應(yīng)關(guān)系
????????我們平常寫的一些程序編譯后得到的可執(zhí)行文件是存儲在磁盤上的,是靜態(tài)的。而當(dāng)我們運(yùn)行這個可執(zhí)行文件后,它會被系統(tǒng)拷貝到內(nèi)存中運(yùn)行,而程序在內(nèi)存中所對應(yīng)的便是進(jìn)程,進(jìn)程是動態(tài)的。
????????一個程序可以對應(yīng)一個或多個進(jìn)程,就是我們將程序多次運(yùn)行便會得到多個進(jìn)程,就像電腦上多開QQ一樣。而一個進(jìn)程只能對應(yīng)一個程序,不可能出現(xiàn)一個進(jìn)程在執(zhí)行兩個程序的代碼。
·進(jìn)程的開始與結(jié)束
????????對于C語言程序而言,進(jìn)程總是從main()函數(shù)開始執(zhí)行,而進(jìn)程的終止通常有以下幾種:
①main()函數(shù)中通過return語句返回。
②應(yīng)用程序中調(diào)用exit()、_exit()或者_(dá)Exit()函數(shù)終止進(jìn)程(正常終止)。
③應(yīng)用程序中調(diào)用abort()函數(shù)終止進(jìn)程(異常終止)。
④進(jìn)程收到一個會被終止的信號,如SIGKILL。
而進(jìn)程在調(diào)用exit()函數(shù)正常終止時可以執(zhí)行一個用戶自定義的回調(diào)函數(shù),通過?atexit()函數(shù)可以設(shè)置。
·進(jìn)程的環(huán)境變量
????????在我們創(chuàng)建一個進(jìn)程時,該進(jìn)程的環(huán)境變量會從其父進(jìn)程中繼承而來。環(huán)境變量通常用于shell程序中,比如HOME表示當(dāng)前用戶的家目錄。
????????Linux為應(yīng)用程序提供了一個全局指針變量來指向環(huán)境變量,但我們通常不會直接解引用該指針來獲取添加或修改環(huán)境變量。而是通過Linux提供的函數(shù)來操作。
·進(jìn)程的內(nèi)存布局
????????進(jìn)程的內(nèi)存布局包含正文段(.text)、數(shù)據(jù)段(.data)、未初始化數(shù)據(jù)段(.bss)、棧、堆。正文段保存的是CPU執(zhí)行的機(jī)器語言指令。數(shù)據(jù)段保存的是已初始化的全局變量和靜態(tài)變量。未初始化數(shù)據(jù)段保存的是未初始化的全局變量和靜態(tài)變量的位置和長度。棧則是保存函數(shù)內(nèi)部的局部變量和調(diào)用函數(shù)時保存的信息。堆則是可以在運(yùn)行時進(jìn)行動態(tài)內(nèi)存分配的區(qū)域。
·子進(jìn)程的創(chuàng)建
????????在Linux系統(tǒng)下,通常使用fork()函數(shù)創(chuàng)建一個新的進(jìn)程。創(chuàng)建出來的進(jìn)程是原進(jìn)程的子進(jìn)程。子進(jìn)程會拷貝父進(jìn)程除了正文段外的所有資源,正文段由于是只讀的,因此只需要一份即可。
·子進(jìn)程與父進(jìn)程的文件共享
????????子進(jìn)程被創(chuàng)建后,由于拷貝了父進(jìn)程的資源,因此子進(jìn)程中可以使用父進(jìn)程在創(chuàng)建子進(jìn)程前所打開的文件描述符,兩個文件描述符均指向同一個文件表,也就是說父子進(jìn)程操作的是同一份文件內(nèi)存副本。
·父進(jìn)程對子進(jìn)程的監(jiān)視
????????由于不確定子進(jìn)程何時結(jié)束,因此通常父進(jìn)程會阻塞等待子進(jìn)程結(jié)束后才結(jié)束。Linux系統(tǒng)提供了一系列wait函數(shù)用于父進(jìn)程對子進(jìn)程的監(jiān)視。常用的有wait()、waitpid()、waitid()函數(shù)。wait函數(shù)除了可以監(jiān)視子進(jìn)程外,還用于在子進(jìn)程終止時回收子進(jìn)程的資源。
·孤兒進(jìn)程與僵尸進(jìn)程
????????當(dāng)一個進(jìn)程終止后,若其還有子進(jìn)程在運(yùn)行,則這些子進(jìn)程變成為孤兒進(jìn)程,他們將由init進(jìn)程收養(yǎng),即它們的父進(jìn)程變?yōu)閕nit進(jìn)程。
????????當(dāng)一個進(jìn)程終止后,若其父進(jìn)程還沒有調(diào)用wait函數(shù)回收其資源,則這個進(jìn)程就變成僵尸進(jìn)程,僵尸進(jìn)程無法被殺死。僵尸進(jìn)程會占用系統(tǒng)進(jìn)程號PID,過多的僵尸進(jìn)程會導(dǎo)致系統(tǒng)無法創(chuàng)建新的進(jìn)程。
????????避免出現(xiàn)過多僵尸進(jìn)程的方法通常有2種,在父進(jìn)程中注冊SIGCHLID信號處理函數(shù),在處理函數(shù)中回收子進(jìn)程的資源;而第二種通常是應(yīng)急處理,就是殺死僵尸進(jìn)程對應(yīng)的父進(jìn)程,讓僵尸進(jìn)程的父進(jìn)程變?yōu)閕nit進(jìn)程,init進(jìn)程自然會回收僵尸進(jìn)程的資源。
·exec族函數(shù)
????????當(dāng)子進(jìn)程被創(chuàng)建時,它的內(nèi)存布局是完全拷貝父進(jìn)程的,因此執(zhí)行的代碼也與父進(jìn)程完全相同,但我們創(chuàng)建子進(jìn)程肯定是需要讓它做其他的事情的。exec族函數(shù)就是用于將新的程序替換進(jìn)程原本對應(yīng)的程序,讓子進(jìn)程執(zhí)行新的代碼。exec族函數(shù)中包含7個exec開頭的函數(shù),分別是execve()、execl()、execlp()、execle()、execv()、execvp()、execvpe(),其中:
①函數(shù)名后帶p的函數(shù)表示只需要提供程序的文件名,系統(tǒng)會自動在環(huán)境變量PATH指定的目錄尋找相應(yīng)的可執(zhí)行文件。
②函數(shù)名后帶l函數(shù)表示傳入程序的main()函數(shù)參數(shù)argv的方式為:可變長參數(shù)。
③函數(shù)名后帶v的表示傳入程序的main()函數(shù)參數(shù)argv的方式為:argv指針數(shù)組。
④函數(shù)名后帶e的函數(shù)表示可以指定自定義的環(huán)境變量列表給新程序。
·進(jìn)程的狀態(tài)
在Linux下,進(jìn)程通常存在6種不同的狀態(tài):
①就緒態(tài):該進(jìn)程滿足被CPU調(diào)度的條件,但還未被調(diào)度執(zhí)行。
②運(yùn)行態(tài):該進(jìn)程當(dāng)前正在被CPU調(diào)度運(yùn)行。
③僵尸態(tài):即僵尸進(jìn)程,該進(jìn)程已結(jié)束,但父進(jìn)程還未回收其資源。
④可中斷睡眠狀態(tài):進(jìn)程等待某種條件成立,條件成立后會進(jìn)入就緒態(tài),可以被信號喚醒。
⑤不可中斷睡眠狀態(tài):同上,但不可被信號喚醒,只能等待相應(yīng)條件成立。與④統(tǒng)稱等待態(tài)(阻塞態(tài))。
⑥暫停態(tài):進(jìn)程暫停運(yùn)行,通常通過信號暫停,如SIGSTOP。可以恢復(fù)到就緒態(tài),通常通過信號恢復(fù),如SIGCONT信號。
·進(jìn)程的關(guān)系
????????在Linux下,進(jìn)程關(guān)系包括:沒有關(guān)系、父子關(guān)系、進(jìn)程組和會話。
·父子關(guān)系:一個進(jìn)程由另一個進(jìn)程創(chuàng)建,則被創(chuàng)建的進(jìn)程是另一個進(jìn)程的子進(jìn)程。
·進(jìn)程組:一個或多個進(jìn)程的集合,每個進(jìn)程組都有一個組長進(jìn)程,進(jìn)程組的ID等于組長進(jìn)程的ID。
·會話:會話是一個或多個進(jìn)程組的集合,每個會話都有一個會話首領(lǐng)。會話的ID等于會話首領(lǐng)的進(jìn)程組ID。
·守護(hù)進(jìn)程
????????守護(hù)進(jìn)程是一種運(yùn)行在后臺的特殊進(jìn)程,通常生命周期是系統(tǒng)啟動到系統(tǒng)關(guān)機(jī),且與控制終端脫離關(guān)系。守護(hù)進(jìn)程通常用于實現(xiàn)服務(wù)器。它們自成進(jìn)程組、自成會話,其pid == gid == sid。
·單例模式
????????一個程序可以對應(yīng)多個進(jìn)程,就是說一個程序被運(yùn)行了多次。但有些情況下不允許一個程序被運(yùn)行多次,只能對應(yīng)一個進(jìn)程,這種情況稱為單例模式。單例模式通常用在服務(wù)器程序,守護(hù)進(jìn)程大部分也是單例模式運(yùn)行。
????????令程序以單例模式運(yùn)行的方法一般是通過文件鎖方式,當(dāng)一個程序第一次運(yùn)行時,獲取到了文件鎖,這時再第二次運(yùn)行該程序,第二個進(jìn)程無法獲取文件鎖,表明已經(jīng)有該程序?qū)?yīng)的進(jìn)程正在運(yùn)行,此時需要終止第二個進(jìn)程。