【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+页表+进程地址空间)+代码和数据

相关推荐
抠脚学代码几秒前
Linux开发-->驱动开发-->字符设备驱动框架
linux·数据结构·驱动开发
Elias不吃糖20 分钟前
第四天学习总结:C++ 文件系统 × Linux 自动化 × Makefile 工程化
linux·c++·学习
噜啦噜啦嘞好20 分钟前
Linux进程信号
linux·运维·服务器
REDcker1 小时前
Linux 进程资源占用分析指南
linux·运维·chrome
samroom1 小时前
Linux系统管理与常用命令详解
linux·运维·服务器
一叶之秋14122 小时前
Linux基本指令
linux·运维·服务器
码割机2 小时前
Linux服务器安装jdk和maven详解
java·linux·maven
亚林瓜子2 小时前
在amazon linux 2023上面源码手动安装tesseract5.5.1
linux·运维·服务器·ocr·aws·ec2
爱学习的大牛1232 小时前
Ubuntu 24.04 安装 FreeSWITCH 完整教程
linux·freeswitch
go_bai2 小时前
Linux--进程池
linux·c++·经验分享·笔记·学习方法