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

news2024/12/22 1:25:23

在这里插入图片描述

文章目录

  • 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终止进程的时候,不会自动刷新缓冲区(直接将数据扔掉了)。
  • 我们目前知道的缓冲区,绝对不在操作系统内部!(具体的后面再详谈)
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从源代码看Chrome 版本号

一直以来都是用Chrome 浏览器&#xff0c;但是看到Chrome 点分4 组数据的表达方式&#xff0c;总是感觉怪怪的&#xff0c;遂深入源代码了解她的版本号具体表示的内容 chrome 浏览器中显示的版本号 源代码中的版本号标识 版本号文件位于 chrome/VERSION &#xff0c; 看到源代…

Docker上安装配置tomcat

目录 1. 拉取镜像 2. 创建运行镜像 3. 查看是否创建成功 ps&#xff1a;如果出现404错误 tomcat目录结构 1. 拉取镜像 这里使用 tomcat:8.5.40 版本作为安装 docker pull tomcat:8.5.40 2. 创建运行镜像 docker run -d --name tomcat -p 8080:8080 \--privilegedtrue …

2024最新版Sublime Text 4安装使用指南

2024最新版Sublime Text 4安装使用指南 Installation and Usage Guide to the Latest Sublime Text 4 in 2024 By JacksonML 0. Sublime Text是什么&#xff1f; Sublime Text 由自定义组件构建&#xff0c;支持Python, Java, C/C等多种编程语言&#xff0c;并为用户提供无与…

记一次 Android CPU高使用率排查

文章目录 背景排查高占用的进程adb shelltoptop -b -H -n 1 | grep 29337 (打印各线程 cpu使用详情)kill -3 29337 (生成trace文件)adb pull /data/anr /Users/gerry.liang/Desktop定位问题 补充说明: 背景 测试同学反馈我们的App CPU使用率 90% 居高不下,经过一番艰难的排查后…

flutter如何实现省市区选择器

前言 当我们需要用户填写地址时&#xff0c;稳妥的做法是让用户通过“滚轮”来滑动选择省份&#xff0c;市&#xff0c;区&#xff0c;此文采用flutter的第三方库来实现这一功能&#xff0c;比调用高德地图api简单一些。 流程 选择库 这里我选择了一个最近更新且支持中国的…

本体匹配方法概述

目录 前言1 基于术语匹配的方法1.1 字符串匹配1.2 语言方法 2 基于虚拟文档的方法3 基于结构的匹配方法3.1 结构信息利用3.2 Anchor-prompt方法 4 大型本体匹配4.1 本体划分4.2 匹配分块4.3 实体间映射 结语 前言 本文将深入探讨本体匹配的不同方法&#xff0c;从基于术语匹配…

Python||五城P.M.2.5数据分析与可视化_使用华夫图分析各个城市的情况(上)

目录 五城P.M.2.5数据分析与可视化——北京市、上海市、广州市、沈阳市、成都市&#xff0c;使用华夫图分析各个城市的情况 1.北京市的空气质量 2.广州市的空气质量 【上海市和成都市空气质量情况详见下期】 五城P.M.2.5数据分析与可视化——北京市、上海市、广州市、沈阳市、成…

108.乐理基础-五线谱-五线谱的拍号、音符与写法

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;十五度记号-CSDN博客 上一个内容联系的答案&#xff1a; 调号、拍号、速度的性质与内涵跟简谱里一样&#xff0c;所以这里只会写它们在五线谱里造型上有什么改变&#xff0c;该怎样去识别。 拍号&#xff1a;五线…

深度学习入门笔记(七)卷积神经网络CNN

我们先来总结一下人类识别物体的方法: 定位。这一步对于人眼来说是一个很自然的过程,因为当你去识别图标的时候,你就已经把你的目光放在了图标上。虽然这个行为不是很难,但是很重要。看线条。有没有文字,形状是方的圆的,还是长的短的等等。看细节。纹理、颜色、方向等。卷…

聚观早报 | 360 AI搜索App上线;岚图汽车与京东达成合作

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 1月30日消息 360 AI搜索App上线 岚图汽车与京东达成合作 三星电子在硅谷新设实验室 小米平板7系列参数曝光 Spa…

【文本到上下文 #8】NLP中的变形金刚:解码游戏规则改变者

一、说明 欢迎来到我们对不断发展的自然语言处理 &#xff08;NLP&#xff09; 领域的探索的第 8 章。在本期中&#xff0c;我们将重点介绍一项重塑 NLP 格局的突破性创新&#xff1a;Transformers。在我们之前对 seq2seq 模型、编码器-解码器框架和注意力机制的讨论之后&#…

OpenCV+ moviepy + tkinter 视频车道线智能识别项目源码

项目完整源代码&#xff0c;使用 OpenCV 的Hough 直线检测算法&#xff0c;提取出道路车道线并绘制出来。通过tkinter 提供GUI界面展示效果。 1、导入相关模块 import matplotlib.pyplot as plt import numpy as np import cv2 import os import matplotlib.image as mpimg …

第三篇:跨平台QT开发-元对象系统

元对象系统 元对象系统是一个基于 标准 C的扩展 &#xff0c;为 Qt 提供了信号与槽机制、实时类型信息,动态属性系统 元对象系统的三个基本条件&#xff1a;类必须继承自 QObject、类声明 Q_OBJECT 宏&#xff08;默认私有 有&#xff09;、元对象编译器 moc。 class ExamDia…

Golang `crypto/hmac` 实战指南:代码示例与最佳实践

Golang crypto/hmac 实战指南&#xff1a;代码示例与最佳实践 引言HMAC 的基础知识1. HMAC 的工作原理2. HMAC 的应用场景 Golang crypto/hmac 库概览1. 导入和基本用法2. HMAC 的生成和验证3. crypto/hmac 的特性 实战代码示例示例 1: 基本的 HMAC 生成示例 2: 验证消息完整性…

[.NET] 查询当前已安装所有 Win32 与 UWP 应用

为了获取当前设备用户已安装的所有应用程序, 一般来讲有两种方案. 一种是通过查询 “shell:AppsFolder” 目录下所有项, 一种是从开始菜单中获取所有快捷方式, 然后加上查询所有已安装的 UWP 应用, 最后得到总列表. 如需代码参考, 请看 github.com/SlimeNull/WindowsAppsQuery …

拥抱个人成长与社会进步:自我认知与开放心态的相互影响

拥抱个人成长与社会进步&#xff1a;自我认知与开放心态的相互影响 Embracing Personal Growth and Societal Progress: The Interplay of Self-Awareness and Open-mindedness 一、引言 I. Introduction 在当今急速发展的时代&#xff0c;个人成长与社会进步交织在一起&…

必收藏面试题:什么是SQL注入?以及Mybatis中#号和$号之间的区别?

本文大纲&#xff1a; 先分析什么是SQL注入漏洞&#xff1f;再分析#{}和${}之间的区别再基于MybatisPlus做验证再介绍#{}和${}的使用场景 什么是SQL注入&#xff1f; 先看两段代码&#xff0c;假如id的值为字符串"100"&#xff0c;大家可以顺便想想每段代码最后拼…

玩美移动为花西子海外官网打造AR虚拟试妆决方案

全球领先的增强现实&#xff08;AR&#xff09;及人工智能&#xff08;AI&#xff09;美妆科技领导者及玩美系列APP开发商——玩美移动&#xff08;纽交所代码&#xff1a;PERF&#xff09;于近日宣布携手知名美妆品牌花西子&#xff0c;在其线海外官方网页提供多项彩妆虚拟试妆…

链表——C语言——day17

链表 链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。在用数组存放数据时&#xff0c;必须事先定义固定的长度&#xff08;即元素个数&#xff09;。链表则没有这种缺点&#xff0c;它根据需要开辟内存单元。 链表有一个“头指针“变量&#xff0c;图中…

电脑怎么录屏?打造专业级视频内容!

随着科技的进步&#xff0c;电脑已经深入到我们的日常生活和工作中。而在这个数字时代&#xff0c;录制屏幕内容变得日益重要。无论是制作教程、分享游戏技巧&#xff0c;还是记录重要的演示&#xff0c;录屏都是一个不可或缺的功能。可是电脑怎么录屏呢&#xff1f;本文将深入…