当前位置: 首页 > news >正文

【Linux进程】理解进程地址空间

一个现象

        在学习进程地址空间之前,让我们先来看一段简单的小代码,并请大家猜猜执行结果是什么:

#include <stdio.h>
#include <unistd.h>

int main()
{
    int val = 10;
    int pid = fork(); // 创建子进程
    if(pid == 0)  // 子进程执行
    {
        val = 20;
        while(1)
        {
            printf("我是子进程, val: %d, &val: %p\n", val, &val); // 记得加\n
            sleep(1); // 防止刷屏
        }
    }
    else // 父进程执行
    {
        while(1)
        {
            printf("我是父进程, val: %d, &val: %p\n", val, &val);
            sleep(1);
        }
    }
    return 0;
}

执行结果居然是:

非常奇怪嗷,两个进程打印地址相同的val,值居然不一样,或者换种说法,这两个值明明都不一样了,为啥地址还一样啊!

        我们知道,一个物理内存地址肯定是只能有一个值的,这个实验里面的父进程和子进程明明地址一样,值却不一样,所以它们就压根不是物理内存地址!而是传说中的虚拟内存地址。


        为了让大家更好的理解接下来学习的内容,我先给大家讲一个小故事:

        有一个日本富豪,年少的时候比较轻狂,这就导致他有4个私生子:A、B、C、D,并且这4个私生子都不知道彼此之间的存在,都以为自己是富豪唯一的继承人。

        私生子A是一个医生,富豪跟他说,小A你好好工作,将来我的亿万家财都是你的;私生子B是一个小企业家,富豪跟他说,小B你好好经营企业,将来我的亿万家财都是你的;私生子C是个不良少年,富豪跟他说,小C你别给我惹是生非,将来我的亿万家财都是你的;私生子D是个学生,富豪跟他说,小D你好好学习,将来我的亿万家财都是你的。

所以现在的情况是这样的:

        故事继续,有一天小B突然找到富豪,说老爹给我30万美金呗,企业运营需要周转,富豪觉得合理,就转给了他钱;小C也找到富豪,说老爹给我3000美金呗,没钱吃饭了,富豪觉得也合理,就转给了他钱。

        所以这4个私生子都相信富豪画的大饼,认为自己有亿万家财,尽管实际上并不是,但只要私生子不一次性要走所有钱,并且富豪觉得理由合理,就会给私生子转钱。

        故事就到这里,事实上,操作系统为进程做的工作和富豪给私生子画饼是类似的:

  • 操作系统相当于富豪
  • 进程相当于私生子
  • 进程地址空间相当于富豪画的亿万家财的大饼
  • 物理地址相当于富豪的钱

进程地址空间

什么是进程地址空间

        进程需要通过进程地址空间来访问物理内存:

  • 对于进程而言:每个进程都有一块一致且独立的内存;
  • 对OS而言:只需要将进程当前使用的数据加载到内存——利用了局部性原理。

说了半天,这么做有什么好处呢?我们先来看看不这么做的坏处吧!

直接给进程分配物理内存会有什么问题?

  • 可能会导致内存践踏,A进程不小心访问到B进程的资源,修改了其中内容,可能导致B进程宕机。
  • 如果需要由OS来进行控制,这大大增加了操作系统的管理难度,毫无疑问是会给它增加很多负担的。

进程地址空间的设计

        既然操作系统需要给每个进程都分配进程地址空间,这就意味这势必要将它们管理起来,所以使用先描述再组织的思想,就能够把管理进程地址空间转化为对数据结构的增删查改了!

进程地址空间布局图:        

根据这个布局图,我们能够意识到,进程地址空间实际上主要进行的是对地址按逻辑进行划分,是一种虚拟地址,所以我们可以设计类似这样的数据结构:

struct addr_room
{
	int code_start;//代码区起始
	int code_end;//代码区结束
	int init_start;//初始化区起始
	int init_end;//初始化区结束
	int heap_start;//堆区起始
	int heap_end;//堆区结束
	......
	其他属性
};

就能够描述进程地址空间了,而Linux内核源码也确实是这样的:

虚拟地址到物理地址

        现在我们知道了进程直接操作的内存并不是物理内存,而是虚拟内存,那么虚拟地址是怎么转换到物理地址的呢?

虚拟地址通过页表转化为物理地址,这实际上是通过MMU这一CPU中的专用硬件模块实现的,并且可以通过TLB(快表)来优化查询速度:

将最近访问的虚拟地址和物理地址的映射关系缓存到TLB中。CPU直接访问TLB若命中则无需查询页表,否则再查询页表。

解释现象

        现在我们就能试着理解前面那个奇怪的现象了:

为什么两个变量明明地址相同,值却不同?

        进程只能通过进程地址空间来访问物理内存,而进程地址空间本质上是操作系统为进程分配的一致且私有的虚拟内存。父进程创建子进程后,子进程和父进程共享代码和数据——也就是说,子进程的地址空间的内容、页表与父进程是一致的,当子进程修改变量的值时,操作系统会为子进程新开辟一块物理内存,将修改后的值存在这块物理内存中,并修改子进程页表的映射关系,所以此时表现出来的现象就是:两个变量地址(虚拟地址)相同,值却不同(虚拟地址在物理地址上映射的位置其实不一样)。

页表的作用

       页表除了能够将虚拟地址转换为物理地址外,还有一个非常重要的作用就是,能够对用户的内存访问进行权限控制:

        当进程想要访问某个空间时,由于需要先通过页表将虚拟地址映射为物理地址才能进一步操作,而MMU检测到访问是权限不允许的时,就能直接在页表层驳回请求,直接报错,从而保护物理内存。

一道面试题

字节实习一面:虚拟内存和物理内存的区别?为什么需要虚拟内存?


  • 定义:物理内存是进程中的变量实际存储的位置,是实际存在的物理硬件;而虚拟内存则是计算机系统进行内存管理的技术,是逻辑抽象的内存空间
  • 容量:物理内存的容量是由硬件决定的,大小有限;而虚拟内存的容量可以比实际物理内存大,操作系统可以通过磁盘扩展虚拟内存。
  • 作用:
    • 物理内存用于存储正在运行的程序和数据
    • 虚拟内存:
      • 为每个进程提供一致且独立的地址空间,实现进程间内存的相互隔离
      • 提高内存访问的安全性,进程通过虚拟地址访问物理地址的过程受页表权限控制
      • 实现内存管理(物理内存分配)与进程管理(进程创建终止)的解耦合,彼此不需要关心对方的细节
      • 提供更大的可用内存空间

相关文章:

  • 8.非监督学习与关系挖掘:聚类分析、客户细分、关联规则与协同过滤的全面解析——Python数据挖掘代码实践
  • 通过php连接redis数据库
  • 网络安全之STP(1)
  • Kotlin泛型: 协变|逆变|不变
  • mysql慢查询日志
  • Python - 爬虫-网页抓取数据-工具wget
  • ngx_http_core_root
  • Vue 中 v-if 和 v-show 的区别
  • 如何设计有效的用户反馈闭环机制
  • 微信小程序面试内容整理-如何优化小程序的启动速度?
  • 人工智能通识速览
  • 基于Spring Boot的消防物资存储系统的设计与实现(LW+源码+讲解)
  • deepseek实战教程-第五篇支持deepseek的大模型应用安装及使用
  • 基于 SGLang 部署 Qwen2.5 7B 模型
  • 正点原子内存管理学习和修改
  • Linux深度解析运行级别管理
  • 自动插入分号机制
  • ElementUI el-menu导航开启vue-router模式
  • C语言 - 整数与浮点数运算的类型转换规则
  • 定时器(java)
  • 西宁做网站的公司捌君博力请/浙江关键词优化
  • 网站制作素材全套/提高工作效率的方法有哪些
  • 2023年山西疫情/seo接单一个月能赚多少钱
  • 武汉网站搜索引擎优化/桔子seo工具
  • 网站如何开通微信支付接口/lol关键词查询
  • 网站开发阶段怎么做测试/广州aso优化公司 有限公司