操作系统常问面试问题 2 —— 虚拟内存以及操作系统的程序内存结构(堆和栈的区别)

1、操作系统的程序内存结构

1.1、程序编译运行过程

源代码(source coprede)→预处理器(processor)→编译器(compiler)→汇编程序(assembler)→目标程序(object code)→链接器(Linker)→可执行程序(executables)
在这里插入图片描述
分配程序执行所需的栈空间、代码段、静态存储区、映射堆空间地址等,操作系统会创建一个进程结构体来管理进程,然后将进程放入就绪队列,等待CPU调度运行。参考1.2

1.2、程序的内存分布

在这里插入图片描述

  • 1、代码段(.text),也称文本段(TextSegment),存放着程序的机器码只读数据,可执行指令就是从这里取得的。如果可能,系统会安排好相同程序的多个运行实体共享这些实例代码。这个段在内存中一般被标记为只读,任何对该区的写操作都会导致段错误(Segmentation Fault)
  • 2、数据段,包括已初始化的数据段(.data)未初始化的数据段(.bss)
    • data:用来存放保存全局的和静态的已初始化变量,
    • bss:后者用来保存全局的和静态的未初始化变量。数据段在编译时分配。
  • 3、堆栈段分为堆和栈:
    • 堆(Heap):用来存储程序运行时分配的变量。
      • 堆的大小并不固定,可动态扩张或缩减。其分配由malloc()、new()等这类实时内存分配函数来实现。
      • 当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);
      • 当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
      • 堆的内存释放由应用程序去控制,通常一个new()就要对应一个delete(),如果程序员没有释放掉,那么在程序结束后操作系统会自动回收。
    • 栈(Stack)是一种用来存储函数调用时的临时信息的结构,如函数调用所传递的参数函数的返回地址函数的局部变量等。在程序运行时由编译器在需要的时候分配,在不需要的时候自动清除。
      • 栈的特性: 最后一个放入栈中的物体总是被最先拿出来,这个特性通常称为先进后出(FILO)队列。
      • 栈的基本操作: PUSH操作:向栈中添加数据,称为压栈,数据将放置在栈顶;
      • POP操作:POP操作相反,在栈顶部移去一个元素,并将栈的大小减一,称为弹栈。

堆和栈的区别:
在这里插入图片描述

2、操作系统的虚拟内存

2.1、背景

内存对于用户来说就是一个字节数组,我们可以根据地址来访问到某个字节或者某些字节:
在这里插入图片描述
1、很久之前的内存模型:
很久很久之前,一台机器上只放置一个程序,操作系统仅仅作为一个函数库存在。对于内存来说,除去操作系统的代码和数据占用的一些空间外,其余空间全部分配给正在运行的那个程序,画个图就是这样:
在这里插入图片描述
2、同时运行多个程序的内存模型
后来人们觉得同时在一台计算机上只运行一个程序太亏了,就设计了一个可以同时运行多个程序的机制。不过内存条只有一个,所以这些用户程序只能共享同一个内存条,只能把内存的不同部分划分给不同的用户程序,画个图就像是这样:
在这里插入图片描述
这样子的话也有一些问题:

  • 不同用户程序只能使用给他们规定好的那部分内存,也就是程序员在敲代码的时候就应该小心翼翼的计算自己使用的内存有没有占到别人家的地儿,这样对码农很不友好有木有。
  • 如果哪个心眼儿坏的家伙故意去读取别人家的程序使用的内存,这不就暴露了么,更严重的,这个坏家伙直接把别人家程序正在使用的内存的某些字节给更新掉,这就是天坑了~

所以,随着对CPU需求的增长,进程以某种合理的平滑的方式慢了下来。但是如果太多的进程需要太多的内存,那么它们中的一些就根本无法运行。为了更加有效的管理内存并且少出错,现代系统提供了一种对主存的抽象的概念,叫做虚拟内存(VM)

2.2、定义

虚拟内存技术使得不同进程在运行过程中,它所看到的是自己独自占有了当前系统的4G内存(32位系统)。所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。

  • 程序员在比实际主存空间大的多的逻辑地址空间编写程序;
  • 程序执行时,把当前需要的程序段和相应的数据块调入主存,其他暂不用的部分存放在磁盘上
  • 指令执行时,通过硬件将逻辑地址(也称虚拟地址或虚地址)转化为物理地址(也称主存地址或实地址)
  • 在发生程序或数据访问失效(缺页)时,由操作系统进行主存和磁盘之间的信息交换

虚拟内存解决的问题就是要让程序员在比主存空间大得多的逻辑地址空间中编写程序 ,程序的机器码是放在硬盘中的, 硬盘相对于主存大的很多,所以可以表示的地址大的很多,但是CPU 是在主存中读取程序的,所以就用了一套逻辑地址来表示运行的数据放在哪个地方,而页表就是来表示逻辑地址和物理地址之间的映射的,假如物理地址中没有数据就是缺页了,那么就从磁盘中先加载到内存中再继续运行。

虚拟存储技术的实质:通过页表建立虚拟空间和物理空间之间的映射
在这里插入图片描述
执行程序 -》 启动进程 -》 调程序到主存中

缺页(页面置换)过程:
在这里插入图片描述
我们看到假如CPU 首先查看 TLS 中有已经缓存的页表项,那么直接到 CaChe 中的拿就好了,走Hit 3 那条路线返回,这是最好的结果,根本不用访问内存,具体逻辑如下:
在这里插入图片描述

扫描二维码关注公众号,回复: 11470538 查看本文章

2.3、特征(优势)

  • 1、 扩大地址空间;
  • 2.内存保护:每个进程运行在各自的虚拟内存地址空间,互相不能干扰对方。虚存还对特定的内存地址提供写保护,可以防止代码或数据被恶意篡改。
  • 3.公平内存分配。采用了虚存之后,每个进程都相当于有同样大小的虚存空间
  • 4、当进程通信时,可采用虚存共享的方式实现
  • 5、当不同的进程使用同样的代码时,比如库文件中的代码,物理内存中可以只存储一份这样的代码,不同的进程只需要把自己的虚拟内存映射过去就可以了,节省内存
  • 6.虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个 程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。在内存中可以保留多个进程 系统并发度提高。
  • 7.在程序需要分配连续的内存空间的时候,只需要在虚拟内存空间分配连续空间,而不需要实际物理内存的连续空间,可以利用碎片

虚拟内存的代价

  • 1.虚存的管理需要建立很多数据结构,这些数据结构要占用额外的内存
  • 2.虚拟地址到物理地址的转换,增加了指令的执行时间
  • 3.页面的换入换出需要磁盘I/0,这是很耗时的
  • 4.如果一页中只有一部分数据,会浪费内存

参考

1、https://mp.weixin.qq.com/s/opMgZrXV-lfgOWrNUMKweg
2、https://www.cnblogs.com/Benjious/p/11611431.html

猜你喜欢

转载自blog.csdn.net/JMW1407/article/details/107674999