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

这篇具有很好参考价值的文章主要介绍了Linux进程控制【创建、终止、等待】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

✨个人主页: Yohifo
🎉所属专栏: Linux学习之旅
🎊每篇一句: 图片来源
🎃操作环境: CentOS 7.6 阿里云远程服务器

  • Good judgment comes from experience, and a lot of that comes from bad judgment.
    • 好的判断力来自经验,其中很多来自糟糕的判断力。

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生



🌇前言

进程 创建后,需要对其进行合理管理,光靠 OS 是无法满足我们的需求的,此时可以运用 进程 控制相关知识,对 进程 进行手动管理,如创建 进程、终止 进制、等待 进程 等,其中等待 进程 可以有效解决僵尸 进程 问题

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生

汽车的中控台,可以对汽车进行各种操作


🏙️正文

本文涉及的代码都是以 C语言 实现的

1、进程创建

在学习 进程控制 相关知识前,先要对回顾如何创建 进程,涉及一个重要的函数 fork

1.1、fork函数

#include <unistd.h>	//所需头文件
pid_t fork(void);	//fork 函数

fork 函数的作用是在当前 进程 下,创建一个 子进程子进程 创建后,会为其分配新的内存块和内核数据结构(PCB),将 父进程 中的数据结构内容拷贝给 子进程,同时还会继承 父进程 中的环境变量表

  • 进程具有独立性,即使是父子进程,也是两个完全不同的进程,拥有各自的 PCB
  • 假设 子进程 发生改写行为,会触发写时拷贝机制

fork 函数返回类型为 pid_t,相当于 typedef int,不过是专门用于进程的,同时它拥有两个返回值:

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

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
通过代码理解 进程 创建

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //进程等待相关函数头文件

int main()
{
  //创建两个子进程
  pid_t id1 = fork();
  if(id1 == 0)
  {
    //子进程创建成功,创建孙子进程
    pid_t id2 = fork();
    if(id2 == 0)
    {
      printf("我是孙子进程,PID:%d   PPID:%d\n", getpid(), getppid());
      exit(1); //孙子进程运行结束后,退出
    }

      wait(0);  //等待孙子进程运行结束
      printf("我是子进程,PID:%d   PPID:%d\n", getpid(), getppid());
      exit(1);  //子进程运行结束后,退出
  }

  wait(0);  //等待子进程运行结束
  printf("我是父进程,PID:%d   PPID:%d\n", getpid(), getppid());

  return 0; //父进程运行结束后,退出
}

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
观察结果不难发现,两个子进程已经成功创建,但最晚创建的进程,总是最先运行,这是因为 fork 创建进程后,先执行哪个进程取决于调度器

得到子进程后,此时可以在一个程序中同时执行两个进程!(父进程非阻塞的情况下)

注意:fork 可能创建进程失败

  • 系统中的进程过多时
  • 实际用户的进程数超过了限制

1.2、写时拷贝

在【进程地址空间】一文中,谈到了写时拷贝机制,实现原理就是通过 页表+MMU 机制,对不同的进程进行空间寻址,达到出现改写行为时,父子进程使用不同真实空间的效果

验证写时拷贝现象很简单,创建子进程后,使其对生命周期长的变量作出修改,再观察父子进程的结果即可

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //进程等待相关函数头文件

const char* ps = "This is an Apple";  //全局属性

int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    ps = "This is a Banana"; //改写
    printf("我是子进程,我认为:%s\n", ps);
    exit(0);  //子进程退出
  }

  wait(0);  //等待子进程退出
  printf("我是父进程,我认为:%s\n", ps);
  return 0;
}

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
不难发现,子进程对指针 ps 指向内容做出改变时,父进程并不受影响,这就是写时拷贝机制

  • 通过地址打印,发现父子进程中的 ps 地址一致,因为此时是虚拟地址
  • 在虚拟地址相同的情况下,真实地址是不同的,得益于 页表+MMU 机制寻址不同的空间

写时拷贝机制本质上是一种按需申请资源的策略

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
注意:

  • 写时拷贝不止可以发生在常规栈区、堆区,还能发生在只读的数据段和数据段
  • 写时拷贝后,生成的是副本,不会对原数据造成影响

2、进程终止

假设某个进程陷入了死循环状态,可以通过特定方法终止此程序,如在命令行中莫名其妙输入了一个指令,导致出现非正常情况,可以通过 ctrl + c 终止当前进程;对于自己写的程序,有多种终止方法,程序退出时,还会有一个退出码,供 父进程 接收

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生

2.1、退出码

echo $?

main 函数中的最后一条语句 return 0 表示当前程序的退出码,0 表示程序正常退出,可以通过指令 echo $? 查看最近一次子进程运行的 退出码

退出码是给父进程看的,可以判断子进程是否成功运行

子进程运行情况:

  • 运行失败或异常终止,此时出现终止信号,无退出码
  • 运行成功,返回退出码,可能出现结果错误的情况

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
进程退出后,OS 会释放对应的 内核数据结构+代码和数据

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

2.2、退出方式

对一个正在运行中的进程,存在两种终止方式:外部终止和内部终止,外部终止时,通过 kill -9 PID 指令,强行终止正在运行中的程序,或者通过 ctrl + c 终止前台运行中的程序

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
内部终止是通过函数 exit()_exit() 实现的
之前在程序编写时,发生错误行为时,可以通过 exit(-1) 的方式结束程序运行,代码中任意地方调用此函数,都可以提前终止程序

void exit(int status);

void _exit(int status);

这两个退出函数,从本质上来说,没有区别,都是退出进程,但在实际使用时,还是存在一些区别,推荐使用 exit()

比如在下面这段程序中,分别使用 exit()_exit() 观察运行结果

int main()
{
  printf("You can see me");
  //exit(-1); //退出程序
  //_exit(-1);  //第二个函数
  return 0;
}

使用 exit() 时,输出语句

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
使用 _exit() 时,并没有任何语句输出
linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
原因:

  • exit() 是对 _exit() 做的封装实现
  • _exit() 就只是单纯的退出程序
  • exit() 在退出之前还会做一些事,比如冲刷缓冲区,再调用 _exit()
  • 程序中输出语句位于输出缓冲区,不冲刷的话,是不会输出内容的

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生


3、进程等待

僵尸进程 是一个比较麻烦的问题,如果不对其做出处理,僵尸进程 就会越来越多,导致 内存泄漏标识符 占用问题

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生

3.1、等待原因

子进程运行结束后,父进程没有等待并接收其退出码和退出状态,OS 无法释放对应的 内核数据结构+代码和数据,出现 僵尸进程

为了避免这种情况的出现,父进程可以通过函数等待子进程运行结束,此时父进程属于阻塞状态

注意:

  • 进程的退出状态是必要的
  • 进程的执行结果是非必要的

也就是说,父进程必须对子进程负责,确保子进程不会连累 OS,而子进程执行的结果是否正确,需要我们自行判断

3.2、等待函数

系统提供的父进程等待函数有两个 wait()waitpid(),后者比较常用

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

pid_t wait(int* status);

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

wait() 函数前面已经演示过了,这里着重介绍 waitpid() 返回值及其参数
wait() 中的返回值和参数,包含在 waitpid()

返回值:

  • 等待成功时,返回 >0 的值
  • 等待失败时,返回 -1
  • 等待中,返回 0

参数列表:

  • pid 表示所等子进程的 PID
  • status 表示状态,为整型,其中高 16 位不管,低 16 位中,次低 8 位表示退出码,第 7 位表示 core dump,低 7 位表示终止信号
  • options 为选项,比如可以选择父进程是否需要阻塞等待子进程退出

需要特别注意 status
linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生

通过代码演示 waitpid() 的使用

int main()
{
  //演示 waitpid()
  pid_t id = fork();  //创建子进程
  if(id == 0)
  {
    int time = 5;
    int n = 0;
    while(n < time)
    {
      printf("我是子进程,我已经运行了:%d秒 PID:%d   PPID:%d\n", n + 1, getpid(), getppid());
      sleep(1);
      n++;
    }

    exit(244);  //子进程退出
  }

  int status = 0; //状态
  pid_t ret = waitpid(id, &status, 0); //参数3 为0,为默认选项

  if(ret == -1)
  {
    printf("进程等待失败!进程不存在!\n");
  }
  else if(ret == 0)
  {
    printf("子进程还在运行中!\n");
  }
  else
  {
    printf("进程等待成功,子进程已被回收\n");
  }

  printf("我是父进程, PID:%d   PPID:%d\n", getpid(), getppid());

  //通过 status 判断子进程运行情况
  if((status & 0x7F))
  {
    printf("子进程异常退出,core dump:%d   退出信号:%d\n", (status >> 7) & 1, (status & 0x7F));
  }
  else
  {
    printf("子进程正常退出,退出码:%d\n", (status >> 8) & 0xFF);
  }

  return 0;
}

不发出终止信号,让程序自然跑完

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
发出终止信号,强行终止进程

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
waitpid() 的返回值可以帮助我们判断此时进程属于什么状态(在下一份测试代码中表现更明显),而 status 的不同部分,可以帮助我们判断子进程因何而终止,并获取 退出码(终止信号)

在进程的 PCB 中,包含了 int _exit_codeint _exit_signal 这两个信息,可以通过对 status 的位操作间接获取其中的值

注意:

  • status 的位操作需要多画图理解
  • 正常退出时,终止信号为0;异常终止时,退出码没有,两者是互斥的
  • code dump 现阶段用不到,但它是伴随着终止信号出现的

如果觉得 (status >> 8) & 0xFF(status & 0x7F) 这两个位运算难记,系统还提供了两个宏来简化代码

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

3.3、等待时执行

//options 参数
WNOHANG

//比如
waitpid(id, &status, WNOHANG);

父进程并非需要一直等待子进程运行结束(阻塞等待),可以通过设置 options 参数,进程解除 状态,父进程变成 等待轮询 状态,不断获取子进程状态(是否退出),如果没退出,就可以干点其他事

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> //进程等待相关函数头文件

int main()
{
  //演示 waitpid()
  pid_t id = fork();  //创建子进程
  if(id == 0)
  {
    int time = 9;
    int n = 0;
    while(n < time)
    {
      printf("我是子进程,我已经运行了:%d秒 PID:%d   PPID:%d\n", n + 1, getpid(), getppid());
      sleep(1);
      n++;
    }

    exit(244);  //子进程退出
  }

  int status = 0; //状态
  pid_t ret = 0;
  while(1)
  {

    ret = waitpid(id, &status, WNOHANG); //参数3 设置为非阻塞状态
    
    if(ret == -1)
    {
      printf("进程等待失败!进程不存在!\n");
      break;
    }
    else if(ret == 0)
    { 
      printf("子进程还在运行中!\n");
      printf("我可以干一些其他任务\n");
      sleep(3);
    }
    else
    {
      printf("进程等待成功,子进程已被回收\n");
      //通过 status 判断子进程运行情况
      if(WIFEXITED(status))
      {
        printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));
        break;
      }
      else
      {
        printf("子进程异常退出,code dump:%d   退出信号:%d\n", (status >> 7) & 1, (status & 0x7F));
        break;
      }
    }
  }

  return 0;
}

程序正常运行,父进程通过 等待轮询 的方式,在子进程执行的同时,执行其他任务

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
当然也可以通过 kill -9 PID 命令使子进程异常终止

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生
可以看到程序能分别捕捉到正常和异常的情况

注意:如果不写进程等待函数,会引发僵尸进程问题


🌆总结

以上就是关于 Linux进程控制(创建、终止、等待) 的相关知识了,我们学习了 子进程 是如何被创建的,创建后又是如何终止的,以及 子进程 终止 父进程 需要做些什么,有了这些知识后,在对 进程 进行操作时能更加灵活和全面

如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!

如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正


linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生

相关文章推荐

Linux进程学习【进程地址】

Linux进程学习【环境变量】

Linux进程学习【进程状态】

Linux进程学习【基本认知】

===============

Linux工具学习之【gdb】

Linux工具学习之【git】

Linux工具学习之【gcc/g++】

Linux工具学习之【vim】

linux 创建等待运行的进程,Linux学习之旅,linux,运维,服务器,进程,云原生文章来源地址https://www.toymoban.com/news/detail-782854.html

到了这里,关于Linux进程控制【创建、终止、等待】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【探索Linux】—— 强大的命令行工具 P.10(进程的控制——创建、终止、等待、程序替换)

    前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的一些知识,也学习了一些Linux的基本操作,也了解并学习了有关Linux开发工具vim 、gcc/g++ 使用、yum工具以及git 命令行提交代码也相信大家都掌握的不错,上一篇文章我们了解了关于进程的地址空间,今

    2024年02月08日
    浏览(53)
  • 【Linux初阶】fork进程创建 & 进程终止 & 进程等待

     🌟hello,各位读者大大们你们好呀🌟 🍭🍭系列专栏:【Linux初阶】 ✒️✒️本篇内容:fork进程创建,理解fork返回值和常规用法,进程终止(退出码、退出场景、退出方法、exit),进程等待(wait、waitpid),阻塞等待和非阻塞等待 🚢🚢作者简介:本科在读,计算机海洋

    2024年02月06日
    浏览(52)
  • Linux之进程控制&&进程终止&&进程等待&&进程的程序替换&&替换函数&&实现简易shell

    1.1 fork的使用 我们可以使用man指令来查看一下 子进程会复制父进程的PCB,之间代码共享,数据独有,拥有各自的进程虚拟地址空间。 这里就有一个代码共享,并且子进程是拷贝了父进程的PCB,虽然他们各自拥有自己的进程虚拟地址空间,数据是拷贝过来的,通过页表映射到

    2024年04月17日
    浏览(58)
  • linux入门之进程控制(上)进程创建,进程等待

    目录 一、进程创建 1.fork函数 2.fork函数返回值 3.写时拷贝 4.fork常规用法 5.fork调用失败原因 二、进程终止 1.进程退出场景 2.进程常见退出方法 2.1_exit函数(直接调用内核) 2.2 exit函数 2.3return退出 三、进程等待 1.进程等待必要性 2.进程等待方法 2.1 wait方法 2.2 waitpid方法 2.3获取

    2024年02月12日
    浏览(56)
  • 【Linux】详解进程终止&&进程等待

             页表中不仅仅只有虚拟地址到物理地址的映射,还包括了很多选项,其中就包括了映射条目的权限。当我们进程的代码和数据加载到内存并和进程地址空间建立映射关系时,如果数据的内容不允许被修改(比如说常量字符串),对应数据在页表中的映射条目的权

    2024年04月14日
    浏览(64)
  • 零基础Linux_10(进程)进程终止(main函数的返回值)+进程等待

    目录 1. 进程终止 1.1 main函数的返回值 1.2 进程退出码和错误码 1.3 进程终止的常见方法 2. 进程等待 2.1 进程等待的原因 2.2 wait 函数 2.3 waitpid 函数 2.4 int* status参数 2.5 int options非阻塞等待 本篇完。 进程终止指的就是程序执行结束了,进程终止退出的场景有三种: 代码运行

    2024年02月07日
    浏览(43)
  • 【Linux系统编程:线程】 线程控制 -- 创建、终止、等待、分离 | 线程互斥与同步 | 互斥量与条件变量 | 生产者消费者模型 | 线程池 | STL/智能指针与线程安全 | 读者写者模型

    写在前面 本文重点: 了解线程概念,理解线程与进程区别与联系。 学会线程控制,线程创建,线程终止,线程等待。 了解线程分离与线程安全。 学会线程同步。 学会使用互斥量,条件变量,posix 信号量,以及读写锁。 理解基于读写锁的读者写者问题。 一、线程概念 💦

    2024年02月04日
    浏览(70)
  • Linux——进程创建与进程终止

    📘北尘_ :个人主页 🌎个人专栏 :《Linux操作系统》《经典算法试题 》《C++》 《数据结构与算法》 ☀️走在路上,不忘来时的初心 在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。 #include unistd.h pid_t fork(void); 返

    2024年02月04日
    浏览(50)
  • Linux - 进程控制(下篇)- 进程等待

     为什么进程需要等待?  我们知道,在Linux 当中, 父子进程之间一些结构 就是一些 多叉树 的结构,一个父进程可能管理或者创建了很多个字进程。 而其实我们在代码当中使用fork()函数创建的子进程的父进程,这个父进程其实也是其他的父进程的子进程,我们在命令行

    2024年02月05日
    浏览(42)
  • 【linux进程控制(二)】进程等待--父进程是如何等待子进程死亡的?

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:Linux从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学更多操作系统知识   🔝🔝 ) 控制一个进程包括如何创建它,如何 终止它,并且如何回收它的资源! 本章重点: 本篇文章着重讲解进程等待的必要性 ,以及

    2024年02月05日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包