0


【云原生】Linux进程控制(创建、终止、等待)

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

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

上帝的指纹


文章目录


🌇前言

**

进程

创建后,需要对其进行合理管理,光靠

OS

是无法满足我们的需求的,此时可以运用

进程

控制相关知识,对

进程

进行手动管理,如创建

进程

、终止

进制

、等待

进程

等,其中等待

进程

可以有效解决僵尸

进程

问题**

汽车中控台

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


🏙️正文

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

1、进程创建

**在学习

进程控制

相关知识前,先要对回顾如何创建

进程

,涉及一个重要的函数

fork

**

1.1、fork函数

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

**

fork

函数的作用是在当前

进程

下,创建一个

子进程

子进程

创建后,会为其分配新的内存块和内核数据结构(

PCB

),将

父进程

中的数据结构内容拷贝给

子进程

,同时还会继承

父进程

中的环境变量表**

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

**

fork

函数返回类型为

pid_t

,相当于

typedef int

,不过是专门用于进程的,同时它拥有两个返回值:**

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

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

进程

创建**

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>//进程等待相关函数头文件intmain(){//创建两个子进程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());return0;//父进程运行结束后,退出}

运行结果
观察结果不难发现,两个子进程已经成功创建,但最晚创建的进程,总是最先运行,这是因为

fork

创建进程后,先执行哪个进程取决于调度器

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

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

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

1.2、写时拷贝

**在【进程地址空间】一文中,谈到了写时拷贝机制,实现原理就是通过

页表+MMU

机制,对不同的进程进行空间寻址,达到出现改写行为时,父子进程使用不同真实空间的效果**

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

#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>//进程等待相关函数头文件constchar* ps ="This is an Apple";//全局属性intmain(){pid_t id =fork();if(id ==0){
    ps ="This is a Banana";//改写printf("我是子进程,我认为:%s\n", ps);exit(0);//子进程退出}wait(0);//等待子进程退出printf("我是父进程,我认为:%s\n", ps);return0;}

结果
**不难发现,子进程对指针

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)

的方式结束程序运行,代码中任意地方调用此函数,都可以提前终止程序

voidexit(int status);void_exit(int status);

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

exit()

**

比如在下面这段程序中,分别使用

exit()

_exit()

观察运行结果

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

使用

exit()

时,输出语句

结果1
使用

_exit()

时,并没有任何语句输出
结果2
原因:

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

输出


3、进程等待

**

僵尸进程

是一个比较麻烦的问题,如果不对其做出处理,

僵尸进程

就会越来越多,导致

内存泄漏

标识符

占用问题**

僵尸进程

3.1、等待原因

**子进程运行结束后,父进程没有等待并接收其退出码和退出状态,

OS

无法释放对应的 内核数据结构+代码和数据,出现

僵尸进程

**

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

注意:

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

也就是说,**父进程必须对子进程负责,确保子进程不会连累

OS

**,而子进程执行的结果是否正确,需要我们自行判断

3.2、等待函数

**系统提供的父进程等待函数有两个

wait()

waitpid()

,后者比较常用**

#include<sys/types.h>#include<sys/wait.h>pid_twait(int* status);pid_twaitpid(pid_t pid,int* status,int options);

**

wait()

函数前面已经演示过了,这里着重介绍

waitpid()

返回值及其参数**

wait()

中的返回值和参数,包含在

waitpid()

返回值:

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

参数列表:

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

**需要特别注意

status

**
status
**通过代码演示

waitpid()

的使用**

intmain(){//演示 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");}elseif(ret ==0){printf("子进程还在运行中!\n");}else{printf("进程等待成功,子进程已被回收\n");}printf("我是父进程, PID:%d   PPID:%d\n",getpid(),getppid());//通过 status 判断子进程运行情况if((status &0x7F)){printf("子进程异常退出,code dump:%d   退出信号:%d\n",(status >>7)&1,(status &0x7F));}else{printf("子进程正常退出,退出码:%d\n",(status >>8)&0xFF);}return0;}

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

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

结果2
**

waitpid()

的返回值可以帮助我们判断此时进程属于什么状态(在下一份测试代码中表现更明显),而

status

的不同部分,可以帮助我们判断子进程因何而终止,并获取 退出码(终止信号)**

**在进程的

PCB

中,包含了

int _exit_code

int _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>//进程等待相关函数头文件intmain(){//演示 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;}elseif(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;}}}return0;}

**程序正常运行,父进程通过

等待轮询

的方式,在子进程执行的同时,执行其他任务**

正常运行
**当然也可以通过

kill -9 PID

命令使子进程异常终止**

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

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


🌆总结

**以上就是关于 Linux进程控制(创建、终止、等待) 的相关知识了,我们学习了

子进程

是如何被创建的,创建后又是如何终止的,以及

子进程

终止

父进程

需要做些什么,有了这些知识后,在对

进程

进行操作时能更加灵活和全面**

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

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


星辰大海

相关文章推荐

Linux进程学习【进程地址】

Linux进程学习【环境变量】

Linux进程学习【进程状态】

Linux进程学习【基本认知】

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

Linux工具学习之【gdb】

Linux工具学习之【git】

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

Linux工具学习之【vim】

承蒙厚爱,感谢支持

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/weixin_61437787/article/details/129396689
版权归原作者 Yohifo 所有, 如有侵权,请联系我们删除。

“【云原生】Linux进程控制(创建、终止、等待)”的评论:

还没有评论