进程的创建(二)

一 fork函数

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

  返回值:成功:父进程:返回子进程的PID,子进程:返回0;失败:返回-1。
  fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程(子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。linux将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。),也就是这两个进程做完全相同的事。

二 exec系列函数

1 函数接口

  fork后的子进程中使用exec函数族,可以装入和运行其它程序(子进程替换原有进程,和父进程做不同的事)。
  fork创建一个新的进程就产生了一个新的PID,exec启动一个新程序,替换原有的进程,因此这个新的被 exec 执行的进程的PID不会改变(和调用exec的进程的PID一样)。

#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[]);
//execve是系统调用
int execve(const char *file,char *const argv[],char *const envp[]);

  参数:path参数表示你要启动程序的名称包括路径名,arg参数表示启动程序所带的参数,一般第一个参数为要执行命令名,不是带路径且arg必须以NULL结束。
  返回值:成功返回0,失败返回-1。
  exec系列函数底层都是通过execve系统调用实现:

#include <unistd.h>
int execve(const char *filename, char *const argv[],char *const envp[]);
/*
DESCRIPTION:
       execve() executes the program pointed to by filename.  filename must be
       either a binary executable, or a script starting with  a  line  of  the form
*/

2 带“p”的函数

  对于带字母p的函数:如果参数中包含/,则将其视为路径名;否则视为不带路径的程序名,在PATH环境变量的目录列表中搜索这个程序。

3 不带“p”的函数

  不带字母p(表示path)的exec函数第一个参数必须是程序的相对路径或绝对路径,例如"/bin/ls"或"./a.out",而不能是"ls"或"a.out"。

4 带“v”的函数

  对于带有字母v(表示vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,就像main函数的argv参数或者环境变量表一样。

5 带“e”的函数

  e(表示environment)结尾的exec函数,可以把一份新的环境变量表传给它,其他exec函数仍使用当前的环境变量表执行新程序。

6 带“l”的函数

  l的exec函数:execl,execlp,execle,带有字母l(表示list)的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有…,…中的最后一个可变参数应该是NULL,起sentinel的作用。

#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);
}

三 wait函数

1 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.
#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 waitpid

  waitpid用来等待某个特定进程的结束,可以指定等待子进程的PID。
  参数options:允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者挂起,也就是说父进程不会暂停执行,waitpid此时是非阻塞的;如果pid指定的目标子进程还没有结束或意外终止,则waitpid立即返回0;如果目标子进程确实正常退出了,在返回该子进程的pid。waitpid调用失败时返回-1并设置errno。如果waitpid函数中的pid为-1,那么它就和wait函数一样,即等待任意一个子进程结束。
  参数的status的含义是一样的。

#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);
}

四 子进程传递信号

  我们知道在事件已经发生的情况下执行非阻塞的调用才能提高程序的效率。对于waitpid函数而言,我们最好在某个子进程退出之后再调用它。那么父进程怎么知道子进程退出了呢?这个可以靠信号SIGCHLD来解决。当一个进程结束时,它将给其父进程发送一个SIGCHLD信号。我们可以在父进程中捕获SIGCHLD信号,并在信号处理函数中调用非阻塞的waitpid以彻底结束一个子进程。

#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);
}

五 system函数

  system执行参数中的可执行文件;也是运行起来另外一个进程;调用该函数会创建一个shell来执行系统命令或可执行文件;父进程被挂起,直到子进程结束且system()调用返回;

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

六 posix_spawn()

  
  

#include <spawn.h>
int posix_spawn(pid_t *restrict pid, const char *restrict path,
        const posix_spawn_file_actions_t *file_actions,
        const posix_spawnattr_t *restrict attrp,
        char *const argv[restrict], char *const envp[restrict]);

  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值