[Linux] 进程创建、退出和等待

标题:[Linux] 进程创建、退出和等待

个人主页@水墨不写bug

(图片来源于AI)

目录

一、进程创建fork()

1) fork的返回值:

2)写时拷贝

​编辑3)fork常规用法

4)fork调用失败的原因

二、什么是进程退出

1)exit()函数于系统调用接口_exit()有什么区别?

三、僵尸进程和进程等待

1)父进程处理僵尸进程就需要进程等待

2)阻塞等待 

3)非阻塞等待:

 4)输出型参数status:

 总结:


正文开始:

一、进程创建fork()

         fork()是Linux操作系统提供的系统调用,作用是从一个已经存在的进程中创建一个新进程。新进程为子进程,原进程为父进程。

//头文件
#include <unistd.h>//函数原型
pid_t fork(void);//返回值:子进程中返回0,父进程返回子进程id,出错返回-1

 

进程调用fork,当控制转移到内核中的fork代码后,内核做:

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

        具体创建过程的内存图示见《Linux:初识进程地址空间》 ,子进程创建后这里仅仅给出一张图:

         最重要的是:当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。如下面的例子:

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>int main()
{printf("fork之前 我的pid:%d\n",getpid());pid_t id = fork();printf("fork之后 我的pid:%d, fork的返回值:%d\n",getpid(),id);return 0;
}

        运行之后,结果如图:

         分析:

        在fork之前,只有一个进程,pid=48247;

        fork之后,父进程(pid=48247)创建出子进程(pid=48248):从父子进程的pid临近和fork给子进程的返回值为0,给父进程的返回值为子进程的pid可以看出来。

         fork之后,父子进程都执行到fork函数这一行,于是,fork之前的代码不会被执行,之后的代码会被父子进程各自执行一次

1) fork的返回值:

        给子进程返回0;

        给父进程返回子进程的pid;

        如果出错了,则子进程没有被创建出来,那么给仅有的一个进程(父进程)返回-1;

2)写时拷贝

        写时拷贝(copy-on-write)是一种内存管理技术,用于优化内存使用和提高性能。它是一种延迟拷贝的策略,在创建副本之前,共享资源将不会被复制。

        写时拷贝的原理是,当一个对象需要被拷贝时,不会立即进行拷贝操作。而是让新对象与原对象共享同一份资源。当其中一个对象修改了该资源时,才会将资源复制到新的对象上。这样可以减少内存的使用,避免了不必要的复制操作。

3)fork常规用法

        一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。

        一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。(进程替换函数)

4)fork调用失败的原因

        系统中有太多的进程;

        实际用户的进程数超过了限制;


二、什么是进程退出

         我们在当初学习C语言的时候,经常会malloc空间,但是动态内存开辟是会有失败的时候,malloc的最保险的用法:

    int size = 10;int* arr = (int*)malloc(size*sizeof(int));if(arr == NULL){perror("malloc fail:");exit(-1);}

         如果malloc失败,通常是非常严重的错误,这个时候进程已经没有必要继续运行下去了,这时候选择打印错误信息,如果调用exit函数退出。

        上述的进程退出只是进程退出的一种场景,其实进程想要退出,无非只有三种场景

1)代码运行完毕;

2)结果正确代码运行完毕,结果不正确;

3)代码异常终止

         当一个进程退出,我们在Linux终端可以使用:

echo $?

         命令查看最近一次进程退出的退出码。

        如果最近的一个进程正常退出,返回值是0;

 一个进程退出有什么方式?其实无非就是:

        1)从main函数返回;

        2)调用: exit();

        3)调用系统调用接口:   _exit();

对于其他的退出方式,一般就是我们主动退出:ctrl+C;

或者遇到空指针,或者被断言断死,其实这些退出方式有一个统称:被系统的信号杀死。

1)exit()函数于系统调用接口_exit()有什么区别?

         这两个函数的最本质功能是一样的,都是让进程退出,但是exit() 函数是对系统调用_exit()函数的封装。

        _exit函数是系统调用,exit是C语言的库函数。

进程调用_exit()会直接退出,但是调用exit会在退出之前做一些善后的处理:

        1. 执行用户通过 atexit或on_exit定义的清理函数。

        2. 关闭所有打开的流,所有的缓存数据均被写入(刷新缓冲区)

        3. 调用_exit

三、僵尸进程和进程等待

        在Linux系统中,僵尸进程(Zombie Process)是指已经结束执行的进程,但是其父进程还没有对其进行完全的清理操作,导致进程的资源没有被释放。

        僵尸进程的产生是因为操作系统在进程结束时不会立即清理相关的资源,而是会将其转变为僵尸进程,等待父进程来处理。父进程需要通过调用wait()或waitpid()系统调用来获取子进程的退出状态,并对其进行清理,释放相应的资源。如果父进程没有处理僵尸进程,那么僵尸进程会一直存在于系统中,占用系统资源。

        僵尸进程通常不会对系统造成直接的危害,因为它们已经停止运行。然而,如果有大量的僵尸进程存在,会浪费系统的进程表资源,并可能导致进程表耗尽的问题。此外,僵尸进程也可能是父进程出现问题或错误的一个指示,比如父进程无法正确处理子进程的退出状态。

1)父进程处理僵尸进程就需要进程等待

        父进程需要调用wait()/waitpid()等方法等待子进程退出。

2)阻塞等待 

        接口wait():等待任意一个子进程退出,但是在等待的过程中父进程什么事都干不了,这样的等待称为“阻塞等待”。

//头文件
#include<sys/types.h>
#include<sys/wait.h>pid_t wait(int*status);
//返回值:
//成功返回被等待进程pid,失败返回-1。//参数:
//输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

如果想要让父进程在等待的过程中做一些事情,就需要另一个接口:waitpid()

//头文件
#include<sys/types.h>
#include<sys/wait.h>pid_ t waitpid(pid_t pid, int *status, int options);//返回值:
//当正常返回的时候waitpid返回收集到的子进程的进程pid;//如果设置了选项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

3)非阻塞等待:

        waitpid()接口如果设置了MNOHANG,那么进程走到这个接口之后,会查看一次子进程是否已经退出,如果已经退出,我们就可根据此时的返回值设置循环的出口;

        如果没有退出,则进程会执行waitpid() 之后的代码逻辑,直到下一次循环再次到达waitpid()这个接口处,再次查看,继续循环。

非阻塞等待实例:


void oper()
{sleep(1);printf("等待不妨碍做其他事\n");
}int main()
{pid_t id = fork();if(id == 0){printf("我是子进程,5秒后退出\n");sleep(5);exit(0);}else{int status = 0;printf("我是父进程,等待子进程退出\n");while(1){pid_t rid = waitpid(id,&status,WNOHANG);if(rid > 0){printf("父进程等到子进程退出\n");break;}oper();}}return 0;
}

运行结果:

 4)输出型参数status:

        什么是输出型参数:输出型参数就是我们穿一个参数,他的值是由操作系统填充的,因此我们在传参的时候需要传地址

        如果传递NULL,表示不关心子进程的退出状态信息。

        如果传递了status的地址,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

        status不能简单的当作整形来看待,需要当作位图来看待,具体细节如下图(status是32位整形,高16位并不携带信息,只有status低16比特位携带信息):

 对于低16位:

        如果正常退出(没有被信号杀掉):那么低8位为0,次低8位表示退出码,这里的退出码,就是函数return的返回值,也是exit()括号内传的值。退出信号的意义是由我们自己赋予的。

        如果异常退出(进程被信号杀掉):退出码虽然可以手动获得,但是已经没有意义了,因为已经发生重大错误,导致进程都没有执行完就退出了 。这时低7位表示进程的退出信号,也就是操作系统发送给进程的异常信号。第8位暂时不考虑。

        低7位正好是2的七次方:64个标识,正好对应kill命令给进程发送的终止信号的个数:

 

 总结:

        正常退出,次低8位 退出码;

        异常退出,低7位 退出信号;

其实,为了便于记忆,有两个专门对status处理的

        

WIFEXITED:

        如果子进程正常退出,返回真;

WEXITSTATUS:

        返回status的次低8位退出码。

实例:


int main()
{pid_t id = fork();if(id == 0){int* p = NULL;*p = 10;exit(0);}else{int status = 0;wait(&status);printf("%d\n",status);if(WIFEXITED(status)){printf("程序正常退出:%d\n",WEXITSTATUS(status));}else{printf("程序挂了:%d\n",(status&0x7f));}}return 0;
}

运行结果:


完~

未经作者同意禁止转载 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/439966.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线性代数书中求解齐次线性方程组、非齐次线性方程组方法的特点和缺陷(附实例讲解)

目录 一、克拉默法则 1. 方法概述 2. 例16(1) P45 3. 特点 (1) 只适用于系数矩阵是方阵 (2) 只适用于行列式非零 (3) 只适用于唯一解的情况 (4) 只适用于非齐次线性方程组 二、逆矩阵 1. 方法概述 2. 例16(2) P45 3. 特点 (1) 只适用于系数矩阵必须是方阵且可逆 …

计算机毕业设计 基于Python的无人超市管理系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

Leecode热题100-560.和为k的子数组

给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1], k 2 输出&#xff1a;2示例 2&#xff1a; 输入&#xff1a;nums [1,2,3], k…

五、类与对象包访问权限

类与对象&包&访问权限 类的定义类的概念定义和使用类对象的初始化类的成员函数 再谈基本类型运算符重载函数中缀函数空值和空类型解构包和导入访问权限控制内部类 类的定义 在之前&#xff0c;我们一直在使用顶层定义&#xff1a; 在Kotlin中&#xff0c;顶层定义&…

计算机网络:计算机网络概述 —— 网络拓扑结构

文章目录 网络拓扑总线型拓扑特点缺陷 星型拓扑特点缺陷 环型拓扑特点缺陷 网状型拓扑优点缺陷 树型拓扑特点缺陷 网络拓扑 网络拓扑指的是计算机网络中节点&#xff08;计算机、交换机、路由器等&#xff09;之间物理或逻辑连接的结构。网络拓扑定义了节点之间的布局、连接方…

基于ssm 框架的java 开发语言的 在线教育学习平台系统设计与实现 源码 论文

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm springcloud等开发框架&#xff09; vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆…

【高等数学学习记录】函数的极限

一、知识点 &#xff08;一&#xff09;知识结构 #mermaid-svg-Dz0Ns0FflWSBWY50 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Dz0Ns0FflWSBWY50 .error-icon{fill:#552222;}#mermaid-svg-Dz0Ns0FflWSBWY50 .erro…

ROW_NUMBER

How to rewrite a query which uses the ROW_NUMBER() window function in versions 5.7 or earlier before window functions were supported e.g., SELECT ROW_NUMBER() OVER (PARTITION BY fieldA) AS rownum, myTable.* FROM myTable; index 用不上的 Solution Assuming…

保姆级手把手使用YOLOv11训练自己数据集(含源代码、网络结构、模型检测和转换 、数据集查找、模型训练)

文章目录 前言项目地址项目内容&#xff1a;网络模型结构性能测试任务描述任务内容 项目运行模型训练 前言 本教程内含YOLOv11网络结构图训练教程推理教程数据集获取等有关的内容~ 项目地址 YOLO11是Ultralytics YOLO系列实时目标检测器的最新迭代版本&#xff0c;它以尖端的…

打卡第三天 P5729 【深基5.例7】工艺品制作

今天是我打卡第三天&#xff0c;做个入门题吧(#^.^#) 原题链接&#xff1a;【深基5.例7】工艺品制作 - 洛谷 题目描述 输入格式 输出格式 输出一个整数表示答案。 输入输出样例 输入 #1 4 4 4 1 1 1 1 2 2 2 输出 #1 56 说明/提示 C&#xff1a; #include<bits/std…

使用 classification_report 评估 scikit-learn 中的分类模型

介绍 在机器学习领域&#xff0c;评估分类模型的性能至关重要。scikit-learn 是一个功能强大的 Python 机器学习工具&#xff0c;提供了多种模型评估工具。其中最有用的函数之一是 classification_report&#xff0c;它可以全面概述分类模型的关键指标。在这篇文章中&#xff…

低组装滚珠导轨:承载力强,适应多样工况!

在自动化行业中&#xff0c;高质量、高效率的生产线是确保产品品质和生产效率的关键。而低组装型滚珠导轨作为生产线中的重要组件之一&#xff0c;能够提供精准的直线运动控制&#xff0c;为自动化设备的稳定运行和高精度检测提供可靠支持。 相对于传统的导轨系统来说&#xff…

汇编语言笔记2

7.MASM,NASM,ATT,ARM的介绍 MASM:Windows下编译汇编指令的软件,可以在DOSBox下运行 NASM:优化版的MASM,主要用于Linux操作系统 ATT:Linux默认的汇编风格(但不友好) ARM:非PC(IOT设备)的汇编,比如写51单片机打开keil4的界面可以看到ARM 8.汇编 C语言 C 之间的关系 发展历程…

Arduino UNO R3自学笔记21 之 Arduino电机的闭环控制

注意&#xff1a;学习和写作过程中&#xff0c;部分资料搜集于互联网&#xff0c;如有侵权请联系删除。 前言&#xff1a;上篇写了电机速度测定&#xff0c;这篇主要是讲测定出的速度用于反馈&#xff0c;使得实际速度快速响应到需要的速度。 1.控制系统介绍 分2大类&#x…

《深度学习》【项目】OpenCV 发票识别 透视变换、轮廓检测解析及案例解析

目录 一、透视变换 1、什么是透视变换 2、操作步骤 1&#xff09;选择透视变换的源图像和目标图像 2&#xff09;确定透视变换所需的关键点 3&#xff09;计算透视变换的变换矩阵 4&#xff09;对源图像进行透视变换 5&#xff09;对变换后的图像进行插值处理 二、轮廓检测…

idea插件市场安装没反应

https://plugins.jetbrains.com/idea重启后还是不行那就

Docker:安装 MongoDB 的详细指南

请关注微信公众号&#xff1a;拾荒的小海螺 博客地址&#xff1a;http://lsk-ww.cn/ 1、简述 MongoDB 是一个流行的 NoSQL 数据库&#xff0c;可以在 Docker 容器中轻松安装和运行。本文将介绍如何在 Docker 中安装 MongoDB&#xff0c;并展示如何在 Java 应用中使用 MongoDB…

kafka发送消费核心参数与设计原理详解

核心参数详解&#xff1a; 发送端参数&#xff1a; 发送方式&#xff1a;默认值一般都是1: 重试参数 &#xff1a; 批量参数&#xff1a; 消费端参数&#xff1a; 自动提交参数&#xff1a; 如果是false&#xff0c;就是说消费完后不提交位移。也就是说比如之前消费的1-5.…

Qt教程(002):Qt项目创建于框架介绍

二、创建Qt项目 2.1 创建项目 【1、New Project】 【2、选择Qt Widgets Application】 【3、设置项目名称和保存路径】 注意&#xff0c;项目名称和路径不要带中文。 【4、选择QWidget】 带菜单栏的窗口QMainWindow空白窗口QWidget对话框窗口QDialog 【5、编译】 2.2 项目框…

前端编程艺术(4)---JavaScript进阶(vue前置知识)

目录 1.变量和常量 2.模版字符串 3.对象 4.解构赋值 1.数组的解构 2.对象的解构 5.箭头函数 6.数组和对象的方法 7.扩展运算符 8.Web存储 9.Promise 10.AsyncAwait 11.模块化 1.变量和常量 JavaScript 中的变量和常量是用于存储数据的标识符。变量可以被重新赋值&am…