【第 8 周】深入理解计算机系统共读心得体会

请大家以回帖的方式将你在共读第一周的心得体会用你自己的话表达出来。

样例:

所在小组

第一组

组内昵称

张三

你的心得体会

可以基于不同知识点进行,有更新请在原回贴更新,每人每周只发一个帖子

一段自己的阐述

第二段自己的阐述

所在小组

第六组

组内昵称

蒋权

你的心得体会

什么是虚拟内存?它的作用是什么?
为了更加有效地管理内存并且少出错,现代系统提供了一种对主存的抽象概念,叫做虚拟内存(VM)。

虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。通过一个很清晰的机制,虚拟内存提供了三个重要的能力:(1)它将主内看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存;(2)它为每个进程提供了一致的地址空间,从而简化了内存管理;(3)它保护了每个进程的地址空间不被其他进程破坏。

物理寻址

虚拟寻址


图 9-2 中,将虚拟地址(VA)翻译成物理地址(PA)的地址翻译工作,是由内存管理单元(MMU)负责的
页表

虚拟内存系统必须由某种方法来判定一个虚拟页是否缓存在 DRAM 中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理内存职工选择一个牺牲也,并将虚拟页从磁盘复制到 DRAM 中,替换这个牺牲也。

这些功能是由软硬件联合提供的,包括操作系统软件、MMU(内存管理单元)中的地址翻译硬件和一个存放在物理内存中叫做页表(page table, pgd)的数据结构,页表将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。操作系统负责维护页表的内容,以及在磁盘与 DRAM 之间来回传送页。

页表就是一个页表条目(Page Table Entry, PTE)的数组。

页命中

通过虚拟地址查询数据时,该数据正好被缓存在物理内存中(缓存命中),则直接返回相应数据

缺页

通过虚拟地址查询数据时,该数据没有被缓存在物理内存中(缓存不命中),此时,地址翻译硬件会触发一个缺页异常,缺页异常调用内核中的缺页处理程序,该程序会选择一个牺牲页(如果该牺牲页曾经被修改过,则会将其写回磁盘,持久化下来),将目标物理页复制到原来牺牲页的位置,接着异常程序处理结束,返回,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址发送到地址翻译硬件,此时,必然会导致一个页命中,返回相应数据给 CPU。

把 PTE 缓存在高速缓存中

尽管页表是虚拟内存使用的一个管理内存的工具,但是,页表本身也是数据,是数据,人们就会想到如何缓存它,以提高访问效率


图 9-14 中,

最坎坷的一条路:

VA → MMU → PTEA → PTEA不命中 → PTEA → 内存 → PTE → PTEA命中 → PTE → MMU → PA → PA不命中 → PA → 内存 → 数据 → PA命中 → 数据 → 处理器

最顺畅的一条路:

VA → MMU → PTEA → PTEA命中 → PTE → MMU → PA → PA命中 → 数据 → 处理器

把 PTE 缓存在 TLB 中

利用 TLB 加速地址翻译

如图 9-14 所示,每次 CPU 产生一个虚拟地址,MMU 就必须查阅一个 PTE,以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这回要求从内存多取一次数据,代价是几十到几百个周期。如果 PTE 碰巧缓存在 L1 中,那么开销及下降到 1 个或 2 个周期。然而,许多系统都视图消除即使是这样的开销,它们在 MMU 中包括了一个关于 PTE 的小的缓存,称为翻译后备缓冲器(Translation Lookaside Buffer, TLB)。

TLB 是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个 PTE 组成的块。

当 TLB 命中时,所有的翻译步骤都是在芯片上的 MMU 中执行的,因此非常快。

多级页表

我认为,多级页面的核心作用是用时间换空间。也就是说,其实多级页表比一级页表运行起来要慢,但是可以减轻内存的压力。

关于空间,见本小节(9.6.3 多级页表)的开头

“到目前为止,我们一直假设系统只用一个单独的页表来进行地址翻译。但是如果我们有一个 32 位的地址空间、4KB 的页面和一个 4 字节的 PTE,那么即使应用所引用的只是虚拟地址空间中很小的一部分,也总是需要一个 4MB 的页表驻留在内存中。对于地址空间为 64 位的系统来说,问题将变得更为复杂。”

算一些这笔账:

32位地址空间,即共有2^32个虚拟地址

每个页面(即页)4KB,即2^12

那么虚拟地址空间中共有2^32除以2^12,即2^20个页面(即页)

每个页都需要对应一个页表条目(即PTE),故页面条目的个数也是2^20

而每个 PTE 的大小是 4 字节,即2^2,那么2^20页表条目的总大小就是 2^22 字节了,即 4MB

关于时间,见本小节的这两段:

“二级页表中的每个 PTE 都负责映射一个 4KB 的虚拟内存页面,就像我们查看只有一级的页表一样。注意,使用 4 字节的 PTE,每个一级和二级页表都是 4KB 字节,这刚好和一个页面的大小是一样的。

这种方法从这两个方面减少了内存要求。第一,如果一级页表中的一个 PTE 是空的,那么相应的二级页表就根本不会存在。这代表一种巨大的潜在节约,因为对于一个典型的程序,4GB 的虚拟地址空间的大部分都会使未分配的。第二,只有一级页表才需要总是在主存中;虚拟内存系统可以在需要时创建、页面调入或调出二级页面,这就减少了主存的压力;只有最经常使用的二级页表才需要缓存在主存中。

所在小组

第七组

组内昵称

杨文

你的心得体会

  1. 虚拟内存是沉默、自动的工作,不需要应用程序的任何干涉。
  2. 我们为什么要理解它?
    • 虚拟内存是核心的
    • 虚拟内存是强大的
    • 虚拟内存是危险的
  3. 计算机系统的主存被组织成一个由 M 个连续的字节大小的单元组成的数组。每个字节都有一个唯一的物理地址。第一个字节的地址为0,接下来为1,以此类推。
  4. CPU 访问内存最自然的方式就是使用物理地址,我们称之为物理寻址。
  5. 现代处理使用虚拟寻址,将虚拟地址转为物理地址的的任务叫地址翻译。
  6. 地址空间是一个非负整数地址的有序集合。
  7. CPU 硬件执行步骤:
  • 处理器生成一个虚拟地址,并把它传送给 MMU
  • MMU 生成 PTE 地址,从高速缓存/主存请求得到它
  • 高速缓存/主存向 MMU 返回 PTE
  • MMU 构造物理地址,并把它传送给高速缓存/主存
  • 高速缓存/主存返回所请求的数据字给处理器。

所在小组

静默组

组内昵称

清风环佩

心得体会

  1. 虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存,即一个连续完整的地址空间,而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
  2. 使用虚拟内存技术的系统使得大型程序的编写变得更容易,对真正的物理内存的使用也更有效率。
  3. 虚拟内存不只是“用磁盘空间来扩展物理内存”的意思,把内存扩展到磁盘只是使用虚拟内存技术的一个结果,它的作用也可以通过覆盖或者把处于不活动状态的程序以及它们的数据全部交换到磁盘上等方式来实现。对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为“连续的虚拟内存地址”,以借此“欺骗”程序,使它们以为自己正在使用一大块的“连续”地址。
  4. 地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断 。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统 必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法,页面置换算法的作用是实现虚拟存储管理。
    • OPT页面置换算法(最佳页面置换算法):理想情况,不可能实现,一般作为衡量其他置换算法的方法。
    • FIFO页面置换算法(先进先出页面置换算法): 总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
    • LRU页面置换算法(最近未使用页面置换算法):LRU(Least Currently Used)算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间T,当须淘汰一个页面时,选择现有页面中其T值最大的,即最近最久未使用的页面予以淘汰。
    • LFU页面置换算法(最少使用页面排序算法): LFU(Least Frequently Used)算法会让系统维护一个按最近一次访问时间排序的页面链表,链表首节点是最近刚刚使用过的页面,链表尾节点是最久未使用的页面。访问内存时,找到相应页面,并把它移到链表之首。缺页时,置换链表尾节点的页面。也就是说内存内使用越频繁的页面,被保留的时间也相对越长。
1赞

所在小组

静默组

组内昵称

Tang_D

心得体会

  1. 虚拟内存给予程序员一片连续的地址空间,使得程序员更加容易的编程,同时也使操作系统更加方便地管理内存
  2. 虚拟内存地址,由mmu处理,pagetable只负责映射,因此,pagetable可以将不同进程的不同虚拟地址映射为同一块物理地址,使共享成为可能。
  3. 同时因为现在内存地址越来越多,导致pagetable的表项也更加多,虽然pagetable的一个表项只占很少的内存,但是因为每一个进程都有自己专属的pagetable所以如果进程多了的话,占据的内存也仍然十分的多。因此,我们需要多级页表,而多级页表的好处是不需要为所有的可能表项都分配内存,按需分配。
    4.为了加快内存读取的速度,我们引入了tlb,作为一个缓存来加速虚拟内存转化为物理内存的速度,因为tlb这一硬件较为昂贵,所以我们只有一块,这就导致了,为什么进程切换是一个开销比较大的操作,因为进程的切换会导致tlb被清空,所有的内存的读取,tlb都会落空。
    5.对于动态的内存分配,是一个在效率和空间利用上的一个取舍,linux使用buddy system来分配内存,这个可以很好的回收内存,一个两个buddy之间只有一个bit不同,其他的分配策略有首次适配,下一次适配等,同时也可以使用多级的链表来分配,也可以参考golang的内存分配体系。
    6.推荐做一下实验malloc lab,或者mit 6.S081 2019的实验。

所在小组

第二组

组内昵称

Joey

心得体会

  1. 虽然页表可以提供快速的虚拟地址到物理地址的映射,但是如果每次都是去主存的某个位置查PTE,就太慢了,所以MMU内部有一个成为TLB的东西作为页表的缓存。当然TLB也会有不命中的情况,这时候就需要从主存里查PTE,更新TLB。PS:PTE查询是O(1),因为VM包含了VPN和VPO,可以直接定位到PTE的位置。

  2. 对于4G的VM,4K的page,一个进程的全部虚拟地址只需要4M的额外空间就可以存下页表。这样做的潜在浪费是,默认所有的VM都和主存有映射关系,而实际上一不是所有的进程都能用4G的内存。多级页表主要是为了解决这种潜在的浪费,特别是在64位环境下。

  3. 多级页表的设计思想是:

    • 每级页表从上到下代表的PTE的地址的密集程度依次递增,而上一级页表的PTE又指向下一级页表的PTE,这样任一VM都可以通过在多级页表中的跳转查到最终的主存位置。
    • 为了避免单页表的潜在浪费,如果上一级页表的PTE不存在,则下一级页表的PTE也不分配。这就意味着页表中的PTE也是按需分配的
    • 虽然看起来多级页表为了避免内存浪费,而付出了需要多次跳转查询的代价,实际效果也没慢多少
  4. 通过虚拟内存系统可以实现对象在进程之间的私有共享或私有映射。(.so是共享映射)

    • 比如fork,只需要拷贝页表后,把父子进程中的所有页表项都标记为私有映射和写时复制就行
    • execve只需要创建好新程序的虚拟地址区域结构,把可执行文件标记为私有映射和写时复制,把.so标记为共享映射,置PC为0,就可以了。至于从磁盘加载可执行文件到主存,虚拟内存系统自己会去介入。
  5. mmap函数可以为虚拟内存和对象(fd)之间创建映射关系,书上讲的比较简略,可以参考这篇文章:https://www.cnblogs.com/huxiao-tee/p/4660352.html

    • mmap读写磁盘文件的优势在于不需要调用I/O函数,把文件数据从内核空间拷贝到用户空间。比如读,必须去内核空间的页缓存里去拿,再拷贝到用户空间。比如写,必须先把数据拷贝到内核空间,再由内核去写(write back策略)。
    • 而mmap建立好映射后,不需要读写都从内核空间拷贝。比如读会触发缺页,直接把文件内容加载到用户可访问到的虚拟内存里。
    • 比如要把大文件A拷贝一份,就开一个虚拟空间MA映射到A,设定映射为只读,再开一个虚拟空间MB映射到新文件B,设定映射为可写。读MA时会引发缺页中断,将A的内容(一页?)加载到内存,再更新PTE,再调用memcpy将读到的内存写到MB上,MB的一页成为了脏页,触发写回将数据同步到磁盘上。
    • 但是mmap也非银弹,如果随机写的情况比较多,大量的脏页回写会引发大量的随机I/O,效率估计就不行了
    • 貌似mmap可以用于申请动态内存,需要把访问权限设置为读写,对象的fd设置为-1,目测这样也正确的触发了缺页

所在小组

第五组

组内昵称

锦锐

心得体会

Virtual Memory

Overview

why we need virtual memory ?

  • not enough RAM, What if we don’t have enough memory ?
  • Holes in our address space, when there multi-progress are running ?
  • Programs writing over each other, how do we keep programs secure ?

What is virtual memory ?

  • Indirection .
  • How does it solve the problems ?
  • Page Tables and Translation.

There are three main reason .

  • uses main memory efficiently
  • simplifies memory management
  • isolates address spaces

Virtual Addressing

  • VM as a tool for caching
  • VM as a tool for memory management
  • VM as a tool for memory protection

Addressing translation

direction Addressing PA

When Page Hit

  • processor sends virtual address to MMU
  • MMU fetches PTE page table in memory
  • MMU sends physical address to cache memory
  • Cache/Memory sends data word to processor

Page Fault

  • Processor sends virtual address to mmu
  • mmu fetches pte from page table in memory
  • valid bit is zero, so mmu triggers page fault exception
  • handler identifies victim (and, if dirty, pages it out to disk)
  • handler pages in new page and updates pte in memory
  • handler returns to original process, restarting faulting instruction.

speed up page fetch with tlb ( translation lookaside buffer )

所在小组

第四组

组内昵称

Helios

总结

地址管理

当一个进程想要访问内存,如果访问的都是物理内存的话,这就容易乱套了,这样程序员和编译器都很难看。当然啦,现在现在只有几k内存的单片机就是这么做的
,关键是人家也不用那么复杂呀,就是可能是亮一个LED灯泡就行了。

所以就给进程安排个虚拟内存呗,让进程觉得自己的小盒子就是整个世界(用gcc编译出来的汇编代码的地址都是一样的,链接的时候去重定向),想怎么用怎么用
,当然他也能时常仰望星空(内核空间),但是一旦出去就会被打回来(报错)。如果想买点地盖个房(write)或者看点其他人的房产记录(read)咋办呢,和政
府去申请呗(系统调用),他是真的啥权限都用。

这个时候政府的压力就大了,还有管理一个个进程小盒子(虚拟内存管理)还要管理各个小盒子能用真实空间(物理内存管理),所以政府就负责两者之间的对应>关系(虚拟内存到物理内存的映射)。

盒子越来越多,但是他们问的常见问题就那个几个,所以把那些放在最快能给他们的地方(TLB),还有次常用的就放在大厅里面谁来看(放在内存中的虚拟内存页
),如果有比较便的问题就去咱们的仓库里面拿(放在磁盘上的虚拟内存页)

内存分配

这对于政府来说是个难题了,有的要大块地,有的小块的地咋办呢。他们之间在政府(内核)中的分配不一样,政府分配最小单位(一页=4K),当大约一定量的时候用mmap,小量的时候用brk,还有些不到最小单位的,政府就把最小单位(4K)分了,等着给人用。如某个系统经常申请大块空间咋办勒,就把最小单位调大一些
(内存大页,4M或者更大),这样分配的最小单位就小了。

如果是线性分配,就会出现随便,那么空间合并是个重要问题(内存碎片)。

所在小组 静默组

组内昵称 维钢、

心得体会
虚拟内存作为内存管理的工具。

  • 简化链接。操作系统为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不用管代码和数据实际存放在物理内存何处。
  • 简化加载。虚拟内存使得容易向内存中加载可执行文件和共享对象文件。要把目标文件中.text和.data节加载到一个新创建的进程中,Linux加载器位代码和数据段分配虚拟页,把它们标记成未缓存的,将页表条目指向目标文件中适当位置。
  • 简化共享。独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一个机制。
  • 简化内存分配。虚拟内存为向用户进程提供一个简单的分配额外内存的机制。一个用户进程中的程序要求额外的内存空间时,操作系统分配一个适当数字的连续虚拟内存页面,并将它们映射到物理内存中随机位置的任意个物理页面而不需要连续的。
  • 虚拟内存作为内存保护的工具我们在通过虚拟内存访问一片内存空间的时候,就会通过页表进行地址翻译来获得真正的物理地址,这样我们只需要在页表里添加几项许可位就可以通知对一个虚拟页面的访问,我们在页表里添加三个许可位,分别为:SUP,READ和WRITE。这三个许可位分别表示是否需要在内核模式下访问,是否可读和可写,这样我们在通过页表进行访问内存的时候就可以同时来检查是否满足访问的条件,如果一条指令违反了这些许可条件,那么CPU就会触发一个一般保护故障,将控制传递给一个内核中的异常处理程序,Linux shell一般将这种异常报告为“段错误(segmentation fault)”

所在小组

第二组

组内昵称

可可

心得体会

虚拟内存

每章的引言,表达这本章的根本用意

在了解了虚拟内存的各种使用和好处(例如内存保护管理)外,在想虚拟内存究竟为什么而提出,又如果在程序员无感知的情况下默默的付出。

程序总是贪婪的,他可以占着很多内存而不使用它。

不能保证每个程序都是好人,他可以去访问甚至修改本不属于他的东西。

而以上的这一切对于OS来说,都是不可饶恕,要去解决的。所以就有了VM。

  • VM 将主存(内存)看成存储在磁盘上的高速缓存

    这样的好处?

    1. 通过类似扇入扇出和局部性来高效的使用内存。而不是交由程序去完成?
  • 为每个进程提供了一致的地址空间

    无论编码还是管理都方便了很多

  • 保护每个进程的地址空间不被其他进程破坏

    安全性保证

动态内存分配

迷惑堆行为

设计分配器的要求和目标

go的内存分配,又是如何

  1. 处理任意请求
  2. 立即响应请求

碎片处理

合理利用再分配

所在小组
第二小组
组内昵称
梁广超
心得体会
1、地址翻译 虚拟地址组成 VPO + VPN,物理地址是组成是PPO + PPN。寻址过程是通过VPN在PTE中找到物理地址的PPN, 再于虚拟地址的VPO组成真实的物理地址
2、为了减少MMU查找PTE时,需要从内存中加载PTE的次数,根据内存局部性原理,MMU中加入了TLB技术
3、多级页表类似跳表,将虚拟地址的VPO分成多级,如果二级PTE没有被分配,那么一级PTE是空的,另外只有一级列表才需要一直在内存,如此可以节省内存
4、内存映射,linux通过将一个虚拟内存区域与一个磁盘对象关联起来,在io多路复用中,epoll相比于select的一个优势就是利用内存映射,减少了数据在用户态和内核态的拷贝
5、垃圾回收,将内存视为一张有向可大图,将不可达的内存碎片,视为垃圾
6、c程序常见内存有关错误
间接引用坏指针
读未初始化内存
允许栈缓冲区溢出
假设指针和她们指向的对象是相同大小的
造成错位错误
引用指针,而不是它所指向的对象
引用不存在变量
引用空闲堆块中的数据
引起内存泄漏

所在小组

第五组

组内昵称

肖思成

你的心得体会

  • 地址翻译是一个 N 元素的虚拟地址空间中的元素和一个 M 元素的物理地址空间中元素之间的映射
  • 常用使用层次结构的页表来亚索页表
  • TLB 是利用VPN的位进行虚拟寻址的
  • 高速缓存:直接映射的缓存是用过物理地址中的字段来寻址的
  • PTE有三个权限位,用来控制对页的访问
  • R/W 位确定页的内容是可以读写的还是只读的
  • U/S 位确定是否能够在用户模式中访问该页
  • XD位是在64位系统中引入的,可以用来禁止从某些内存页取命令
  • 内核虚拟内存包含内核中的代码和数据结构
  • Linux 通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容
  • 映射到匿名文件的区域中的页面有时也叫请求二进制零的页
  • 一旦一个虚拟页面被初始化,它就在一个由内核维护的专门的交换文件之间换来换去。交换文件也叫做交换空间或者交换区域
  • 在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数
  • 一个对象可以被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象
  • 一个映射到共享对象的虚拟内存区域叫做共享区域。类似的,也有私有区域

所在小组
第七组

组内昵称
吴奇驴

你的心得体会

  1. Linux内存映射:通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容;感觉可用来进行加载文件加速,就像是那个共享库。
  2. 动态内存分配:经常直到程序运行时候才知道数据结构的大小。
  3. Mark&Sweep垃圾收集器:分标记和清除两阶段。
  4. C语言常见的与内存有关的错误:间接引用坏指针、读为未初始化指针、允许栈缓冲区溢出、误解指针运算。

最近看了感觉有用的东西(因为没来的及看完)

  1. https://github.com/holbertonschool/Hack-The-Virtual-Memory
  2. https://mp.weixin.qq.com/s/IQYUNzVgSOFUHB9c1SM0Bw

感想:

  1. 这章好多需要理解的名词和实际编写的代码,一下子看不完,但是觉得还是应该坚持一下。
  2. 最近连续碰到两个bug,一个是socket按字节传输时候内存对齐的问题,一个是go里面json编码对大的数字编码问题,感觉基础知识还是挺重要的。

所在小组

第三组

组内昵称

kippa

虚拟存储器是硬件异常,硬件地址翻译,主存,磁盘文件和内核软件的完美交互。为每个进程提供一个大的,一致的和 私有的地址空间。提供了3个重要能力:

将主存看成磁盘地址空间的高速缓存
为每个进程提供一致的地址空间,简化存储器管理
保护了每个进程的地址空间不被其他进程破坏

  • 计算机系统的主存被组织为M个连续的字节大小的单元组成的数组。每个字节的地址叫物理地址。CPU访问存储器的最自然的方式使用物理地址,这种方式称为物理寻址。
    现代处理器使用的是虚拟寻址(virtual addressing)的寻址形式。

  • CPU通过生成一个虚拟地址来访问主存,虚拟地址转换为物理地址叫做地址翻译。

  • 虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

所在小组

第二组

组内昵称

李显良

虚拟存储

虚拟存储器是由系统自动提供的,它也是一种有限的存储器资源,作为程序员需要深入了解原理才能写高效果程序

虚拟存储器的作用

  1. 主内存中自动缓存最近使用的存放在磁盘上的虚拟地址空间内容
  2. 虚拟存储器简化了存储器管理,进而简化了链接和进程间的数据共享
  3. 虚拟存储器通过在每条页表条目上加入保护位,从而保护了各进程间的安全性

动态内存的实现

内存碎片

内存碎片:字节对齐
外部碎片:  没有满足的空闲块

free怎么知道块有多大: block size,返回有效负载p0, 一个word记录size和a(1;分配,0:free)4字节(64)

怎么寻找一个合适的空闲块进行内存分配

  1. 首次适配: 估计利用率不好
  2. 下次适配:记录最后适配的位置,内存碎片更多
  3. 最佳适配: 扫描搜索时间多

如何跟踪空闲块

  1. free: 前后块通过header footer关联,便于前后块合并
  2. 多个空闲列表管理不同大小内存块

内存碎片优化

  1. 应用可以使用内存池来减少系统的内存碎片

常用c常见的内存错误

  1. 间接引用坏指针
  2. 读未初始化的存储器
  3. 缓冲区溢出
  4. 数组越界
  5. 指针运算错误
  6. 引用不存在的变量(函数内部变量)
  7. 应用空闲块的数据
  8. 没有释放堆内存引起内存泄漏

所在小组

第三组

组内昵称

h0n9xu

你的心得体会

参见 chapter09

所在小组

第六组

组内昵称

之昂

心得体会

  • 图a展示了当页面命中时,CPU硬件执行的步骤。

    • 第1步:处理器生成一个虚拟地址,并把它传送给MMU。
    • 第2步:MMU生成PTE地址,并从高速缓存/主存请求得到它。
    • 第3步:高速缓存/主存向MMU返回PTE。
    • 第4步: MMU构造物理地址,并把它传送给高速缓存/主存。
    • 第5步:高速缓存/主存返回所请求的数据字给处理器。
  • Corei7地址翻译过程

    ​ 从CPU产生虚拟地址的时刻一直到来自内存的数据字到达CPU。Core i7采用四级页表层次结构。每个进程有它自己私有的页 表层次结构。当一个Linux进程在运行时,虽然Core i7体系结构允许页表换进换出,但是与已分配了的页相关联的页表都是驻留在内存中的。CR3控制寄存器指向第一级页表 (L1)的起始位置。CR3的值是每个进程上下文的- -部分,每次上下文切换时,CR3的值都会被恢复。

  • Linux虚拟内存系统

    ​ Linux为每个人进程维护了一个单独的虚拟地址空间。内核虚拟内存包含内存中的代码和数据结构。内核虚拟内存的某些区域映射到所有进程共享的物理页面。每个进程共享内核的代码和全局数据结构。有趣的是,Linux也将一组连续的虚拟页面(大小等于系统中DRAM的总量)映射到相应的一-组连续的物理页面。这就为内核提供了一种便利的方法来访问物理内存中任何特定的位置,例如,当它需要访问页表,或在一些设备,上执行内存映射的I/O操作,而这些设备被映射到特定的物理内存位置时。

所在小组

第六组

组内昵称

吴斌

你的心得体会

  • 虚拟内存管理,需要一个类似索引或者叫目录的实体,这个实体叫就是页表(page table)

  • 每个进程都有一个独立的虚拟地址空间,每个进程也都有自己的页表

  • 目前的Linux(2.6) 页表如果采用单层,页表数据量会非常大,也会显著降低页表查询的效率,一般实现上是三级或者四级页表 具体参加下图

  • 进程的虚拟地址空间分为用户区和内核区,以32位系统为例,0-3G虚拟地址空间为用户去,而3-4G空间为内核区,更加详细的可以参见下图

所在小组

第三组

组内昵称

wyhqaq

总结

  • 地址翻译

    页面命中时:

    1. 处理器生成虚拟地址,传送给MMU
    2. MMU生成PTE地址,从高速缓存/主存请求得到
    3. 高速缓存/主存向MMU返回PTE
    4. MMU构造物理地址,并传送给高速缓存/主存
    5. 高速缓存/主存返回数据给处理器

    页面未命中

    1-3 与命中时一样

    1. PTE有效位为0,触发MMU异常,传递到操作系统的缺页异常处理程序
    2. 缺页处理程序选择一个牺牲页,如果页面已经被修改,则将它换出到此案
    3. 缺页处理程序调入新的页面,并更新PTE
    4. 缺页处理程序重新回到原来的进程,再次执行导致缺页的指令。
  • 内存映射

    1)Linux文件系统中的普通文件

    2)匿名文件

    在任何时刻,交换空间都限制着当前运行的进程能够分配的虚拟页面的总数。

    写时复制:保护私有对象被两个进程同时映射的读写一致问题。

    fork函数:将两个进程中的每个区域结构都标记为私有的写时复制。

    execve函数:删除原有的虚拟地址的用户部分,创建新的区域结构

    mmap,munmap函数:创建固定大小的虚拟内存区域

  • 动态内存分配

    动态内存分配器,将堆作为大小不同的块集合来维护。每个块要么是已分配的,要么是空闲的。

    分配器核心目标:1.最大化吞吐率 2.最大化内存利用率

  • 垃圾收集

    Mark&Sweep收集器:由标记和清楚阶段组成

所在小组

第五组

组内昵称

孙恒

总结

现在系统通过将虚拟内存片和磁盘上的文件片关联起来,来初始化虚拟内存片,这个过程称为内存映射。内存映射为共享数据、创建新的进程以及加载程序提供了一种高效的机制。应用可以使用mmap函数来手动地创建和删除虚拟地址空间的区域。然而,大多数程序依赖于动态内存分配器,例如malloc,它管理虚拟地址空间区域内一个称为堆的区域。分配器有两种类型。显示分配器要求应用显式地释放它们的内存块,隐式分配器(垃圾收集器)自动释放任何未使用的和不可达的块

虚拟内存不只是“用磁盘空间来扩展物理内存”的意思,把内存扩展到磁盘只是使用虚拟内存技术的一个结果,它的作用也可以通过覆盖或者把处于不活动状态的程序以及它们的数据全部交换到磁盘上等方式来实现。对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为“连续的虚拟内存地址”,以借此“欺骗”程序,使它们以为自己正在使用一大块的“连续”地址

内存映射(memory mapping)

Linux通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来,已初始化这个虚拟内存区域的内容,这个过程称为内存映射。虚拟内存区域可以映射到两种 类型的对象中的一种:

  • Linux 文件系统中的普通文件: 一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。

    文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始内容。如果区域比文件区要大,那么就用零来填充这个区域的余下部分。

  • 匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。

在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数

写时复制

只要没有进程试图写自己的私有区域,它们就可以在继续共享物理内存中所存储的共享对象的一个单独副本。然而,只要有一个进程试图写私有区域内的某个页面,那么这个写操作就会触发一个保护故障(Protection failure). 当故障处理程序注意到保护异常是由于进程试图写私有的写时复制区域中的一个页面而引起的,它就会在物理内存中创建这个页面的一个新副本,更新页表条目指向这个新的副本,然后恢复这个页面的可写权限。当故障处理程序返回时,CPU重新执行这个写操作,现在在新创建的页面上这个写操作就可以正确执行了。

关于内存的常见的错误示例

间接引用坏指针,读取未初始化的内存,允许栈缓冲区溢出,假设指针和它们指向的对象大小相同,引用指针而不是它所指向的对象,误解指针运算,引用不存在的变量,以及引起内存泄漏

指针运算

指针的算术操作是以它们指向的对象的大小为单位来进行计算的。这个单位的大小并不一定是字节。例如,下面函数的目的是扫描一个int的数组,并返回一个指针。指向val首次出现的位置。

垃圾回收器将内存视为一张有向可达图(reachability graph)