[Linux 进程(六)] 写时拷贝 - 进程终止

在这里插入图片描述

文章目录

  • 1、写时拷贝
  • 2、进程终止
    • 2.1 进程退出场景
      • 2.1.1 退出码
      • 2.1.2 错误码
      • 错误码 vs 退出码
      • 2.1.3 代码异常终止引入
    • 2.2 进程常见退出方法
      • 2.2.1 exit函数
      • 2.2.2 _exit函数

本片我们主要来讲进程控制,讲之前我们先把写时拷贝理清,然后再开始讲进程控制。

1、写时拷贝

我们第一篇进程文章中,讲到了系统接口fork()创建子进程,最后我们提了五个问题,第五个问题:如何理解同一个id变量,怎么会有不同的值? 写时拷贝将为你解答该问题。记不清的伙伴点这里回顾那篇文章
通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
在这里插入图片描述
当父进程创建子进程之后,子进程的页表是拷贝父进程的,但子进程要在数据段进行写入(代码段不支持修改),就需要重新申请空间,将原数据拷贝后再做写入我们并不是将整块数据进行改写的,可能只是修改部分数据),并修改页表,这部分工作是由操作系统做的。 但是该工作是需要时机的,操作系统并不知道你什么时候是要做写入的。
我们先说一个**结论**:父进程创建子进程的时候,首先将自己的读写权限改为只读,然后再创建子进程。

用户是不知道的!用户将来可能会对数据(权限为读写,代码段是只读) 进行写入!此时,页表的转换会因为权限问题出错,这时操作系统就接入了。但是出错也分真假:

  • 真出错。代码段是不可以写入的,但是我们修改的区域在code_start~code_end(代码区起始结束区域),这时就是越界/真出错。
  • 假出错。对数据区的写入,数据区是可以读写的,只是我们页表中改成了只读。这样的不是出错,是触发进行重新申请内存,拷贝内容的策略机制。

我们终于明白了,子进程拷贝下父进程的页表后,将数据对应的页表条目权限改为只读,通过让操作系统触发异常的方式,让操作系统帮我们进行写时拷贝的,完成后再把对应的页表条目改为读写,没有写入的依旧是只读。

2、进程终止

我们先来提出一个 问题:我们C语言代码main函数最后都有一个return 0,返回0时给谁返回呢?
main函数也是被调用的,所以注定谁调用就给谁返回。我们写一段代码来看看:

#include <stdio.h>

int main()
{
    
    return 10;
}

我们main函数中什么都不写,直接返回值为10。
当编译运行后,它的父进程是bash,会将返回值交给父进程,用指令echo $?获取刚刚的结果。
在这里插入图片描述

打印出来这是现象。
?是环境变量,保存的是最近一个子进程执行完毕的退出码。
在这里插入图片描述

第二次查看退出码为0,是因为上一个echo执行是成功的,0代表了成功。
由此我们展开下面的话题:

2.1 进程退出场景

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

2.1.1 退出码

在多进程环境中,我们创建子进程就是为了帮我们去做事,这里“我们”是父进程,子进程事做的怎么样,父进程是需要知道的。在main函数中,返回值0代表正确,非0代表错误,父进程就是依靠返回值来判断是否正确的做完了任务。
当返回0,正确大家不会关心这个过程;但是返回非0,意味着错误,我们最想知道的是错误的原因是什么。所以我们可以用不同的数字表示不同的原因!但是不便于人阅读,所以我们需要一些能够将数字转化成错误码的字符串描述方案。C语言给我们有提供一批接口,我们也可以自定义一批我们自己的错误码与错误信息,把不同数字转化成不同出错原因的接口:

在这里插入图片描述

我们写一段代码来打印一下所有的退出码与对应的信息:

#include <stdio.h>
#include <string.h>

int main()
{
    for(int i = 0; i < 200; i++)
    {
        printf("%d: %s\n", i, strerror(i));
    }
    return 0;
}

在这里插入图片描述

这就是退出码,不同的退出码代表不同的出错原因。
我们来举例子看一下:
在这里插入图片描述

退出码是2,错误信息描述是没有这样的文件或目录。跟我们上面查看的退出码以及对应信息是匹配的。
结论main函数的退出码是可以被父进程获取的,用来判断子进程的运行结果。

2.1.2 错误码

C语言还有一个错误码,errno,我们下面来学一下看看有什么不同:
我们先写一个代码测试一下:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
    printf("before: %d\n", errno);
    FILE* fp = fopen("./log.txt", "r");
    printf("after: %d, error string: %s\n", errno, strerror(errno));

    return 0;
}

我们当前路径下是不存在log.txt文件的,以读的方式打开肯定是错误的,我们打开前输出一次,打开后输出一次。
在这里插入图片描述
这说明,错误码会在调用接口的时候被设置。

错误码 vs 退出码

  • 错误码通常是衡量一个库函数或者是一个系统调用(Linux内核也是用C语言写的,所以它也可以访问errno)函数的调用情况。
  • 退出码通常是一个进程退出的时候,他的退出结果。
  • 相同点:当失败时,用来衡量 函数/进程 出错时的出错详细原因。

当我们写的代码有多个系统接口和库函数,我们可以把退出码和错误码设置成一致:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
    int ret = 0;
    printf("before: %d\n", errno);
    FILE* fp = fopen("./log.txt", "r");
    if(NULL == fp)
    {
        printf("after: %d, error string: %s\n", errno, strerror(errno));
        ret = errno;
    }

    return ret;
}

strerror()函数可以将错误码转化成错误信息。
在这里插入图片描述
错误信息一输出用户就知道是哪出错了,echo $? 输出的退出码父进程bash也就知道了。

2.1.3 代码异常终止引入

代码异常终止,一般代码都没跑完,退出码也就没意义了。
我们举两个异常的例子:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
    printf("before: %d\n", errno);
    FILE* fp = fopen("./log.txt", "r");
    if(NULL == fp)
    {
        printf("after: %d, error string: %s\n", errno, strerror(errno));
    }

    int a = 10;
    a /= 0; // 除0错误

    return 0;
}

在这里插入图片描述

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()
{
    printf("before: %d\n", errno);
    FILE* fp = fopen("./log.txt", "r");
    if(NULL == fp)
    {
        printf("after: %d, error string: %s\n", errno, strerror(errno));
    }

    int* ptr = NULL;
    *ptr = 10; // 野指针

    return 0;
}

在这里插入图片描述

野指针一般是段错误。
代码跑起来之后就是进程,出问题是进程异常了,异常后它就不跑了,操作系统管理的进程,其实是操作系统把进程杀掉了(通过发送信号的方式杀掉的)。
我们查看一下信号:

在这里插入图片描述

可以看到SIG前缀是统一的,我们刚才的两个错误分别可以转换为8号与11号信号,FPE代表Floating point exception,SEGV代表Segmentation fault。
我们再来测试一下,看看其他的信号可不可以杀掉不是对应问题的进程:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    while(1)
    {
        printf("I am a normal process: %d\n", getpid());
    }

    return 0;
}

在这里插入图片描述

我们发现,其实代码并没有错误,但是用户用的8号信号杀掉的进程,所以显示的就是8号所对应的异常信息。
结论进程出异常,异常信息会被操作系统检测出来,进而转换为信号然后杀掉进程。
最后,子进程把父进程交给的任务完成的怎么样,只要守好退出码信号编号为0就是正确,因为错误码从1开始的两个数字就可以很好的监督任务的完成程度。

2.2 进程常见退出方法

2.2.1 exit函数

我们先来查看一下exit怎么使用!
在这里插入图片描述

结论参数是进程的退出码,类似于main函数的return n。
了解了使用方法,我们来写一段代码试试:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("I am a process, pid: %d, ppid:%d\n", getpid(), getppid());
    exit(12); // 参数是进程的退出码,类似于main函数的return n

    //return 0;
}

在这里插入图片描述
我们再来看一个场景:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int func()
{
    printf("call func function done!\n");
    // 任意地点调用exit,表示进程退出,不进行后续执行
    exit(21);
}

int main()
{
    func();

    printf("I am a process, pid: %d, ppid:%d\n", getpid(), getppid());
    // 参数是进程的退出码,类似于main函数的return n
    exit(12);

    //return 0;
}

在这里插入图片描述

结论任意地点调用exit,表示进程退出,不进行后续执行。
我们可以在验证一下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int func()
{
    printf("call func function done!\n");
    // 任意地点调用exit,表示进程退出,不进行后续执行
    exit(21);
}

int main()
{
    exit(31);
    
    func();

    printf("I am a process, pid: %d, ppid:%d\n", getpid(), getppid());
    // 参数是进程的退出码,类似于main函数的return n
    exit(12);

    //return 0;
}

在这里插入图片描述

经过这次的验证说明我们得出的结论是正确的。

2.2.2 _exit函数

依旧先查看怎么使用!
在这里插入图片描述

我们来使用一下试试:

#include <stdio.h>
#include <stdlib.h>

int func()
{
    printf("call func function done!\n");
    return 11;
}

int main()
{
    func();

    printf("I am a process, pid: %d, ppid:%d\n", getpid(), getppid());
    // 参数是进程的退出码,类似于main函数的return n
    exit(12);

    //return 0;
}

在这里插入图片描述

我们发现和exit的现象是一样的。
我们再来看看:

#include <stdio.h>
#include <unistd.h>

int func()
{
    printf("call func function done!\n");
    //return 11;
    // 任意地点调用exit,表示进程退出,不进行后续执行
    _exit(21);
}

int main()
{
    func();

    printf("I am a process, pid: %d, ppid:%d\n", getpid(), getppid());
    // 参数是进程的退出码,类似于main函数的return n
    //_exit(12);

    //return 0;
}

在这里插入图片描述

我们看到,_exit和exit 它两表现出的结果是一致的,但是这并不能说明它两没有区别!
为了让大家看到不一致性,我们继续写代码来观察:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("you can see me!");
    sleep(3);
    exit(1);
}

在这里插入图片描述
在这里插入图片描述

我们打印的字符串没有\n,因为缓冲区的原因,字符串不会立即刷新出来,在进程退出后,exit对缓冲区强制刷新,才将字符串打印在屏幕上!
我们这次改为_exit来试试:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("you can see me!\n");
    sleep(3);
    _exit(1);
}

在这里插入图片描述
在这里插入图片描述
我们发现,_exit函数并不会在进程退出时对缓冲区做强制刷新!
结论:

  • exit是库函数(3号手册),_exit是系统调用(2号手册);
  • exit终止进程的时候,会自动刷新缓冲区。_exit终止进程的时候,不会自动刷新缓冲区(直接将数据扔掉了)。
  • 我们目前知道的缓冲区,绝对不在操作系统内部!(具体的后面再详谈)
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

与指定数字相同的数的个数 T1061

#include<bits/stdc.h> using namespace std; int n,m; const int N110; int f[N]; int a0; int main(){cin>>n>>m;for(int i0;i<n;i)cin>>f[i];for(int j0;j<n;j){if(f[j]m){a;}}cout<<a<<endl;return 0; }

安科瑞消防设备电源监控系统在地铁工程的设计与应用

【摘要】&#xff1a;本文介绍了地铁工程中消防设备电源监控系统设置的必要性及规范求&#xff0c;分析了监控设计方案&#xff0c;提出该系统在地铁工程中的应用要求及建议&#xff0c;以供地铁工程建设参考。消防设备电源监控系统主要针对消防用电设备的电源进行实时的监控&a…

react 之 zustand

zustand可以说是redux的平替 官网地址&#xff1a;https://zustand-demo.pmnd.rs/ 1.安装 npm i zustand2.基础使用 // zustand import { create } from zustand// 1. 创建store // 语法容易出错 // 1. 函数参数必须返回一个对象 对象内部编写状态数据和方法 // 2. set是用来…

Springboot做查询数据库某个表的数据时,后台一切正常前台显示不了数据

当我在用springboot做项目的时候查询整个表的数据或者条件查询的时候发现我的后台功能一切正常但是我的前台界面就是显示不了数据&#xff0c;这个问题解决也很简单&#xff0c;就是需要我们平时多加注意&#xff0c;不要漏代码&#xff01;&#xff01;&#xff01; Builder …

51单片机学习笔记 --步进电机驱动说明

文章目录 工作原理代码编写驱动方式全步进驱动半步进驱动微步进驱动 工作原理 工作原理简要说明&#xff0c;和单片机一起配合使用的步进电机多为28BYJ28 五线四相步进电机&#xff0c;配合ULN2003驱动板进行控制&#xff0c;如图所示&#xff0c;对于扭矩、精度要求较高的还有…

【开源】JAVA+Vue.js实现校园二手交易系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 二手商品档案管理模块2.3 商品预约管理模块2.4 商品预定管理模块2.5 商品留言板管理模块2.6 商品资讯管理模块 三、实体类设计3.1 用户表3.2 二手商品表3.3 商品预约表3.4 商品预定表3.5 留言表3.6 资讯…

2024年第九届信号与图像处理国际会议(ICSIP 2024)

2024第九届信号与图像处理国际会议&#xff08;ICSIP 2024&#xff09;将于2024年7月12-14日在中国南京召开。ICSIP每年召开一次&#xff0c;在过去的七年中吸引了1200多名与会者&#xff0c;是展示信号和图像处理领域最新进展的领先国际会议之一。本次将汇集来自亚太国家、北美…

Multisim14.0仿真(四十三)LM311应用

一、LM311简介&#xff1a; lm311是一款高灵活性的电压比较器&#xff0c;能工作于5V-30V单个电源或正负15V分离电源。 二、LM311主要特性&#xff1a; ★ 快速响应时间&#xff1a;165 ns。 ★ 选通能力。 ★ 最大输入偏置电流&#xff1a;300nA。 ★ 最大输入偏置电流&#…

【LLM KBQA】FlexKBQA:一种结合LLM的KBQA框架

前言 大语言模型&#xff08;LLMs&#xff09;在知识库问答&#xff08;KBQA&#xff09;领域的应用主要集中在包括但不限于以下几个方面&#xff1a; 直接生成答案&#xff1a;一些方法直接利用LLMs生成答案&#xff0c;而不是生成中间的程序&#xff08;如SPARQL查询&#…

iTOP-3A5000开发板支持PCIE 3.0、USB 3.0和 SATA 3.0.显示接口2 路、HDMI 和1路VGA等

性能强 采用全国产龙芯3A5000处理器&#xff0c;基于龙芯自主指令系统 (LoongArch)的LA464微结构&#xff0c;并进一步提升频率&#xff0c;降低功耗&#xff0c;优化性能。 桥片 采用龙芯 7A2000&#xff0c;支持PCIE 3.0、USB 3.0和 SATA 3.0.显示接口2 路、HDMI 和1路 VGA…

C++ pair+map+set+multimap+multiset+AVL树+红黑树(深度剖析)

文章目录 1. 前言2. 关联式容器3. pair——键值对4. 树形结构的关联式容器4.1 set4.1.1 set 的介绍4.1.2 set 的使用 4.2 map4.2.1 map 的介绍4.2.2 map 的使用 4.3 multiset4.3.1 multiset 的介绍4.3.2 multiset 的使用 4.4 multimap4.4.1 multimap 的介绍4.4.2 multimap 的使…

【TCP】四次挥手(终止连接)

前言 TCP&#xff08;传输控制协议&#xff09;是互联网协议&#xff08;IP&#xff09;中的一种重要传输层协议&#xff0c;用于在通信的计算机之间建立可靠的、有序的和错误校验的数据传输。在TCP连接中&#xff0c;数据传输是双向的&#xff0c;因此需要一种机制来开始和结…

【操作宝典】IntelliJ IDEA新建maven项目详细教程

目录 &#x1f33c;1. 配置maven环境 &#x1f33c;2. 创建maven项目 &#x1f33c;3. 创建maven项目完整示例 a. 导入spring boot环境 b. 修改maven配置 c. 下载jar包 d. 创建Java类 &#x1f33c;1. 配置maven环境 【安装指南】maven下载、安装与配置详细教程-CSDN博客…

多层面深度分析【HarmonyOS NEXT】开发者内容

一、IDE工具层面 DevEco Studio作为专门用于开发鸿蒙操作系统&#xff08;HarmonyOS&#xff09;的应用程序的集成开发环境&#xff08;IDE&#xff09;&#xff0c;提供一个清晰、直观的用户界面&#xff0c;使得开发人员可以更容易地进行编码、调试和测试我们的应用。新的版…

eduSRC那些事儿-4(未授权漏洞+社会工程学)

点击星标&#xff0c;即时接收最新推文 本文对edusrc挖掘的部分漏洞进行整理&#xff0c;将案例脱敏后输出成文章&#xff0c;不包含0DAY/BYPASS的案例过程&#xff0c;仅对挖掘思路和方法进行相关讲解。 未授权漏洞 一般都出现在内网&#xff0c;也有外网出现的情况&#xff0…

C语言指针的几种用途

先看题目&#xff0c;写一个fun函数&#xff0c;统计一个字符串中某个字符出现的次数&#xff0c;以及这个字符第一次出现的位置。 看起来很简单&#xff0c;似乎几行就可以搞定&#xff0c;但是写出来之后&#xff0c;才发现代码怎么这么长&#xff01;程序里多处使用了指针&…

问题:在电容耦合的放大电路中,耦合电容对输入交流信号应可视为( )。 #微信#媒体#学习方法

问题&#xff1a;在电容耦合的放大电路中&#xff0c;耦合电容对输入交流信号应可视为( )。 A:电流源; B:断路; C:短路; D:电压源 参考答案如图所示

CSS要点总结

一、CSS 快速入门 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>css 快速入门</title><!-- 解读1. 在 head 标签内&#xff0c;出现了 <style type"text/css"></style…

美国纳斯达克大屏怎么投放:投放完成需要多长时间-大舍传媒Dashe Media

陕西大舍广告传媒有限公司&#xff08;Shaanxi Dashe Advertising Media Co., Ltd&#xff09;&#xff0c;简称大舍传媒&#xff08;Dashe Media&#xff09;&#xff0c;是纳斯达克在中国区的总代理&#xff08;China General Agent&#xff09;。与纳斯达克合作已经有八年的…

大数据平台-可视化面板介绍-Echarts

应对现在数据可视化的趋势&#xff0c;越来越多企业需要在很多场景(营销数据&#xff0c;生产数据&#xff0c;用户数据)下使用&#xff0c;可视化图表来展示体现数据&#xff0c;让数据更加直观&#xff0c;数据特点更加突出。 目录 01-使用技术 02- 案例适配方案 03-基础…