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

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

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
2月前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
40 0
|
4月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
684 2
|
4月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
72 2
|
1天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
26天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
98 4
linux进程管理万字详解!!!
|
17天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
58 8
|
14天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
25天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
66 4
|
26天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
28天前
|
消息中间件 存储 Linux