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

目录

创建进程

写时拷贝

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语言库函数

NAMEexit - cause normal process terminationSYNOPSIS#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.hqwc.cn/news/597797.html

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

相关文章

渗透测试技巧总结67条

Tips 1. 手动端口探测 nmap的-sV可以探测出服务版本&#xff0c;但有些情况下必须手动探测去验证 使用Wireshark获取响应包未免大材小用&#xff0c;可通过nc简单判断 eg. 对于8001端口&#xff0c;nc连接上去&#xff0c;随便输入一个字符串&#xff0c;得到了以下结果&am…

制作framework

参考学习地址 https://www.jianshu.com/p/a15ad98bc965 注意事项&#xff1a; 1、在自动生成的.h文件中引入头文件时&#xff0c;需要完整路径 2、编译成功后如何查看位置 3、合并模拟器和真机 4、最后如何使用 合并并替换后&#xff0c;就把真机部分复制出来使用就行 5、…

实战搭建网易有道的QAnything(一) 前提准备工作

前言&#xff1a; 早上地铁上刷到了关于有道的QAnything的介绍&#xff0c;刚好也有搭建一个知识库的想法&#xff0c;既然有想法那就干起来&#xff0c;电脑的操作系统用的win11&#xff0c;显卡用了两块4060。 一、安装windows子系统 1. 开始-》运行-》控制面板 打开原始的控…

JS小项目-计算器

需求&#xff1a;根据素材制作如图所示页面&#xff0c;在页面输入第一个数和第二个数&#xff0c;单击&#xff08;加&#xff09;、&#xff0d;&#xff08;减&#xff09;、&#xff0a;&#xff08;乘&#xff09;、&#xff0f;&#xff08;除&#xff09;按钮时&#xf…

二 maven构建项目

一 Maven的GAVP Maven工程相对之前的工程&#xff0c;多出一组gavp属性&#xff0c;gav需要我们在创建项目的时指定&#xff0c;p有默认值&#xff0c;后期通过配置文件修改。 GAVP是指 GroupId、ArtifactId、Version、Packaging 等四个属性的缩写&#xff0c;其中前三个是必…

Windows系统C盘空间优化进阶:磁盘清理与Docker日志管理

Windows系统C盘空间优化进阶&#xff1a;磁盘清理与Docker日志管理 文章目录 Windows系统C盘空间优化进阶&#xff1a;磁盘清理与Docker日志管理磁盘清理工具 使用“运行”命令访问磁盘清理利用存储感知自动管理空间清理WinSxS文件夹结合手动清理策略 小结删除临时文件总结&…

虚拟机打不开

问题 另一个程序已锁定文件的一部分&#xff0c;进程无法访问 打不开磁盘“G:\centeros\hadoop104kl\hadoop100-cl2.vmdk”或它所依赖的某个快照磁盘。 模块“Disk”启动失败。 未能启动虚拟机。 原因 前一次非正常关闭虚拟机导致.lck 文件是VMWare软件的一种磁盘锁文件&…

购买代码签名证书时需提供哪些认证资料?

在软件开发与发布过程中&#xff0c;确保软件的可靠性和完整性至关重要&#xff0c;为此购买代码签名证书是必不可少的环节。然而&#xff0c;许多开发者对于购买该证书所需的具体材料并不十分清楚。下面就为大家详细介绍购买代码签名证书所需材料&#xff0c;助您更好地筹备和…

HTTPS证书是什么?怎么获取?

HTTPS证书&#xff0c;全称是安全套接层&#xff08;SSL&#xff09;或传输层安全&#xff08;TLS&#xff09;证书&#xff0c;是一种数字证书&#xff0c;用于在互联网上建立安全的加密连接&#xff0c;确保数据在客户端&#xff08;如Web浏览器&#xff09;与服务器端&#…

Scaling Law解析

文章目录 scaling law一个token的计算量幂律关系幂律规律实际指导 scaling law 幂律法则&#xff1a;对大模型数据量、参数量、算力之间的最优分配 不仅仅是对语言大模型&#xff0c;对主要基于tranformer的多模态大模型基本都有效 对于Decoder-only结构模型(GPT架构)&#…

springboot整合ShardingSphere分库分表并插入1kw条记录

目录 一&#xff0c;数据分片 二&#xff0c;水平分片 三&#xff0c;创建数据库表 四&#xff0c;springboot项目导入依赖 五&#xff0c;创建类 六&#xff0c;bug bug放到最后了。 一&#xff0c;数据分片 数据分片指按照某个维度将存放在单一数据库中的数据分散地存…

鸿蒙TypeScript 开发学习第9天:【TypeScript Number】

1、TypeScript Number TypeScript 与 JavaScript 类似&#xff0c;支持 Number 对象。 Number 对象是原始数值的包装对象。 语法 var num new Number(value);复制注意&#xff1a; 如果一个参数值不能转换为一个数字将返回 NaN (非数字值)。 2、Number 对象属性 下表列出…