Linux操作系统程序地址空间

之前对c语言中的各个变量在内存中的位置大概有了了解。但我们可能对它并不理解。我们不知道这个地址空间究竟是什么。

这个地址空间是内存吗?

不是内存,那是什么呢?

 

系统中,只要是一个进程就要被操作系统管理,只要被操作系统管理,那么创建子进程时,就要拷贝父进程的内核数据结构,比如子进程需要创建PCB,否则无法对子进程管理。

父子进程谁先跑不一定,由系统调度器决定 

  • 见现象
​
#include<stdio.h>
    2 #include<unistd.h>
    3 #include<sys/types.h>
    4 #include<stdlib.h>
    5 int global_value=0;
    6 int main()
    7 {
    8 pid_t id=fork();
    9 if(id<0)
   10 {
   11   printf("fork  error\n");                                                                                                                                                   
    12     return ;
   13 }
   14 else  if(id==0)
   15 {
   16   int  n=0;
   17   while(1)
   18   {
   19    printf("子进程, pid: 
%d,ppid:%d|global_value:%d,&global_value:%p\n",getpid(),getppid(),global_value,&global_value);
   20   sleep(1);
   21   n++;
   22   if(n==10)
   23   {
   24     global_value=300;
   25     printf("子进程全局变量已经更新了\n");
   26   }
   27   }
   28 }
   29 else  if(id>0)
   30 {
   31   while(1)
   32   {
W> 33     printf("父进 
程,pid:%d,ppid:%d|global_value:%d&global_value:\n",getpid(),getppid(),global_value,&global_value);
   34   sleep(2);
   35   }
   36 }
   37 sleep(1);
   38 return 0;
   39 }

​

 父子进程global_value值不同,地址相同。

多进程读取同一个地址的时候,怎么可能出现不同的结果?(继续往下看,答案在后面)

 地址没变,打出来的值不同,说明地址一定不是物理地址,物理地址相同打出来的值一定相同,所以之前语言阶段学习的地址(指针)不是物理地址。而是虚拟地址(线性地址)[逻辑地址]

  • 感性理解虚拟地址空间

进程认为自己独占系统资源,实际并不是,(设计时的理念)

漂亮国有个大富翁有10亿美金,有三个私生子,(私生子彼此不知道对方存在),大儿子是工厂老板,二儿子是金融机构的CEO,三儿子在MIT读书,大富翁告诉大儿子,要好好工作,等到自己老了不行后将10亿美金都给他,大富翁又告诉二儿子,要经营好告诉,等自己老了不行时把10亿美金给他,大富翁又告诉小儿子,要好好读书,等自己老了不行时把10亿美金都给他。当大富翁老了,三个儿子都想要这笔钱,但他们只能找各种理由每次要一点,尽力去索要那10亿美金。

这里,大富翁就是操作系统,三个儿子就相当于三个进程,三个儿子每次要的钱相当于是这个进程申请的内存或对象空间。 大富翁画的三个大饼(要好好工作,等到自己老了不行后将10亿美金都给他)相当于进程地址空间。

计算机的很多理念不是凭空产生的,而是来源于生活。

  • 系统如何画饼

画饼的本质是在大脑中绘制蓝图,相当于一个结构体对象,

所以地址空间本质是内核的一种数据结构--mm_struct

struct mm_struct中应该有哪些成员呢?

1.地址空间描述的基本空间大小是字节

2.32位下,2的32次方个地址

3.2的32次方个字节约为4GB

4.每个字节都有一个地址

  • 如何理解区域划分

小时候,我们上学时,为了和同桌有相同的桌子面积,我们用尺子在桌子上画出一道线,这个过程相当于一个区域划分。

 

  • 如何 理解区域调整

 此时男生因为较胖,想为自己多争取点空间,女生答应了请求,于是在原先桌子中间的线两旁,设置了10cm的缓冲区。

 

2的32次方个地址所占的4GB空间相当于这个区域,我们用unsigned int 类型表示地址。

 mm->code_start相当于一个区域的起始地址

mm->code_end相当于一个区域的结束地址

这些地址都是虚拟地址。所谓的区域调整,就是改变 start和end的值。

我们定义局部变量,malloc和new相当于扩大栈区和堆区;

函数调用完毕,及free相当于缩小栈区或堆区。

各个进程在4GB的空间,被分配不同的区域,相当于大富翁为儿子画的大饼,即共分10亿美金。

  • 证明此结构

 

源码

如何让进程找到内存中的代码和收据,用页表。页表用来把虚拟地址和物理地址进行映射。此过程由操作系统自动操作。虚拟地址是连续的,也叫线性地址。

内存和磁盘I/O过程,基本单位是4kb。

 

每个进程认为自己独占2的32次方地址。其实是虚拟地址,而且这虚拟地址,进程也不会全用到。

为什么要用页表映射地址呢?

可以看做是小时候家长为自己管理压岁钱,怕乱花,家长相当于页表,起到拦截作用。

为什么存在地址空间?

1.如果让进程直接访问物理内存,万一进程越界非法操作呢?非常不安全 。

地址空间让我们写代码出现错误时,比如出现野指针时,并不影响内存及物理地址。 

2.可以更方便的进行进程和进程之间数据代码的解耦,保证了进程得独立性。(下面进行解答)

  • 回顾一下开篇那个global_value问题 

 

父进程开辟子进程后,子进程相当于父进程的拷贝,子进程在父进程的虚拟地址处开辟,即虚拟地址不变,此时父子进程的物理地址也是相同的,数据也相同,当子进程更改数据时,因为进程具有独立性,一个进程对被共享的数据进行修改时,如果影响了其他进程,不能称子为独立性所以当任何一个进程要对共享数据进行修改时,操作系统首先要重新在内存上为这个进程开辟一个物理内存空间,然后把原先数据拷贝到新的空间里面,然后将子进程页表物理地址更改为指向新的物理地址,然后把要更改的变量进行更改。此过程和虚拟地址无关。上层用的虚拟地址的同一区域,底层通过页表被映射到了物理地址的不同区域。此时我们看到虚拟地址一样,但内容却不一样。

 我们把任何一方尝试更改数据时,操作系统先进行数据拷贝,再更改页表的映射,然后让进程修改的过程叫写时拷贝。

操作系统为了保证进程的独立性,做了很多工作,通过地址空间,页表,让不同进程映射到不同物理内存中。

进程=内核数据结构+对应的代码和数据,通过写时拷贝让不同进程代码和数据独立,不同进程这俩个都是独立的,保证了进程的独立性。

问题:子进程刚被创建时的物理地址,页表,虚拟地址和数据,和父进程都是一样的,那怎么保证父子进程独立性的?

答:父子进程都有它自己独立的进程,虚拟的空间的,虽然说最开始的时候子进程和父进程一样,页表的映射关系也是一模一样的,但是它是两个独立的页表,各有各的页表,当发生数据修改的时候,会有一个写时拷贝的存在。父子进程发生数据修改时是独立的。进程的独立性主要体现在数据修改时是独立的,而不是体现在代码和数据一样。

 3.让进程以统一的视角看待进程对应的代码和数据等各个区域,方便编译器也以统一的视角编译代码。

问题:一个可执行程序,在磁盘中还没有加载到内存时有没有地址?

1.汇编指令有地址。说明程序在汇编阶段(汇编,编译,链接,可执行)代码就有地址,链接就是把库函数中的地址填入到可执行程序中,让程序运行时能找到库函数,此地址是逻辑地址。

2.虚拟地址不仅操作系统会遵守,编译器也会遵守。编译器编译代码时,就是按照虚拟地址空间的方式对代码和数据进行编址的。程序的代码区和数据区是以32位地址编址的。

main函数调用fun函数,是在代码的内部进行跳转的。 

 函数加载到内存后,函数内部的东西不变。

这些地址是编译阶段就有的, 栈空间和堆空间编译生成可执行程序时,这些地址没有,因为它们是在内存中动态申请的,

3.上面说的地址是程序内部的地址。是虚拟地址(逻辑地址)。代码要占空间,要在内存中保存,当程序被加载到物理内存中后,该程序对应的指令和数据,都天然具有了物理地址

当程序加载到内存后,这些函数和变量可以相互通过虚拟地址(逻辑地址)找到。 这些变量和函数都有了物理地址。

                我们现在有了俩套地址

  1. 标识物理内存中代码和数据的地址
  2. 在程序内部互相跳转用的地址--虚拟地址。

操作系统通过输入到内存中的程序的虚拟地址和大小,给定程序地址空间的区域 。

CPU中有个pc指针,叫程序计数器,它也是读取和输出虚拟地址的。

 整个CPU访问过程中,CPU没有见到物理地址。

所以平时debug程序,运行起来时,CPU内部寄存器用的就是虚拟地址。调试时查看的是虚拟地址。

编译器写程序时32位和64位程序指的就是程序编译时虚拟地址(逻辑地址)按32还是64位进行编。

 两种程序中逻辑地址编码方式,上面较新,是线性编辑的,下面较旧,是靠偏移量编辑的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南种北李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值