当前位置: 首页 > news >正文

进程终止与进程等待

fork 函数

fork 函数是 Linux 中一个非常重要的函数,它的作用是从已存在的进程中创建一个新进程。这个新进程就是当前进程的子进程。

fork() 函数使用方法:它在头文件 #include <unistd.h> 中,函数原型为

pid_t fork(void);

用一个 pid_t 类型的变量来接收 fork() 函数的返回值。当创建进程成功时,fork() 函数会给子进程返回 0,给父进程返回新创建的子进程的 id;当创建失败时,fork() 函数会返回 -1(给父进程返回,因为子进程根本没创建出来)

当创建子进程成功后,操作系统会做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

当一个进程调用fork之后,就有两个二进制代码相同的进程。但它们都运行到相同的地方后,每个进程都会可以开始它们自己的旅程。可以使用下面的代码测试(Linux系统):

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(void)
{pid_t pid = fork();printf("Before: pid is %d\n", getpid());if (pid == -1) perror("fork()"), exit(1);if (pid == 0){printf("child:pid is %d, fork return %d\n", getpid(), pid);}else{printf("After:pid is %d, fork return %d\n", getpid(), pid);}sleep(1);return 0;
}

运行结果:

fork()函数 内核示意图

fork() 函数的常规用法及错误原因

常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数 

错误原因

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

C/C++中捕捉错误的方式(代码运行完毕,结果错误)

errno 是C语言调用C函数时的错误码,当调用函数出错时,它会在内部给出错误码(数字),但这个数字往往我们不知道是什么错误,那我们就要用 strerror 函数来解析这个错误码。strerror 函数可以将错误码以字符串的形式描述起来。

代码异常终止

当代码异常终止时,一般操作系统会给这个进程发信号,让这个进程以某种错误而终止,其表现就是这个进程被操作系统杀掉!它的内部逻辑类似于手动调用 kill 指令。

kill -n id

n 为选项,表示各种信号;id 为被发送信号进程的 id

使用如下指令可以查看 Linux 中全部的信号

kill -l

进程终止

进程退出有三种场景:

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

这三种情况可以用2个数字组合完全覆盖!

当进程正常终止时:可以通过 echo $? 查看进程退出码。

进程正常终止有三种情况:1. 从main返回;2. 调用exit;3. 调用_exit

异常退出:ctrl + c,信号终止

exit() 函数

#include <unistd.h>
void exit(int status);

status 定义了进程的终止状态,也就是进程的结果正确还是不正确。status 是一个整形值,父进程可以通过 wait函数来获取该值,得到子进程的最终执行结果!

_exit() 函数

#include <unistd.h>
void _exit(int status);

_exit() 函数与 exit() 函数的作用可以说是一模一样,只是在细节上有所差异:在终止进程时,exit() 函数会自动刷新缓冲区,而_exit() 函数不会!

其实相当于 exit()函数 是先调用了 _exit() 函数,如图

由此可以得出一个结论:我们所认识的缓冲区,并不在操作系统内部!否则 exit() 和 exit() 都应该能刷新缓冲区。

进程等待

什么是进程等待?

通过 wait/waitpid 的方式,让父进程对子进程进行资源回收的等待过程。那为什么要进行等待呢?

第一,可以解决子进程僵尸问题带来的内存泄漏问题(进程僵尸只有父进程回收才能解决,且不能被杀掉,所以这是目前必须使用进程等待解决的问题);第二,父进程创建子进程的目的,就是让子进程来完成任务,而父进程需要知道子进程任务到底完成的如何,就必须通过等待的方式来获取子进程退出的信息(两个数字:退出码和信号)这个退出信息也许并不是必须的,但是系统需要提供这样的基础功能!

如何进行等待

在 Xshell 终端中输入如下指令,查看 waitpid 和 wait 的使用方式

wiat 和 waitpid 方法

man waitpid

wait 函数:

返回值:成功返回被等待进程 pid,失败则返回 -1。

参数:输出型参数,获取子进程退出状态,若不关心则可以设置成为NULL

waitpid 函数

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

参数:pid:Pid=-1,等待任一个子进程。与wait等效。Pid>0,等待其进程ID与pid相等的子进程;status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

options:

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

那么父进程是如何得知子进程的退出信息呢?

子进程在退出的时候,要修改状态 Z,并将自己的退出信息和退出码写入 pcd 中,父进程通过读取子进程的 pcd 获取这些信息。注意:不能使用全局变量获取子进程的信息,因为进程之间具有独立性,各有各的进程地址空间,子进程对数据的修改不改变父进程中的数据!

获取子进程的 status

当需要获取子进程的 status时,通常采用位图的思想,使用位运算!

如图,经过位运算得到需要的数字:

是否收到信号的判定方法:exit sig 是否等于0,等于0说明没收到信号,不等于0说明收到了异常的信号;当一个进程异常了(收到信号),exit code 就变得没有意义了。

进程的阻塞等待和非阻塞等待

设 wait/waitpid 的返回值为 rid 

  • rid > 0 :等待成功
  • rid == 0:等待成功,但对方(子进程)还没有退出
  • rid < 0:等待失败 

对于 waitpid 方法而言,它的第三个参数 options 可以控制父进程是阻塞等待还是非阻塞等待。

阻塞等待:子进程不退出,父进程就一直等待子进程,waitpid 不返回。这种情况在计算机中叫 宕(dang)机或应用夯(hang)住了。这种等待的缺点就是父进程在等待的过程中,什么事都做不了!

进程的阻塞式等待代码

int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { //childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(257);}else {int status = 0;pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5Sprintf("this is test for wait\n");if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));}else {printf("wait child failed, return.\n");return 1;}}return 0;
}

非阻塞等待:如果子进程的退出条件不满足,wait/waitpid 不会阻塞,而是立即返回!所以非阻塞等待一般要重复读多次调用,这种一般叫做:非阻塞轮询方案进行进程等待 这样做的好处是:在子进程没有退出的情况下,父进程可以在等待的过程中,顺便做一些占用时间比较少的事情。

进程的非阻塞式等待代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { //childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(1);}else {int status = 0;pid_t ret = 0;do{ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待if (ret == 0) {printf("child is running\n");}sleep(1);} while (ret == 0);if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));}else {printf("wait child failed, return.\n");return 1;}}return 0;
}
http://www.lryc.cn/news/302509.html

相关文章:

  • MySQL 基础知识(六)之数据查询(二)
  • 蓝桥杯嵌入式STM32G431RBT6知识点(主观题部分)
  • ELAdmin 部署
  • 计算机功能简介:EC, NVMe, SCSI/ISCSI与块存储接口 RBD,NUMA
  • linux上安装bluesky的步骤
  • 视频监控需求八问:视频智能分析/视频汇聚平台EasyCVR有何特性?
  • django rest framework 学习笔记2
  • 第四篇【传奇开心果系列】Python文本和语音相互转换库技术点案例示例:pyttsx3自动化脚本经典案例
  • model.train()和model.eval()两种模式的原理
  • docker的底层原理六: 联合文件系统(UnionFS)
  • 【动态规划专栏】专题一:斐波那契数列模型--------1.第N个泰波那契数
  • 自养号测评低成本高效率推广,安全可控
  • ubuntu22.04@laptop OpenCV Get Started: 015_deep_learning_with_opencv_dnn_module
  • 【elk查日志 elastic(kibana)】
  • RapidMiner数据挖掘2 —— 初识RapidMiner
  • 基于STM32的光照检测系统设计
  • 车辆管理系统设计与实践
  • 板块一 Servlet编程:第四节 HttpServletResponse对象全解与重定向 来自【汤米尼克的JAVAEE全套教程专栏】
  • 漫谈:C/C++ char 和 unsigned char 的用途
  • 安全保护制度
  • 沁恒CH32V30X学习笔记07---多功能按键框架使用
  • 如何看显卡是几G?
  • 虚拟机--pc端和macOS端互通
  • (14)Hive调优——合并小文件
  • Linux 驱动开发基础知识——LED 模板驱动程序的改造:设备树(十一)
  • 学习文档:QT QTreeWidget及其代理
  • 代码随想录算法训练营——总结篇
  • 更改WordPress作者存档链接author和用户名插件Change Author Link Structure
  • Kernelized Correlation Filters KCF算法原理详解(阅读笔记)(待补充)
  • 安卓游戏开发之图形渲染技术优劣分析