Linux-程序地址空间

目录

 1. 程序地址空间分布

2. 两个问题

3. 虚拟地址和物理地址

4. 页表

5. 解决问题

6. 为什么要有地址空间


 1. 程序地址空间分布

测试一下: 

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>

int gal_init = 1;
int gal_uninit;


int main(int argc, char* argv[], char* env[])
{
    printf("code add:%p\n",main);
    const char* str = "hello";
    
    printf("str add:%p\n",str);
    printf("init global add:%p\n",&gal_init);
    printf("uninit global add:%p\n",&gal_uninit);
    char* heap1 = (char*)malloc(10);
    char* heap2 = (char*)malloc(10);
    char* heap3 = (char*)malloc(10);
    char* heap4 = (char*)malloc(10);
    
    printf("heap1 add:%p\n",heap1);
    printf("heap2 add:%p\n",heap2);
    printf("heap3 add:%p\n",heap3);
    printf("heap4 add:%p\n",heap4);

    printf("stack1 add:%p\n",&heap1);
    printf("stack2 add:%p\n",&heap2);
    printf("stack3 add:%p\n",&heap3);
    printf("stack4 add:%p\n",&heap4);


    int i = 0;
    for(;i < argc; ++i)
    {
        printf("%s:%p\n",argv[i],&argv[i]);
    }
    printf("env add:%p\n",env);
    return 0;
}

运行结果:

code add 0x40057d
int a add 0x7ffc2b16df7c
static int b add 0x601044
str add 0x40081d
init global add 0x60103c
uninit global add 0x601048
heap1 add 0x2301010
heap2 add 0x2301030
heap3 add 0x2301050
heap4 add 0x2301070
stack1 add 0x7ffc2b16df70
stack2 add 0x7ffc2b16df68
stack3 add 0x7ffc2b16df60
stack4 add 0x7ffc2b16df58
./myproc 0x7ffc2b16e078
env add 0x7ffc2b16e088

这些都是之前就了解过的内容,今天详细聊聊地址空间

2. 两个问题

1. 安全问题:

这些程序都在同一个地址空间中,如果发生了越界访问,野指针问题,这些问题该怎么办?

2. 一个特殊现象问题:

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

int main()
{
    int num = 0;
    int ret = fork();
    if(ret == 0)
    {
        int i = 3;
        while(i--)
        {
            printf("I am child,num = %d,&num = %p\n",num,&num);
            sleep(1);                
        }
        num = 1;
        while(1)
        {
            printf("I am child,num = %d,&num = %p\n",num,&num);
            sleep(1);
        }
    }
    else
    {
        while(1)
        {
            printf("I am father,num = %d,&num = %p\n",num,&num);
            sleep(1);
        }
    }
    return 0;
}

为什么父进程和子进程中的num同一个地址,但是却有两个值?

3. 虚拟地址和物理地址

在Linux地址下,这种地址叫做 虚拟地址

我们在用C/C++语言所看到的地址,全部都是虚拟地址!

物理地址,用户一概看不到,由OS统一管理

由OS负责将 虚拟地址 转化成 物理地址 

上面的地址空间分布就是虚拟地址,每个进程被创建,就会有对应的虚拟地址表

虚拟地址表在linux下就是由mm_struct结构体来描述的

Linux下的进程管理PCB:task_struct就有一个指针指向mm_struct,程序和虚拟地址空间联系起来了

4. 页表

OS是如何把虚拟地址转换成物理地址的呢?

页表

页表是一种key-value的数据结构,记录虚拟地址和物理地址一一映射关系

task_struck有一个指针指向页表

页表里的值(虚拟地址,物理地址)是哪里来的呢?

代码在被编译器编译后的每一条语句每一个函数都会有对应的地址,这个地址是逻辑地址,和虚拟地址一样,作为页表的虚拟地址。当程序被加载到物理内存中时,就会有对应的物理地址,然后在对应到页表里面。

程序被执行时,使用的地址是虚拟地址,需要用页表映射到物理地址 

5. 解决问题

到这里,我们就可以解决第一个问题,每个进程都有一个虚拟地址和页表,这些都由OS来维护,在页表对应的每一个物理地址后面,都有一个像文件访问权限一样的标志,如果这个物理地址没有访问权限,就会直接报错终止。有效的保护了物理内存。

第二问题的答案就在图中,fork函数创建子进程时,其实就是拷贝了大部分对应的task_struct,mm_struct和页表,因为父子进程之间大部分属性都一样,但当需要改变num的值时,子进程就在物理空间上重新开了一块空间,拷贝父进程,OS也会更新对应的页表映射关系。这个叫做写时拷贝。但是虚拟地址还是一样的,只是映射关系发生了变化。所以num相同的虚拟地址,不同的值。

6. 为什么要有地址空间

安全性,有效的保护了物理内存

因为地址空间和页表是OS创建并维护的,凡是想用地址空间和页表进行映射,都需要在OS的监管下来进行访问。

内存管理模块和进程管理模块完成了解耦

提高内存的利用率

用户申请的物理空间,malloc和new其实是在虚拟地址上申请的,OS通过延迟分配,提高物理内存的利用率

地址空间和页表的存在可以将内存分布有序化(按照地址空间分布)和进程的独立性(不同的进程映射到不同的物理空间)每一个进程不知道其他进程的存在

对于程序的分批加载,当程序刚刚新建的状态下,进程就只创建内核结构,程序和数据还没有加载到内存中。

程序的分批换出,当进程短时间内不会再被执行了,比如阻塞了,进程的数据和代码被换出到磁盘中的swap区,页表的映射关系也改为磁盘地址。这个过程就叫挂起。

完,写的不好的地方多有体谅,还在学

猜你喜欢

转载自blog.csdn.net/bananawolf/article/details/137376117