进程地址空间

目录

是什么?

为什么要有地址空间?

1、风险管理

2、行为分离

3、统一化

好处


是什么?

进程地址空间本质是内核中的一种数据类型。

struct mm_struct

{

​	// 进程地址空间的划分

}

 这张图的虚拟地址便是进程地址空间的分布,由下至上,需要注意的是:堆区的地址是从下至上增长,栈区是从上之下增长,也就是说,开辟的第一个堆空间的地址,便是当前进程的堆区最小地址;开辟的第一个堆空间的地址,便是当前进程的堆区最小地址,也就是main() 程序入口。

既然我们看了上面这些区间,那来验证一下真实性

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

int g_unval;
int g_val = 100;

int main(int argc, char *argv[], char *env[])
{
    const char *s ="hello world";
    printf("code addr: %p\n", test2);
    printf("string rdonly addr: %p\n", s);
    printf("init addr: %p\n", &g_val);
    printf("uninit addr: %p\n", &g_unval);
    char *heap = (char*)malloc(10);
    char *heap1 = (char*)malloc(10);
    char *heap2 = (char*)malloc(10);
    char *heap3 = (char*)malloc(10);
    char *heap4 = (char*)malloc(10);
 
    printf("heap addr: %p\n", heap1);
    printf("heap addr: %p\n", heap2);
    printf("heap addr: %p\n", heap3);
    printf("heap addr: %p\n", heap4);
 
    printf("stack addr: %p\n", &s);
    printf("stack addr: %p\n", &heap);
    int a = 10;
    int b = 30;
    printf("stack addr: %p\n", &a);
    printf("stack addr: %p\n", &b);

    for(int i = 0; argv[i]; ++i) {
        printf("argv[%d]   %p\n", i, argv[i]);
    }
    for (int i = 0; env[i]; ++i) {
        printf("env[%d]   %p\n", i, env[i]);
    }


    return 0;
}

 

 通过这段代码,可以验证区域的正确性,栈的最大地址都比命令行参数的最小地址还小

为什么要有地址空间?

1、风险管理

如果进程可以直接访问到物理地址,那么就可以以非法手段直接或间接的访问或者修改其他进程的数据或代码,比如指针形式的修改,那么这一缺陷就会被不法分子利用,而页表是在OS的管理下进行的,当进程要访问物理地址时,OS会先将地址进行映射,然后检测要访问的区间是否合法,即是否在该进程的允许地址范围内,如果不在则直接中断进程,以此能达到保护数据的目的。

通过添加一层软件成(地址空间),完成OS有效的对进程操作内存进行风险管理(权限管理,rwx......),本质目的是保护物理地址中的数据不被非法访问。

2、行为分离

在进程申请空间的时候,OS系统实际上分配的是虚拟地址,如果申请的空间小,则会基于物理地址的映射关秀,如果申请的空间过大并且此刻又不会即刻使用,那么则不会基于物理地址的映射关系,而是仅仅基于一个虚假的虚拟地址,只是告诉进程:你已经申请到一片虚拟地址了,可以继续执行其他的操作了。当要进行使用这片物理地址的使用,OS系统发现之前没有给这片空间分配物理地址,则会去内存里面去给他开辟物理地址,如果此时内存已经用完了,那么则会将其他进程暂时不会使用的物理地址转存在其他存储设备上,比如磁盘,然后将空余出来的内存物理地址空间拿给该进程使用,而这片空间如何申请到的,进程不需要得知,即屏蔽内存申请到的过程,进程申请空间的透明化,只需要使用即可。

将内存申请和内存使用在时间上进行划分开。通过虚拟地址空间,来屏蔽底层申请内存的过程,达到进程读写内存和OS进行内存管理操作,进行软件层面上的分离。

3、统一化

进程是如何找到第一行执行代码的地址的呢?

地址空间进行了各种代码的区域划分,如果每个进程的各个区域的首地址都不相同,那么进程就需要在每个进程里面以不同的方式去查找相同区域的内容,这使得查找的方式不够统一,使得实现方法变困难,而如果将每个区域的首地址都进行相对的确定,那么查找方式也会相对统一,也就是虚拟地址是相对统一的,不一样的知识虚拟地址与物理地址的映射关系不同。

站在CPU和应用层的角度,进程统一可以看作每个进程都使用4G的空间,而且每个空间的相对位置是比较确定的。

好处

通过以上3个为什么,也将一个结论证明了:每个进程都认为自己是独占4G的系统资源的

何以验证正确性呢?接下来让我们看以下代码,其父子进程都有一个全局变量g_val,如果子进程修改了值,父进程指向的还会是该变量吗?

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

int g_val = 100;

int main()
{
    pid_t id = fork();
    if(id == 0){
        int i = 0;
        while (i < 5)
        {
           i++;
           sleep(1); 
           printf("child[%d]:  %d  :  %p  \n", getpid(), g_val, &g_val);
           if (i == 3) {
               printf("-----------------------------更改数据\n");
               g_val = 200;
               printf("-----------------------------完成更改\n");
           }
        }
    }
    else if (id > 0){
        int i = 0;
        while (i < 3)
        {
           sleep(1); 
            printf("parent[%d]:  %d  :  %p  \n", getpid(), g_val, &g_val);
        }
    }
    else {
        ;
    }

    return 0;
}

大家说这奇怪不奇怪?地址居然是一样的,而且同一个地址还有两个值。而在上面我们看了进程地址空间的3个为什么以及进程带来的好处之后,我们再来带着疑问继续往下看。

 

 进程不同,修改了值,为什么地址却一样?因为修改的是虚拟地址对应到物理地址的映射关系。

​        该程序中,子进程的代码与父进程的代码一样,虚拟地址也是继承过去的,因此代码与数据在没有发生写时拷贝之前都指向同一位置。当发生了修改的时候,子进程就会先将值进行写时拷贝一份在其他地址,但是虚拟地址并没有发生变化,因此虚拟地址还是一样的,不一样的是映射关系发生了变化,同一个变量拷贝了一份,子进程的g_val的虚拟地址指向新的变量,也就是物理地址发生了改变。代码的共享也就是他们的代码段指向的同一片空间。由此我们也可以得出一个结论,正常使用中,我们所打印出来的所有地址都是虚拟地址而并非真实的物理地址。

本次的分享就到此结束了,感谢大家的观看!!!若有疑问可以留言私信,看到必回。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值