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

目录

1.直接写代码看现象

2.引入最基本的理解

3.细节问题-理解它


1.直接写代码看现象

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <unistd.h>
int g_val = 100;

int main()
{
    printf("father is running, pid: %d, ppid: %d\n", getpid(), getppid());


    pid_t id = fork();
    if(id == 0)
    {
        //child
        int cnt = 0;
        while(1)
        {
            printf("I am child process, pid: %d, ppid: %d. g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
            cnt++;
            if(cnt == 5)
            {
                g_val = 300;
                printf("I am child process, change %d -> %d\n", 100, 300);
            }
        }
    }
    else
    {
        //father
        while(1)
        {
            printf("I am father process, pid: %d, ppid: %d. g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
        }
    }
}

并且我们还能观察到,从一开始父子进程便是相同的

由此可推出,我们打印出来的地址绝对不是物理地址,而是虚拟地址,故我们在C/C++中看到的地址是虚拟地址,由操作系统负责转化为物理地址

关于地址,从低地址到高地址存储的依次是:代码段、初始化全局数据区、未初始化全局数据区、堆区、栈区、命令行参数与环境变量。其中,堆区的空间是从小到大增长的,而栈区的空间是从大到小增长的

其中在32位机器中,使用32位(比特位)来表示一个地址,这意味着它可以表示 2^32个不同的地址,也就是4GB;在64位机器中,使用64位(比特位)来表示一个地址,它可以表示 2^64 个不同的地址,也就是16EB

需要注意的是,这里的EB指的是艾字节(Exabyte),1EB等于 1,0241,024 PB(拍字节),而1PB等于 1,0241,024 TB(太字节),1TB等于 1,0241,024 GB(千兆字节),1GB等于 1,0241,024 MB(兆字节),1MB等于 1,0241,024 KB(千字节),1KB等于 1,0241,024 字节。因此,16EB是一个非常巨大的数字,远远超过了当前大多数应用场景的需求。

2.引入最基本的理解

我们在回到刚刚代码示例那里,当子进程修改g_val的值时,为了保证进程之间的独立性(也即是说,子进程的数值修改不应该影响父进程),此时就会发生写时拷贝,会给子进程的g_val开辟独立的物理地址空间,而不是与父进程共享同一块空间,通过观察我们发现,代码依旧共享同一块空间,只是数据区不同了而已

3.细节问题-理解它

  • 什么叫做地址空间?

在32位机器下,数据与地址总共32根线,每根数据与地址线可以产生充电和放电两种状态,即产生0或1。因此地址总线排列组合形成地址范围为[0, 2^32 ],这就是地址空间

  • 如何理解地址空间上的区域划分

【示例】小学生划分38线

张三和李四是同桌,桌子共长200cm,他们约定每人100cm。即课桌划分为[0,100], [101,200]这两个区域划分,如果要记录区域的结果,我们就需要先描述再组织

cpp 复制代码
struct desktop{
  int zhangsan_start;
  int zhangsan_end;
  int lisi_start;
  int lisi_end;
};

操作系统为每个进程创建了进程地址空间和mm_struct,用于记录每个进程的各个区域的起始位置和结束位置。在已经被分配给某进程的空间范围内,该进程可以随意使用和访问

  • 为什么要有进程地址空间?

【示例】大富翁的3个私生子

远在几千万千里的大富翁有三个私生子,3个私生子互相不知道对方的存在。这个大富翁对每一个私生子说,我有100亿,等我某一天驾鹤西去,这100亿都是你的。当某个儿子有需求时,他像父亲申请10w,此时大富翁就给他10w。但如果申请100亿,可能无法成功(因为有一部分被大富翁其他私生子占用了),但他并不会觉得这100亿不是他的,而是觉得自己申请的太多了

而这里的大富翁等同于操作系统,而这三个私生子等同于系统上的进程。操作系统有4GB的内存空间,进程坚信自己拥有操作系统的全部空间,但通常情况下,进程并不会申请过大的空间。

因此:1️⃣将无序变成有序,让进程以统一视角看待物理内存以及自己运行的各个区域,

在操作系统中,虚拟内存是一种内存管理功能,它让每个进程都认为自己拥有连续的、独立的地址空间,即使物理内存可能是不连续的,并且被多个进程共享。这样,每个进程都感觉自己拥有整个内存,而实际上它们是在操作不同的物理内存区域。这种抽象提供了以下几个好处:

因此:1️⃣将无序变成有序,让进程以统一视角看待物理内存以及自己运行的各个区域,

在操作系统中,虚拟内存是一种内存管理功能,它让每个进程都认为自己拥有连续的、独立的地址空间,即使物理内存可能是不连续的,并且被多个进程共享。这样,每个进程都感觉自己拥有整个内存,而实际上它们是在操作不同的物理内存区域。这种抽象提供了以下几个好处:

进程隔离:每个进程都不能访问其他进程的内存,这增强了系统的稳定性和安全性。

内存保护:操作系统可以设置权限,防止进程访问它不应该访问的内存区域。

内存扩展:通过虚拟内存,系统可以使用硬盘空间作为临时的内存使用,即交换空间(swap space),从而扩展可用内存的大小。

【示例2】:压岁钱的管理

小明新年获得了200块压岁钱,母亲担心小明会乱花钱,于是和小明说"你的压岁钱由我来保管,你需要买什么就和我说,我在从这里给你钱"。于是有一天小明要买一款游戏机150元,找妈妈去要,结果妈妈说"游戏机,会害了你的学习,不给买"。再一次小明要去买学习资料,向妈妈申请50块钱,妈妈同意了。因此,新增一个人,作为中间层,可以对非法请求进行拦截

因此:在操作系统中,页表除了包含虚拟地址到物理地址的映射关系,还记录了该区域的读写权限。当用户对其已申请空间做了超出读写权限外的操作,则会被操作系统识别到,并终止该进程。

也就是2️⃣操作系统会拦截非法请求-->对物理内存进行保护

**注意:**物理地址本身没有读写权限,我们在语言中的const等限制某个地址空间的读写权限,本质是在页表中添加读写权限

如果直接使用物理地址而不是虚拟地址,当我们对也指针进行访问时,由于物理地址没有读写权限控制,导致我们修改了其他进程的数据,破坏了进程的独立性。因此,使用虚拟地址+页表的方式可以保证进程的独立性

在操作细则中,由于内存空间十分宝贵,进程中的代码和数据不一定会被全部加载到内存,因此页表中还有一个字段,表示虚拟地址指向的代码和数据是否在磁盘上。如果虚拟地址映射物理地址时,发现该数据或代码位于磁盘上,则会引发页中断(即当前页表无法映射),此时系统在将对应的代码和数据加载到内存中

★ 惰性加载是一种按需加载资源(如图片、视频、脚本或其他数据)的策略,仅在真正需要时才进行加载。这与预加载(Preloading)相反,预加载是在页面加载时提前加载所有可能需要的资源。

同时,如果因为内存资源紧张,可能会将某个进程挂起,即将它的代码和数据先保存到磁盘中,待内存资源不紧张时再重新加载,但重新加载后的物理地址可能与之前的物理地址不再相同。假设进程没有使用虚拟地址空间+页表映射的方式,则每次将进程代码和数据加载到内存就需要改动PCB到地址空间内容,而不是修改页表的内容。这样将使得进程管理与内存管理耦合度过高。同时,物理内存中几乎所有的数据和代码都是乱序的,由于页表的存在,它可以将物理地址和虚拟地址进行映射,在进程视角,可以将内存分布有序化

因此:3️⃣因为有地址空间和页表的存在,将进程管理模块和内存管理模块进行了解耦合

此时我们也可以知道C/C++申请的地址是虚拟地址。

此时我们要重新定义一下我们对进程的概念进程=内核数据结构(PCB+页表+进程地址空间)+代码和数据

相关推荐
LKAI.10 分钟前
搭建Elastic search群集
linux·运维·elasticsearch·搜索引擎
gywl2 小时前
openEuler VM虚拟机操作(期末考试)
linux·服务器·网络·windows·http·centos
日记跟新中3 小时前
Ubuntu20.04 修改root密码
linux·运维·服务器
码农君莫笑3 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
BUG 4043 小时前
Linux——Shell
linux·运维·服务器
大霞上仙4 小时前
Linux 多命令执行
linux·运维·服务器
晨欣4 小时前
Kibana:LINUX_X86_64 和 DEB_X86_64两种可选下载方式的区别
linux·运维·服务器
AI青年志4 小时前
【服务器】linux服务器管理员查看用户使用内存情况
linux·运维·服务器
dessler5 小时前
Docker-run命令详细讲解
linux·运维·后端·docker
PyAIGCMaster5 小时前
ubuntu装P104驱动
linux·运维·ubuntu