返回顶部
首页 > 资讯 > 操作系统 >Linux进程控制【创建、终止、等待】
  • 901
分享到

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

linux运维服务器进程云原生 2023-08-20 07:08:00 901人浏览 薄情痞子
摘要

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

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

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

上帝的指纹



🌇前言

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

汽车中控台

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


3D9;️正文

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

1、进程创建

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

1.1、fork函数

#include //所需头文件pid_t fork(void);//fork 函数

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

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

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

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

进程创建图解
通过代码理解 进程 创建

#include #include #include #include  //进程等待相关函数头文件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; //父进程运行结束后,退出}

结果
观察结果不难发现,两个子进程已经成功创建,但最晚创建的进程,总是最先运行,这是因为 fork 创建进程后,先执行哪个进程取决于调度器

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

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

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

1.2、写时拷贝

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

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

#include #include #include #include  //进程等待相关函数头文件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;}

结果
不难发现,子进程对指针 ps 指向内容做出改变时,父进程并不受影响,这就是写时拷贝机制

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

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

图解
注意:

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

2、进程终止

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

终止进程

2.1、退出码

echo $?

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

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

子进程运行情况:

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

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

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

2.2、退出方式

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

外部终止
内部终止是通过函数 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() 时,输出语句

结果1
使用 _exit() 时,并没有任何语句输出
结果2
原因:

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

图解


3、进程等待

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

僵尸进程

3.1、等待原因

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

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

注意:

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

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

3.2、等待函数

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

#include #include 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
status

通过代码演示 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;}

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

结果
发出终止信号,强行终止进程

结果2
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 #include #include #include  //进程等待相关函数头文件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;}

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

正常运行
当然也可以通过 kill -9 PID 命令使子进程异常终止

异常终止
可以看到程序能分别捕捉到正常和异常的情况

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


🌆总结

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

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

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


星辰大海

相关文章推荐

Linux进程学习【进程地址】

Linux进程学习【环境变量】

Linux进程学习【进程状态】

Linux进程学习【基本认知】

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

Linux工具学习之【gdb】

Linux工具学习之【git】

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

Linux工具学习之【vim】

感谢支持

来源地址:https://blog.csdn.net/weixin_61437787/article/details/129396689

--结束END--

本文标题: Linux进程控制【创建、终止、等待】

本文链接: https://www.lsjlt.com/news/376218.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Linux进程控制【创建、终止、等待】
    ✨个人主页: Yohifo 🎉所属专栏: Linux学习之旅 🎊每篇一句: 图片来源 🎃操作环境: CentOS 7.6 阿里云远程服务器 Good judgment comes fro...
    99+
    2023-08-20
    linux 运维 服务器 进程 云原生
  • 【Linux】Linux进程控制 --- 进程创建、终止、等待、替换、shell派生子进程的理解…
    柴犬: 你好啊,屏幕前的大帅哥or大美女,和我一起享受美好的今天叭😃😃😃 文章目录 一、进程创建1.调用fork之后,内核都做了什么?2.如何...
    99+
    2023-09-08
    linux 运维 服务器
  • C语言控制进程之进程等待详解
    目录进程等待的必要进程等待的方法wait函数waitpid函数获取子进程退出信息进程等待的必要 当一个进程终止的时候,它的资源,比如说PCB,数据等不会被立马清理掉。它会保持在已经终...
    99+
    2024-04-02
  • Linux线程:创建(pthread_create),等待(pthread_join),退出(pthread_exit)
    目录 一 线程说明 ①线程与进程: ②线程优点: ③线程缺点: 二 线程开发API概要 三 线程控制流程 ① 线程创建(pthread_create) ② 线程退出(pthread_exit) ③ 线程等待(pthread_join) ④ ...
    99+
    2023-09-04
    linux 服务器 vim c语言
  • C语言详解分析进程控制中进程终止的实现
    目录进程退出的形式进程退出的几种方法进程退出的形式 进程退出的几种情况 正常退出(自愿,代码运行完其结果正确)错误退出(自愿,代码运行完其结果不正确)异常退出(非自愿,代码异常直接终...
    99+
    2024-04-02
  • Golang控制通道实现协程等待详解
    目录前言方法一-睡眠等待方法二-通道什么是通道通道的特性什么是非缓冲通道什么是缓冲通道通道的简单使用非缓冲通道缓冲通道小心死锁使用通道实现协程等待前言 上一次简单了解了协程的工作原理...
    99+
    2022-11-21
    Golang通道实现协程等待 Go实现协程等待
  • Python多线程之线程创建和终止
    python主要是通过thread和threading这两个模块来实现多线程支持。python的thread模块是比较底层的模块,python的threading模块是对thread做了一些封装,可以更加方便的被使用。但是python(c...
    99+
    2023-01-31
    线程 多线程 Python
  • Linux进程控制
    Linux进程控制 进程创建fork函数初识fork函数返回值写时拷贝fork常规用法fork调用失败的原因 进程终止进程退出场景进程常见退出方法_exit函数exit函数 ...
    99+
    2023-09-22
    linux 运维 服务器 后端
  • Linux--进程控制
    前言:         这篇文章主要是讲解Linux下的进程控制,我们会学习到进程等待,进程程序替换, 微型shell,重新认识shell运行原理 。最后也编写了一个属于我们自己的shell,尽管功能不够齐全,但是还是感觉挺有意思,挺好玩的...
    99+
    2023-09-16
    linux 运维 服务器
  • 【Linux】进程控制
    文章目录 一、进程创建1、再谈 fork 函数2、fork 函数返回值3、写时拷贝4、fork 常规用法5、fork 调用失败原因 二、进程终止1、进程退出码2、进程退出的情况3、进程退出的方法 三、进程等待1、为什么要进行...
    99+
    2023-08-22
    linux 运维 服务器
  • linux如何查找进程及终止进程操作
    本篇内容主要讲解“linux如何查找进程及终止进程操作”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“linux如何查找进程及终止进程操作”吧!使用linux操作系统,难免遇到一些软件“卡壳”的问...
    99+
    2023-06-13
  • 深剖 Linux 进程控制
    目录 传统艺能😎pc 指针🤔为什么要写时拷贝?🤔退出码🤔exit 与 _exit🤔slab 分配器ᾑ...
    99+
    2023-09-07
    linux 服务器 进程
  • Linux进程控制【进程程序替换】
    ✨个人主页: Yohifo 🎉所属专栏: Linux学习之旅 🎊每篇一句: 图片来源 🎃操作环境: CentOS 7.6 阿里云远程服务器 Good judgment comes fro...
    99+
    2023-08-22
    linux 运维 服务器 云原生 后端
  • 如何创建Linux进程
    如何创建Linux进程,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。下面在Linux系统中创建进程使用fork、vfork、clone这三个命令进行操作。fork  ...
    99+
    2023-06-28
  • Linux中怎么使用killall命令终止进程
    Linux中怎么使用killall命令终止进程,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1、基本用法假如我们 3 个进程在运行,分别是 hello1, hello2, ...
    99+
    2023-06-16
  • shell监控linux系统进程创建脚本分享
    #!/bin/shwhile truedo ps ax -o command | sort | uniq > 1.txt usleep 100000 ps ax -o command | sort ...
    99+
    2022-06-04
    脚本 进程 系统
  • 【Linux】Linux进程控制及程序替换
    🍎作者:阿润菜菜 📖专栏:Linux系统编程 进程控制及程序替换 进程创建fork的用法进程调用fork,内核做了什么fork函数的返回值问题fork创建失败的原因 进程等待进程等待是什么...
    99+
    2023-08-24
    linux 运维 服务器
  • C# 创建控制台应用程序
    目录在学习C#语言的时候,首先要学习控制台的应用程序,这样才能专注于语言的学习,减少学习的梯度,也有利于输出自己需要输出的内容。因此第一步学习C#语言时,一定要先使用控制台的应用程序...
    99+
    2024-04-02
  • linux中nohup和后台运行进程查看及终止
    目录1.nohup2.&3.如果某个进程起不来,可能是某个端口被占用4.终止后台运行的进程nohup 命令运行由 Command参数和任何相关的 Arg参数指定的命令,忽略所有挂断(SIGHUP)信号。在注销...
    99+
    2022-06-03
    nohup后台运行 linux nohup进程
  • Linux中怎么终止某个用户的所有进程
    本篇内容主要讲解“Linux中怎么终止某个用户的所有进程”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Linux中怎么终止某个用户的所有进程”吧!在linux系统管理中,我们有时候需要kill掉...
    99+
    2023-06-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作