程序的建立、等待以及退出
學習了程序的進步概念之後,接下來我們就來學習一下程序的建立、等待以及終止等。
1、程序的建立
在Linux中,fork()函式是非常重要的函式,它從已存在的程序中再建立一個新程序。新程序為子程序,而原程序為父程序。
(1) pid_t fork(void);
返回值:子程序返回0,父程序返回子程序id,出錯返回-1;
程序呼叫fork(),當控制轉移到核心中的fork程式碼後,核心做:
- 分配新的記憶體塊和核心資料結構給子程序;
- 將父程序部分資料結構內容拷貝至子程序;
- 新增子程序到系統程序列表當中;
- fork返回,開始排程器排程;
當一個程序呼叫fork()之後,就有兩個二進位制程式碼相同的程序,而且他們都執行到相同的地方。但每個程序都將開始他們自己的旅行。
下面就來看一段程式碼:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
printf("Before:pid is %d\n",getpid());
if((pid=fork())==-1)
{
perror("fork");
exit(1);
}
printf("After:pid is %d,fork return %d\n ",getpid(),pid);
sleep(1);
return 0;
}
執行結果如下:
有圖可看出,一行Before,兩行After。程序31373先列印Before,然後再列印After。另一個After訊息由31374列印。且31373 return 31374,而31374 return 0。不知道你注意沒?31374沒有列印Before,這是為什麼呢?
由此可以看出,fork()之前父程序獨立執行,fork()之後,父子兩個執行流分別執行。注意:fork()之後,誰先執行完全由排程器決定。
通常,父子程式碼共享,父子再不寫入時,資料也是共享的,當任意一方試圖寫入時,便以寫時拷貝的方式各自一份副本進行修改。父子程序共用同一塊虛擬地址空間,通過頁表對映到不同的實體地址。
fork()的常規用法:
- 一個父程序希望複製自己,使父子程序同時執行不同的程式碼段。例如:父程序等待客戶端請求,生成子程序來處理請求。
- 一個程序要執行一個不同的程式,例如:子程序從fork()返回後呼叫exec()函式。
fork()呼叫失敗的話有兩個因素:(1)系統中有太多的程序;(2)實際使用者的程序數超過了限制;
(2)Vfork()函式
在Linux中,用來建立子程序的還有Vfork()函式,但是Vfork函式與fork()函式有些不同;
- Vfork()函式用於建立一個子程序,而子程序與父程序共享地址空間,fork的子程序具有獨立地址空間;
- Vfork()函式保證子程序先執行,在它呼叫exec或者(exit)之後父程序才肯被排程;
下面就來看看Vfork()函式的使用:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int glob=100;
int main()
{
pid_t pid=vfork();
if(pid==-1)
{
perror("vfork");
exit(1);
}
else if (pid==0)
{//child
sleep(5);
glob=200;
printf("child glob is %d\n",glob);
exit(0);
}
else
{//parent
printf("parent glob is %d\n",glob);
}
return 0;
}
執行結果:
由截圖結果可見子程序修改了父程序的變數值,由此更能說明子程序在父程序的地址空間中執行。
2、程序的退出
程序的退出場景有三種:
(1)程式碼執行完畢,執行結果正確;
(2)程式碼執行完畢,執行結果錯誤;
(3)程式碼未執行完畢,異常退出;
程序的退出場景有三種,就說明程序的退出方式也不會只有一種,下面說說程序退出的方式:
(1)正常退出 (可以通過命令echo $ ?檢視程序退出碼):
從main函式返回;呼叫exit;_exit;
(2)異常退出:Ctrl + C,訊號終止;
_exit()函式:
#include<unistd.h>
void _exit(int status);
引數:status定義了程序的終止狀態,父程序通過wait來獲取該值;
說明:雖然status是int,但是隻要低8位可以被父程序使用。所有_exit(-1)時,在終端執行$?可以發現返回值是255
exit()函式:
#include<unistd.h>
void exit(int status);
exit最後也會呼叫exit,但在呼叫exit之前,還做了幾件事:
- 執行使用者通過atexit或on_exit定義的清理函式;
- 關閉所有開啟流,所有的快取資料均被寫入;
- 呼叫_exit
return 退出
return是一種更常見的退出程序方法,執行return n等同於執行exit(n),因為呼叫main函式的執行時函式會將main的返回值當中exit的引數。
3、程序等待
(1) 程序等待必要性
- 之前學過,子程序退出,父程序如果不管不顧,就可能造成‘殭屍程序’的問題,進而造成記憶體洩漏。
- 此外,程序一旦變成殭屍狀態,那就變成了銅牆鐵壁,可以任意殺人的大魔王 kill -9也就無能為力了,因為誰也沒有辦法殺死一個已經死去的程序,就像你永遠無法叫醒一個裝睡的人一樣。
- 還有,父程序派給子程序的任務完成的任務完成的如何,我們需要知道。比如: 子程序執行完成,結果正確還是錯誤,或者是否正常退出。
- 父程序通過程序等待的方式,回收子程序的資源,獲取子程序的退出狀態。
(2)程序等待的方法
wait()方法:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
引數:輸出型引數,獲取子程序退出狀態,不關心則可以設定為NULL;
返回值:成功返回被等待程序的pid,失敗返回-1;
waitpid()方法:
pid_t waitpid(pid_t pid ,int *ststus ,int options);
返回值:當正常返回的時候waitpid返回收集到的子程序的程序ID;
如果設定了選項WNOHANG,而呼叫中waitpid發現沒有已退出的子程序可收集,則返回0;
如果呼叫中出錯了,則返回-1,這時error會被設定出相應的值以指示錯誤所在;
引數:pid: pid=-1,等待任何一個子程序。與wait等效。
pid>0,等待其程序ID與pid等待的子程序。
status: WIFEXITED(status):若為正常終止子程序返回的狀態,則為真。(檢視程序是否是正常退出)
WEXITSTATUS(status):若WIFEXITED非零,提取子程序退出碼。(檢視程序的退出碼)
options:WNOHANG:若pid指定的子程序沒有結束,則waitpid()函式返回0,不會等待。若正常結束,則返回該子程序的ID。
- 如果子程序已經退出,呼叫wait/waitpid 時 ,wait/waitpid會立即返回,並且釋放資源,獲得子程序退出資訊。
- 如果在任意時刻呼叫wait/waitpid,子程序存在且正常執行,則肯引起阻塞。
- 如果不存在該子程序,則立即出錯返回。
下面來看看函式具體呼叫過程:
以上就是程序的建立、等待、退出的簡單回顧。