【Linux学习】进程地址空间与写时拷贝

文章目录

  • Linux进程内存布局图:
      • 内存布局的验证
  • 进程地址空间
    • 写时拷贝


Linux进程内存布局图:

地址空间的范围,在32位机器上是2^32比特位,也就是[0,4G]。
在这里插入图片描述

内存布局的验证

  • 代码验证内存布局: 验证代码:
   #include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/types.h>int init=10;int uninit;int main(){printf("code addr:%p\n",&main);printf("init addr:%p\n",&init);printf("uninit addr:%p\n",&uninit);char* heap = (char* )malloc(20);printf("heap addr:%p\n",heap);printf("stack addr:%p\n",&heap);return 0;                                                                                                                             }

运行结果及分析:根据下图运行结果分析,验证了上图的内存分布。
在这里插入图片描述

  • 验证堆向上增长与栈向下增长:

验证代码:

    char* heap1 = (char* )malloc(20);char* heap2 = (char* )malloc(20);char* heap3 = (char* )malloc(20);char* heap4 = (char* )malloc(20);char* heap5 = (char* )malloc(20);printf("heap1 addr:%p\n",heap1);printf("heap2 addr:%p\n",heap2);printf("heap3 addr:%p\n",heap3);printf("heap4 addr:%p\n",heap4);printf("heap5 addr:%p\n",heap5);printf("stack1 addr:%p\n",&heap1);printf("stack2 addr:%p\n",&heap2);printf("stack3 addr:%p\n",&heap3);printf("stack4 addr:%p\n",&heap4);printf("stack5 addr:%p\n",&heap5);                                                                                                    

运行结果:堆向上增长,栈向下减小,与内存分布图一样。
结论:堆栈相对而生。

在这里插入图片描述

  • 验证命令行参数与环境变量:

验证代码:

   int main(int argc,char* argv[],char* env[]){for(int i = 0;argv[i];i++){printf("&argv[%d]:%p \n",i,argv+i);}for(int i = 0;env[i];i++){printf("&env[%d]:%p \n",i,env+i);}return 0;}

运行结果及分析:环境变量与命令行参数这两张表(不是表指向的内容),比栈区大,其中,是先有命令行参数这张表,才有环境变量这张表。
在这里插入图片描述

  • 验证表指向的内容的地址存放:

    注意区分下面代码与上面代码的不同!
    验证代码:

   int main(int argc,char* argv[],char* env[]){for(int i = 0;argv[i];i++){printf("argv[%d]:%p \n",i,argv[i]);}for(int i = 0;env[i];i++){printf("env[%d]:%p \n",i,env[i]);}return 0;}

结果+分析:无论是表还是表指向的项目,都在栈上部的。
在这里插入图片描述

  • 验证静态变量在内存分布中的位置:
    这里就不验证了,直接得出结论:静态变量是存放在初始化数据与未初始化数据之间的。静态变量默认是会被初始化的,哪怕用户定义出来没有赋值,编译器也会初始化。例如int 类型的静态变量,会被编译器初始化为0;

看看一个这样的代码
代码:

   #include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/types.h>int g_val = 1000;int main(){pid_t id = fork();if(id==0){//子进程while(1){printf("child  pid:%d  ppid:%d  g_val=%d  &g_val:%p\n",getpid(),getppid(),g_val,&g_val);sleep(1);}}//父进程                                                                                                                              else{while(1){printf("father  pid:%d  ppid:%d  g_val=%d  &g_val:%p\n",getpid(),getppid(),g_val,&g_val);sleep(1);}}return 0;}

运行结果:符合我们预期的,数据本来就是父子进程共享的,除非要写入,进程之间时具有独立性的,写入的时候需要写时拷贝。
在这里插入图片描述

奇怪的现象:

测试代码:

   #include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/types.h>int g_val = 1000;int main(){pid_t id = fork();if(id==0){//子进程int cnt = 0;while(1){printf("child  pid:%d  ppid:%d  g_val=%d  &g_val:%p\n",getpid(),getppid(),g_val,&g_val);sleep(1);cnt++;                                                                                                                            if(cnt==3){printf("child change g_val\n");g_val=2000;}}//父进程else{while(1){                                                                                                                                   printf("father  pid:%d  ppid:%d  g_val=%d  &g_val:%p\n",getpid(),getppid(),g_val,&g_val);sleep(1);}}return 0;}

运行结果+分析:奇怪的现象(如下图):同一个变量,子进程尝试对g_val进行写入的时候,会进行写时拷贝,但是为什么地址一样,但是值却不一样呢?
在这里插入图片描述

解释上面的现象:

  1. 地址一样却值不一样,所以这个地址肯定不是物理地址。
  2. 如果是物理地址,绝对不可能在一个地址中存放的内容不一样。

这个地址叫做虚拟地址/线性地址。
结论:我们平时用到的语言的地址全部都不是物理地址,是虚拟地址。所以下面这个图的空间排布的情况不是物理内存,它叫做进程地址空间。
在这里插入图片描述

进程地址空间

每一个进程都有一个task_struct(PCB),PCB里面有该进程的进程地址空间,进程地址空间和内存之间是用一张表(叫做页表:里面存放的是虚拟地址与物理地址)建立关系的,如下图,页表对应一个映射关系,是虚拟地址与物理地址之间的关系。根据虚拟地址可以找到对应的物理地址。下面的结构都是操作系统内部在维护的。

在这里插入图片描述

  • 说明:上面的图就足矣说名问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!

其中,父进程创建子进程后,子进程也会有一个这样的结构,也会有进程地址空间,页表,并且父进程PCB的大部分属性都会被子进程继承下来,页表也会被继承下来(类似浅拷贝),这时父子进程都指向同一个物理内存。以上面的示例分析:当子进程尝试对g_val进行修改时,操作系统会在内存中重新开一个空间,将修改后的值放在这个空间里,再改变页表中g_val的虚拟地址对应的物理地址,注意:改的是物理地址,虚拟地址没有改变,所以上面示例的结果打印出来的地址(虚拟地址)没有改变。如下图:
在这里插入图片描述
根据上面的解释,也能够很好的解释fork()返回值问题了!

什么是进程地址空间?

进程地址空间是数据结构,具体到进程中,是有特定的数据结构对象。
如下图所示:在进程的PCB中,有一个指针,指向自己的进程地址空间,进程地址空间里面,包含一个结构体,结构体里面有很多start和end,划分区域。

在这里插入图片描述

为什么要有地址空间和页表?

  1. 在进程看来,有了页表,可以将物理内存从无徐变为有序,因为页表是有序的。让进程以统一的视角,看待内存;
  2. 将进程管理和内存管解耦合,进程管理与内存管理互不干扰。
  3. 地址空间+页表是保护内存安全的重要手段(拦截非法:例如:野指针,越界问题)。

内存申请问题(malloc/new)

申请内存,本质是进程的地址空间中申请。
这样:可以充分保证:

  1. 内存使用率,不会空转。
  2. 提升new/malloc的速度。

写时拷贝

  1. 为什么需要写时拷贝?
    答:进程之间要做到独立性。
  2. 创建子进程的时候,为什么不直接将父进程的代码和数据拷贝一份给子进程呢?
    答:因为子进程并不是会对父进程的所有数据都要进行写入操作,如果fork()创建子进程的时候,直接拷贝一份代码和数据,会降低fork()的效率。
  3. 为什么是要拷贝呢,只开空间不拷贝行不行?
    答:因为子进程不一定是对这个数据直接进行覆盖式的写入,可以只是对该数据进行局部修改或则是基于之前的值进行操作。

如何做到写时拷贝的?

前面所说的页表,不只是有虚拟地址与物理地址的转换的,还可以带很多选项的,如下图(介绍其中一个:权限):
下图代码字符串"hello Linux"是具有常属性的,不能被修改,当我们尝试去修改的时候,会报错(运行报错)。
是因为在页表有权限,虚拟地址映射到物理地址的时候,会做权限审核,如下图所示,当只有可读权限,没有修改的权限的时候,尝试去修改,就会报错。
在这里插入图片描述

写时拷贝的细节:

当要进行写时拷贝的时候,会将父子进程页表里大部分内容的映射权限设置为只读权限,当父子进程任何一方要去进行尝试写入的时候,操作系统会进行判断,如果是数据段,对数据进行写入时合理的,就会引发缺页中断,操作系统会将权限改为读写,然后写时拷贝后,再把页表对应的条目改为读写。


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

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

相关文章

linux系统安全加固

目录 1、账户安全基本措施 1&#xff09;系统账户清理 2&#xff09;密码安全控制 3&#xff09;命令历史限制 2、用户切换及提权 1&#xff09;使用 su命令切换用户 2&#xff09;使用sudo机制提升权限 3、系统引导和安全登录控制 1&#xff09;开机安全控制 2&…

python数据处理与分析入门-Pandas数据可视化例子

相关内容 Matplotlib可视化练习 Pandas 数据可视化总结 柱状图 reviews[points].value_counts().sort_index().plot.bar()散点图 reviews[reviews[price] < 100].sample(100).plot.scatter(xprice, ypoints)蜂窝图 reviews[reviews[price] < 100].plot.hexbin(xprice…

Helm安装kafka3.7.0无持久化(KRaft 模式集群)

文章目录 2.1 Chart包方式安装kafka集群 5.开始安装2.2 命令行方式安装kafka集群 搭建 Kafka-UI三、kafka集群测试3.1 方式一3.2 方式二 四、kafka集群扩容4.1 方式一4.2 方式二 五、kafka集群删除 参考文档 [Helm实践---安装kafka集群 - 知乎 (zhihu.com)](https://zhuanlan.…

Nginx - 健康检查终极指南:探索Upstream Check模块

文章目录 概述upstream_check_module模块安装和配置指南模块安装步骤基本配置示例详细配置说明检查类型和参数常见问题及解决方案 SSL检查和DNS解析功能SSL检查配置示例和说明配置示例 DNS解析配置示例和说明配置示例 结合实际应用场景的高级配置示例综合SSL检查与DNS解析 总结…

Doris【部署 03】Linux环境Doris数据库部署异常问题收集解决(不断更新)

Linux环境Doris数据库部署异常问题 1.FE1.1 Unknown system variable character_set_database1.2 notify new FE type transfer: UNKNOWN1.3 mysql_load_server_secure_path1.4 Only unique table could be updated1.5 too many filtered rows 2.BE2.1 Have not get FE Master …

正确可用--Notepad++批量转换文件编码为UTF8

参考了:Notepad批量转换文件编码为UTF8_怎么批量把ansi转成utf8-CSDN博客​​​​​​https://blog.csdn.net/wangmy1988/article/details/118698647我参考了它的教程,但是py脚本写的不对. 只能改一个.不能实现批量更改. 他的操作步骤没问题,就是把脚本代码换成我这个. #-*-…

graspnet+Astra2相机实现部署

graspnetAstra2相机实现部署 &#x1f680; 环境配置 &#x1f680; ubuntu 20.04Astra2相机cuda 11.0.1cudnn v8.9.7python 3.8.19pytorch 1.7.0numpy 1.23.5 1. graspnet的复现 具体的复现流程可以参考这篇文章&#xff1a;Ubuntu20.04下GraspNet复现流程 这里就不再详细…

数据库系统概论(第5版)复习笔记

笔记的Github仓库地址 &#x1f446;这是笔记的gihub仓库&#xff0c;内容是PDF格式。 因为图片和代码块太多&#xff0c;放到CSDN太麻烦了&#xff08;比较懒&#x1f923;&#xff09; 如果感觉对各位有帮助的话欢迎点一个⭐\^o^/

41-4 DDOS攻击防护实战

一、UDP FLOOD攻击 # hping3 -q -n -a <攻击IP> -S -s <源端口> --keep -p <目的端口> --flood <被攻击IP> hping3 --udp -s 6666 -p 53 -a 192.168.1.6 --flood 192.168.1.13 这个命令是使用hping3工具进行UDP Flood攻击的命令。下面是各个选项的作…

three.js能实现啥效果?看过来,这里都是它的菜(06)

这是第五期了&#xff0c;本期继续分享three.js可以实现的3D动画案例&#xff0c;有老铁反馈再发案例的时候&#xff0c;是否可以顺道分享一下three.js的知识点&#xff0c;好吧&#xff0c;安排。 材质动画 材质动画可以实现各种复杂的视觉效果&#xff0c;包括但不限于以下…

【css】引入背景图时候,路径写入@会报错

看报错信息 我的写法 解决办法 在前面加个~

js解决数字小数计算出现的精度丢失问题(2024-05-24)

精度丢失的原因 js小数进行数值运算时出现精度丢失问题 JavaScript 的number类型在进行运算时都先将十进制转二进制&#xff0c;此时&#xff0c;小数点后面的数字转二进制时会出现无限循环的问题。 为了避免这一个情况&#xff0c;要舍0进1&#xff0c;此时就会导致精度丢失…

企业宽带跑pcdn会被查吗?

企业宽带使用PCDN技术&#xff0c;本身并不违反相关规定&#xff0c;因此一般不会被查。PCDN是一种内容分发网络技术&#xff0c;通过将内容缓存在离用户更近的服务器上&#xff0c;减少数据传输的延迟&#xff0c;提高访问速度。这种技术可以提高网页加载速度和视频播放流畅度…

Excel未响应时强关后,Excel插件消失

目录 我们分析一下插件消失的原因&#xff1a; 针对上面表现出来的2个问题&#xff0c;进行针对性的解决 &#xff1a; 1、不被关进去&#xff0c;是不是就没有后续的一系列的问题了&#xff0c;各自安好 2、保留住自动加载的行为 PS&#xff1a;配置受信任的位置注册列表…

2024电工杯B题保姆级分析完整思路+代码+数据教学

2024电工杯B题保姆级分析完整思路代码数据教学 B题题目&#xff1a;大学生平衡膳食食谱的优化设计及评价 接下来我们将按照题目总体分析-背景分析-各小问分析的形式来 总体分析&#xff1a; 题目要求对两份一日膳食食谱进行营养分析和调整&#xff0c;然后设计优化的平衡膳…

生成模型 | 从 VAE 到 Diffusion Model (上)

文章目录 一&#xff0c;GAN(对抗式生成网络&#xff09;二&#xff0c;Auto-Encoder(AE) 和 Denoising Auto-Encoder (DAE)三&#xff0c;VAE四&#xff0c;VQ-VAE (Vector Quantized Variational Autoencoder)VQ-VAE 2小总结&#xff1a; 五&#xff0c;DALL-E &#xff08;O…

硅谷裸机云服务器性能测评哪些内容

硅谷裸机云服务器&#xff0c;作为云计算领域的一股新兴力量&#xff0c;近年来受到了广泛关注。其强大的性能和灵活性为用户提供了更高效、更稳定的云计算服务。那么&#xff0c;硅谷裸机云服务器的性能测评究竟包括哪些内容呢?接下来&#xff0c;我们就来科普一下。 首先&am…

如何让大模型更聪明?

如何让大模型更聪明&#xff1f; *随着人工智能技术的飞速发展&#xff0c;大模型在多个领域展现出了前所未有的能力&#xff0c;但它们仍然面临着理解力、泛化能力和适应性等方面的挑战。那么&#xff0c;如何让大模型变得更聪明呢&#xff1f; 方向一&#xff1a;算法创新 …

留学培训行业PaaS应用系统架构的设计与实践

随着留学需求的增长和教育培训市场的不断扩大&#xff0c;留学培训行业正面临着越来越多的挑战和机遇。在这个背景下&#xff0c;利用PaaS&#xff08;Platform as a Service&#xff09;平台来构建留学培训行业的应用系统架构&#xff0c;将成为提升服务质量和效率的重要手段。…

Nacos 2.x 系列【8】集成 Spring Cloud Gateway

文章目录 1. 概述1.1 API 网关1.1 Spring Cloud Gateway 2. 集成案例2.1 入门案例2.2 动态路由 1. 概述 1.1 API 网关 API网关已经成为了微服务架构的一个标配组件&#xff0c;是系统对外的唯一入口。所有的客户端都通过统一的网关接入微服务&#xff0c;在网关层处理所有非业…