多程式函式系列fork(), wait(), exec()系列,system(), posix_spawn()例項詳解

readyao發表於2015-12-15

fork():

fork()用來建立一個子程式,該子程式是執行該函式的父程式的一個複製的映像;

#include <unistd.h>
pid_t fork(void);

注意:fork()函式有一個特點是一次呼叫返回兩個值;
如果返回值為0,則是子程式;如果返回值大於0,則是父程式(此時返回值就是子程式的PID);(如果返回值為-1,則建立子程式失敗)
問題就在於為什麼這個fork()函式會返回兩個不同的值呢???
當執行了該函式的時候已經建立了一個子程式的程式空間,fork()會在父程式空間返回一個值(子程式PID),同樣也會在子程式空間中返回一個值(0);

wait():

wait函式是在父程式中使用,用來獲取子程式的狀態;
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

wait系統呼叫會使父程式暫停執行,直到它的等待的子程式結束為止。也就是說wait是阻塞的。
wait可以返回兩個資訊,直接返回子程式的PID,還有status(注意這個值不是在子程式中呼叫exit函式中的退出碼,下面有專門的巨集使用該status),子程式的退出狀態資訊存放在wait的引數status指向的記憶體中。
WIFEXITED(status)//如果子程式正常退出,那麼返回1;見例項輸出結果;
WEXITSTATUS(status)//返回子程式的退出碼;如果退出碼的值很大,那麼它只會返回退出碼的低八位
 WIFEXITED(status)
              returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning from main().
 WEXITSTATUS(status)
        returns  the  exit status of the child.  This consists of the least significant 8 bits of the status argument that the child specified in a
        call to exit(3) or _exit(2) or as the argument for a return statement in main().  This macro should be employed only if WIFEXITED  returned
        true.
 WIFSIGNALED(status)
        returns true if the child process was terminated by a signal.
 WTERMSIG(status)
        returns  the  number  of the signal that caused the child process to terminate.  This macro should be employed only if WIFSIGNALED returned
        true.
 WCOREDUMP(status)
        returns true if the child produced a core dump.  This macro should be employed only if WIFSIGNALED returned true.  This macro is not speci‐
        fied  in POSIX.1-2001 and is not available on some UNIX implementations (e.g., AIX, SunOS).  Only use this enclosed in #ifdef WCOREDUMP ...
        #endif.
 WIFSTOPPED(status)
        returns true if the child process was stopped by delivery of a signal; this is possible only if the call was done using WUNTRACED  or  when
        the child is being traced (see ptrace(2)).
 WSTOPSIG(status)
        returns the number of the signal which caused the child to stop.  This macro should be employed only if WIFSTOPPED returned true.
 WIFCONTINUED(status)
        (since Linux 2.6.10) returns true if the child process was resumed by delivery of SIGCONT.

waitpid():

waitpid用來等待某個特定程式的結束,可以指定等待子程式的PID;

引數options:允許改變waitpid的行為,最有用的一個選項是WNOHANG,它的作用是防止waitpid把呼叫者的執行掛起,也就是說父程式不會暫停執行;也就是說waitpid此時是非阻塞的:如果pid指定的目標子程式還沒有結束或意外終止,則waitpid立即返回0;如果目標子程式確實正常退出了,在返回該子程式的pid。waitpid呼叫失敗時返回-1並設定errno。

如果waitpid函式中的pid為-1,那麼它就和wait函式一樣,即等待任意一個子程式結束。

引數的status的含義是一樣的。


例項1:fork(), wait()例項

/*************************************************************************
	> File Name: simple_fork.cpp
	> Author: 
	> Mail: 
	> Created Time: 2015年12月15日 星期二 14時52分24秒
 ************************************************************************/

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;


int main(int argc, char *argv[])
{
    pid_t pid;

    cout << "This is main process, PID is " << getpid() << endl;
    
    pid = fork();
    if (pid < 0){
        cout << "fork error..." << endl;
        exit(-1);
    }
    else if (pid == 0){//This is the child process
       cout << "This is child process, PID is " << getpid() << endl;
       sleep(3);//子程式休眠3秒,這樣就可以看到wait函式阻塞了父程式,因為三秒之後,wait語句下面的語句才開始執行
       exit(11);//將子程式的退出碼設定為11
    }
    else{//This is the main process
        cout << "This is main process waiting for the exit of child process." << endl;
        int child_status;
        pid = wait(&child_status);
        cout << "This is main process. The child status is " << child_status << ", and child pid is " << pid << ", WIFEXITED(child_status) is " << WIFEXITED(child_status) << ", WEXITSTATUS(child_status) is " << WEXITSTATUS(child_status) << endl;
    }

    exit(0);
}



例項2:fork(), waitpid()例項,建立兩個子程式,只是等待第二個子程式,並且設定為非阻塞

/*************************************************************************
	> File Name: simple_fork.cpp
	> Author: 
	> Mail: 
	> Created Time: 2015年12月15日 星期二 14時52分24秒
 ************************************************************************/

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;


int main(int argc, char *argv[])
{
    pid_t pid;
    int i = 0;
    cout << "This is main process, PID is " << getpid() << endl;
   
    for (i = 0; i < 2; ++i){
        
	    pid = fork();
	    if (pid < 0){
	        cout << "fork error..." << endl;
	        exit(-1);
	    }
	    else if (pid == 0){//This is the child process
	       cout << "This is child process, PID is " << getpid() << endl;
	       exit(11);//將子程式的退出碼設定為11
	    }
	    else{//This is the main process
	        cout << "This is main process waiting for the exit of child process." << endl;
	    }

    }
    int child_status;
    pid_t child_pid2;
    sleep(1);//一定要等待,因為waitpid設為了無阻塞的,如果不等待,當執行完waitpid下面的語句的時候子程式2可能還沒有退出,那麼就得不到它的退出碼了
    child_pid2 = waitpid(pid, &child_status, WNOHANG);
    cout << "This is main process. The second child status is " << child_status << ", and child pid is " << child_pid2 << ", WIFEXITED(child_status) is " << WIFEXITED(child_status) << ", WEXITSTATUS(child_status) is " << WEXITSTATUS(child_status) << endl;
		
    exit(0);
}


父程式用SIGCHLD來處理子程式

我們知道在事件已經發生的情況下執行非阻塞的呼叫才能提高程式的效率。對於waitpid函式而言,我們最好在某個子程式退出之後再呼叫它。那麼父程式怎麼知道子程式退出了呢?這個可以靠訊號SIGCHLD來解決。當一個程式結束時,它將給其父程式傳送一個SIGCHLD訊號。我們可以在父程式中捕獲SIGCHLD訊號,並在訊號處理函式中呼叫非阻塞的waitpid以徹底結束一個子程式。

實現程式碼如下:
/*************************************************************************
	> File Name: sigchld.cpp
	> Author: 
	> Mail: 
	> Created Time: 2016年03月02日 星期三 21時29分36秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>

void sigchld_handler(int signum)
{
    int status;
    pid_t pid;

    if((pid = waitpid(-1, &status, WNOHANG)) < 0){
        printf("waitpid error\n");
        return;
    }
    printf("The signal is %d, child:%d exit status is %d\n", signum, pid, WEXITSTATUS(status));
}

//不可被訊號終端的睡眠函式
void unbreak_sleep(int sec)
{
    while(sec > 0){
        sec = sleep(sec);
    }
}

int main()
{
    signal(SIGCHLD, sigchld_handler);
    
    pid_t pid = fork();
    if(pid < 0){
        printf("fork error\n");
        return -1;
    }
    else if(pid == 0){//子程式
        printf("子程式:%d....等待3秒之後退出,退出碼是100\n", getpid());
        sleep(3);
        printf("子程式退出\n");
        exit(100);
    }
    else if(pid > 0){//父程式
        printf("父程式:%d,建立的子程式的pid = %d, 父程式等待7秒\n", getpid(), pid);        
        //sleep(7);
        unbreak_sleep(7);
    }

    printf("父程式退出\n");
    exit(0);
}
輸出:
父程式:17365,建立的子程式的pid = 17366, 父程式等待7秒
子程式:17366....等待3秒之後退出,退出碼是100
子程式退出
The signal is 17, child:17366 exit status is 100
父程式退出
值得注意的是sleep是可中斷睡眠,它會被訊號打斷。比如在父程式中呼叫sleep(7)等待的時候,當父程式執行完訊號處理函式之後會直接退出。但是當在父程式中呼叫的是unbreak_sleep(7)的情況下,執行完訊號處理函式之後會接著等待直到等待了7秒之後才退出。
關於sleep等待的解釋參考博文:linux中sleep詳解例項

exec系列函式:

當我們呼叫fork()建立了一個程式之後,通常將子程式替換成新的程式映象,這可以用exec系列的函式來進行。當然,exec系列的函式也可以將當前程式替換掉。

#include <unistd.h>
extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
總結:函式名字中l(函式中的字母l不是數字1)代表可變引數列表,函式名字中p代表在path環境變數中搜尋file檔案。envp代表環境變數


例項3:fork()和exec()函式系列

/*************************************************************************
	> File Name:fork_exec.cpp 
	> Author: 
	> Mail: 
	> Created Time: 2015年12月15日 星期二 14時52分24秒
 ************************************************************************/

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

int main(int argc, char *argv[])
{
    pid_t pid;
    char *arg[] = {"ls", "-l", NULL}; 

    cout << "This is main process, PID is " << getpid() << endl;
    pid = fork();
    if (pid < 0){
        cout << "fork error..." << endl;
        exit(-1);
    }
    else if (pid == 0){//This is the child process
       cout << "This is child process, PID is " << getpid() << endl;
       //execl("/bin/ls", "ls", "-l", NULL); 
       //execlp("ls", "ls", "-l", NULL);
       //execle("/bin/ls", "ls", "-l", NULL, NULL);
       //execv("/bin/ls", arg);
       //execvp("ls", arg);
       execve("/bin/ls", arg, NULL);//上面的六個函式的執行結果都是一樣的
       exit(11);//將子程式的退出碼設定為11
    }
    else{//This is the main process
        cout << "This is main process waiting for the exit of child process." << endl;
        int child_status;
        pid = wait(&child_status);
        cout << "This is main process. The child status is " << child_status << ", and child pid is " << pid << ", WIFEXITED(child_status) is " << WIFEXITED(child_status) << ", WEXITSTATUS(child_status) is " << WEXITSTATUS(child_status) << endl;
    }

    exit(0);
}



system函式:

system執行引數中的可執行檔案;也是執行起來另外一個程式;

呼叫該函式會建立一個shell來執行系統命令或可執行檔案;父程式被掛起,直到子程式結束且system()呼叫返回;

#include <stdlib.h>
int system(const char *command


例項4:system()

/*************************************************************************
	> File Name: system.cpp
	> Author: 
	> Mail: 
	> Created Time: 2015年12月15日 星期二 16時46分16秒
 ************************************************************************/

#include <iostream>
#include <cstdlib>

using namespace std;

int main(int argc, char **argv)
{
    int status;
    cout << "This is main process..." << endl;
    status = system("ls -l");
    //status = system("ls");
    if(status < 0){
        cout << "system is error." << endl;
        exit(-1);
    }

    cout << "WEXITSTATUS(status) is " << WEXITSTATUS(status) << endl;
    exit(0);
}



posix_spawn():

例項5: posix_spawn()

見之前寫的部落格:http://blog.csdn.net/linux_ever/article/details/50295105


最後總結一下非同步程式和同步程式

非同步程式和同步程式
非同步程式:各個程式可以同時執行,也可以不同時執行;
同步程式:各個程式不可以同時執行;
fork(), fork()-exec(), posix_spawn()建立的程式都是非同步程式;
system()建立的程式是同步程式;


相關文章