多进程函数系列fork(), wait(), exec()系列,system(), posix_spawn()实例详解

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()创建的进程是同步进程;


你可能感兴趣的:(System,wait,fork,exec系列,posix_spawn)