Linux进程控制【进程创建终止和等待】

简介: Linux进程的创建、终止和等待,包含丰富系统接口介绍,详细讲解,干货满满!

Linux进程控制【进程创建终止和等待】

创建进程之后,还需要对其进行管理,本文就来讲讲程控制中的,进程创建、进程终止和进程等待

1. 进程创建

进程的创建需要用到fork函数

1.1 fork函数

fork 函数的作用是在当前进程下,创建一个子进程

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

fock后内核会做的操作

  • 分配新的内存块和内核数据结构(PCB)给子进程
  • 将父进程部分数据结构内容拷贝给子进程,同时还会继承父进程中的环境变量表
  • 将子进程添加到系统进程列表中
  • fork返回开始调度器调度

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了上限

fork 函数返回类型为pid_t,相当于typedef int,只是专门用于进程,它拥有两个返回值

  • 进程创建失败,返回-1
  • 进程创建成功,给子进程返回0,给父进程返回子进程的PID

进程具有独立性,fork创建子进程后,父子进程拥有各自的 PCB,二者独立运行

来试用一下

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

int main()
{
   
   
  pid_t id = fork(); //获取返回值
  assert(id != -1);  //创建失败的情况

  if(id == 0) //子进程
  {
   
   
    printf("我是子进程, 我的PID是: %d, PPID是: %d\n", getpid(), getppid());
  }
  else if(id > 0) //父进程
  {
   
   
    printf("我是父进程, 我的PID是: %d, PPID是: %d\n", getpid(), getppid());
  }

  return 0;
}

这里我们会发现后创建的进程反而先运行,这是因为fork创建进程后,先执行哪个进程是取决于调度器

1.2 写时拷贝

上文中已经讲到了写时拷贝机制,这里我就做些补充,不演示现象了

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本,不会对原数据造成影响,如图

补充

  • 写时拷贝本质就是一种按需申请资源的机制
  • 写时拷贝可以发生在栈区、堆区、只读的数据段等

2. 进程终止

进程退出的场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

2.1 进程退出码

使用指令echo $?,可以查看最近一次子进程运行的退出码

echo $?  //查看退出码

比如我们main函数最后一条常用的return 0中,0就是退出码,表示正常退出

main 函数退出,表示整个程序退出,程序中的函数退出,仅表示该函数运行结束

进程退出后,OS 会释放进程的内核数据结构 + 代码和数据

  • 退出码是给父进程看的,用于判断子进程运行状态
  • 子进程运行失败或异常终止,会出现终止信号,无退出码,运行成功,返回退出码,但可能出现结果错误的情况

2.2 进程退出方法

之前讲过进程的外部终止方法,ctrl + ckill -9 PID,进程的内部终止常用exit(-1)的方式来终止程序运行

进程常用退出方法

  • main函数中return返回
  • 调用exit()函数
  • 调用_exit函数
void exit(int status);
void _exit(int status);

我们通过同一段代码来看看exit()和_exit()的区别

#include <stdio.h>
#include <stdlib.h>  //exit()
#include <unistd.h>  //_exit()

int main()
{
   
   
  printf("Can you see me");
  //exit(-1);
  _exit(-1);
  return 0;
}

使用exit()的退出结果

使用_exit()退出的结果

我们发现使用exit()会打印语句,而使用_exit()不会

推荐使用exit(),来看看exit()函数和_exit()函数的区别

  • _exit()函数就是单纯的退出程序,exit()是对 _exit()封装实现的

  • exit()函数执行用户定义的清理函数

  • 关闭所有打开的流,所有缓存器均被写入

  • exit()在退出时,会先冲刷缓冲区,再调用_exit()

3. 进程等待

之前讲到过,子进程运行结束后,父进程没有等待并接收其退出码和退出状态,OS 无法释放进程的内核数据结构 + 代码和数据,就会导致僵尸进程,会造成内存泄漏等问题,进程等待就是用来解决这一问题的

3.1 等待的必要性

父进程可以通过函数等待子进程运行结束,来避免僵尸进程的出现

==进程等待的必要性==

  • 僵尸进程可能会造成内存泄漏
  • 回收子进程资源,获取子进程退出信息
  • 进程的退出状态是必要的,进程的执行结果是非必要的

3.2 进程等待方法

使用系统提供的wait()函数和waitpid()函数来进行进程等待,其中waitpid()比较常用

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

pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);

statusint*类型,指向的是一个int大小的空间,32个比特位,其中从右向左数,第7-15次低八位表示退出状态,第0-6低七位位表示终止信号,第7位这个比特位表示core dump标志位,高十六位不用管

wait()函数

  • 成功返回等待进程PID,失败则返回-1;参数不关心则可以设置NULL

wait()使用演示

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

int main()
{
   
   
  pid_t id = fork(); //获取返回值
  assert(id != -1);  //创建失败的情况

  if(id == 0) //子进程
  {
   
   
    printf("我是子进程, 我的PID是: %d, PPID是: %d\n", getpid(), getppid());
    exit(0); //子进程退出
  }

  wait(0); //等待子进程退出
  printf("我是父进程, 我的PID是: %d, PPID是: %d\n", getpid(), getppid());
  return 0;
}

waitpid()函数

返回值:

  • 当正常返回的时候waitpid返回收集到的子进程的进程ID
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在

参数:

  • pid:

    pid= -1,等待任一个子进程。与wait等效

    pid > 0,等待其进程IDpid相等的子进程

  • status:
    WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

    WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

  • options:

    WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID

waitpid()使用演示,获取进程的退出码和终止信号

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

int main()    
{
   
       
  pid_t id = fork();  //创建子进程    
  if(id == 0)  //子进程     
  {
   
       
    int count = 0;    
    while(1)    
    {
   
       
      if(count == 5)    
      {
   
       
        break;    
      }    
      printf("我是子进程,我已经运行了%d秒,PID:%d,PPPID:%d\n", count, getpid(), getppid());    
      count++;                                             
      sleep(1);    
    }    
    exit(12);  //子进程退出    
  }

  //父进程    
  int status = 0; //状态    
  pid_t f_id = waitpid(id, &status, 0);    
  printf("我是父进程,PID: %d,PPID:%d,f_id: %d,子进程退出码: %d,子进程终止信号: %d\n", getpid(), getppid(), f_id, (status >> 8) & 0xFF, status & 0x7F);    

  return 0;    
}

程序正常终止

程序异常终止

觉得(status >> 8) & 0xFF(status & 0x7F) 这两个位运算有些麻烦的话,系统还提供了两个宏来获取退出码

  • WIFEXITED 判断进程退出情况,宏为真,表示进程正常退出
  • WEXITSTATUS 相当于(status >> 8) & 0xFF`,直接获取退出码

举个例子

waitpid(id, &status, WNOHANG);

如果子进程没有退出,那么父进程在wait的时候,是在干什么呢?

  • 在子进程没有退出的时候,父进程只有一直在调用waitpid进程等待,这种等待就是阻塞等待

3.3 非阻塞等待

如果不想父进程在waitpid处卡住,而是让他去做别的事情,这个就叫非阻塞等待

父进程可以通过设置 options 参数,进程解除(阻塞)状态,让父进程变成等待轮询状态,不断获取子进程状态,如果子进程没退出,就可以处理别的任务

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

int main()    
{
   
       
  pid_t id = fork();    
  if(id == 0)    
  {
   
       
    //子进程    
    int count = 0;    
    while(1)    
    {
   
       
      if(count == 3)    
      {
   
       
        break;    
      }    
      printf("我是子进程,我已经运行了%d秒,PID:%d,PPPID:%d\n", count + 1, getpid(), getppid());     
      count++;    
      sleep(1);    
    }    
    exit(12);    
  }    
  //父进程    
  while(1)    
  {
   
       
    int status = 0;    
    pid_t f_id = waitpid(id, &status, WNOHANG);    
    if(f_id < 0)    
    {
   
       
      printf("err\n");    
      exit(-1);    
    }    
    else if(f_id == 0)                                                                                 
    {
   
       
      printf("子进程还没有退出,我先干点儿别的事儿\n");    
      sleep(1);    
      continue;    
    }    
    else    
    {
   
       
      printf("我是父进程,PID: %d,PPID: %f_id:%d,子进程退出码: %d,子进程终止信号: %d\n", getpid(), getppid(), f_id, (status >> 8) & 0xFF, status & 0x7F);    
      break;    
    }    

    return 0;    
  }    
}

当然也可以使用宏来获取退出码

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

int main()    
{
   
       
  pid_t id = fork();    
  if(id == 0)    
  {
   
       
    //子进程    
    int count = 0;    
    while(1)    
    {
   
       
      if(count == 3)    
      {
   
       
        break;    
      }    
      printf("我是子进程,我已经运行了%d秒,PID:%d,PPPID:%d\n", count + 1, getpid(), getppid());   
      count++;    
      sleep(1);    
    }    
    exit(12);    
  }    
  //父进程    
  while(1)    
  {
   
       
    int status = 0;    
    pid_t f_id = waitpid(id, &status, WNOHANG);    
    if(f_id < 0)    
    {
   
       
      printf("err\n");    
      exit(-1);    
    }    
    else if(f_id == 0)                                                                                 
    {
   
       
      printf("子进程还没有退出,我先干点儿别的事儿\n");    
      sleep(1);    
      continue;    
    }    
    else    
    {
   
       
       if(WIFEXITED(status)) //收到信号
       {
   
   
            printf("等待成功,退出码是: %d\n", WEXITSTATUS(status));
       }
       else
       {
   
   
             printf("等待成功,退出信号是: %d\n", status & 0x7F);
       }
      break;    
    }    

    return 0;    
  }    
}


Linux进程控制—进程的创建、终止和等待,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正

目录
相关文章
|
3天前
|
NoSQL Linux 程序员
【linux进程信号(一)】信号的概念以及产生信号的方式
【linux进程信号(一)】信号的概念以及产生信号的方式
|
3天前
|
Linux
【linux进程间通信(一)】匿名管道和命名管道
【linux进程间通信(一)】匿名管道和命名管道
|
3天前
|
Java Shell Linux
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
|
3天前
|
算法 Linux Shell
【linux进程(二)】如何创建子进程?--fork函数深度剖析
【linux进程(二)】如何创建子进程?--fork函数深度剖析
|
3天前
|
存储 Linux Shell
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
|
4天前
|
消息中间件 Unix Linux
Linux的学习之路:17、进程间通信(1)
Linux的学习之路:17、进程间通信(1)
20 1
|
4天前
|
存储 安全 Linux
Linux的学习之路:9、冯诺依曼与进程(1)
Linux的学习之路:9、冯诺依曼与进程(1)
18 0
|
9天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
|
11天前
|
监控 Linux
linux监控指定进程
请注意,以上步骤提供了一种基本的方式来监控指定进程。根据你的需求,你可以选择使用不同的工具和参数来获取更详细的进程信息。
14 0
|
12天前
|
消息中间件 监控 Linux
Linux进程和计划任务管理
通过这些命令和工具,你可以有效地管理Linux系统中的进程和计划任务,监控系统的运行状态并保持系统的稳定和可靠性。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
103 2