Linux进程地址空间、写时拷贝

1.进程地址空间

感知进程地址空间

C/C++有内存的概念,内存空间包括栈、堆、代码段等等,下面是32位下的内存分布图,自底向上(由0x000000000xFFFFFFFF);
在这里插入图片描述

下面通过程序来验证各个数据在该空间的地址,由此感知整个地址空间的分布情况。

#include <stdio.h>
#include <stdlib.h>int g_val= 10;	// 初始化数据
int un_g_val;	// 未初始化数据int main(int argc, char *argv[], char *env[])
{printf("code addr			    :%p\n", main);		// main函   数地址,即为程序开始的地址printf("init global     addr		:%p\n", &g_val);	// 初始化数据地址printf("uninit global   addr		:%p\n", &un_g_val);// 未初始化数据地址char *m1 = (char*)malloc    (sizeof(char)*10);char *m2 = (char*)malloc    (sizeof(char)*10);printf("heap1 addr			    :%p\n", m1);		// 先申请的堆空间变量地址printf("heap2 addr			    :%p\n", m2);		// 后申请的堆空间变量地址printf("stack1 addr			    :%p\n", &m1);		// 先申请的栈空间变量地址printf("stack2 addr			    :%p\n", &m2);		// 后申请的栈空间变量地址int i = 0;for (i = 0; argv[i]; i++){printf("argv%d addr			:%p\n", i, &argv[i]);		}for (i = 0; env[i]; i++){printf("env%d addr			:%p\n", i, &env[i]);		}return 0;
}

程序运行后结果如下,完全符合上图的地址分布:
由低到高依次为正文代码段->初始化数据->未初始化数据->堆空间->栈空间->命令行参数环境变量
同时,栈和堆中间有一大块空白区域且向中间渐进。

root@hcss-ecs-e6eb:~/learning/Linux-learning/Process# ./myprocess 
code addr			:0x558e4fa8a189
init global addr		:0x558e4fa8d010
uninit global addr		:0x558e4fa8d018
heap1 addr			:0x558e514606b0
heap2 addr			:0x558e514606d0
stack1 addr			:0x7fff4a3a7618
stack2 addr			:0x7fff4a3a7620
argv0 addr			:0x7fff4a3a7748
env0 addr			:0x7fff4a3a7758
env1 addr			:0x7fff4a3a7760
env2 addr			:0x7fff4a3a7768
env3 addr			:0x7fff4a3a7770
env4 addr			:0x7fff4a3a7778
env5 addr			:0x7fff4a3a7780
...

上述地址是在64位机器打印出来的,但实际上该机器内存只有2GB,而实际感知到的内存空间为 2 64 B = 2 34 G B 2^{64}B=2^{34}GB 264B=234GB,这中间可是相差了很多,可以初步推断:C/C++所谓的内存地址不是物理地址,而是一个虚拟地址/线性地址

通过下面的程序可以更好地进行理解:设置一个全局变量g_val,父子进程都拥有这个全局变量,观察子进程修改该全局变量值前后该变量在父子进程的状态变化。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int g_val = 100;int main()
{pid_t id = fork();if (id == 0){while (1){printf("I'm child process:%d, ppid:%d,  g_val:%d, &g_val:%p\n", getpid(), getppid(), g_val, &g_val);g_val = 200;printf("after modify, I'm child process:%d, ppid:%d, g_val:%d, &g_val:%p\n", getpid (), getppid(),   g_val, &g_val);sleep(2);}}else {while (1){printf("I'm father process:%d, ppid:%d, g_val:%d, & g_val:%p\n", getpid(), getppid(), g_val, &g_val);sleep(2);}   }return 0;
}

可以看到,修改前g_val的值和地址在父子进程中都是一致的,但是子进程将g_val的值进行修改之后,虽然值不同,但是地址相同。若该地址是真实的物理地址,显然不可能出现这种情况,C/C++中使用的地址是虚拟地址。

I'm father process:1068014, ppid:1061991, g_val:100, &g_val:0x55a8287d7010
I'm child process:1068015, ppid:1068014, g_val:100, &g_val:0x55a8287d7010
after modify, I'm child process:1068015, ppid:1068014, g_val:200, &g_val:0x55a8287d7010
I'm father process:1068014, ppid:1061991, g_val:100, &g_val:0x55a8287d7010
I'm child process:1068015, ppid:1068014, g_val:200, &g_val:0x55a8287d7010

虚拟地址在Linux下也称为线性地址
内存是硬件,操作系统肯定不会让用户直接进行操作,当进程分配到CPU资源后,CPU通过一个虚拟地址来访问贮存,这个虚拟地址被送到内存之前先转换成适当的物理地址,这一任务叫地址翻译。CPU芯片上叫做内存管理单元(Memory Management Unit, MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。

在这里插入图片描述

同一变量地址相同,其实是虚拟地址相同,内容不同是因为映射到了不同的物理地址。 这也能解释为什么fork函数会有两个返回值,两个返回id虽然同一虚拟地址,但是其指向的物理空间却不相同。

认识进程地址空间

地址空间(address space)是一个非负整数地址的有序集合。

操作系统通过进程控制块task_struct来管理进程,每个进程都有一块独立的进程地址空间,这个空间不是物理上内存真正存在的空间task_struct有一个类型为mm_struct的成员,操作系统通过进程控制块中的mm_struct来管理这块空间。在mm_struct结构体中,通过记录各段的首尾地址即区间方式来维护各段。

struct task_struct{//.../* 进程地址空间 1) mm: 指向进程所拥有的内存描述符 2) active_mm: active_mm指向进程运行时所使用的内存描述符对于普通进程而言,这两个指针变量的值相同。但是,内核线程不拥有任何内存描述符,所以它们的mm成员总是为NULL。当内核线程得以运行时,它的active_mm成员被初始化为前一个运行进程的active_mm值*/struct mm_struct *mm, *active_mm;//...
};struct mm_struct{//...//维护代码段和数据段unsigned long start_code, end_code, start_data, end_data;//维护堆和栈unsigned long start_brk, brk, start_stack;//维护命令行参数,命令行参数的起始地址和最后地址,以及环境变量的起始地址和最后地址unsigned long arg_start, arg_end, env_start, env_end;//...// 页表指针pgd_t * pgd;  
}

概念上而言,虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每字节都有一个唯一的虚拟地址,作为到数组的索引。磁盘上数组的内容被缓存到主存中。

除此之外,mm_struct中还有一个重要成员页表指针,该指针指向了一个存放在物理内存中叫做页表(page table)的数据结构,每个进程指向一个页表,页表是一种映射关系,虚拟地址通过MMU得到虚拟页号和页内偏移量,虚拟页号通过页表映射到该物理页号,MMU拼接上页内偏移量就能得到最终物理地址

在这里插入图片描述

操作系统通过地址空间+页表,将物理内存不连续的区域统一映射到同一块区域,让进程以统一的视角来看待内存。同时进程管理和内存管理解耦合,方便OS设计。地址空间+页表是保护内存安全的重要手段。

2.写时拷贝

写时拷贝(copy-on-write, COW)就是等到修改数据时才真正分配内存空间,这是对程序性能的优化,可以延迟甚至是避免内存拷贝,当然目的就是避免不必要的内存拷贝。

进程这一抽象能够为每个进程提供自己的私有的虚拟地址空间即进程地址空间,可以免受其他进程的错误读写。
不过,许多进程有同样的只读代码区域。例如,每个运行的Linux Shell 程序 bash的进程都有相同的代码区域。
那么如果,每个进程都在物理内存中保持这些常用代码的副本,就是极端的浪费了。

一个对象可以被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象
一个进程对共享对象的任何写操作,对其他将该共享对象映射到虚拟内存中的进程都是可见的。不可见的即是私有对象


假设进程1将一个共享对象映射到它的虚拟内存的一个区域中,若假设进程2将同一共享对象也映射到它的地址空间(并不一定要和进程1在相同的虚拟地址处),会出现下图的情况,以节省物理内存的消耗。即使对象被映射到多个共享区域,物理内存中也只需要存放共享对象的一个副本。

在这里插入图片描述

私有对象用一种叫做写时拷贝的巧妙技术被映射到虚拟内存中。私有对象开始生命周期的方式与共享对象的一样,在物理内存中只保存有私有对象的一个副本。
就拿上面程序中父子进程来说,子进程修改g_val之前,两个进程都将其私有对象(代码、数据、g_val等等)映射到他们虚拟内存的不同区域,但是共享这个对象的同一物理副本,修改g_val前,此时相应私有区域都被标记为只读。
在这里插入图片描述

然而子进程修改g_val时,子进程试图写私有区域的某个页面,这个写操作会触发一个保护故障
当故障处理程序注意到保护异常是由于进程试图写私有的写时拷贝区域中的一个页面引起的,它就会在物理内存中创建这个页面的一个新副本,更新页表条目指向新的副本,然后恢复这个页面的可写权限。

在这里插入图片描述

写时拷贝充分地使用了最稀有的物理内存。

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

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

相关文章

python成功解决AttributeError: can‘t set attribute ‘lines‘

文章目录 报错信息与原因分析解决方法示例代码代码解释总结 报错信息与原因分析 在使用 matplotlib绘图时&#xff0c;若尝试使用 ax.lines []来清除图表中的线条&#xff0c;会遇到AttributeError: can’t set attribute错误。这是因为 ax.lines是一个只读属性&#xff0c;不…

从零搭建微服务项目Pro(第6-2章——微服务鉴权模块SpringSecurity+JWT)

前言&#xff1a; 在上一章已经实现了SpringBoot单服务的鉴权&#xff0c;在导入SpringSecurity的相关依赖,以及使用JWT生成的accessToken和refreshToken能够实现不同Controller乃至同一Controller中不同接口的权限单独校验。上一章链接如下&#xff1a; 从零搭建微服务项目Pr…

win安装软件

win安装软件 jdk安装 jdk安装 首先去官网下载适合系统版本的JDK&#xff0c;下载地址&#xff1a; http://www.oracle.com/technetwork/java/javase/downloads/index.html进入下载页面&#xff0c;如下图&#xff1a; 首先选择&#xff1a;Accept License Agreement单选按钮&…

Prompt-Tuning 提示词微调

1. Hard Prompt 定义&#xff1a; Hard prompt 是一种更为具体和明确的提示&#xff0c;要求模型按照给定的信息生成精确的结果&#xff0c;通常用于需要模型提供准确答案的任务. 原理&#xff1a; Prompt Tuning原理如下图所示&#xff1a;冻结主模型全部参数&#xff0c;在…

【Vue生命周期的演变:从Vue 2到Vue 3的深度剖析】

Vue生命周期的演变&#xff1a;从Vue 2到Vue 3的深度剖析 1. 生命周期钩子的概念与意义 Vue框架通过生命周期钩子函数使开发者可以在组件不同阶段执行自定义逻辑。这些钩子函数是Vue组件生命周期中的关键切入点&#xff0c;对于控制组件行为至关重要。 2. Vue 2中的生命周期…

java ai 图像处理

Java AI 图像处理 图像处理是人工智能&#xff08;AI&#xff09;领域中非常重要的一个应用方向。通过使用Java编程语言和相应的库&#xff0c;我们可以实现各种图像处理任务&#xff0c;如图像识别、图像分类、图像分割等。本文将介绍一些常见的图像处理算法&#xff0c;并通过…

从 0~1 保姆级 详细版 PostgreSQL 数据库安装教程

PostgreSQL数据库安装 PostgreSQL官网 【PostgreSQL官网】 | 【PostgreSQL安装官网_Windows】 安装步骤 step1&#xff1a; 选择与电脑相对应的PostgreSQL版本进行下载。 step2&#xff1a; 双击打开刚才下载好的文件。 step3&#xff1a; 在弹出的setup窗口中点击 …

Keil MDK中禁用半主机(No Semihosting)

在 ARM 编译器&#xff08;如 Keil MDK&#xff09; 中禁用半主机&#xff08;Semihosting&#xff09;并实现标准库的基本功能&#xff0c;需要以下步骤&#xff1a; 1. 禁用半主机 #pragma import(__use_no_semihosting) // 禁用半主机模式作用&#xff1a;防止标准库函数&…

github | 仓库权限管理 | 开权限

省流版总结&#xff1a; github 给别人开权限&#xff1a;仓库 -> Setting -> Cllaborate -> Add people GitHub中 将公开仓库改为私有&#xff1a;仓库 -> Setting -> Danger Zone&#xff08;危险区&#xff09; ->Change repository visibility( 更改仓…

快速部署大模型 Openwebui + Ollama + deepSeek-R1模型

背景 本文主要快速部署一个带有web可交互界面的大模型的应用&#xff0c;主要用于开发测试节点&#xff0c;其中涉及到的三个组件为 open-webui Ollama deepSeek开放平台 首先 Ollama 是一个开源的本地化大模型部署工具,提供与OpenAI兼容的Api接口&#xff0c;可以快速的运…

极狐GitLab 项目导入导出设置介绍?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 导入导出设置 (BASIC SELF) 导入和导出相关功能的设置。 配置允许的导入源 在从其他系统导入项目之前&#xff0c;必须为该…

信奥还能考吗?未来三年科技特长生政策变化

近年来&#xff0c;科技特长生已成为名校录取的“黄金敲门砖”。 从CSP-J/S到NOI&#xff0c;编程竞赛成绩直接关联升学优势。 未来三年&#xff0c;政策将如何调整&#xff1f;家长该如何提前布局&#xff1f; 一、科技特长生政策趋势&#xff1a;2025-2027关键变化 1. 竞…

AI测试用例生成平台

AI测试用例生成平台 项目背景技术栈业务描述项目展示项目重难点 项目背景 针对传统接口测试用例设计高度依赖人工经验、重复工作量大、覆盖场景有限等行业痛点&#xff0c;基于大语言模型技术实现接口测试用例智能生成系统。 技术栈 LangChain框架GLM-4模型Prompt Engineeri…

操作系统-PV

&#x1f9e0; 背景&#xff1a;为什么会有 PV&#xff1f; 类比&#xff1a;内存&#xff08;生产者&#xff09; 和 CPU&#xff08;消费者&#xff09; 内存 / IO / 磁盘 / 网络下载 → 不断“生产数据” 例如&#xff1a;读取文件、下载视频、从数据库加载信息 CPU → 负…

工厂方法模式详解及在自动驾驶场景代码示例(c++代码实现)

模式定义 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;通过定义抽象工厂接口将对象创建过程延迟到子类实现&#xff0c;实现对象创建与使用的解耦。该模式特别适合需要动态扩展产品类型的场景。 自动驾驶感知场景分析 自动驾…

基于 S2SH 架构的企业车辆管理系统:设计、实现与应用

在企业运营中&#xff0c;车辆管理是一项重要工作。随着企业规模的扩大&#xff0c;车辆数量增多&#xff0c;传统管理方式效率低下&#xff0c;难以满足企业需求。本文介绍的基于 S2SH 的企业车辆管理系统&#xff0c;借助现代化计算机技术&#xff0c;实现车辆、驾驶员和出车…

IntelliJ IDEA download JDK

IntelliJ IDEA download JDK 自动下载各个版本JDK&#xff0c;步骤 File - Project Structure &#xff08;快捷键 Ctrl Shift Alt S&#xff09; 如果下载失败&#xff0c;换个下载站点吧。一般选择Oracle版本&#xff0c;因为java被Oracle收购了 好了。 花里胡哨&#…

MCP协议在纳米材料领域的深度应用:从跨尺度协同到智能研发范式重构

MCP协议在纳米材料领域的深度应用&#xff1a;从跨尺度协同到智能研发范式重构 文章目录 MCP协议在纳米材料领域的深度应用&#xff1a;从跨尺度协同到智能研发范式重构一、MCP协议的技术演进与纳米材料研究的适配性分析1.1 MCP协议的核心架构升级1.2 纳米材料研发的核心挑战与…

OpenAI发布GPT-4.1:开发者专属模型的深度解析 [特殊字符]

最近OpenAI发布了GPT-4.1模型&#xff0c;却让不少人感到困惑。今天我们就来深入剖析这个新模型的关键信息&#xff01; 重要前提&#xff1a;API专属模型 &#x1f4bb; 首先需要明确的是&#xff0c;GPT-4.1仅通过API提供&#xff0c;不会出现在聊天界面中。这是因为该模型主…

DemoGen:用于数据高效视觉运动策略学习的合成演示生成

25年2月来自清华、上海姚期智研究院和上海AI实验室的论文“DemoGen: Synthetic Demonstration Generation for Data-Efficient Visuomotor Policy Learning”。 视觉运动策略在机器人操控中展现出巨大潜力&#xff0c;但通常需要大量人工采集的数据才能有效执行。驱动高数据需…