【Linux详解】——进程地址空间

news2024/11/18 4:28:10

📖 前言:本节将以新的视角看的地址空间的特点,与以前对指针的认识做区分。


目录

  • 🕒 1. C/C++ 地址空间回顾
  • 🕒 2. 进程地址空间
    • 🕘 2.1 感性理解概念
    • 🕘 2.2 如何“画饼”
    • 🕘 2.3 区域划分
  • 🕒 3. 进程地址空间与内存
    • 🕘 3.1 虚拟地址和物理地址
    • 🕘 3.2 多进程的映射关系
  • 🕒 4. 地址空间存在的意义
    • 🕘 4.1 保证安全性
    • 🕘 4.2 保证独立性
    • 🕘 4.3 保证统一性

🕒 1. C/C++ 地址空间回顾

我们以前在学习 C/C++ 的动态内存管理的时候,通常把地址空间划分为如下几个区域:

在这里插入图片描述

那么这个C/C++地址空间是什么,是内存吗?我们以一个例子来测试:

# Makefile
mytest:mytest.c
	gcc -o $@ $^ #-std=c99
.PHONY:clean
clean:
	rm -f mytest
// mytest.c
#include <stdio.h>
#include <unistd.h>

int global_value = 100;

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        printf("fork error\n");
        return 1;
    }
    else if(id == 0)
    {
        int cnt = 0;
        while(1)
        {
            printf("I'm a child process, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value);
            sleep(1);
            cnt++;
            if(cnt == 10)
            {
                global_value = 300;
                printf("The child process has changed the global value\n");
            }
        }
    }
    else
    {
        while(1)
        {
            printf("I'm a parent process, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value);
            sleep(2);
        }
    }
    sleep(1);
}
输出结果:
I'm a parent process, pid: 26634, ppid: 23075 | global_value: 100, &global_value: 0x60105c
I'm a child process, pid: 26635, ppid: 26634 | global_value: 100, &global_value: 0x60105c
I'm a child process, pid: 26635, ppid: 26634 | global_value: 100, &global_value: 0x60105c
I'm a parent process, pid: 26634, ppid: 23075 | global_value: 100, &global_value: 0x60105c
I'm a child process, pid: 26635, ppid: 26634 | global_value: 100, &global_value: 0x60105c
I'm a child process, pid: 26635, ppid: 26634 | global_value: 100, &global_value: 0x60105c
          								...当cnt=10后...
The child process has changed the global value
I'm a child process, pid: 26635, ppid: 26634 | global_value: 300, &global_value: 0x60105c
I'm a child process, pid: 26635, ppid: 26634 | global_value: 300, &global_value: 0x60105c
I'm a parent process, pid: 26634, ppid: 23075 | global_value: 100, &global_value: 0x60105c
I'm a child process, pid: 26635, ppid: 26634 | global_value: 300, &global_value: 0x60105c
I'm a child process, pid: 26635, ppid: 26634 | global_value: 300, &global_value: 0x60105c
I'm a parent process, pid: 26634, ppid: 23075 | global_value: 100, &global_value: 0x60105c

观察结果,对于值,子进程改变成300,父进程仍然是100可以理解,但是我们惊讶的发现,两个进程中global_val的都指向同一个地址。与我们之前理解的指针大相径庭。指针是内存中变量的地址,我们现在打印的地址同样是以指针的形式打印的,那么只能说明我们之前理解的指针是错误的,指针指向的位置并不是物理内存。因为如果是物理内存,那么不可能发生同一个地址的变量的值不相同的情况。

结论:我们所看到的打印出来的地址空间分布都是虚拟地址(又叫线性地址、逻辑地址)。 我们称这种地址为虚拟地址空间。它并不是真正的物理内存上的空间。

🕒 2. 进程地址空间

🕘 2.1 感性理解概念

设计进程的理念——进程它会认为自己是独占系统资源的(事实上并不是)

我们以虚拟机来举例子,假设我们的物理机有16G内存,预先分配给虚拟机是4G,那么这个虚拟机自己就会认为这4G就是全部,使用它浏览网页、安装软件,这些操作都不会影响到物理机因为我们的虚拟机管理软件会控制它不会干扰到物理机。同理对于一个进程,它认为自己需要16G空间,操作系统会直接给你16G的空间吗?那是不可能的,因为操作系统还要兼顾其他的进程,但每一个进程被操作系统拒绝后,也仍然会认为自己拥有全部的内存的使用权。因此,操作系统给进程画的大饼,就是进程(虚拟)地址空间

生活中也有这样的例子,比如银行存钱,我们所存的钱一定是在银行原封不动的存放着吗?事实上是不可能的,你的钱可能会被放贷,也可能会被用来理财,但你的余额在你看来仍是那些没有变化。但如果有人说银行要倒闭,这时候一旦所有人都去取钱,也就是挤兑,这时银行是不能实现将每一个人的钱都取出来的。

🕘 2.2 如何“画饼”

我们以迅雷为例

struct 进程信息
{
    char* name;		// 迅雷
    char* when;		// 什么时候运行与结束
    char* target;	// 下载完一部电影
    char* memory;	// 需要多少内存(假设100M)
    // 程序运行
}

地址空间的本质:是内核的一种数据结构
在Linux中这种数据结构叫mm_struct,操作系统会为每个进程创建一个 mm_struct 对象,然后通过管理结构体对象来间接管理进程地址空间。

🕘 2.3 区域划分

在此之前,我们需要一些预备知识:

  1. 地址空间描述的基本空间大小是字节
  2. 32位下,有232个地址。232×1字节 = 4GB的空间范围
  3. 每一个字节都有唯一的一个地址,并且都是虚拟地址(unsigned int (32bits))

在这里插入图片描述

根据上图我们知道,对于区域划分,就是通过改变边界的大小,从而实现地址空间的动态分配。

struct mm_struct {
    //uint32_t:32位系统下的无符号整型
	uint32_t code_start, code_end;
    uint32_t date_start, code_end;
    uint32_t heap_start, heap_end;
    unit32_t stack_start, stack_end;
    ...
};

我们上面也提到过,操作系统会给进程画大饼,也就是说,每一个进程被创建出来,形成对应的task_struct(进程控制块),都会有232次方个空间(4GB)里面包括进程的pid、ppid、进程优先级、进程属性等,每个task_struct都会对应一个mm_struct(每一个都是大饼),task_struct通过其中的指针变量指向对应的mm_struct

查阅源码后可以印证我们的结论,我们之前一直所谈的C/C++地址空间实际上是进程的地址空间。

🕒 3. 进程地址空间与内存

🕘 3.1 虚拟地址和物理地址

通过上面知识我们知道,虚拟地址是连续的,因此我们也称之为线性地址。而物理地址是数据在内存与磁盘间传输的过程(即IO),IO的单位是4KB,那么我们就将内存中4KB的大小空间看成一个page,因此对于内存的数据来说,如果内存的全部大小为4GB,那么我们可以把内存分割成4GB/4KB个page,即我们可以将内存想象为一个结构体数组:struct page mem[4GB/4KB],通过偏移量就可以访问内存中所有的page,也就可以访问到内存的所有数据。

对于这些虚拟地址,作为数据来说,也需要存放在物理地址的某一个位置,因此这就会与内存产生关联。而虚拟地址与物理地址产生关联的媒介就产生了,我们将这个媒介称之为页表

在这里插入图片描述

举个例子,如果内存中的某一个位置a=10,当我们编写代码时,代码的数据首先会被加载到虚拟地址中,通过页表的映射,映射到了相应的物理地址,之后就会将原有的数据修改为新的数据。

因此我们能做的,就是编辑代码让其在虚拟地址上保存,而通过页表映射到内存等其他的所有工作,都是由操作系统自动帮你完成的。

🕘 3.2 多进程的映射关系

在这里插入图片描述
这两个进程只能看到自己所对应的mm_struct(虚拟地址空间),就像我们前面提到的大饼,操作系统在处理这两个进程时将其编译到虚拟地址空间以及页表的过程就是操作系统给进程画的大饼,因为mm_struct都对应着2^32个地址,对于进程而言似乎可以使用全部,实际上操作系统并不允许任何一个进程完全占用所有的内存空间。

🕒 4. 地址空间存在的意义

🕘 4.1 保证安全性

进程直接访问物理内存可能会出现越界、恶意进程读取信息等非法操作,通过页表可以对非法的虚拟地址进行拦截,相当于变相的保护物理内存

🕘 4.2 保证独立性

还是开篇那个例子,为什么相同地址下父进程和子进程的数值不同呢?

当我们编译完代码生成.c文件时,数据已经存储在磁盘了,当程序运行时,其数据会被加载到物理内存中,global_val=100也就被存放在了内存的某一块地址,由于父进程和子进程都需要访问global_val,于是global的内存中的地址就会通过页表映射到虚拟空间的某一个地址中,从而正常访问global_val,并且对应的虚拟地址也是相同的,因此开始时我们能看到父进程和子进程对应的global_val的数值和地址都相同。
在这里插入图片描述
当子进程要改变global_val的值,由于进程与进程之间的独立性,子进程一旦要改变global_val,操作系统就会将子进程页表与内存的物理地址之间的联系断开,并在物理内存的另一个位置将原来物理地址的数据拷贝过来,这一操作被称为写时拷贝。 这样子进程改变global_val的值也不会影响到父进程的global_val。因此我们所看到的子进程与父进程的虚拟地址仍是相同的地址。
在这里插入图片描述
进程 = 内核数据结构 + 进程对应的代码和数据

内核数据结构是独立的,不同进程对应的代码和数据也是不一样的,因此进程就是独立的。因此我们也得出结论:地址空间的存在,可以更方便的进行进程和进程的数据代码的解耦,保证了进程的独立性。

🕘 4.3 保证统一性

  1. 在程序编译链接的时候,磁盘中的程序就有了地址,这个地址也被我们称为逻辑地址(虚拟地址)(在Linux下,虚拟地址和逻辑地址是一样的)
  2. 虚拟地址空间的规则不仅OS需要遵守,编译器同样需要遵守!也就是说在编译链接的过程中,编译器在编译你的代码的时候,就是按照虚拟地址空间的方式进行编址的。

在这里插入图片描述
假设这个exe是以32位地址空间编址的。在编译时,main中的fun()会通过逻辑地址跳转到定义的fun()函数,当代码加载到内存时,这个逻辑地址仍然存在,也就是程序内部使用的地址在加载到内存中时仍然存在,但当我们将代码加载到内存时,代码既然也是数据,那么就一定需要在物理内存中的某个物理地址进行保存,此时这段代码既有外部的物理地址,也有内部的逻辑地址,相当于有了两套地址。

那么,当这段代码通过页表的映射加载到进程的mm_struct时,这段代码就被存放在这个进程对应的进程地址空间中,这个过程就是物理地址通过映射传输的,那么当CPU的寄存器通过指令读取此代码时,出来的一定是虚拟地址。

当CPU找到了虚拟地址之后,就会通过页表的映射,按照来时的路线去寻找内存中的main()函数的代码,将这个实际存在的代码通过CPU读取。

在这里插入图片描述

由此可以得出结论:地址空间让进程以统一的视角,来看待进程对应的代码和数据等各个区域,方便编译器也以统一的视角来进行编译代码(使用和编译的统一是指都是在虚拟地址空间的统一,因为规则一样,所以编完即可使用。)


OK,以上就是本期知识点“进程地址空间”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟📌~
💫如果有错误❌,欢迎批评指正呀👀~让我们一起相互进步🚀
🎉如果觉得收获满满,可以点点赞👍支持一下哟~

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

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

相关文章

SQL面试题

有3个表S(学生表)&#xff0c;C&#xff08;课程表&#xff09;&#xff0c;SC&#xff08;学生选课表&#xff09; S&#xff08;SNO&#xff0c;SNAME&#xff09;代表&#xff08;学号&#xff0c;姓名&#xff09; C&#xff08;CNO&#xff0c;CNAME&#xff0c;CTEACHER&…

SAP FICO 定义成本组件结构

成本组件结构定义 我们在使用CK11N核算物料标准成本时候可以看到有项目明细&#xff0c;也可以看到有成本构成&#xff0c;那么问题来了&#xff0c;怎么将项目明细分类到各个成本构成上面呢&#xff1f; 【后台配置路径】&#xff1a; SPRO→控制→产品成本控制→产品成本计划…

【云原生】k8s之包管理器Helm

内容预知 前言 1.Helm的相关知识 1.1 Helm的简介与了解 1.2 Helm的三个重要概念 1.3 Helm2与Helm3的的区别 &#xff08;1&#xff09;helm2的部署方式与使用 &#xff08;2&#xff09;Helm3的部署与使用 2.Helm在k8s集群中的部署 &#xff08;1&#xff09;将Helm安…

【docker概念和实践 3】 注册阿里云账号、应用阿里云数据源

一、说明 阿里云是什么&#xff1f;是出租、出售运算资源的平台。几乎囊括各个领域的运算、存储、服务器、云端资源。阿里云的明星产品四大件是&#xff1a;1、即云服务器ECS、2、云数据库RDS、3、负载均衡SLB 4对象存储OSS。 其它多种服务&#xff1a;云小站_专享特惠_云产品推…

OpenSceneGraph图形状态管理内幕

在这个教程中&#xff0c;我们将了解 Open Scene Graph 如何表示 OpenGL 图形状态&#xff0c;并探索 Open Scene Graph 优化渲染以最小化状态更改次数的一些方法。 推荐&#xff1a;用 3D场景编辑器快速搭建数字孪生场景。 1、OpenGL状态机 Open Scene Graph 最重要的优化之一…

Android的linux内核解耦

1、boot内容查看Boot Image Header&#xff0c;version 2版本包含内容最多&#xff0c;包括了内核、设备树、根目录、recovery设备树&#xff0c;cmdline。boot拆包与内容解析参考1、Android bootimg kernel&#xff08;boot.img&#xff09;2、linux的ramdisk解耦2.1、ramdisk…

Python学习笔记——文件操作

输入和输出Python两种输出值的方式: 表达式语句和 print() 函数。第三种方式是使用文件对象的 write() 方法&#xff0c;标准输出文件可以用 sys.stdout 引用。如果你希望输出的形式更加多样&#xff0c;可以使用 str.format() 函数来格式化输出值。如果你希望将输出的值转成字…

H3C路由器带宽保证(命令行)配置方法

1 配置需求或说明 1.1适用产品系列 本案例适用于如MSR810、MSR93X系列的路由器。 1.2配置需求及实现的效果 某企业路由器接入业务有语音业务、管理部门业务和普通业务。要求当网络出现拥塞时&#xff0c;语音业务加速转发&#xff0c;管理部门业务确保转发&#xff0c;剩余或…

小满OKKICRM与金蝶云星空对接集成客户档案

小满OKKICRM与金蝶云星空对接集成客户列表查询(更新列表)&客户新增(小满客户对接金蝶客户-P)数据源平台:小满OKKICRM小满科技成立于2013年&#xff0c;是阿里巴巴集团战略投资的高新技术企业。小满科技以“人工智能大数据”为核心驱动力&#xff0c;为外贸企业提供智能CRM解…

合并所有重叠的区间

Python-合并区间 题目 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 示例 1: 输入&#xff1a;interva…

【Ajax】模板引擎

一、模板引擎的基本概念渲染UI结构时遇到的问题var rows [] //遍历空数组 $.each(res.data, function (i, item) { // 循环拼接字符串rows.push(<li class"list-group-item"> item.content <span class"badge cmt-date">评论时间&#xff1a;…

87.序列到序列学习(seq2seq)以及代码实现

1. 机器翻译 2. Seq2Seq 双向RNN可以做encoder&#xff0c;但不能做decoder。 3. 编码器-解码器细节 4. 训练 5. 衡量生成序列的好坏的BLEU 上面的公式既加入了段序列的惩罚项&#xff0c;又加入了更难出现的长序列的高权重。 6. 总结&#xff1a; Seq2seq从一个句子生成另一…

【网络通信】【电信运营商实战工程师】思科设备篇-网络工程师必备基础知识

电信运营商实战工程师系列文章. 思科设备篇-网络工程师必备基础知识. 文章目录1. 电信运营商网络设备机房2. 认识并管理运营商网络设备3. GNS3 安装与配置4. IPv4地址及子网划分 VLSM-CIDR 详解5. OSI 七层参考模型及进制转换技巧1. 电信运营商网络设备机房 知识点&#xff1a;…

win-bat批处理命令

基本知识 cmd 与 powershel 命令和关键字不区分大小写&#xff0c;变量名区分大小写 DOS 是磁盘操作系统&#xff1b;命令提示符是 DOS 系统的界面中输入 DOS 命令的提示位置&#xff1b;cmd 是系统运行其自带 DOS 的命令 PID 是 processid&#xff08;进程号&#xff09;&am…

36-剑指 Offer 38. 字符串的排列

题目 输入一个字符串&#xff0c;打印出该字符串中字符的所有排列。 你可以以任意顺序返回这个字符串数组&#xff0c;但里面不能有重复元素。 示例: 输入&#xff1a;s "abc" 输出&#xff1a;["abc","acb","bac","bca&quo…

二维前缀和数组二维差分数组

二维前缀和数组&二维差分数组 一维前缀和 用途&#xff1a;快速求出数组中nums[i,j]nums[i,j]nums[i,j]元素之和 定义&#xff1a;sums[i1]sums[i1]sums[i1]为nums数组前iii个元素之和 sums[i1]∑j0inums[j]sums[i 1] \sum _{j0} ^{i}nums[j] sums[i1]j0∑i​nums[j] …

神经网络——day67:Residual Network

Deep Residual Learning for Image RecognitionDeep Residual Learning for Image Recognition1. Introduction2. Related WorkResidual Representations(剩余表示).Shortcut Connections(快捷连接).3. Deep Residual Learning3.1. Residual Learning3.2. Identity Mapping by …

Java项目:学生管理系统

Java项目&#xff1a;学生管理系统一、学生管理系统基础版需求1. 初始菜单2. 学生类&#xff1a;3. 添加功能&#xff1a;4. 删除功能&#xff1a;5. 修改功能&#xff1a;6. 查询功能&#xff1a;代码1. 学生类2. 测试类输出结果a. 添加b. 删除c. 修改d. 查询e. 退出二、学生管…

前端监控 二三事

有必要针对 JS 错误做监控吗&#xff1f; 我们可以先假设不对 JS 错误做监控&#xff0c;试想会出现什么问题&#xff1f; JS 错误可能会导致渲染出错、用户操作意外终止&#xff0c;如果没有 JS 错误监控&#xff0c;开发者完全感知不到线上这些异常情况。特别是像电商、支付…

【2-神经网络优化】北京大学TensorFlow2.0

课程地址&#xff1a;【北京大学】Tensorflow2.0_哔哩哔哩_bilibiliPython3.7和TensorFlow2.1六讲&#xff1a;神经网络计算&#xff1a;神经网络的计算过程&#xff0c;搭建第一个神经网络模型神经网络优化&#xff1a;神经网络的优化方法&#xff0c;掌握学习率、激活函数、损…