1390 words
7 minutes
项目

xv6操作系统#

关于锁#

内存分配器#

物理内存空间128MB。一个物理页帧4kb。 空闲空间会比128MB小,因为内核代码和数据会占用一部分

背景#

  • 内存保存在单向链表freelist当中,并使用自旋锁进行保护
  • 内存相关操作
    • kfree:释放内存,头插
    • kalloc:分配内存,取头
  • 物理内存是在多进程之间共享的,所以不管是分配还是释放页面,都要先获得一个内存分配的锁,才能对freelist进行操作

解决方案#

  • old:一个内存锁和存储空闲页帧的链表
  • new:为8个CPU每个CPU都分配了独立的内存锁和独立的存储空闲页帧的链表。在系统初始化的时候平均分配给8个freelist
  • 窃取:
    • 释放自身的锁
    • 按顺序请求别freelist的锁
      • 为空,释放锁,重复操作
      • 不为空:获取空闲页帧,释放锁

磁盘缓存#

背景#

由于对磁盘的读取非常慢,而内存的速度要快得多,因此将最近经常访问的磁盘块缓存在内存里可以大大提升性能。内存中的BufferCache就担任了磁盘缓存的作用。

数据结构#
  • bcache
    • 一个静态数组,表示bcache的内存空间。
    • 一个head指针:对bcache的操作是通过双休链表完成的LRU
    • 一个自旋锁:保护bcache结构
  • buf:一个bcache里有很多个buf缓存块
    • valid:是否是从磁盘中读来的
    • disk:是否已经刷回到磁盘
    • blockno:缓存的磁盘块号
    • refcnt:被多少个进程拥有
    • data:数据区域
    • lock:保护这一个缓存块
    • next/prev:组成链表
基本操作#

所有的buf都被组织到一条链表中,因此如果有多个进程要使用buffer,它们并发的请求只能被顺序地处理

解决方案(哈希桶)#

  • 获取一个buf进行读/写操作bget(int blockno)
    • 求得哈希值
    • 获取桶的锁,从前往后遍历这个桶里的buf
    • 如果命中:增加buf的引用计数、为该buf上锁,释放哈希桶的锁,返回buf
    • 如果不命中:从后往前寻找引用树为0的buf、为buf上锁,释放哈希桶的锁,修改buf中的内容。返回buf
    • 该哈希桶中没有空闲的buf,进入全局窃取阶段
      • 先释放哈希桶的锁(要不然会有死锁)
      • 再依次获得全局锁与该哈希桶的锁(顺序反了也会有死锁)
      • 再查看自己有没有命中(避免一个桶里blockno重复)。如有:获得buf的锁、释放哈希桶的锁、全局锁
      • 如果没有命中:遍历除了自己的哈希桶。
        • 找到了引用数为0的:初始化这个buf,并将其添加到自己的哈希桶中,头插。获得这个buf的锁、释放这个被窃取的哈希桶、释放自己哈希桶、释放全局锁。
  • brelse
    • 释放buf的锁
    • 获得对应的哈希桶的锁
    • 将其插入进去、并修改引用计数。如果引用计数为0的话,插入尾部
    • 释放哈希桶的锁

关于页表#

VPN 2 PPN#

alt text

背景#

用户程序运行和内核运行的时候,使用的是不同的页表 N(进程数) + 1个页表

  • 用户态页表:每个进程都有自己的
  • 内核态页表:共享内核态页表,所有进程用同一个,使用恒等映射

进程切换:用户态变、内核态不变 用户态-内核态切换:用户态不变、内核态变

处于内核态时,如果要翻译用户态的虚拟地址:因为内核页表不含对应的映射,计算机硬件MMU(内存管理单元)不能自动帮助完成这件事。因此,我们需要先找到用户程序的页表,仿照硬件翻译的流程,一步一步的找到对应的物理地址,再对其进行访问。时间开销大。

解决方案#

  • 共享内核页表->独立内核页表(N个进程,N个用户态页表,N个内核态页表) 释放页表但不释放叶子页表指向的物理页帧(存储内核态代码和数据) alt text
  • 简化软件地址翻译(背景中的难点)(N个进程,N个用户态页表,N个内核态页表, 内核态页表的的部分二级页表项指向用户态的叶子页表)
    • 内核页表直接共享用户页表的叶子页表,即内核页表中次页表的部分目录项直接指向用户页表的叶子页表。
      • 内核态通过内核态页表:能访问内核/用户的代码和数据
      • 用户态用户态页表:只能访问用户态的代码和数据
    • 当我们在内核页表中拥有了用户进程的映射后,内核就可以直接访问用户进程的虚拟地址。比如可以直接解引用用户地址空间中的指针,就能获取到对应地址的数据。很多系统调用,或者内核的一些操作,都需要进行虚实地址翻译,因此我们在拥有了用户进程的地址映射后,原有麻烦的软件模拟翻译就可以被去除,很多操作都可以被简化,性能也可以得到提升。 alt text