进程地址空间

目录

一、地址空间分布

初步认识地址空间布局

堆区向上生长,栈区向下生长

申请栈区向下生长,栈区内部向上使用

几个小细节

二、理解进程地址空间

物理地址与虚拟地址

地址空间与区域划分

为啥要有进程地址空间


一、地址空间分布

初步认识地址空间布局

堆区向上生长,栈区向下生长

申请栈区向下生长,栈区内部向上使用

几个小细节

当局部变量被static修饰了, 之所以不会随着函数栈帧的销毁而释放,是因为被static修饰后变量位于全局区,变成了全局变量了!!

字符串常量不能被修改是因为字符串常量区与代码区距离非常近,而代码是不可被写入的, 系统对代码区而后对字符串常量区的处理方法是一样的!!!

环境变量和命令行参数两张表是在栈区之上存储的!

二、理解进程地址空间

物理地址与虚拟地址

同一个地址读取到的值却不一样,说明C/C++看到的地址一定不是物理地址!

进程看到的不是物理地址,而是虚拟地址,但是计算机中,所有的数据最终必须在内存中,因为冯诺依曼体系结构决定了cpu要从内存读取数据, 而每个运行的进程在系统内都会维护一张pcb!

而下图又表明每个进程都能打印出虚拟地址,所以每一个进程运行之后,都会有一个进程地址空间的存在!!! 所以pcb中一定有某个字段保存了进程地址空间的起始地址,能够维护它!!!

进程拿到的是虚拟地址,但是最终能够访问到物理内存上的数据,物理内存上的数据一定有自己的物理地址,所以虚拟地址和物理地址之间一定有某种映射关系,这种映射关系被保存在一个表里面,这个表叫做页表!所以每一个进程也要有自己的页表映射结构!

当父进程创建子进程时,最终要保证父子进程的独立性,所以系统会把父进程的pcb,进程地址空间和页表都给子进程拷贝一份!

页表也会拷贝一份,所以页表上的物理地址父子进程也是一样的,所以访问到的是同一块内存地址的数据!这就是代码中子进程在修改g_val变量之前的状态!

而当子进程要修改g_val时,为了保证父子进程的独立性,系统会以写时拷贝技术在物理内存中重新开辟一块空间,将父进程的g_val值拷贝到这块空间上,再让子进程的页表的物理地址更新,然后把g_val修改成200, 而页表的虚拟地址并没有变,所以打印出的地址是一样的!

写时拷贝发生在物理内存中了,由操作系统来做,不影响上层语言的使用!

理解两个点:

1.空间

两个设备之间交互本质是把数据从一个设备拷贝到另一个设备,而要拷贝数据,必须要有线!!

2.变量

源文件编译形成了可执行程序,还有变量名的概念吗? 没有了,所有的变量名都会变成各种地址!

地址空间与区域划分

地址空间:

操作系统给每个进程都画了一个饼,告诉每个进程可以用多大的内存空间,饼就是虚拟地址空间!

区域划分:

讲个故事:

小胖和小花在桌子中间画了一条线,本质是在进行区域划分,而描述这个行为只需要通过一个结构体,设置start和end变量即可描述该行为,整个桌子的宽度范围就是桌子的空间地址!而进行区域调整只需要改变start和end即可; 同时无论对于小胖还是小花, 自己的区域范围内都有很多刻度,每个刻度都可以摆放自己的物品!

每个进程要被OS管理,而OS给每个进程画的大饼(虚拟地址空间)也要被管理起来(否则饼可能画失败),如何管理? 先描述再组织!

所以除了给每个进程创建pcb结构体之外,还要给每个进程创建struct mm_struct结构体,把进程地址空间以数据结构的形式组织起来进行管理!!

mm_struct 结构体中有哪些属性来描述地址空间呢??

由于地址空间也是一堆的区域划分,结合小胖和小花的故事,我们就知道如何描述了!

struct mm_struct

{

   long code_start;

   long code_start;

   long data_start;

   long data_end;

   long heap_start;

   long heap_end;

   long stack_start;

   long stack_end;

   //......
}

每个区域里面有详细的地址来保存数据!

所以堆栈相对而生,向上/向下生长其实就是在调整区域!

查看Linux原码知, task_struct中有一个字段是指针,指向了mm_struct

为啥要有进程地址空间

1.让进程以统一的视角看待内存,所以任意一个进程通过地址空间+页表可以将乱序的内存数据,变成有序,分门别类的规划好! 无序 -》有序

所以无论磁盘的程序加载到内存的任意位置,只需要改变页表的物理地址即可,进程pcb和进程地址空间完全不受影响,打印出来的地址一直都是同样规整的结果!

2.存在虚拟地址空间,可以有效的进行进程访问内存的安全检查!

页表中还有一个访问权限字段,表明了对应内存的物理地址是只读的还是可读可写亦或是没有给进程分配的空间,这样就可以进程的非法访问做拦截!

所以我们就可以理解为啥 char* str = "hello world"; *str='H' 这样的代码是会崩溃的,因为进程地址空间中的字符串常量区对应的页表栏目是只读的,所以无法修改

进程进行各种转化/访问,前提都是这个进程正在被cpu运行!

每个进程都要有自己的页表,可问题系统咋知道哪个页表和哪个进程对应呢??

cpu中有个CR3寄存器,保存的是页表的物理地址,所以cpu可以直接通过CR3的内容找到页表,从而完成虚拟地址到物理地址的转化以及后续的各种访问数据的操作!

再次深入理解进程切换:

上一篇博客提到进程切换,进程被切走CR3的内容(寄存器的硬件上下文)要被保存下来,而进程被重新调度时先回复进程的硬件上下文,就可以继续运行了,所以每一个进程都有自己的页表!

同时进程的切换是pcb也要切换,而pcb中保存了进程地址空间的地址,所以只要pcb变了,进程地址空间自然而然就切换了!!

挂起状态在Linux中又是如何体现的呢??

有没有可能可执行程序并没有一次性加载到内存呢??(比如太大了) 但是进程同样可以跑起来?

如果程序太大了,我们就可以分批加载程序到内存中,每次加载一部分的时候,内存都要分配空间,建立虚拟地址与物理地址的映射,同时将页表的表明内存分配的一列置成11, 这样就可以边加载边运行!

当进程访问虚拟地址对应的物理空间没有被申请时,操作系统就暂停执行而帮助我们去申请内存,填充内容并修改页表,这个过程就叫做缺页中断(内存管理);

缺页中断整个过程进程压根不知道!!这属于进程管理的范畴!同样内存也不关心进程管理中的各种调度等!

由于进程地址空间和页表以及映射关系的存在,进程管理和内存管理在系统层面实现了解耦!!!

3.将进程管理和内存管理实现解耦

3.1通过进程映射,可以让进程映射到不同的物流内存处,从而实现进程的独立性

补充:

想把很多空间灵活使用,就是说除了我们上文提到过的宏观上的地址空间的区域划分,还想进一步对一些空间(诸如栈区,堆区)进行详细的划分,查看Linux源码知道,mm_struct结构体内部还有struct vm_area_struct* mmap的字段,指向了一个结构体对象,结构体里面依旧是_start与_end字段,当然还有1个结构体指针,指向下一个结构体,形成了一张链表! 具体细节后面还会再说!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值