关于Linux下的进程创建与终止(进程篇 - 涉及写时拷贝,fork函数)

news2024/11/17 10:56:12

目录

创建进程

写时拷贝

fork函数

进程终止

进程终止时,操作系统都做了什么?

进程终止的常见方式有哪些?

如何使用代码终止掉一个进程?


创建进程

写时拷贝

在了解下面的内容之前,我们需要先聊一聊写时拷贝这一概念。

什么是写时拷贝呢?

        通常,父进程与子进程的代码是共享的,父进程与子进程不再进行写入时,数据也是共享的,当任意一方试图写入的时候,便会以写时拷贝的方式各自生成一份副本,这项技术被称为写时拷贝。

示例:

为什么会存在写实拷贝这一项技术呢?

        一般而言,创建子进程,子进程是必须要独立出去的,因为进程是具有独立性的,理论上,子进程也必须要有自己的代码以及数据。

        可是,fork创建子进程,我们并没有加载的过程,这也就意味着,子进程并没有自己的代码与数据! 所以,子进程必须 “使用” 父进程的代码与数据。

        代码:都是不可以被写的,进程只有读取的权限,所以说父子进程共享代码,没有问题。但是

        数据:是可以被父进程或者子进程更改的,所以从这一方面来说,必须分离。

对于数据来说:

        创建进程的时候,就直接进行拷贝分离,吗???可是拷贝子进程根本不会用到的空间,或者拷贝到子进程只会读取但是不会写入的空间,无疑增大了内存的负担。那么什么样的数据子进程会进行写入呢???系统根本无法提前预知哪些数据会被写入。

        所以,系统选择了写实拷贝的技术,来实现父进程与子进程的数据分离,完成了进程独立性的技术保障。子进程或者父进程需要进行写入的时候,系统再进行分配空间,必要的时候拷贝原数据。这是高效使用内存的一种表现。

也可以参考程序地址空间这一概念

fork函数

linux fork 函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1
进程调用 fork ,当控制转移到内核中的 fork 代码后,内核做:
  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

注:fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

示例1:

示例2:

fork函数用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。
  • 一个进程要执行一个不同的程序

fork函数调用失败原因

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

fork的两个返回值是什么?

给父进程返回子进程的pid,给子进程返回0

fork为什么会有两个返回值?

因为在实现fork这个函数的时候,在这个函数内部,return返回之前,fork函数就已经开始生效了,这也就意味着return会被执行两次,一次是原本的父进程,一次是fork在函数创建子进程的返回值。

示例:在实现fork函数的内部,就已经是两个执行流了

一个变量怎么可能同时保存不同的值?

pid_t id = fork();

因为return会被执行两次。
return的本质,不就是对id进行写入吗!
此时发生了写时拷贝!,所以父子进程各自其实在物理内存中,有属于自己的变量空间!只不过在用户层用同一个变量(虚拟地址!)来标识了。也可以参考程序地址空间这一概念

问题:fork之后,是调用fork函数这一行之后的代码开始与父进程共享?还是父进程所有的代码都与子进程共享??

注:pc:程序计数器(当前正在执行代码的下一行代码的地址)

示例:

  • 1.我们的代码汇编之后,会有很多行代码,而且每行代码加载到内存之后,都有对应的地址
  • 2.因为进程随时可能被中断(可能并没有执行完),下次回来,还必须从之前的位置继续运行(不是最开始哦!),就要要求CPU必须随时记录下,当前进程执行的位置,所以,CPU内有对应的寄存器数据这就是进程的上下文数据),用来记录当前进程的执行位置!
  • 3.寄存器在CPU内,只有一份,寄存器内的数据,是可以有多份的!
  • 创建的时候,要不要给子进程  ---  进程的上下文数据?  答案是需要的。
    所以,虽然父子进程各自调度,各自会修改EIP,但是已经不重要了,因为子进程已经认为自己的EIP起始值,就是fork之后的代码!!
     


进程终止

进程终止时,操作系统都做了什么?

谈到进程终止,首先我们需要明白,创建进程时,操作系统都做了些什么?

        创建进程 ---> 操作系统要管理进程 ---> 创建内核数据结构test_struct ---> 创建对应的地址空间mm_struct ---> 创建页表,构建映射关系 ---> 在一定程度上,将该进程对应的代码和数据加载至内存

        所以,进程终止,就是操作系统释放进程申请的相关数据结构和对应的数据与代码(本质上就是释放内存资源,也可能是CPU或者是磁盘等等,但是只会占很少的一部分,更多的还是内存)。

进程终止的常见方式有哪些?

情况示例:

a.代码跑完,结果正确

b.代码跑完,结果不正确

c.代码没有跑完,程序崩溃

老样子,谈上述前两种情况之前,先来聊一聊main函数的返回值。

        好像从我们开始学习写代码的时候,老师就告诉我们,main函数的最后一行要写 return 0;可是为什么?mian函数返回的意义是什么?return 0的含义是什么? return 其他的值可不可以?

        答案是这样的,main函数返回的意义是返回给上一级进程,用来评判该进程的执行结果;return 0代表的是进程的退出码;可以return其他值,并不总是0;这里0标识 sucesss,非0标识运行结果不正确。

        非0值有很多,不同的非0值就可以标识不同的错误原因,这样在程序结束之后,当结果不正确时,方便我们快速定位原因。

示例:Linux下可以通过strerror函数来获取系统的错误信息。

strerror

#include<string>

char* strerror(int errnum);

示例1: 

Linux下的运行结果:

另外,Linux下,我们也可以通过指令,来获取进程的退出码

echo $? : 获取最近一个进程执行完毕的退出码

示例2:

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

int sum(int top)
{
    int s = 0;
    int i = 0;
    for(i = 1; i <= top; i++)
    {
        s += i;
    }

    return s;
}

int main()
{
    int ret = 0;

    int res = sum(100);
    if(res != 5050)
    {
        //代码运行结果不正确
        ret = 1;
    }
    
    return ret;
}

运行结果:

当我们故意调整一下逻辑:

int sum(int top)
{
    int s = 0;
    int i = 0;
    for(i = 1; i < top; i++)
    {
        s += i;
    }

    return s;
}

运行结果:

        因为函数是我们自己实现的,所以我们可以对返回值做判断,来确定程序运行的结果是正确的还是不正确的,进而设置main函数的返回值

示例3:

示例4:这里使用kill指令发送9号信号杀掉11111进程,但是系统此时是没有11111进程的

按道理来说这里的错误码应该是3啊。怎么会是1呢??? --- 因为这里的退出码叫自定义退出码,是可以自定义设置的

所以:我们自己可以使用这些退出码和含义,但是,如果你想自己定义,也可以自己设计一套退出方案!

关于情况c.代码没有跑完,程序崩溃

示例:这是经典的野指针错误

int main()
{
    printf("hello world\n");
    printf("hello world\n");
    printf("hello world\n");

    int *p = NULL;
    *p = 12345; // 野指针

    printf("hello world\n");
    printf("hello world\n");

    return 0;
}

输出结果:段错误

程序崩溃的时候,退出码无意义。一般而言退出码对应的return语句,没有被执行,即使被执行,我们也不会关心,因为我们更想知道,程序为什么会崩溃?
 

如何使用代码终止掉一个进程?

  • 1. return语句,就是终止进程的 !
  • 2. exit函数在代码的任何地方调用,都表示直接终止进程
     

注意: main函数内的return是终止进程,main函数调用其他函数,其他函数内部的return不是终止进程,而是return返回

        main函数内的 exit函数 是终止进程,main函数调用其他函数,其他函数内部的 exit函数 依旧会直接终止进程

示例1: 

int main()
{
    printf("hello world\n");
    printf("hello world\n");
    printf("hello world\n");

    return 1;

    printf("hello world\n");
    printf("hello world\n");

    return 0;
}

输出:

示例2:

int main()
{
    printf("hello world\n");
    printf("hello world\n");
    printf("hello world\n");

    exit(111);

    printf("hello world\n");
    printf("hello world\n");

    return 0;
}

输出:

示例3:

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

int sum(int top)
{
    int s = 0;
    int i = 0;
    for(i = 1; i < top; i++)
    {
        s += i;
    }

    exit(222);

    return s;
}

int main()
{
    printf("hello world\n");
    printf("hello world\n");
    printf("hello world\n");

    exit(111);

    printf("hello world\n");
    printf("hello world\n");

    return 0;
}

输出:

深入了解

_exit函数 :Linux下的系统接口

#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然 status int ,但是仅有低 8 位可以被父进程所用。所以 _exit(-1) 时,在终端执行 $? 发现返回值是255

exit函数 :c语言库函数

NAME
       exit - cause normal process termination

SYNOPSIS
       #include <stdlib.h>

       void exit(int status);
exit 最后也会调用 exit, 但在调用 exit 之前,还做了其他工作:
  • 1. 执行用户通过 atexit或on_exit定义的清理函数。
  • 2. 关闭所有打开的流,所有的缓存数据均被写入
  • 3. 调用_exit

二者区别:

c语言库–会把缓冲区的内容打印在显示器

Linux系统接口–不会把缓冲区内容打印在显示器

return 退出
return 是一种更常见的退出进程方法。执行 return n 等同于执行 exit(n), 因为调用 main 的运行时函数会将 main 的返回值当做 exit 的参数。

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

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

相关文章

MySQL-基本SQL语句编写:运算符练习

运算符练习 1.选择工资不在5000到12000的员工的姓名和工资 SELECT last_name,salary FROM employees #where salary not between 5000 and 12000; WHERE salary < 5000 OR salary > 12000;2.选择在20或50号部门工作的员工姓名和部门号 SELECT last_name,department_id…

ChatGPT 之联盟营销

原文&#xff1a;ChatGPT for Affiliate Marketing 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第二章 制定转化对话 制定转化对话是每个营销人员和企业所有者都应该掌握的关键技能。它涉及创建和传递引人入胜的信息&#xff0c;吸引您的受众并激励他们采取行动。…

【windows】--- nginx 超详细安装并配置教程

目录 一、下载 nginx二、安装三、查看是否安装成功四、配置五、关闭 nginx六 负载均衡七 配置静态资源1. 根目录下的子目录(root)2.完全匹配(alias) 刷新配置&#xff08;不必重启nginx&#xff09;八、后端鉴权 一、下载 nginx 打开 nginx 的官网&#xff1a;nginx.org/ &…

【操作系统】STM32-操作系统——持续更新

【操作系统】STM32-操作系统——持续更新 文章目录 前言一、ucosii二、freertos1.介绍2.移植 总结 前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、ucosii UCOSII移植到STM32F103C8T6上之移植记录&#xff08;一&#xff09; UCOSII移植到ST…

GIS 如何帮助减少交通排放

GIS 可通过多种方式帮助减少交通排放。从智能路线和减少拥堵&#xff0c;到鼓励人们骑自行车或步行&#xff0c;以及帮助指导政府交通政策&#xff0c;GIS 正在帮助改善空气质量。 GIS 长期以来一直被用来提高各行业多种类型车辆行驶的效率&#xff0c;并将继续这样做。通过分…

CURL状态码60问题解决

问题说明 就是没法验证ssl证书是否合法 时间长了&#xff0c;系统自带的ca肯定不会有新的ca机构增加 解决方法 CentOS下 yum update ca-certificates 或者手动更新指定位置上的ca文件 手动下载 https://curl.se/docs/caextract.html 临时处理方案(不推荐) curl_setopt(…

网站统计中的数据收集原理及实现

网站数据统计分析工具是网站站长和运营人员经常使用的一种工具&#xff0c;比较常用的有谷歌分析、百度统计和腾讯分析等等。所有这些统计分析工具的第一步都是网站访问数据的收集。目前主流的数据收集方式基本都是基于javascript的。本文将简要分析这种数据收集的原理&#xf…

C++ 静态库与动态库的生成和使用:基于 VS Studio 生成 newmat 矩阵库的静态库与动态库

文章目录 Part.I IntroductionChap.I 预备知识Chap.II 静态库与动态库区分 Part.II 静态库的生成与使用 (newmat)Chap.I 生成静态库Chap.II 使用静态库 Part.III 动态库的生成与使用 (newmat)Chap.I 生成动态库Chap.II 使用动态库 Part.IV 文件内容Chap.I test.cpp (静态库)Cha…

linux常用目录结构(目录命令)--6986字详谈

前面与大家讨论了linux的发展与由来&#xff08;这一块挺多的&#xff0c;小编还没有编写完成&#xff0c;希望大家理解&#xff09;&#xff0c;紧接着谈到了vmware安装及运行所存在的故障&#xff08;鉴定错误&#xff0c;虚拟机没有网&#xff0c;蓝屏等常见现象的总结及处理…

学透Spring Boot — 004. Spring Boot Starter机制和自动配置机制

如果你项目中一直用的是 Spring Boot&#xff0c;那么恭喜你没有经历过用 Spring 手动集成其它框架的痛苦。 都说 Spring Boot 大大简化了 Spring 框架开发 Web 应用的难度&#xff0c;这里我们通过配置 Hibernate 的两种方式来深刻体会这一点&#xff1a; 使用 Spring 框架集…

centos7.2系统部署ZooKeeper集群和Kafka集群(集群应用系统商城前置环境)

本次实验将使用centos7.2系统部署部署ZooKeeper集群因为Kafka依赖于ZooKeeper&#xff0c;所以我们一并进行部署。 实验所示的资源软件已上传至百度网盘&#xff0c;需要自取。 链接&#xff1a;https://pan.baidu.com/s/1a-7_iAIX0DBAMkF9bhiTcA?pwd2333 提取码&#xff1…

C++:stack类和queue类

stack的介绍和使用 1. stack 是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。 2. stack 是作为容器适配器被实现的&#xff0c;容器适配器即是对特定类封装作为其底层的容器&#xff0c;并…

壁纸小程序Vu3(预览页面:弹窗)

1.展示跳转后的分类列表图片 classlist.vue <template><view class"classlist"><view class"content"><navigator class"item" v-for"item in 10"><image src"../../common/images/64.png" mode…

DFS(排列数字、飞机降落、选数、自然数的拆分)

注&#xff1a;1.首先要知道退出条件 2.还原现场 典型&#xff1a;全排列 题目1&#xff1a; 代码&#xff1a; #include<bits/stdc.h> using namespace std; int a[1005],p[1005],v[1005]; int n; void dfs(int x) {//此次dfs结束条件,即搜到底 if(xn1){for(int i1;i&…

AcWing刷题-计算系数

计算系数 代码&#xff1a; MOD 10007 a, b, k, n, m [int(x) for x in input().split()]def pow(a, n):r 1 % MODwhile n:if n & 1: r r * a % MODa a * a % MODn >> 1return rdef C(n, m):r 1 % MODfor i, j in zip(range(n, -1, -1), range(1, m 1)):r …

CLR学习

视频链接&#xff1a;《CLR十分钟》系列之CLR运行模型_哔哩哔哩_bilibili 什么是 CLR 公共语言运行时&#xff08;Common Language Runtime CLR&#xff09; 是一个可有多种编程语言使用的 运行时&#xff0c;CLR 的核心功能&#xff08;比如 内存管理&#xff0c;程序集加载…

解锁网络安全新境界:雷池WAF社区版让网站防护变得轻而易举!

网站运营者的救星&#xff1a;雷池WAF社区版 ️ 嘿朋友们&#xff01;今天我超级激动要跟你们分享一个神器——雷池WAF社区版。这个宝贝对我们这帮网站运营者来说&#xff0c;简直就是保护伞&#xff01; 智能语义分析技术&#xff1a;超级侦探上线 先说说为啥我这么稀饭它。雷…

Qt环形颜色选择控件, 圆环颜色选择器

参考文章Qt编写自定义控件&#xff1a;环形颜色选择控件_qconicalgradient圆环渐变-CSDN博客 感谢作责提供的方法&#xff0c;下面程序的基础思路同参考文章。 为了更方便使用&#xff0c;这个选择器是基于64色表的&#xff0c;会显示选中的索引和色值。颜色选择时计算方式也…

Vite源码学习--调试源码

简介 当我们开始构建越来越大型的应用时&#xff0c;需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈&#xff1a;通常需要很长时间&#xff08;甚至是几分钟&#xff01;&#xff09;才能启…

接口日志表结构

表&#xff1a;ZTALL_IFLOG MANDT MANDT CLNT 3 0 0 客户端 UUID SYSUUID_C32 CHAR 32 0 0 16-byte UID in 32 chars (hexadecimal) IFSNR ZE_IFSNR CHAR 30 0 0 接口编号(系统ID流水号) FUNCNAME RS38L_FNAM CHAR 30 0 0 功能模块的名称 STATUS BAPI_MTYPE CHAR 1 0 0 消息类…