多进程的实现和一些理论基础

创建子进程

众所周知,要实现多进程,就是用fork函数来创建子进程。下面就从fork函数说起。
先从官网里找到fork函数资料,可以参考:fork()函数官方链接,里面讲得非常详细了,这里提取一些要点出来。

头文件
#include <sys/types.h>
#include <unistd.h>

函数原型
pid_t fork(void);

参数和返回值

  1. 当函数执行成功
    父进程中,返回值是子进程的pid;
    子进程中,返回值是0。
  2. 当函数执行失败
    父进程中,返回值是-1,可以通过errno获得错误信息;
    没有创建出子进程。

fork继承了什么?又没继承什么?

继承:uid,gid,euid,egid,sgid,进程组id,会话id,当前工作目录和根目录文件描述符fd信号屏蔽和处理存储映射资源限制 等。
没继承:pid,进程时间,文件锁,未处理信号 等。

僵尸进程

什么是僵尸进程?

A child that terminates, but has not been waited for becomes a “zombie”. The kernel maintains a minimal set of information about the zombie process (PID, termination status, resource usage information) in order to allow the parent to later perform a wait to obtain information about the child. As long as a zombie is not removed from the system via a wait, it will consume a slot in the kernel process table, and if this table fills, it will not be possible to create further processes.

上面是官方解释如果一个子进程结束,但是它的父进程没有调用wait,那么这个子进程就会变成僵尸进程

  • 如果子进程先退出,父进程还未退出,此时子进程必须等到父进程捕获到子进程的退出状态后,子进程才能真正结束,否则,此时的子进程就会变成僵尸进程。
  • 如果父进程先退出,子进程还未退出,此时子进程的父进程会变为init(pid=1)进程,init进程在子进程结束时候,会默认执行wait操作,所以该子进程一定不会变成将僵尸进程。(备注:任何一个进程都必须有父进程)

如何避免僵尸进程?

第一种方法:就是在父进程用wait或者waitpid来回收子进程资源,即可避免僵尸进程。
第二种方法:子进程退出会发送SIGCHLD信号给父进程,在父进程上,可以选择忽略或者使用信号处理函数接收处理它,就可以避免僵尸进程。

为何第二种方法这样可以?
官方链接在这,截取出来如下:
POSIX.1-2001 specifies that if the disposition of SIGCHLD is set to SIG_IGN or the SA_NOCLDWAIT flag is set for SIGCHLD, then children that terminate do not become zombies and a call to wait() or waitpid() will block until all children have terminated, and then fail with errno set to ECHILD.
更具体地,linux内核版本2.6以上是如此,因为linux2.6符合POSIX标准,但是linux2.4或者之前就不是这样了,这个扯得就多了,但是我们现在开发正常内核版本都在2.6以上,所以问题也不大,这里就不详细讨论,有兴趣自己看下官方

如下图,一般父进程在fork之前就先定义SIGCHLD的处理动作。
避免僵尸进程

wait和waitpid

wait和waitpid的作用是为了获得子进程状态改变的信息,特别地,更为重要地,是为了回收子进程资源,避免僵尸进程
子进程状态改变包含:
子进程终止(the child terminated)
子进程由于信号停止(the child was stopped by a signal)
子进程由于信号继续(the child was resumed by a signal)

当子进程结束时,父进程调用wait或者waitpid,会回收子进程的相关资源(pid,子进程结束状态status,资源使用信息)。如果没有回收,子进程就变成僵尸子进程会占用系统进程表的一个槽位,如果该表满了,就无法继续创建新的进程了。

wait函数表现:
1、当子进程还未结束,那么父进程调用wait会阻塞,直到子进程结束 或 信号处理函数中断。
2、当子进程已经结束,那么父进程调用wait会立刻返回。

头文件
#include <sys/types.h>
#include <sys/wait.h>

函数原型
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

参数和返回值
The value of pid can be:

  • < -1 :meaning wait for any child process whose process group ID is equal to the absolute value of pid.
  • -1 :meaning wait for any child process.
  • 0:meaning wait for any child process whose process group ID is equal to that of the calling process at the time of the call to waitpid().
  • >0:meaning wait for the child whose process ID is equal to the value of pid.

The value of options is an OR of zero or more of the following constants:

  • WNOHANG:return immediately if no child has exited.
  • WUNTRACED:also return if a child has stopped (but not traced via ptrace(2)).
  • WCONTINUED (since Linux 2.6.10):also return if a stopped child has been resumed by delivery of SIGCONT.

备注:本来是只有子进程终止时候,waitpid才返回,上面also的意思,就是现在当子进程stop(WUNTRACED)或者resume(WCONTINUED)时,waitpid也会返回,而不是一直阻塞在那里。

wstatus 是一个入参返回值,可以指定为NULL(如果你不关心返回状态信息),也可以指定为一个整形数的地址,然后这个值可以用一些宏来读取,如:WIFEXITED(wstatus),WEXITSTATUS(wstatus)等等,具体参考:点我

返回值
wait函数,当执行成功, 返回子进程的pid;当执行失败,返回-1。
waitpid函数,当执行成功,返回改变进程状态的子进程pid,但是当指定了WHOHANG,子进程还没退出,则返回0。当执行失败,返回-1。

错误值
在这里插入图片描述
特别地,当父/子进程指定了SIGCHLD信号处理动作为SIG_IGN,那么父进程上调用wait()或者waitpid()函数会在所有子进程退出后返回错误,设置errno为ECHILD。

wait和waitpid区别
waitpid比wait功能强大很多,wait只是waitpid的一个特例,如下:
wait(&wstatus); 等价于 waitpid(-1, &wstatus, 0);
看上面waitpid的pid参数就知道,waitpid不但可以回收子进程(pid=-1)资源,也可以回收指定子进程pid的进程资源(pid>0,比如fork很多次,就有很多子进程,这时候只回收其中一个子进程的情况)。

示例一
官方示例
The following program demonstrates the use of fork(2) and waitpid(). The program creates a child process. If no command-line argument is supplied to the program, then the child suspends its execution using pause(2), to allow the user to send signals to the child. Otherwise, if a command-line argument is supplied, then the child exits immediately, using the integer supplied on the command line as the exit status. The parent process executes a loop that monitors the child using waitpid(), and uses the W*() macros described above to analyze the wait status value.

// gcc -g -O0 test_fork.c -o test_fork

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

int
main(int argc, char *argv[])
{
   pid_t cpid, w;
   int wstatus;

   cpid = fork();
   if (cpid == -1) {
       perror("fork");
       exit(EXIT_FAILURE);
   }

   if (cpid == 0) {            /* Code executed by child */
       printf("Child PID is %jd\n", (intmax_t) getpid());
       if (argc == 1)
           pause();                    /* Wait for signals */
       _exit(atoi(argv[1]));

   } else {                    /* Code executed by parent */
       do {
           w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
           if (w == -1) {
               perror("waitpid");
               exit(EXIT_FAILURE);
           }

           if (WIFEXITED(wstatus)) {
               printf("exited, status=%d\n", WEXITSTATUS(wstatus));
           } else if (WIFSIGNALED(wstatus)) {
               printf("killed by signal %d\n", WTERMSIG(wstatus));
           } else if (WIFSTOPPED(wstatus)) {
               printf("stopped by signal %d\n", WSTOPSIG(wstatus));
           } else if (WIFCONTINUED(wstatus)) {
               printf("continued\n");
           }
       } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
       exit(EXIT_SUCCESS);
   }
}

在这里插入图片描述
在这里插入图片描述
示例二
使用WNOHANG

// gcc -g -O0 test_fork.c -o test_fork

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

int
main(int argc, char *argv[])
{
   pid_t cpid, w;
   int wstatus;

   cpid = fork();
   if (cpid == -1) {
       perror("fork");
       exit(EXIT_FAILURE);
   }

   if (cpid == 0) {            /* Code executed by child */
       printf("Child PID is %jd\n", (intmax_t) getpid());
       if (argc == 1)
           pause();                    /* Wait for signals */
       _exit(atoi(argv[1]));
   } else {                    /* Code executed by parent */
        do {
            w = waitpid(cpid, &wstatus, WNOHANG);
            if (w == -1) {
                perror("waitpid");
                exit(EXIT_FAILURE);
            }
            
        } while (w == 0);

        printf("break waitpid, parent process exit..\n");
        exit(EXIT_SUCCESS);
   }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值