【Linux】进程地址空间

发布于:2024-05-07 ⋅ 阅读:(30) ⋅ 点赞:(0)

目录

一、进程地址空间

1.1 程序地址空间回顾

1.2 虚拟地址

1.3 地址空间的概念

 1.4 为什么要有地址空间

1.5 简述分页机制 


一、进程地址空间

1.1 地址空间回顾

以上的结果可以很简单地证明,在此不做赘述。

1.2 虚拟地址

#include<stdio.h>
#include<unistd.h>
int g_val = 100;
int main()
{
    pid_t id = fork();
    if(id==0)
    {
        //child
        int count = 3;
        while(1)
        {
    		printf("child: pid: %d,ppid: %d, g_val:%d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
            sleep(1);
            if(count == 0)
            {
                g_val = 200;
            }
            count--;
        }
        
    }
    else if(id>0)
    {
        //father
        while(1)
        {
             printf("father: pod: %d,ppid: %d, g_val:%d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
            sleep(1);
        }
       	
    }
    else
    {
        //todo
    }
    return 0;
}

 

父进程和子进程代码共享,所以前4次打印的g_val的值和地址相同,第5次打印时,父进程g_val是100,子进程的g_val变成了200(写时拷贝),但是两个g_val的地址一样,这是为什么?

原因:我们C/C++看到的地址不是物理地址,而是虚拟地址/线性地址。 

在计算机科学中,程序运行时使用的地址并非直接指向物理内存的地址,而是操作系统提供的虚拟地址。这些虚拟地址是抽象的,由操作系统管理,它们允许程序之间和操作系统之间进行隔离,以防止程序相互干扰。

当程序启动时,操作系统会将程序的代码和数据从磁盘或其他存储介质加载到物理内存中。这个过程称为内存加载或内存映射。在这个过程中,操作系统会创建一个映射,将虚拟地址转换为物理地址。这个映射通常由内存管理单元(MMU)完成,它是CPU的一部分,负责处理虚拟地址到物理地址的转换。

每个进程都有自己的地址空间,包括代码段、数据段、堆、栈等。这些部分在物理内存中可能是不连续的,但是它们在进程的虚拟地址空间中是连续的。操作系统通过页表(Page Table)来维护这些映射关系。

因此,当我们看到一个程序的虚拟地址时,这些地址实际上是操作系统为了进程间的隔离和内存管理而提供的抽象表示。每个进程都有自己的虚拟地址空间,这些地址与物理内存中的位置通过页表进行映射。

总结来说,虚拟地址是操作系统提供给程序的一种抽象地址,它允许程序在不了解物理内存布局的情况下运行。操作系统通过内存管理单元和页表将这些虚拟地址转换为物理地址,以访问实际的内存位置。

1.3 地址空间的概念

地址空间:一个实体(如进程、设备或内存段)可以访问的内存地址的总数。
第一张图中地址空间4GB的含义是指一个进程可以访问的内存地址数量为2的32次方个字节(32位的计算机系统)。使用32位地址线,每条地址线可以表示2个可能的状态(通常是0或1),所以总共有2的32次方个可能的地址。对地址空间的的操作需要先描述在组织。

在Linux内核中描述进程的地址空间(或虚拟内存管理器)的结构体称作mm_struct用于管理进程的地址空间。每个进程都有一个mm_struct实例,它包含了进程内存管理相关的所有信息。
(task_struct中有一个struct mm_struct* mm) 

mm_struct数据结构的主要组成部分包括:

  • pgd:这是一个指针,指向页全局目录,它是一个数据结构,用于存储页表的根地址。
  • vm_start:指向虚拟地址空间中第一个可用的虚拟地址。
  • vm_end:指向虚拟地址空间中最后一个可用的虚拟地址。
  • start_code:指向代码段的起始地址。
  • end_code:指向代码段的结束地址。
  • start_data:指向数据段的起始地址。
  • end_data:指向数据段的结束地址。
  • start_brk:指向堆的起始地址。
  • brk:指向堆的当前结束地址。
  • start_stack:指向栈的起始地址。
  • //...

栈和堆的调整,即调整进程地址空间中的...start和...end

 1.4 为什么要有地址空间

  1. 无序变有序:让进程以统一的视角看待内存,因此任意一个进程都可以通过"地址空间+页表"将乱序的内存数据变成有序。代码段、数据段、堆、栈等在物理内存中可能是不连续的,但是它们在进程的虚拟地址空间中是连续的。
  2. 内存保护:地址空间的最主要目的是提供内存保护。每个进程的地址空间都是隔离的,这意味着一个进程不能直接访问另一个进程的内存,这有助于防止一个进程的错误影响到其他进程。保护物理内存不受到任何进程内地址的直接访问,在虚拟地址到物理地址的转化过程中方便进行合法性校验。
  3. 进程管理与内存管理解耦:地址空间允许操作系统和硬件更有效地管理内存(物理虚拟地址转换、内存分配和回收可以独立于进程进行,无需进程参与)。通过将内存划分为不同的区域,如代码段、数据段、栈和堆,操作系统可以更好地控制内存的使用和回收。进程可以无需关心物理内存的具体布局和分配细节。
  4. 虚拟内存:地址空间与虚拟内存系统紧密相关。虚拟内存允许进程使用超过物理内存大小的地址空间,当物理内存不足时,操作系统可以将部分内存换出到磁盘上的交换空间。
  5. 内存优化:地址空间有助于优化内存的使用。例如,局部变量通常存储在栈上,而全局变量和静态变量存储在数据段中。
  6. 内存隔离:地址空间有助于实现内存隔离。通过页表,每个进程都有自己的地址空间,映射到不同的物理内存处,这意味着操作系统可以独立地管理每个进程的内存。
  7. 硬件支持:地址空间的概念得到了硬件的支持。内存管理单元(MMU)是CPU的一部分,它负责处理虚拟地址到物理地址的转换。

1.5 简述分页机制 

分页机制
分页机制是一种内存管理技术,它将物理内存划分为固定大小的物理页,并将虚拟内存划分为相同大小的虚拟页。当一个进程运行时,它的虚拟地址空间中的每个虚拟页都必须有一个对应的物理页。页表的作用就是维护这种映射关系。

页表的层次结构
页表是一个层次结构,通常包括三级页表:

  1. 页全局目录
    每个进程都有自己的页全局目录。
    页全局目录包含指向页目录的指针。
    页全局目录的物理地址存储在CR3寄存器中。
  2. 页目录
    每个进程也有自己的页目录。
    页目录包含指向页表的指针。
  3. 页表
    每个进程的页表包含指向物理页的指针。
    页表的每个条目称为页表项,它包含物理页的地址和访问权限等信息。

CR3寄存器
CR3寄存器存储了页全局目录的物理地址,用于快速地将虚拟地址转换为物理地址。当CPU生成一个虚拟地址时,内存管理单元(MMU)会使用CR3寄存器中的页全局目录地址来查找对应的页目录。然后,MMU会继续使用页目录中的信息来查找页表,最后使用页表中的信息来找到物理地址。

每个进程都有自己的页表
每个进程都有自己的页表是为了实现内存隔离和保护。这种设计确保了每个进程的内存空间都是独立的,一个进程的错误不会影响到其他进程。页表的层次结构允许操作系统高效地管理内存资源,并为每个进程提供一个似乎是无限大小的内存空间。

所以进程切换时,不仅要把PCB和匹配的代码数据进行切换,还要把地址空间和页表也要切换。
因为进程地址空间的地址在PCB中,所以PCB变化时地址空间也变化。上下文保存时CR3也被保存好,因此页表也被保存好了。因此只要把上下文保存好,那么整个进程的PCB、虚拟地址空间、页表都会自动切换。