Linux虚拟地址空间

这篇具有很好参考价值的文章主要介绍了Linux虚拟地址空间。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

父子进程地址相同的变量值不同问题

  #include<stdio.h>
#include<unistd.h>
int g_val = 100;


int main()
{
        pid_t id = fork();
        
    
	if (id == 0)
	{
		// 子进程
		int i = 0;
		while (1)
		{
			printf("I am child process, id:%d, g_val:%d, &g_val:%p\n", getpid(), g_val, &g_val);
			i++;
			if (i == 5)
			{
				g_val = 200;
				printf("Child process changed g_val success!!!\n");
			}
			sleep(1);
		}
	}
	else {
		while (1)
		{
			printf("I am parent process, id:%d, g_val:%d, &g_val:%p\n", getpid(), g_val, &g_val);
			sleep(1);
		}
	}
	return 0;
}

运行结果

[yzl@VM-4-5-centos tmp]$ ./proc
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:100, &g_val:0x601054
Child process changed g_val success!!!
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:200, &g_val:0x601054
I am parent process, id:28298, g_val:100, &g_val:0x601054
I am child process, id:28299, g_val:200, &g_val:0x601054

上述代码为,创建子进程,若干秒后,子进程改变全局变量值,发现子进程与父进程打印此全局变量值时,值不同,且地址相同

同一个地址处的值在同一时刻不可能不同,于是引出了虚拟地址空间的概念。即这里子进程与父进程打印的地址并非实际的物理地址,而是一种虚拟地址(线性地址)。

Linux下进程虚拟地址空间分布

虚拟地址空间使得每个进程看待内存时都有一个统一的视角,并且在他们看来,内存的分布是井然有序的。具体分布如下图

linux虚拟地址空间如何实现,Linux,linux,运维,服务器

  1. 栈堆相向增长,堆向高地址增长,栈向低地址增长。这两个区域是动态变化的。

  2. 虚拟地址空间分为两个空间:1. 内核空间,在32位下占1G 2. 用户空间,在32位下占3G即[0, 3GB] 用户空间 [3GB, 4GB] 内核空间

  3. static修饰局部变量,本质上是将此变量属性变为全局属性,存储在全局区。而语法的限制使得此static变量仅能在局部可见。

  4. 上图虚拟内存分布仅适用于Linux操作系统,不适于Windows。

  5. 一个有关堆区的知识:当C语言使用malloc函数时,申请10字节空间,实际在内存中会占用大于10字节的空间,多出的空间用于存储一些属性。这也是为什么free时传首地址即可,而不需要传空间大小。

什么是虚拟地址空间?

  1. 虚拟地址空间(进程地址空间)在操作系统内核中是一个数据结构,在Linux内核中,就是一个struct结构体

  2. 在Linux下,进程地址空间是一个名为mm_struct的结构体,主要存储各区域(堆,栈,全局数据区,只读代码区等)的范围,即start和end,用于划分各个区域。

  3. 页表是和虚拟地址空间结构体配套的内核数据结构,页表的作用是:保存对应进程中每一个虚拟地址到物理内存中的物理地址的映射关系。即起一个映射配对的作用。 因为实际上数据,代码,变量等肯定最终要存储在物理内存中。

  4. 页表起映射作用,就类似于C++中的map数据结构,key 是虚拟地址, value是对应的物理地址

  5. 每一个进程都有一份地址空间结构体变量mm_struct和页表实例化对象。在磁盘中的二进制可执行程序加载到内存中时,要创建对应的PCB结构体,同时,也会创建对应的地址空间结构体变量和页表。

进程直接访问物理内存(无虚拟空间)

在早期计算机操作系统内部,进程直接访问物理内存。这样做有很多弊端。在说弊端之前,有一个点需要明确:内存本身是可以随意读写的,物理内存是不存在只读的情况的。

比如,最典型的野指针问题,一个进程的野指针很容易破坏其他进程,甚至影响操作系统内的安全数据。其次,进程直接访问物理内存,使得进程和物理内存耦合度很大,内存管理变得不方便,从而内存碎片等问题也变得更难处理。

基于进程直接访问物理内存的弊端,衍生出虚拟地址空间

再述虚拟地址空间!

linux虚拟地址空间如何实现,Linux,linux,运维,服务器

程PCB,虚拟地址空间,页表,物理内存的关系大致如上图所示。

  1. PCB中有一个struct mm_struct* mm指针数据成员指向这个进程对应的mm_struct

linux虚拟地址空间如何实现,Linux,linux,运维,服务器

  1. 因为内存本身是随意读写的,所以,在地址空间+页表的作用下可以在某些虚拟地址与物理地址的映射关系中,用某些数据(比如页表中存储)表明这个内存是只读的。以此来保护某些数据。这些都是地址空间+页表的作用,而非使用内存的权限控制。

  2. 基于第二点,地址空间+页表可以对某些内存进行权限管理,比如常量代码区设为只读。同时,对于某些内存的非法访问,也可以及时禁止。从而保护物理内存。

  3. 我们知道,进程是具有独立性的,那么,在地址空间+页表的作用下,只要使得各个进程的虚拟地址通过页表映射的物理内存是不同的,则可以保证进程之间互不干扰,即进程独立性。

虚拟地址空间结构体是如何区域划分?

通过定义栈区,堆区,常量代码区,全局数据区等区域的start,end。来对这些区域进行划分。

比如栈区,堆区是动态变化的。那么只需要增大或者减小end,即可对栈区堆区的空间大小进行控制。再比如只读代码区,在源文件编译之后,可执行程序内部已经有了虚拟地址。若此文件加载到内存中变为进程,则mm_struct中的常量代码区的start和end即可通过这些编译生成的虚拟地址来确定start和end

Linux内核源码
linux虚拟地址空间如何实现,Linux,linux,运维,服务器

如图,为Linux内核源码中mm_struct的定义,即虚拟地址空间的定义。可以看到,它确实是通过定义各个区域的start,end来划分各个区域的。类型是unsigned long

解答最初的问题

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int g_val = 100;
  4 
  5 int main()
  6 {
  7     pid_t id = fork();
  8     if(id == 0)
  9     {
 10         // 子进程
 11         int i = 0;
 12         while(1)
 13         {
 14             printf("I am child process, id:%d, g_val:%d, &g_val:%p\n", getpid(), g_val, &g_val);
 15             i++;
 16             if(i == 5)
 17             {
 18                 g_val = 200;
 19                 printf("Child process changed g_val success!!!\n");
 20             }
 21             sleep(1);                                                                                                                                                                                        
 22         }
 23     }
 24     else {
 25         while(1)
 26         {
 27             printf("I am parent process, id:%d, g_val:%d, &g_val:%p\n", getpid(), g_val, &g_val);
 28             sleep(1);
 29         }
 30     }
 31     return 0;
 32 }

linux虚拟地址空间如何实现,Linux,linux,运维,服务器

最初,父进程与子进程打印出同一个变量地址相同,但是值不同。我们现在知道了,这个地址其实是虚拟地址,而非物理地址。

一个事实:父进程创建子进程时,除了一些子进程独有的属性,比如典型的pid。其余大部分属性和数据都是从父进程那里拷贝过来的。包括mm_struct 和 页表。

所以,起初,在子进程执行g_val = 200;之前,也就是修改这个全局变量之前。因为子进程的mm_struct和页表是直接从父进程那里拷贝过来的。故父子进程的g_val的虚拟地址,以及这个虚拟地址映射的物理内存中的数据都是一样的

这样做的原因是:如果有某些数据,父子进程都是只读的,也就是不会修改,那么这份数据在内存中只保存一份即可,没必要给子进程在内存中再创建一份相同的,只读的数据。(写时拷贝)

而当子进程执行g_val = 200;时,这是子进程对这个全局数据执行写操作。因为父子进程访问的g_val不应该互相干扰。故此时,OS在内存中的其他区域,拷贝了一个新的,子进程的g_val,赋值为200,并改变子进程的页表的映射关系即可!(不需要改变g_val的虚拟地址)。

从而当子进程修改g_val后,父子进程打印的这个全局数据的虚拟地址相同但是映射到物理内存不同区域值不相同。才有了最初的现象。

这种子进程写数据时进行拷贝的操作,称为写时拷贝!

延伸问题: 一个pid变量怎么可能保存不同的值?

pid_t pid = fork();

我们知道,fork函数内部的主体逻辑就是创建子进程,而当fork函数return之前,则子进程已经创建好了

所以有了两个进程执行流,两个执行流会执行两次return其次,return了两次给pid赋值,也就是子进程执行流的return给pid本质就是对pid变量进行写操作!会发生写时拷贝,那么各自就有各自的pid了(虽然虚拟地址相同,页表映射到物理内存是不同的,看到的是自己的pid变量)

fork return两次,第二次return发生了写时拷贝,则父子进程各自在物理内存中,都有属于自己的id变量空间!

只不过在用户层用同一个变量pid(虚拟地址)来标识了。

为什么存在虚拟地址空间?(虚拟地址空间的好处)

保护物理内存

  • 凡是非法的访问或者映射,OS都会识别到,并终止你的进程。(页表机制会将虚拟地址空间会划分为多个页,通过设置页表项的权限位答到保护物理敏感内存的目的)

原因就是:比如const char* p = “abcd”; p指针保存的是虚拟地址,且这个虚拟地址所映射的物理内存不可被写,这都是基于虚拟地址空间(代码段不能修改等等)+页表的作用。

除了这种保护只读的数据,当存在野指针或者非法访问时,虚拟地址空间+页表也能以某种方式告诉OS,从而OS可以发信号终止这个进程。

样一来,物理内存的访问都在OS的监管之下。保护了物理内存,物理内存中的数据,其他进程,以及内核的相关有效数据

内存管理和进程管理低耦合

因为有虚拟虚拟地址空间+页表物理内存中的数据可以随意存储。只要保证虚拟地址可以通过映射找到对应的数据即可

物理内存管理进程管理因此可以做到关联性很低

内存管理模块和进程管理模块 完成了解耦合。在操作系统层面,这两个模块关联性很低,维护成本也会降低(各维护各的);

延时分配,提高整机效率

  • 我们在C语言中进行malloc时,申请内存本质是在虚拟地址空间中申请,并不会立即向物理申请内存空间

原因是:如果我malloc时就立刻申请物理内存,且不立刻使用,则这就是一种内存资源浪费

所以,因为有地址空间存在,上层申请内存,其实是在虚拟地址空间中申请。

而当你进行对物理内存的访问时,才执行相关的内存管理算法(缺页中断等),帮你申请内存,构建页表映射关系。然后再让你进行内存访问。 (这些是由操作系统完成的,进程0感知)

虚拟地址存在,但是物理内存中没有对应的空间。称为缺页(映射)中断。

那么,这样延时分配的好处就是:确保物理内存中的有效使用是100%的不会出现物理内存中申请空间但不使用的情况。提高整机效率。

使内存分布有序化,实现进程独立性

  • 地址空间+页表的存在,可以使得内存分布有序化!每个进程看到的是完整的虚拟地址->实现进程独立;

在虚拟地址中,每个进程以完整虚拟地址的角度来看待内存布局,这个布局是有序的(代码段,数据段,堆栈等分区);

因为页表的存在,可以建立虚拟地址和物理地址的映射关系,物理内存存放的数据是随机无序的。

通过让不同进程看到的完整分布的内存虚拟地址 映射 到物理内存的任意区域 -> 实现进程独立性

内存扩展(物理内存不够时,通过内存页和硬盘的交换达到扩展(了解)

内存共享(允许多进程通过虚拟地址+页表等机制,访问同一个物理内存(eg:fork的写时拷贝)(了解)

小结

综上:

可以说虚拟地址空间是OS内核中的一种数据结构主要保存各个数据区的start和end

32位系统下,虚拟地址空间使得每个进程都认为自己独占4GB内存,它们也看不到其他进程的存在。内核通过页面的映射等管理手段,从而让物理内存中的进程和进程之间,进程和内核之间可以互不干扰;文章来源地址https://www.toymoban.com/news/detail-823753.html

到了这里,关于Linux虚拟地址空间的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux系统化学习】进程地址空间 | 虚拟地址和物理地址的关系

    ========================================================================= 个人主页点击直达: 小白不是程序媛 Linux专栏: Linux系统化学习 代码仓库: Gitee ========================================================================= 目录 虚拟地址和物理地址 页表 进程地址空间 进程地址空间存在的意义 我们在学

    2024年02月05日
    浏览(37)
  • 【Linux-14】进程地址空间&虚拟空间&页表——原理&知识点详解

    前言 大家好吖,欢迎来到 YY 滴 系列 ,热烈欢迎! 本章主要内容面向接触过Linux的老铁 主要内容含: 欢迎订阅 YY 滴C++专栏!更多干货持续更新!以下是传送门! YY的《C++》专栏 YY的《C++11》专栏 YY的《Linux》专栏 YY的《数据结构》专栏 YY的《C语言基础》专栏 YY的《初学者易

    2024年04月29日
    浏览(35)
  • 【看表情包学Linux】进程地址空间 | 区域和页表 | 虚拟地址空间 | 初识写时拷贝

       🤣  爆笑 教程  👉 《看表情包学Linux》👈   猛戳订阅     🔥 💭 写在前面: 本章核心主题为 \\\"进程地址空间\\\",会通过验证 Linux 进程的地址空间来开头,抛出 \\\"同一个值能有不同内容\\\" 的现象,通过该现象去推导出 \\\"虚拟地址\\\" 的概念。然后带着大家理解为什么虚拟地

    2024年01月20日
    浏览(48)
  • Linux内核源码分析 (B.2)虚拟地址空间布局架构

    Linux内核只是操作系统当中的一部分,对下管理系统所有硬件设备,对上通过系统调用向 Library Routine 或其他应用程序提供API接口。 内存管理可以通过以下三个维度进行介绍: 用户空间 相当于应用程序使用 malloc() 申请内存,通过 free() 释放内存。 malloc() / free() 是 glibc 库的内

    2024年02月09日
    浏览(50)
  • [Linux]环境变量 进程地址空间(虚拟内存与物理内存的关系)

    hello,大家好,这里是bang_bang,今天我们来讲一下语言层级上的程序地址空间和系统层级上的进程地址空间的区别,在下面中我举的例子会设计到环境变量,所以开篇我先讲讲环境变量。 目录 1️⃣环境变量 🍙 基本概念 🍙环境变量相关命令 🍥查看环境变量echo 🍥添加全局环

    2024年02月15日
    浏览(36)
  • 如何在 IDEA 中设置远程连接服务器开发环境并实现固定地址远程 Linux 环境

    本文主要介绍如何在IDEA中设置远程连接服务器开发环境,并结合Cpolar内网穿透工具实现无公网远程连接,然后实现远程Linux环境进行开发。 IDEA的远程开发功能,可以将本地的编译、构建、调试、运行等工作都放在远程服务器上执行,而本地仅运行客户端软件进行常规的开发

    2024年02月05日
    浏览(41)
  • Linux本地部署1Panel服务器运维管理面板并实现公网访问

    1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理等 下面我们介绍在Linux 本地安装1Panel 并结合cpolar 内网穿透工具实现远程访问1Panel 管理界面 执行如下命令一键安装 1Panel: 安

    2024年02月04日
    浏览(53)
  • 【Linux】程序地址空间?进程地址空间

    了解进程的运行:  运行结果:我们会发现这打印的结果乱七八糟,因为它也不知道什么时候该干什么  我们让代码睡眠1秒:打印的结果就正常了  以前我们学习的内存管理(程序地址空间):  为了验证上面虚拟地址,我们运行下面代码: (这种问题出现的原因在下面的为

    2024年02月13日
    浏览(76)
  • 虚拟机VMware,linux,centos,如何将项目部署到服务器上面

    vmware 是安装虚拟机的软件,centos是系统,linux是系统内核 将本地项目上线到服务器上面,如何实现呢? 准备好服务器,可以选择阿里云服务器 首先需要搭建环境,运行的主要环境是jdk+tomcat+mysql; 通过远程连接工具,将jdk版本可以直接拖拽到服务器上面, 实现tomcat配置 实现

    2024年02月15日
    浏览(40)
  • Linux: 进程地址空间究竟是什么?进程地址空间存在意义何在?

     在C/C++中,我们常将内存分为: 代码区、常量区、全局区(静态区)、堆、栈 等等。相关内存区域划分如下:(X86, 32位平台) 如何验证C/C++中各区域的相对位置呢?  我们可以在每个区域中选择一个地址来验证C/C++中各区域的相对位置!!具体如下: 【源代码】: 【运行

    2024年04月08日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包