進程間的相關知識
進程:一個正在運行的程序。一個進程主要包括三個因素
1. fork()
(1)函數原型:pid_t fork()
*pid_t是一個宏,其實質是一個整形,且是一個16位的整形(-32768-----32768),因此linux中可以創建的最大進程數爲32768
(2)fork的複製過程
I:先申請一個pid(如果當前進程數已經達到了版本規定的上限,那麼fork時將會出錯);
II:先進行進程描述符(PCB)的複製,在接下來進行進程實體的複製
III:然後進行寫實拷貝
*寫實拷貝:內核並不需要複製整個進程地址空間,只有在寫入的時候才進行資源的複製。而且她的操作是對頁進行操作的
(3)fork函數的簡單應用
Eg:A.有如下代碼段:
int main(){
pid_t pid1;
pid_t pid2;
pid1 = fork();
pid2 = fork();
}
a.執行這個程序後一共會產生幾個進程?
b.如果其中一個進程的輸出結果是pid1:1001;pid2:1002其他進程的輸出結果爲?
解:設由shall啓動的進程爲p0;
I:當程序執行到pid1=fork()時,p0啓動一個新進程p1,且p1的pid爲1001;
II:p0中的fork將1001返回給pid1,繼續執行pid2=fork(),此時啓動了一個新的進程爲p2,p2的pid2爲1002;
III:p0中的第二個fork將1002返回給pid2。因此,pid1:1001,pid2:1002;
IV:因爲p2生成時p0中的pid1爲1001,所以p2繼承了p0中的pid1:1001,但是p2作爲一個子進程它的返回值pid2:0,p2是從第二個fork後運行的,所以,pid1:1001,pid2:0;
V:p1作爲p0的一個子進程,在第一個fork之後將0返回給pid1,在進行第二個fork時,p1產生了一個新進程p3;p1中的第二個fork將p3的pid返回給pid2,由題可知pid2爲1003,因此,p1進程 pid1:0 pid2:1003
IV:p3作爲p1的子進程繼承p1中的pid1:0,而它本身也爲一個子進程,所以 pid2 :0
綜上所述:執行這個程序後一共會產生4個進程,分別爲:
p0;
p1:pid1:0 pid2:1003
p2: pid1:1001 pid2:1002
P3: pid1:0 pid2:0
B.fork() && fork()會產生幾個進程?
解:當第一個fork爲0,不執行第二個fork,則只有一個fork() && fork();當第一個fork>0,執行第二個fork,則會出現兩個fork() && fork();所以總共會有3個進程產生;
(4)vfork()
特點:父子進程共享數據段,並且保證子進程先於父進程運行,在它調用exec或者_exit時,父進程纔會被運行
(5)兩種特殊的進程
A. 僵死進程
I.描述:子進程已經結束,而父進程還在繼續
II.處理方法:
a. 程序調用signal(SIGCHLD,SIG_IGN),來忽略SIGCHLD信號,這樣子進程結束後會由內核釋放資源
b. 對子進程的退出捕獲他們的退出信號SIGCHLD,父退出信號時,在信號處理函數中調用waitpid()操作來釋放他們的資源。
B.孤兒進程
I.描述:父進程已經結束,而子進程還在繼續。
II.處理:孤兒進程會由進程號爲1的init所收養,並且會爲他們完成狀態收集工作
2. exec()
(1)函數的功能:調用它並沒有產生新的進程,一個進程一旦調用exec()函數,它本身就死亡了。就好比鬼上身了一樣,身體還是你的,但是靈魂和思想已經被替換了-----系統把代碼段替換成新的程序的代碼,在這其中唯一保留的就是進程的ID,對於系統而言,還是同一個進程,只不過是執行另一個程序罷了。
***I.只有fork()和vfork()才能創建一個新的進程
II.在使用exec()之前,首先要使用fork(),創建一個子進程,子進程調用exec()函數
(2)exec()函數族
I.說明:
int execl(const char *path,const char *arg,...);
*與execv函數的用法類似,只是在傳遞argv參數的時候每個命令行參數都聲明爲一個單獨的參數(“.....”說明參數個數不確定),這些參數要以一個空指針結尾。
int execv(const char *path,char *const argv[]);
*通過路徑名方式調用可執行文件作爲新的進程映像
int execle(const char *path,const char *arg,....,char *const envp[]);
*與execl的用法類似
int execve(const char*pathname,const char *argv[],char *const envp[]);
*參數pathname是將要執行的程序的路徑名,參數argv,envp與main函數的argv,envp對應
int execlp(const char *file,const char *arg,....);
*與execl函數類似
int execvp(const char *file,char*const argv[] );
*與execv函數用法類似
II.區別
a. 前四個取路徑名做參數,後兩個取文件名做參數
b. 與參數表的傳遞有關(l表示list,v表示矢量vector)。函數execl、execlp和execle要求將新程序的每個命令行參數都說明爲一個單獨的參數,這中參數表以空指針結尾。而execv、execve和execvp則要先構造一個指向各參數的指針數組,然後將該數組的地址作爲這三個函數的地址。
3.進程間通訊
(1)進程間通訊的幾種方式
管道 消息隊列 信號量 共享內存 套接字
A. 管道
a. 實現進程間通訊的原理:
就像現實中管道的兩端一樣,由一個進程進行寫操作,其餘的進程進行讀操作。如果管道爲空,那麼read會阻塞;如果管道爲滿則write會阻塞。
b.分類
管道可以分爲有名管道和無名管道兩類。他們之間的區別是:
I.有名管道:可以在任意進程之間進行通訊,通訊是雙向的,任意一端都可讀可寫,但同一時間只能一端讀,一端寫。
II.無名管道:只能在父子進程間通訊,不能在網絡間通訊,並且是單向的,只能一端讀另一端寫。
c.特點:通訊數據遵循先進先出的原則
B.消息隊列
a. 特點: I.是消息的鏈表。具有特定的格式,存放在內存當中,由消息隊列標識符標識。
II.消息隊列允許一個或者多個進程向他寫入與讀取消息。
III.消息隊列可實現消息的隨機查詢,不一定要以先進先出的順序讀取,也可以按照類型進行讀取。
b.相關函數
C.信號量
a. 概念:用來同步進程的特殊變量;
b. 手段:通過控制程序的推進速度,使得同一個過程只有一個進程訪問;
c. 操作方式:對信號量進行操作使用p v操作,p v操作都是原子操作;
*p操作:獲取資源,信號量的值減1;
*v操作:釋放資源,信號量的值加1;
D.共享內存
a. 實現原理:共享內存區域說白了就是多個進程共享的一塊物理內存地址。假設有10個進程將這塊區域映射到自己的虛擬地址上,那麼,這10個進程間就可以相互通信。由於是同一塊區域在10個進程的虛擬地址上,當第一個進程向這塊共享內存的虛擬地址中寫入數據時,其他9個進程也都會看到。因此共享內存是進程間通信的一種最快的方式。
E.套接字
4. 信號
(1)signal()函數【響應方式】
signal(參數1,參數2);
參數1:需要進行處理的信號;
參數2:處理的方式(1.默認 2.忽略 3.自定義)
Eg:a.signal(SIGINT,SIG_ING)
*SIG_ING代表忽略SIGINT這個信號
b.signal(SIGINT,SIG_DEF)
*SIG_DEF表示默認操作(對於大多數信號,系統的默認操作是結束該進程)
(2)kill()函數【是一個信號發送函數】
int kill(pid_t pid,int sig)
*pid = 0:信號被髮送到和當前進程在同一個進程組的進程
pid = 1:信號發給所有進程表中的進程;
@@@ raise()也是一個信號發送函數,不過他可以允許進程向自己發送信號。
int raise(int sig);
5. 進程狀態轉移圖