文章目录
- 1.进程等待必要性
- 1.1什么是进程等待?**
- 1.2为什么需要进程等待?
 
- 2.进程等待的方法
- 2.1wait方法
- 2.2waitpid方法
- 2.2.1获取子进程status
- 2.2.2options选项,父进程等待的三种方式
 
 
1.进程等待必要性
1.1什么是进程等待?**
通过系统调用
wait/waitpid,来进行对子进程状态监测与回收的功能!
1.2为什么需要进程等待?
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define N 10
void RunChild()
{
    int cnt = 5;
    while (cnt)
    {
        printf("I am Child Process, pid: %d, ppid:%d\n", getpid(), getppid());
        sleep(1);
        cnt--;
    }
}
int main()
{
    //wait / waitpid
    for (int i = 0; i < N; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            RunChild();
            exit(i);
            printf("create child process: %d success\n", id); // 这句话只有父进程才会执行
        }
    }
    sleep(10);
    return 0;
}
代码运行结果如下:
 
 
 ①僵尸进程无法被信号杀死,需要进程等待杀掉它,解决内存泄漏问题;②创建子进程,父进程(用户)需要知道子进程任务的完成的情况,可以通过进程等待子进程任务的完成的情况!
2.进程等待的方法
父进程通过调用
wait/waitpid,对子进程资源的回收!

2.1wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
作用:
可以回收任意退出的子进程
eg1:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define N 10
void RunChild()
{
    int cnt = 5;
    while (cnt)
    {
        printf("I am Child Process, pid: %d, ppid:%d\n", getpid(), getppid());
        sleep(1);
        cnt--;
    }
}
int main()
{
    for (int i = 0; i < N; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            RunChild();
            exit(i);
            printf("create child process: %d success\n", id); // 这句话只有父进程才会执行
        }
    }
    sleep(10);
    // 等待
    for(int i = 0; i < N; i++)
    {
        // wait当任意一个子进程退出的时候,wait回收子进程
        pid_t id = wait(NULL);
        if(id > 0)
        {
            printf("wait pid:%d success\n", id);
        }
    }
    return 0;
}
代码运行结果如下:
 
2.2waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
eg2:
 //pid_t id = wait(NULL);将上面的eg1的代码语句替换成以下一句,可以达到相同的效果
 pid_t id=waitpid(-1,NULL,0);
代码运行结果如下:
 
2.2.1获取子进程status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。- 如果传递
NULL,表示不关心子进程的退出状态信息。- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特
位):
eg3:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{ 
    pid_t id=fork();
    if(id < 0)
    {
      perror("fork");
      return 1;
    }
    else if(id == 0)
    {
      int cnt=3;
      while(cnt)
      {
        printf("I am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
        cnt--;
        sleep(1);
      }
      exit(12);
    }
    else{
      int status = 0;
      pid_t ret = waitpid(id,&status,0);
      if(ret==id)
      {
        printf("wait success, ret:%d, exit sig:%d, exit code:%d\n",ret,status&0x7F,(status>>8)&0xFF);
      }
    }
    return 0;
}
代码运行结果如下:
 
 使用信号杀掉子进程的指令:
kill -l -pid
//其中-l为信号选项,可以选用任意异常信号终止进程

 eg4:
//使用指令
kill -9 30773
代码运行结果如下:
 
 eg5:
//在子进程允许的代码中添加以下,让子进程异常退出
int* p=NULL;
*p=100;
代码运行结果如下:
 
 总结:
①进程异常会被操作系统识别成信号杀掉;进程没有出现异常,信号码为0,这时我们只需要观察进程退出即可,子进程结束后代码和数据立即释放掉,PCB资源并没有被立即释放,操作系统需要从子进程PCB中获取信号码和退出码数据封装到status中,让父进程通过wait/waitpid
在这里插入代码片系统接口函数获取!
②可以使用以下两个宏,一个判断进程是否正常终止,一个获取进程的退出码
WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
2.2.2options选项,父进程等待的三种方式
①options选项若是为0,父进程阻塞等待子进程结束,等待方式:阻塞等待
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
    pid_t id=fork();
    if(id < 0)
    {
      perror("fork");
      return 1;
    }
    else if(id == 0)
    {
      int cnt=3;
      while(cnt)
      {
        printf("I am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
        //printf("I am child, pid:%d, ppid:%d",getpid(),getppid());
        cnt--;
        sleep(1);
      }
      sleep(20);
      exit(12);
    }
    else if(id>0){
      int status = 0;
      printf("I am parent, pid: %d 子进程,你知道我在等你吗?\n",getpid());
      pid_t ret = waitpid(id,&status,0);
      
      if(ret>0)
      {
        if(WIFEXITED(status))
        {
          printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));
        }
        else{
          printf("进程出异常了\n");
        }
      }
    }
    return 0;
}
代码运行结果如下:
 
②optiont选项若是为WNOHANG ,若pid指定的子进程没有结束,则waitpid()函数返回0,等待方式:进行非阻塞等待,配合循环使用(非阻塞轮询)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{  
    pid_t id=fork();
    if(id < 0)
    {
      perror("fork");
      return 1;
    }
    else if(id == 0)
    {
      int cnt=3;
      while(cnt)
      {
        //int* p=NULL;
        //*p=100;
        printf("I am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
        //printf("I am child, pid:%d, ppid:%d",getpid(),getppid());
        cnt--;
        sleep(1);
      }
      //sleep(20);
      exit(12);
    }
    else if(id>0)
    {
      int status = 0;
      printf("I am parent, pid: %d 子进程,你知道我在等你呀!\n",getpid());
      while(1)//轮询
      {
          pid_t ret = waitpid(id,&status,WNOHANG);
          if(ret>0)
          {
            if(WIFEXITED(status))
            {
              printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));
            }
            else{
              printf("进程出异常了\n");
            }
            break;
          }
          else if(ret<0)
          {
            printf("wait failed\n");
            break;
          }
          else if(ret == 0)
          {
              printf("你好了没?子进程还没有退出,我在等等...\n");
              sleep(1);
         }
      }
    }
    return 0;
}
代码运行结果如下:
 
③optiont选项若是为WNOHANG ,若pid指定的子进程没有结束,则waitpid()函数返回0,等待方式:非阻塞轮询+父进程做自己的事情
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#define TASK_NUM 10
typedef void(*task_t)();//函数指针
task_t tasks[TASK_NUM];//函数指针数组
void task1()
{
  printf("这是一个执行打印日志的任务,pid:%d\n",getpid());
}
void task2()
{
  printf("这是一个执行检测网络健康状态的一个任务,pid:%d\n",getpid());
}
void task3()
{
  printf("这是一个进行绘制图形界面的任务,pid:%d\n",getpid());
}
int AddTask(task_t t);//函数声明
//任务的管理代码
void InitTask()
{
  for(int i = 0; i < TASK_NUM; i++) tasks[i]=NULL;
  AddTask(task1);
  AddTask(task2);
  AddTask(task3);
}
int AddTask(task_t t)
{
  int pos=0;
  for(; pos < TASK_NUM; pos++)
  {
    if(!tasks[pos])break;//判断函数指针数组是否还有位置
  }
  if(pos == TASK_NUM)return -1;
  tasks[pos]=t;
  return 0;
}
void ExecuteTask()
{
  //执行任务
  for(int i = 0; i < TASK_NUM; i++)
  {
    if(!tasks[i])continue;
    tasks[i]();
  }
}
int main()
{
    pid_t id=fork();
    if(id < 0)
    {
      perror("fork");
      return 1;
    }
    else if(id == 0)
    {
      int cnt=3;
      while(cnt)
      {
        printf("I am child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
        cnt--;
        sleep(1);
      }
      exit(12);
    }
    else if(id>0){
      int status = 0;
      InitTask();//初始化和加载
      printf("I am parent, pid: %d 子进程,你知道我在等你呀!\n",getpid());
      while(1)//轮询
      {
          pid_t ret = waitpid(id,&status,WNOHANG);
          if(ret>0)
          {
            if(WIFEXITED(status))
            {
              printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));
            }
            else{
              printf("进程出异常了\n");
            }
            break;
          }
          else if(ret<0)
          {
            printf("wait failed\n");
            break;
          }
          else if(ret == 0)
          {
              ExecuteTask();//父进程做自己的事情
              usleep(500000);
          }
      }
    }
    return 0;
}
代码运行结果如下:
 
 问题1: 父进程以非阻塞轮询+做执行自己的任务的方式等待子进程时,究竟是等待子进程重要,还是父进程执行其他任务重要?
答:当然是等待子进程回收资源重要,只不过子进程执行任务时,父进程会闲置造成资源浪费,为充分利用父进程资源,便设置一些任务让父进程去执行!
问题2: 父进程以非阻塞轮询+做执行自己的任务的方式等待子进程时,如果父进程执行其他任务过重,造成没有在子进程结束的时候立即回收,会不会对有什么影响呢?
答:父进程在等待子进程时,执行的任务都是轻量化的工作,如网络写入、记录日志、错误处理等工作,不会影响子进程的回收;如果父进程执行任务过多,没能在子进程结束的时候立即回收,当父进程执行其他任务结束时便会对子进程资源进行回收,不会有什么影响!这种延迟回收的方式,在父进程创建多个子进程时,等待所有子进程全部结束时再一起回收,反而会更好!




















