linux程式設計之程序控制
(一)fork
#include <unistd.h>
pid_t fork(void);
返回值:有兩個,一個是子程序的ID,另一個為零。當返回值為0時,進入子程序,大於0進入父程序,返回-1時建立程序錯誤。
子程序與父程序的比較:
(1)子程序與父程序的程序ID不同。且父程序ID不同。
(2)記憶體佈局: 子程序是父程序的副本,其中子程序的資料空間,堆,棧是父程序的副本,但是共享真正文段。
(3)執行順序:子程序與父程序執行順序不確定,取決於核心所使用的排程演算法。
(4)檔案共享:父程序所有開啟的檔案描述符都會賦值到子程序,父程序與子程序每個相同的開啟檔案描述符共享一個檔案表項。即子程序操作檔案直接影響父程序對該檔案的操作,最明顯的是檔案偏移量收到影響。其圖如下所示:
例如:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { int x = 33,y = 44; pid_t pid; if( (pid = fork()) == -1 ) { perror("fork err\n"); exit(EXIT_FAILURE); } else if( pid == 0 ) { x ++; y ++; printf("child : x = %d y = %d\n",x,y); exit( EXIT_SUCCESS); } wait(); printf("parent: x = %d y = %d\n",x,y); return 0; }
fork的使用場景:
(1)父程序希望複製自己,使得父程序和子程序同時執行不同的程式碼段,即在網路服務中是最常見的,父程序等待客戶端的服務請求,當請求到來時,父程序fork一個子程序來處理子程序的請求,這時父程序可以繼續等待下一個服務請求。
(2)使得子程序執行一個不同的程式,需要fork一個子程序,在此子程序中執行exec執行新的程式。
(二)vfork函式
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
返回值為:與fork幾乎相同
與fork函式的不同:
(1)vfork一般是建立一個程序來執行exec函式,即執行一個新的程式。
(2)vfork中,子程序不將父程序的地址空間完全複製到子程序中,其子程序在父程序空間中執行。
(3)vfork保證子程序先執行,父程序等待子程序執行完成後在再執行。
(4)vfork子程序的資源與父程序是共用的,因此父程序中變數經過子程序的對其的改變則父程序也會改變。
例如:
//注意與上個fork的例子進行比較
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int x = 33,y = 44;
pid_t pid;
if( (pid = vfork() ) == -1 )
{
perror("fork err\n");
exit(EXIT_FAILURE);
}
else if( pid == 0 )
{
x++;
y++;
printf("child : x = %d y = %d\n",x,y);
exit(0);
}
wait();
printf("parent : x = %d y = %d\n",x,y);
return 0;
}
(三)wait函式
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
該函式的都是等待子程序的退出,如果成功返回退出程序的ID,如果出錯返回-1。
(1)呼叫wait函式
- 如果所有的子程序都在執行,則此時父程序阻塞。
- 如果一個子程序終止,等待的父程序立即獲取其終止狀態並立即返回。
- 如果沒有任何子程序則出錯返回。
(2)獲得終止狀態
- WIFEXITED(status) 判斷子程序是否正常終止。若為正常終止則是真,可以執行WEXITSTATUS(status)來獲得子程序傳送給exit或者_exit引數低8位。
- WIFSIGNALED(status) 判斷是否為異常終止,若為異常終止則為真,可以用WTERMSIG(status)獲得子程序終止的訊號編號。
- WIFSTOPPED(status) 判斷子程序暫停,暫停則是真。可以使用WSTOPSIG(status)獲得子程序暫停的訊號編號。
例如:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
void pr_exit(int status)
{
if( WIFEXITED(status) )//判斷是否正常終止
{
printf("normal termination,exit,status = %d\n",WEXITSTATUS(status));
}
else if( WIFSIGNALED(status) ) //判斷該子程序是否異常終止
{
printf("abnormal termination ,signal number = %d\n",WTERMSIG(status));
}
else if( WIFSTOPPED(status) )//子程序暫停
{
printf("child stopped ,signal number = %d\n",WSTOPSIG(status));
}
}
int main()
{
pid_t pid;
int status;
if( ( pid = fork()) == -1 )
{
perror("fork err\n");
exit(EXIT_FAILURE);
}
else if( pid == 0 )
{
exit(7);
}
if( wait(&status) != pid )
{
perror("wait err\n");
}
pr_exit(status);
if( ( pid = fork()) == -1 )
{
perror("fork err\n");
exit(EXIT_FAILURE);
}
else if( pid == 0 )
{
abort();
}
if( wait(&status) != pid )
{
perror("wait err\n");
}
pr_exit(status);
if( ( pid = fork()) == -1 )
{
perror("fork err\n");
exit(EXIT_FAILURE);
}
else if( pid == 0 )
{
status = status / 0;
}
if( wait(&status) != pid )
{
perror("wait err\n");
}
pr_exit(status);
return 0;
}
注意: 如果有多個子程序退出,而需要等待特定的子程序退出時,可以使用wait的返回值與某個期望的ID比較。
(三)waitpid函式
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int
options);
- pid == -1 等待任一個程序,此情況和wait等效。
- pid > 0 等待程序ID與pid相同的程序
- pid == 0 等待組ID等於呼叫程序組ID的任一個子程序。
- pid < -1 等待組ID等於pid絕對值的任一個子程序
- options:可以進一步控制waitpid的操作,一般為0。
該函式的都是等待特定子程序的退出,如果成功返回退出程序的ID,如果出錯返回-1。
(1)呼叫waitpid函式
- 等待特定的子程序終止。
- 如果所有的子程序都在執行,則此時父程序阻塞。但是有一個選項可以使得不阻塞
(2)獲得終止狀態與wait相同
(四)exec函式
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg,
..., char * const envp[]);
int execlp(const char *file, const char *arg,
...);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
(1)當程序呼叫exec函式時,exec並不建立新的程序,前後ID不改變,該程序執行的程式被完全替代為exec新程式,新的程式從main函式開始執行,exec只是用磁碟上的一個新的程式替換了當前程序的證正文段,資料段,堆段和棧段。
(2)函式區別:
- 字母p代表這些函式取filename作為引數。並且用PATH環境變數尋找可執行檔案。
- 字母l表示函式取一個引數表,與字V互斥。
- v表示該函式取argv[]向量。
- 字母e表示函式取envp[]陣列,而不適用當前的環境。
例如:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char * env_init[] = {"USER=unkonwn","PATH=/tmp",NULL};
int main()
{
pid_t pid;
if( (pid = fork()) == -1 )
{
perror("fork err\n");
exit(EXIT_FAILURE);
}
else if( pid == 0 )
{
if( (execle("/home/kaye/APUE/unit8/show","arg1","arg2","arg3",(char*)0,env_init)) < 0 )
{
perror("execle err\n");
exit(1);
}
}
if( waitpid( pid,NULL,0) != pid )
{
perror("waitpid fail\n");
exit(3);
}
if( (pid = fork()) == -1 )
{
perror("fork err\n");
exit(2);
}
else if( pid == 0 )
{
if( execlp("show","arg4","arg5","arg6",(char *)0) < 0 )
{//必須把該可執行檔案的路徑加入到環境變數中,這樣才能找到該可執行檔案
perror("execlp err\n");
exit(3);
}
}
return 0;
}
(五)典型的程序
(1)孤兒程序
產生原因: 父程序終止,子程序會被Init程序收養,此時的子程序就是孤兒程序。
產生過程: 程序終止時,核心逐個檢查所有活動程序,以判斷他是否是正要終止程序的子程序,如果是,則該程序的父程序,ID就更改為1,這樣就保證了每個程序的都有一個父程序。
(2)僵死程序
產生原因:一個程序已經終止,但是其父程序,尚未對其進行善後處理(獲取終止子程序的有關資訊,釋放他仍佔用的資源)的程序被稱為僵死程序。父程序沒有等待取得子程序的終止狀態。
避免方法: 核心為每個終止子程序儲存了一定量的資訊,所以當終止程序的父程序呼叫wait或waitpid時,可以得到這些資訊,這些資訊包括程序ID,程序的終止狀態,以及程序使用的CPU時間總量。
注意:如果一個孤兒程序終止時會成為僵死程序麼?:其實是不會的,原因為init程序中的子程序終止時,init會呼叫一個wait函式取得其終止狀態,這樣可以防止產生僵死程序。
例如:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid ;
if( ( pid = fork() ) == -1 )
{
perror("fork err\n");
exit(EXIT_FAILURE);
}
else if( pid == 0 )
{
printf("aaaa\n");
exit(1);
}
else
{
system("ps");//此時僵死程序
wait();
system("ps");//經過wait後,沒有僵死程序
sleep (2);
}
return 0;
}