【408】操作系统 - 刻骨铭心自测题1(上)

草原

OS练习题

第一部分:

1:

(单选)进程相较于线程的优势,以下哪一项说法正确?
a. 进程创建更快、更轻量化
b. 进程间隔离更彻底、安全
c. 进程间通信更容易实现
d. 进程间切换速度更快
  • ❌A D:
    进程负责资源申请,是资源申请的基本单位。

    线程是CPU进行任务调度和执行的基本单位,比进程轻量。

    进程控制管理块叫PCB,线程控制管理块叫TCB,用户级线程控制管理块更轻量叫LWP。

  • ✅ B:
    进程具有独立地址空间,逻辑隔离,只要OS不出问题,一个进程的错误不会影响其他进程。

    同一个进程的线程之间共享进程申请的资源,比如逻辑地址空间。一个线程的越界将导致整个进程挂断。

    因为进程是非常安全的,所以我们在写代码时只强调线程安全。

  • ❌C:
    进程封闭性好,所以通信只能借助第三方,如:匿名管道,命名管道,挂载共享内存,消息队列,socket套接字,信号量。

    线程由于共享进程资源,所以通信比较方便,只需要对一份都看得见的资源实现互斥即可,通信方式有全局变量,互斥量,条件变量,信号量。

  • 同进程下线程共享和独有资源:

    • 共享:
      1. 文件描述符表
      2. 信号处理方式
      3. 当前工作目录
      4. 用户id与组id
    • 独有:
      1. 线程ID
      2. 栈(每个线程创建自己的临时变量)
      3. 上下文寄存器(进程切换时保存当前内容)
      4. 调度优先级(PRI和NICE)
      5. 信号屏蔽字(毕竟可以通过sigprocmask()设定)
      6. errno

2:

加速一个矩阵运算的方法包括:
a. 在单核处理器上使用多线程
b. 在多核处理器上使用多线程
c. 通过优化运算顺序提高缓存命中率
  • ❌A:
    单核处理器实现多线程可以达到多个任务貌似都在执行,但是因为调度开销,每个任务的效率远远不如单核单线程。可用于单核服务器响应多个用户的请求。

  • ✅B:
    a*b的矩阵和b*c的矩阵相乘,交给a*c个核心,则每个核心执行b次乘法和b-1次加法,多个核心并行,速度比单核单线程快a*c倍

  • ✅C:
    CPU访问内存的过程:

    1. CPU内核发起VA(virtual address)请求数据,先在CPU内部的MMU内部的TLB(快表)中查询是否有VA 前20位对应的 PA(physical address),若有则不必Translation Table Walk。
    2. 若TLB中不存在VA前20位对应的PA,则需要通过MMU的页表地址寄存器CR3找到一级页表(页目录)的物理地址,到一级页表中查询前10位对应的页表项(二级页表的PA),到二级页表(常直接说页表)中查询中间10位对应的页表项(页框的PA),最后的VA对应的PA就是二级页表页表项+最后12位偏移量。这个过程先后经过了MMU、一级页表、二级页表、内存,像是一个人在行走,所以叫Translation Table Walk
    3. 就是因为TLB在CPU内部,所以访问TLB索取PA,比访问内存中的两级页表索取PA快的多。
    4. TLB击落:多核处理器中任意一个处理器更改了TLB中VA对应的PA时,其他处理器也随之更改同步。

3:

(单选)虚拟地址向物理地址翻译的过程中,哪一项不会被用到
a. CPU 中的 MMU
b. CPU 中的 TLB
c. 二级存储(磁盘)
d. 指向页目录的寄存器(CR3)
e. 内存中的页表
- ✅A B D E:
  1. TLB中含有VA前20位对应的PA,不经过Translation Table walk,直接在内存中寻找PA+12位偏移。
  2. TLB中不含VA前20位对应的PA,需要经过Translation Table walk,先查询CR3寄存器索取页目录的PA,再访问页目录PA,访问页表PA,访问内存PA+12位偏移。
  3. 不论TLB还是CR3寄存器,都存储在MMU内部
  • ❌C:

    1. CPU和磁盘内文件永远不能直接交互,文件在使用前先调度进入内存中,当修改完成后再写回磁盘。
    2. 文件:以硬盘为载体存储在计算机上的信息集合。
    3. 文件系统:OS中负责管理和存储文件的软件机构。
    4. 文件的打开和关闭:为了避免重复检索目录。
      OS维护着一张包含所有打开文件的表,并为每个文件表项编号,文件打开后本身存储在内存中,文件在内存中的索引存储到了表项;文件关闭后从内存写回磁盘,删除表项中的索引和编号。
    5. 文件系统的六大层次结构:
      用户调用接口
      文件目录系统
      存储控制验证模块
      逻辑文件系统和文件信息缓冲区
      物理文件系统
      辅助分配模块 + 设备管理程序模块
    6. 文件存储的盘块分配:
      顺序分配
      链接分配:
      1. 显式链接:文件各物理块末尾的指针(盘块号)直接存放在文件分配表FAT当中。
      文件分配表FAT在整个磁盘中只有一张,块号对应-1意思是该块是存储该文件的最后一块,块号对应-2意思是该块空闲,FAT在OS启动后即加载入内存,减少磁盘IO。
      2. 隐式链接:除了最后一个盘块外,其余盘块都有指向下一个盘块的指针。
      索引分配

4:

线程A和B共享整型x的值,分别执行两行代码A{x=0;x=1;};B{x=0; x=2;},程序结束时,x 的值可能是:
a. 0
b. 1
c. 2
d. 3
  • ✅B C:
    理论上x值取决于最后一条赋值语句,可能是x=1或者x=2,进程调度比较随机。
  • ✅实际测试的时候,因为两条赋值语句非常轻量,基本上不会因为时间片到期而引发进程调度,所以写代码时后pthread_create()的进程决定了x的值。
  • 附上Linux代码一份,使用g++ 文件名 -lpthread命令编译:
#include <iostream>
#include <pthread.h>
using namespace std;
int x;
void *func1(void *argv){
    
      
  //pthread_detach(pthread_self());
  *((int*)argv) = 0;
  cout<<"1  "<<*((int*)argv)<<endl;
  *((int*)argv) = 1;
  cout<<"1  "<<*((int*)argv)<<endl; 
  return (void*)&"0";
}
void *func2(void *argv){
    
     
  //pthread_detach(pthread_self());
  *((int*)argv) = 0;
  cout<<"2  "<<*((int*)argv)<<endl;
  *((int*)argv) = 2;
  cout<<"2  "<<*((int*)argv)<<endl;
  return (void*)&"0";
}
int main(){
    
    
  pthread_t tid1, tid2;
  pthread_create(&tid2, nullptr, func2, &x);
  pthread_create(&tid1, nullptr, func1, &x);
  pthread_join(tid2, nullptr);
  pthread_join(tid1, nullptr);
  cout<<"x = "<<x<<endl;
  return 0;
}


5:

(单选)以下那种情况,不需要使用同步机制(锁、信号量等)?
a. 线程间没有共享资源
b. 资源无限
c. 没有并发的程序
d. 以上都不需要
  • ✅D:
    A:没有临界资源,不需要互斥量,更不必同步
    B:存在访问资源时加互斥的原因就是资源有限,可以说资源无限的情况下绝大多数情况不需要互斥。(可能线程通信还需要吧,进程通信可以走匿名管道、命名管道、共享内存、socket、消息队列)
    C:串行相当于在程序执行期间所有的资源都由一个执行流控制,不需要互斥

6:

(单选)设计操作系统,不需要考虑哪些因素?
a. 系统的性能
b. 系统的可靠性
c. 系统的安全性
d. 以上都需要
  • ✅D

7:

(单选)以下哪项不是(常见)操作系统的任务/功能?
a. 管理硬件资源
b. 隔离进程的地址空间
c. 处理系统调用、中断、异常
d. 防止用户进程进入死锁状态
  • ✅D:
    • 死锁必要条件:
      1. 互斥
      2. 不剥夺
      3. 请求保持
      4. 循环等待
    • 死锁预防策略:破坏四个必要条件
    • 死锁避免算法:
      1. 通过银行家算法
      2. 找到一条系统状态安全序列
    • 死锁检测算法:
      1. 资源分配图中有无环
    • 死锁解除算法:
      1. 资源剥夺:挂起一些死锁进程,抢占其资源
      2. 撤销进程法:强制撤销部分或全部死锁进程,抢占其资源
      3. 进程回退法:一个或多个进程回退到足够避免死锁的地步,属于进程自愿释放资源。

8:

以下哪些关于操作系统的说法是正确的?
a. BIOS 是操作系统的一部分
b. 用户态与内核态是指 CPU 运行的状态
c. 中断处理程序(包括系统调用)是用户态进入内核态唯一的入口程序。
d. 部分中断可以被屏蔽;该屏蔽操作是特权指令。
  • ❌A:

    • 主板通电后,CPU总线上挂在了主板上ROM和内存条RAM,并做好了实模式的段式地址映射,之后做以下工作:

      1. CPU内cs(代码段)寄存器置为0xffff
      2. CPU内ip(指令指针)寄存器置为0x0000
      3. CPU访问的第一条指令为cs段寄存器值<<4+ip段内偏移 = 0xfff0
      4. 实模式的段式地址转化是(段寄存器<<4+偏移值),没有段表
    • 0xfff0为BIOS程序在ROM的入口地址,BIOS程序不会加载入内存RAM,主要任务是:

      1. 检查硬件环境
      2. 建立中断向量表和中断服务程序,同时把结果显示在显示器上
      3. 将OS可访问的第一个扇区——磁盘0磁头1柱面1扇区的操作系统引导扇区(OBR)内440字节的BootLoader(BootStrap)加载入内存
    • 内存中的BootLoader程序又做了以下工作:

      1. 关中断,专心做下面的几件事:
      2. A20使能
      3. 设置 段寄存器内的段选择子值
        保护模式下段寄存器和选择子都是16位,
        段选择子高13位是查找段描述符的索引
        低2位是请求特权级别,中间一位标注当前查询的是GDT还是LDT
        由于不再采用段寄存器内段基址<<4+偏移量的方案获取任意PA,
        而是查询 段描述符为保护模式指示内存分段情况以及段基址
      4. 查询48位的GDTR寄存器中低16位的 GDT界限 是否越界,
        不越界则通过高32位找到并初始化内存中的全局描述符表,
        段描述符中高16为是段基址,低16位为段界限
      5. CR0寄存器第0位(PE位)置为1,进入保护模式,开启分页机制
      6. 开启分页机制执行了三步:
        a. 初始化页目录和页表
        b. 页目录地址写入CR3寄存器
        c. CR0寄存器的PG位置为1
      7. 设置堆栈
      8. 将OS内核从磁盘加载入内存,将计算机控制权交给OS Kernel
  • ✅B:

    • 为了防止特权指令被乱用,提出了用户态和内核态的概念
      CPU通过查看 CS段寄存器中的低2位 来判断自己处于什么状态
      ring0为内核态,此时CPU可以任意访存,执行所有指令
      ring3则用户态,此时CPU只能访问用户申请的内存,执行非特权指令
    • 常见的特权指令有:
      1. 启动设备指令
      2. 停机指令
      3. 设置时钟指令
      4. IO指令
      5. 存储保护
      6. 清内存指令
      7. 中断操作
    • 用户态常见任务:
      1. 进程调度
      2. 发起外部中断
      3. 缺页发生
      4. 准备系统调用
    • 内核态常见任务:
      1. 命令解释
      2. 中断向量表中的所有中断处理程序
      3. 缺页处理
      4. 时钟中断处理
      5. 系统调用
    • 用户态进入内核态方法:
      1. 中断,如自行访管
      2. 异常(也叫同步中断)
      3. 软中断——系统调用(是陷阱的具体实现,陷阱是形象化说法,比喻不断内核用户转换就像是踩了一个个陷阱)
  • ✅D:

    • 屏蔽中断sti指令属于特权指令,当CPU外部的可屏蔽中断来到后,
      标志寄存器中可屏蔽中断位(IF位)置为1时,响应
      标志寄存器中可屏蔽中断位(IF位)置为0时,不响应
    • 中断分类:
      1. 内中断(不可屏蔽):
        从当前执行的指令中产生,由CPU内部检测,在指令执行过程响应
        除0中断 和 自行中断(访管)在处理完成后自动跳过中断指令
        也有一些指令在处理异常后继续执行
        1. 自行中断,如访管
        2. 软件中断,如/0,越界,程序代码含中断
      2. 外中断(几乎都可屏蔽)
        1. 外设请求,如键盘输入,打印机
        2. 人工干预,如程序控制台,时钟中断
    • 中断分类2:
      1. 软中断是执行中断指令产生的,
        软中断的中断号由指令直接指出,
        无需使用中断控制器,软中断不可屏蔽。
      2. 硬中断是由外设引发的,
        硬中断的中断号是由中断控制器提供的,
        硬中断是可屏蔽的
    • 中断处理流程:
      1. 关中断
      2. 由硬件—中断隐指令保存断点(pc寄存器或pc+psw内容)
      3. DMA总线将中断向量从向量表送到CPU(由硬件完成,执行完后进入核心态)
      4. 由软件协助硬件,使用系统集中栈 或 进程独立核心栈 保存现场和屏蔽字
      5. 执行中断服务程序
      6. 恢复现场和屏蔽字
      7. 开中断
      8. 中断返回
    • 现场:
      32个用户寄存器 + Pc寄存器
  • ✅C:

    • 用户态到内核态的方法:
      1. 中断
      2. 异常(同步中断)
      3. 软中断——系统调用(陷阱的具体实现)
    • 所有系统调用

9:

以下关于堆栈的说法,正确的是?
a. 用户态进程对用户栈的操作(push,pop)会陷入操作系统内核
b. 同一个进程的不同线程共享一个用户态的栈
c. 同一个进程的不同线程共享一个中断栈
d. 用户态进程陷入内核后,用户态栈指针会被存在中断栈中
e. 通过 malloc 分配的堆,在物理内存上可能是不完全连续的
  • ❌A:

    • 用户态使用0~3G的低地址空间,其中含有用户栈
      内核态使用3G~4G的高地址空间,其中含有内核栈
    • 中断栈可能是直接使用内核栈(中断多了容易溢出),
      也可能是操作系统内所有进程线程同一使用一个(不易溢出)
      1. 若使用内核栈,中断可以被中断:
        一个中断到来后,内核栈本身是空的
        1. 先将用户态堆栈地址压入内核栈,
        2. 再将堆栈寄存器置为刚才是空的的内核栈地址
        一个中断处理结束,
        1. 先将内核栈顶的用户栈地址写入堆栈寄存器
        2. 再将内核栈栈顶弹出
      2. 若使用独立中断栈,中断不可以被中断:
        因为中断栈不记录当前中断发生在哪个线程/进程中,需要当即处理完成后返回线程/进程
  • ❌B:

    • 进程是申请资源的基本单位,同一进程的线程之间共享大部分地址空间,除了栈
    • 默认情况下,线程栈是从进程的堆中分配栈空间,每个线程拥有独立的栈空间。
      为了避免线程之间的栈空间踩踏,线程栈之间还会有以小块guardsize用来隔离保护各自的栈空间,一旦另一个线程踏入到这个隔离区,就会引发段错误。
    • 进程栈大小时执行时随机确定的,与编译链接无关。
      进程栈比线程栈要大,但不会超过2倍。
      线程栈是固定大小的,可以使用ulimit -a 查看,使用ulimit -s 修改
  • ❓C:

    • 若内核栈不充当中断栈,则所有线程共享OS中唯一一个中断栈
    • 若内核栈充当了中断栈,则问题变成了线程是否有独立的内核栈
      1. 开始所有内核栈为空,每个线程的内核栈可能是共享进程的,也可能是用时新建
      2. 实践证明,Linux系统下线程是有着独立的内核栈的
  • ✅D:见A的解释

  • ✅E:

    • malloc出来的内存在VA上连续,但是每一字节VA对应的PA由页表负责映射。
      所以malloc出的内存可能是物理上不连续的,但是在逻辑上一定连续

10:

(单选)以下关于线程的说法,不正确的是
a. 在单核处理器上无法实现并发(concurrency)
b. 线程是操作系统最小的独立调度单元
c. 同一个进程的不同线程,对同一个指针取值,得到的结果是一 样的
d. 线程间切换需要保存部分寄存器值和栈指针
  • ❌A:
    • 并发是指 两个或者多个事件在同一段时间内发生,宏观同时,微观较替
    • 并行是指 两个或者多个事件在同一时刻发生
    • 2001年,IBM 推出了世界第一个多核处理器IBM® POWER4。
      但是此前人们就可以同时玩扫雷和听音乐了
  • ✅B:
    • 概念
    • 线程分为内核级线程(KST)和用户级线程(ULT)
      1. KST高并发(唯一优点),大开销,效率低,需要kernel切换
      2. ULT低并发(唯一缺点),小开销,效率高,不需要kernel切换(Bytheway 进程调度在用户态)
      3. 可能一个ULT对多个KST,可能多个ULT对一个KST,可能多个ULT对多个KST
      4. Go的协程和python多线程就是ULT
  • ✅C:
    • 对指针取值就是*p,说明变量已经存在于进程中,通过传址到不同线程
    • 不同线程的栈上变量是独有的,其余地址空间是共享进程的
      所以不论进程中的该变量是在进程的堆还是栈上开辟,线程访问的都是公共的
  • ✅D:
    • 线程在切换的过程中需要保存当前线程Id、线程状态、堆栈、寄存器状态等
      1. SP:堆栈指针,指向当前栈的栈顶地址
      2. PC:程序计数器,存储下一条将要执行的指令
      3. EAX:累加寄存器,用于加法乘法的缺省寄存器

11:

对于一个 32-bit 操作系统,当页大小从 4KB 变为 8KB 后,会导致
a. 页表变大
b. 页表项用于保存页信息(dirty、r/w、valid/present 等)的比特数变多
c. 表示页内偏移的比特数变多
d. 可以寻址的物理地址变大
  • 32bit操作系统可访存232 = 4GB
  • ❌A:
    定性认识,页表项都是4B,页表项数越少,存储页表耗费内存越少,
    一级页表的页表项数等于页数,8KB页数更少,页表张数少,耗费内存少
    • 一级页表:
      一页4KB,共计220
      一条页表项需要20/8 = 3字节记录编号,加上页信息需要4字节
      一页可存储页表项 4KB / 4B == 1K条
      需要页表 220 / 210 = 210 = 1K张
      每张4KB,乘以1K张,等于4MB
    • 一级页表:
      一页8KB,共计219
      一条页表项需要19/3 = 3字节记录编号,加上页信息需要4字节
      一页可存储页表项 4KB / 4B == 1K条
      需要页表 219 / 210 = 512张
      每张4KB,乘以512张,等于2MB
  • ✅B:
    • 一级页表:
      一页4KB,共计220页,虚拟地址页内偏移可达212
      页表项需要20位编号,留下12位页信息
      一页8KB,共计219页,虚拟地址页内偏移可达213
      页表项需要19位编号,留下13位页信息(AVG位加一位)
    • 页信息位图:
      页目录项
    • CaChe命中后的两种写入内存方法:
      1. write-through:
        CPU向cache写入数据时,同时向内存也写一份,使得两者保持一致

      2. write-back:
        CPU向cache写入数据时,将更新了的cache区域标记,当该区域要被新的区域取代时,才写回内存

      3. post write(不常用):
        CPU向cache写入数据时,将写入的数据保存到更新缓冲器,更新缓存器适时将数据写回内存

    • CaChe未命中后的两种处理方式:
      1. Write allocate:
        将写入位置读入缓存,然后采用write-hit(缓存命中写入)操作。写缺失操作与读缺失操作类似。
      2. No-write allocate:
        并不将写入位置读入Cache缓存,而是直接将数据写入内存。这种方式下,只有读内存操作的内存会被记录到Cache缓存。
  • ✅C:
    1. 一页4KB(22 * 210)时,有220页,前20位找页表,后12位页内偏移
    2. 一页8KB(23 * 210)时,有219页,前19页找页表,后13位页内偏移
    3. 逻辑地址的页内偏移,在页目录或二级页表的页表项中是页信息位图
  • ❌D:
    • 可访存容量多少就是2总线根数,等于内存大小

12:

以下关于缺页中断(page fault)的说法正确的是?
a. 发生缺页中断的程序会产生异常并崩溃
b. 缺页中断可能是由于访问的虚拟地址没有相应的物理页映射
c. 缺页中断产生的前提是 TLB Miss
d. 缺页中断处理程序中可能会修改页表
  • ❌A:

    • 用户态发生内中断——缺页中断后,保存寄存器和堆栈指针值,
      陷入内核态进行中断处理,不会崩溃。
    • 缺页中断处理过程:
      1. 首先硬件会陷入内核,在堆栈中保存程序计数器。
        大多数机器将当前指令的各种状态信息保存在CPU中特殊的寄存器中。
      2. 在页面换入换出的过程中可能会发生上下文换行,
        所以需要保存通用寄存器及其它通用寄存器中易失性信息
      3. 检查虚拟页面地址的有效性及安全保护位。如果发生保护错误,则杀死该进程。
      4. 如果单纯没有VA对应的PA,则操作系统找一个空闲的页作为VA的映射
      5. 如果没有空闲页框则需要通过页面置换算法找到一个需要换出的页,
        如果找的页中的内容被修改了,则需要将修改的内容保存到磁盘上,
        此时将页标记为繁忙,之后写磁盘调用,发生上下文切换(在等待磁盘写的过程中让其它进程运行)
        页框干净后,继续写磁盘调用,发生上下文切换,将磁盘中空闲页的内容写入内存页中
      6. 当磁盘中的页内容全部写入内存页后,向操作系统发送一个中断。
        操作系统更新内存中的页表项,将虚拟页面映射的页号更新为写入的页,
        并将页标记为正常状态。
      7. 恢复缺页中断发生前的状态,将程序指令器重新指向引起缺页中断的指令。
      8. 调度引起页面中断的进程,操作系统返回汇编代码例程。
      9. 汇编代码例程恢复现场,将之前保存在通用寄存器中的信息恢复。
  • ✅B:

    • CPU向内存写入数据的过程:
      1. CPU访问MMU中的TLB,查看是否可以直接获取VA对应的PA,
      2. 若不存在则需要Translation Table walk,查询页目录,二级页表寻找PA
      3. 找到PA后,查看CaChe是否存储了PA及其存储值,若不存储则需要访存PA,
      4. 修改内存后,选择性将PA和内存值加载入Cache
    • 缺页中断的发生过程:
      1. TLB中没有VA对应的PA
      2. 页目录中没有VA前10位对应的页表
      3. 页目录中有VA前10位对应的页表,但是页表中没有中间10位对应的页表项
    • 三种缺页中断
  • ✅C:

    • 缺页中断指的是页表中缺少VA前20位对应的页表,也就查不到VA对应的页的起始PA
    • 既然已经前去查询页表,说明TLB中没有该VA对应的PA
  • ❌D:

    • 如果是存在VA前20位对应的页表项,但是权限不对,则直接kill 进程
    • 如果是不存在VA前20位对应的页表项,则是物理页不在内存中
      1. 若内存未满,则直接从磁盘中调入一张可写的页
      2. 若内存已满,则需要通过页面置换算法,将一页写回磁盘,之后从磁盘中调入一张可写的页
      3. 新的页到来之后,需要新加一个页表项,记录VA对应内存中刚调入的页的PA

13:

以下关于缓存 Cache(指内存的缓存,非 TLB)的说法,正确的是?
a. 完全基于虚拟地址查找相应的数据块
b. 计算机中有多级缓存,距离 CPU 越近,缓存越小
c. 进程切换时,需要清空缓存
d. 缓存命中率与应用程序的指令顺序相关
  • ☀️:Cache在计算机组成原理时候其实已经学习过了
    1. Cache是CPU内部的高速缓存,存储主存中PA及对应的data
      Cpu访问Cache的速度大约是访存速度的60倍
      实际情况1级Cache既支持PA,又支持VA,其余2 3级Cache在CPU外,只支持PA。
      • 早期ARM9的1级Cache使用虚拟地址作为索引和标记,称为VIVT方式,有严重的Cache重名 / 歧义问题
      • 后期ARM11开始使用虚拟地址作为索引,物理地址作为标记,称为VIPT方式,解决了Cache重名 / 歧义问题
      • VIPT工作流程:
        1. VA送往MMU/TLB中翻译的同时,也送去Cache寻组(现实使用组关联映射),MMU完成翻译获取PA后,由PA寻找组内具体的某一Cacheline
        2. 也就是说VI是指用VA去寻找Cache块分组
        3. PT是指用PA去指定组内寻找具体某一Cacheline
    2. Cache的加速原理: 局部性原理
      1. 时间局部性:最近访问的指令或者数据,在不久的将来可能因为循环的存在而再次访问
      2. 空间局部性:即将访问的指令或者数据,很可能与现在使用的信息在存储空间上邻近
    3. 内存分块:
      • 内存由2n个字节地址构成,将其每2b个字节分为一块,则块内偏移为2b个地址
      • 假设分了m块,则每个地址的前logm位表示是第logm
      • 内存分块比内存分页小
        Cache分块:
      • Cache地址分为两段:高位表示缓存块号 (行号),低位表示块内偏移 (行长)
      • Cache内存远远小于主存,所以Cache的缓存块号远远小于内存总块数
      • 依据空间局部性原理,Cache不是以字节为单位缓存主存,而是以块为单位
    4. 著名的数组访存快慢问题:
    • 数组在主存中最低维紧密排列,之后次低维(先存储同列,再存储同行)
      数组存储方式
    • Cache的空间局部性原理使得Cache中存储arr[x][y]时,也存储了附近的arr[x][y±i]
      所以逐行遍历arr[][]比逐列遍历快不到60倍
    1. Cache的基本结构:
      • Cache存储体:存储主存调入的指令和数据块,以块为单位与主存交换信息
      • 主存—Cache地址变换机构:通过查表实现主存地址(PA)到缓存地址(块号+块内偏移)的转化(Cache块数远少于主存),为了区分表项全0是该块没有PA与之对应,还是全0PA与之对应,为表项增加了有效位,1为对应,0为没有对应。
      • 替换控制部件(硬件):缓存已满时按照页面置换算法进行数据块替换,并修改地址变换机构
      • 等部分组成
    2. Cache块和内存块的映射规则:
      • 全相联映射:主存的一块,可以映射到Cache的任意一块
      • 直接映射:主存的一块,只允许映射到Cache的固定一块
      • 组相联映射:主存的一块,只允许映射到Cache的某组内几块
    3. Cache例题:
      • 假设某个计算机的主存地址空间大小为256MB,按字节编址其数据Cache有8个Cache行,行长为64B。
        解析:256MB说明地址为28位,8行说明Cache地址变换机构维护的表有8个表项,行长64B说明块内偏移为6位
      • 全相联映射下查表O(n),但是不频繁更换Cahceline:
        • cpu发出地址为1111 1000 0100 1010 1010 1010 1010,后6位是块内偏移,前22位在表项中用于记录和块号的对应关系,到地址变换机构查表
        • 后6位是块内偏移,按照前22位(1111 1000 0100 1010 1010 10)查询地址机构内对应表:
          Cache地址转化
        • 若查询得到的块对应有效位为1,则返回data;否则需要访存和置换Cache块对应PA
    • 直接映射下,查表O(1),但是频繁更换Cacheline:
      • cpu发出地址为1111 1000 0100 1010 1010 1010 1010,后6位是块内偏移,中间log行数 = log8 = 3位是块号,最前面19位在表项中用于记录和Cache块的对应关系
      • 先用中间3位锁定块号,再查看前19位是否存在于表项中,若存在则直接写入,不存在则访存
        直接映射查表
    • x路组相联分组映射下,查表O(x),更换Cacheline也不频繁了
      • 假设有2路组相联,还是8行,每行64字节
      • cpu发出地址为1111 1000 0100 1010 1010 1010 1010,后6位是块内偏移,中间log组数 = log4 = 2位是组号,最前面20位在表项中用于记录和Cache块的对应关系
      • 先用中间2位锁定组号,再查看前20位是否存在于这组的表项中,若存在则直接写入,不存在则访存
        2路组相联
  • ❌A:
    • 1级Cache通过PA & VA都可以查询到Cache块内数据
    • 2、3级Cache只能通过PA查询到Cache块内数据
  • ✅B:
    • 1级Cache在CPU内,2、3级Cache在CPU外
    • 越高速的存储设备,存储容量越小,造价越贵
  • ❓C:
    • 进程切换时,负责记录VA对应的PA的TLB必须刷新 (miss 和 减少权限时也刷新)
    • 进程切换时,因为使用的PA不一致,所以Cache大概率切换
    • Cache有一个概念叫做Cache歧义,是由进程切换后Cache不变导致的,
      为了防止Cache歧义,进程切换后Cache也清空,从不断Miss到重新建立需要很长时间
  • ✅D:
    • Cache按照时间局部性 和 空间局部性 来缓存内容
    • 指令执行顺序如果有很强的时间重复性,和空间集中性,则可以通过访问Cache避免访存

14:

在一个多线程任务中有以下代码,其中 x 是多线程共享变量
----------------------begin----------------------
static int x = 0;
// T1 时刻
lock.acquire();
// T2 时刻
x = 1;
// T3 时刻
lock.release();
// T4 时刻
------------------------end---------------------
以下说法正确的是?
a. T1 时刻,x 值必为 0
b. T2 时刻,x 值必为 0
c. T3 时刻,x 值必为 1
d. T4 时刻,x 值必为 1
e. 以上说法都不对
  • ❌A:
    • 第一个走进临界区的线程,此时x还没有被修改,是0
    • 之后走进临界区的线程,x都已经被修改,是1
  • ❌B:
    • 第一个走进临界区的线程,x=1执行前,x还没有被修改,是0
    • 之后走进临界区的线程,x都已经被修改,x=1未被执行前,x已经是1
  • ✅C:
    • 此时进入临界区的只有当前线程,刚刚执行完毕x=1,未放锁之前没有其他线程来修改
  • ✅D:
    • 走到T4时,至少一个线程执行了x = 1,没有线程执行x=0,所以x一旦修改为1后不可能回0
  • ❌E

15:

以下关于条件变量(Condition Variable, cv)的说法,正确的是?
a. Wait(&lock)函数必须在持有 lock 锁的过程中才能调用
b. Signal()函数会释放锁
c. Signal()函数返回时,有可能不持有 lock 锁,程序需要重新获取锁
d. Broadcast()函数至少会成功醒一个等待线程
  • ❌A:
    • wait()是申请锁,是条件变量值–
    • 没有锁的时候尝试申请锁,如果此时条件变量值>0,则可以申请到,获取锁后条件变量值–
    • 如果此时条件变量值为0,则申请不到锁,但是条件变量还是–,-1表示有一个线程在阻塞等待获取锁
  • ✅B:
    • signal()是释放锁,是条件变量++
    • 当条件变量值++后>0,可以供wait()的进程使用锁,否则还有 |条件变量值| 个进程在阻塞等待
  • ✅C:
    • signal()释放锁之后,当前线程重新参与到众多线程对锁的抢夺中,很可能抢夺不到下一轮的锁
  • ❌D:
    • pthread_cond_signal()至少唤醒一个等待获取锁的进程,开始争夺锁
    • pthread_cond_broadcast()会唤醒全部等待获取锁的进程,开始争夺锁

16:

以下关于三类常见文件系统:FAT、FFS 和 NTFS 的说法,正确的 是?
a. FAT 文件系统对大文件的随机读取速率较差;
b. FFS 文件系统的采用非对称(深度)树状结构索引的目的是为了同时支持小文件和大文件的高效存储和查找;
c. NTFS 文件系统对小文件最友好,因为可以直接存储数据在 MFT 中,而其他两个文件系统都需要索引;
d. FAT 文件系统可以支持稀疏化文件表示;
e. FAT 文件系统使用 next fit 分配算法;FFS 文件系统使用 first fit 分配算法;NTFS 文件系统使用 best fit 分配算法
  • ✅A:

    • 显式链接文件系统 中 FAT文件系统查找文件的过程:
      1. 在内存的FCB表中查找<文件名 : 起始块号>
      2. 到硬盘的起始块中查找到部分数据
      3. 到FAT表中查找起始块号对应的下一块块号,若不为EOR
      4. 到硬盘的对应块号中查找到部分数据
      5. 到FAT表中查找起始块号对应的下一块块号,若为EOR
      6. 结束查找,已经找到了全部数据
    • 可以看出,只能通过 链式前向星 的方法来查找第x块,
      所以FAT 文件系统对大文件的随机读取速率较差。
    • 为了避免磁头重复移动,FAT文件系统存储文件时按照next fit原则
  • ✅B:

    • FFS采用BLA方式寻找数据,而非CHS方式
    • BLA将硬盘分为两大部分,BOOT Block & Block Groups
      Block Group是很大的一个数组,每个数组含有6个部分:
      其中Block Bitmap 和 inode Bitmap都是位图,所以FFS采用的是first fit算法Block Group - 我们采用Block group[1 2 3]来存储文件,其中inode节点存储着文件信息的元数据,Block存储真实的文件, 每个BLock块大小固定,而且文件元数据存储在了inode中,而非其所在的目录文件中 - FFS的数据结构是多级索引的固定大小的非对称(深度不一)树 文件编号是inode Table的索引,非常适合存取小文件和大文件 在这里插入图片描述
  • ✅C:

    • NTFS文件系统采用的是最佳适应算法,不会将所有位图都遍历,而是仅仅缓存一部分:
      NTFS的一项功能:SetEndOfFile() ⽤于指定创建时文件的预期大小
      NTSF

    • NTFS的所有文件元数据(文件相关信息,类似FFS的inode)都存储在MTF区
      每个MFT的记录项是1KB左右,格式为:记录头 + 属性1 + 属性2 + 属性3 + …
      属性包含文件名 + 文件大小 + 文件修改时间 …,长度可以扩展
      其中DATA属性可以直接存储完全小文件的文件内容,或大文件的文件指针
      NTFSMFT

    • NTFS内容目录组织为B树 或者 B+树

      1. MTF文件编号 0 为MFT本身
      2. MTF文件编号 5 为根目录 /
      3. MTF文件编号 6 为可用空间位图
      4. MTF文件编号 8 为含卷的坏块列表
      5. MTF文件编号 9 为 $Secure,即安全访问和控制信息
  • ❌D:

    • 仅仅FAT不支持稀疏文件,NTFS对比FAT的优点还有:
      1. 文件加密 & 文件 和 文件夹 的权限
      2. 稀疏文件 & 磁盘压缩
      3. 单个文件大小,分区大小
      4. SetEndOfFile() ⽤于指定创建时文件的预期大小(文件配额)
      5. 支持活动目录(当前工作目录) & 域
    • FFS支持稀疏文件:
      其中一个 或 多个空白空间范围被文件数据包围,空白空间不占用磁盘空间
    • NTFS也支持稀疏文件:
      无用的0字节被算法压缩,不再占用很多空间
    • ls显示稀疏文件大小 远远大于 du显示稀疏文件大小
  • ✅E:

    • 发明时间:
      1. FAT为 微软1970年代发明的文件分配表,至今还在闪存棒和数码相机中使用
      2. FFS为 1980年代发明,有良好的空间局部性,后来的EXT2 EXT3基于此
      3. NTFS为 1990年代微软发明的新技术文件系统,是MS主流文件系统,代表EXT4 XFS APPLE的分层文件系统
    • 适应算法:
      1. FAT为next fit
      2. FFS为 first fit
      3. NTFS为 best fit
    • 逻辑结构:
      1. FAT为 单链表
      2. FFS为 非对称树,有良好的空间局部性
      3. NTFS为 B / B+树,树结构更灵活

17:

以下关于虚拟文件系统(vfs)的说法,正确的是?
a. 仅仅是一层标准 API 的定义
b. 目的是为了更好地支持不同种类的 I/O 硬件设备
c. 与传统文件系统一样,vfs 中也有 inode、dentry 的概念
d. I/O 系统调用会先被 vfs 接收,然后传递给相应的文件系统
  • ❌A:
    • 早期OS是对硬件的量身定制,网络文件系统出世之后,人们开始对单个系统支持多种文件类型感兴趣
    • 现代的VFS支持数十种FS,允许新功能新设计对程序透明,拥有独立于后备存储的层。有着内存文件系统 和 可配置伪文件系统 和 网络文件系统 的支持
    • VFS不只是API包装器,也是一段重要的代码
  • ✅B:
    • VFS使得用户视角忽略硬件差异,只能看到:
      1. 单一编程接口,POSIX下统一的各种封装了系统调用的函数
      2. 单一文件系统树,可以透明安装远程文件系统
      3. 可选为每个文件系统定制库
  • ✅C:
    • VFS也采用BLA对磁盘管理,其中每个Block group中含有6个部分
      1. super block反应了该文件所属的真实文件系统,大小,状态
      2. inode有两种,一种是VFS的inode,一种是具体文件系统的inode。
        前者在内存中,后者在磁盘中。所以每次其实是将磁盘中的inode调进填充内存中的inode,这样才是算使用了磁盘文件inode。
      3. 目录项dentry用于描述文件的逻辑属性,每个文件都有一个目录项结构体,
        结构体内部包含了inode指针,各个文件的目录项构成了庞大目录树,
        目录项只存在于内存中,,并没有实际对应的磁盘上的描述。
        一个有效的目录项dentry是一个结构体,其内部的inode指针必须有效
  • ✅D:
    • Linux系统真实使用的文件系统就是VFS,其层次如下:
      VFS层次

18:

以下关于使用奇偶校验方法实现的 RAID 5 磁盘冗余技术的说 法,正确的是?
a. 相较于 RAID 1 完全镜像的方式,节省了磁盘空间
b. 当超过 1 个磁盘损坏时,无法恢复数据
c. 当只有 1 个磁盘损坏,但未知是哪个磁盘时,无法恢复数据
d. 奇偶校验值不放在同一个磁盘上主要是为了防止该磁盘成为I/O 瓶颈
  • RAID0:分开存储在多个硬盘,没有安全措施

  • RAID1:每一块硬盘都有一块备份盘

  • RAID10:分开存储在多个硬盘,每个硬盘又有一块备份盘

  • RAID5:假设有n>2块硬盘,将数据分为(n-1)份,对(n-1)份数据求出整体的一份校验信息。
    写入n轮,前n-1轮主要是写数据,最后一轮主要是写校验信息。
    每一轮选择一块硬盘作为总校验信息载体,其余作为数据载体。

  • ✅A:
    - RAID1最耗费硬盘资源

  • ✅B:
    - 奇偶检验 类似 异或

  • ❌C:
    - 不需要知道损坏磁盘具体是什么,只需要知道校验信息和其余磁盘存储内容即可

  • ✅D:
    - 当读取一份数据发现损坏时:
    1. 若校验信息在每一块磁盘都有,则此时有一个磁盘磁头正在校验信息处
    2. 若校验信息仅仅在一块磁盘,则此时磁盘磁头需要移动到此部分的校验信息处


19:

以下关于虚拟机(virtual machine)的说法,正确的是?
a. 虚拟机中的操作系统内核(guest kernel)运行在内核态,可以执行特权指令
b. 当中断发生时,硬件决定将它发送给 host kernel 还是 guest kernel
c. guest kernel 通过 iret 指令从内核中返回到 guest 用户程序时,会陷入到 host kernel 中
d. 使用 shadow page tables 实现虚拟机中的内存映射,需要 host kernel 追踪 guest kernel 中对页表的修改
  • 最详细清楚的虚拟机介绍文章内容总结:

  • 虚拟化三大条件:

    1. 等价性:VMM需要在宿主机上为虚拟机模拟出与在物理机上运行一样的环境
    2. 高效性:虚拟机指令执行性能需要与直接在物理机上执行的性能相比无明显损耗
    3. 资源控制:VMM可以完全控制系统资源,由vmm协调控制宿主机对虚拟机的资源分配
  • 陷入和模拟模型:

    1. “特权级压缩”:虚拟机的内核模式和用户模式,都在物理机的用户模式运行。
    2. 当虚拟机执行非特权指令时,直接CPU执行
    3. 当虚拟机执行特权指令时,本质是直接在用户态执行特权指令,引发处理器异常,陷入VMM,由VMM异常处理函数代理虚拟机完成对系统资源的访问,也就是VMM模拟内核态
    4. “特权级压缩”又满足了虚拟化标准中VMM控制系统资源的要求,虚拟机将不会因为可以直接运行特权指令而修改宿主机的资源,从而破坏宿主机的环境。
  • x86架构虚拟化的障碍:

    1. 根据CPU的CS段寄存器声明的权限,指令分为接触系统资源的特权指令,和不接触系统资源的非特权指令
    2. 但是x86架构下,敏感的非特权指令也可以访问系统资源,而且虚拟机执行这些敏感指令时不会被VMM拦截
    3. 解决方案有两种:其一是修改Guest的代码,不符合虚拟化透明准则。其二是二进制翻译,将敏感指令翻译为等价的特权指令,动态翻译比静态翻译效果好,不需要创建新文件。
  • 内存虚拟化技术:

    1. 客户机 和 宿主机都有自己的地址空间,这个地址空间又因为进程而被分为虚拟地址空间和物理地址空间,下面讲虚拟机如何访问物理机的物理内存空间
    2. 基本概念:
      HPA:Host Physical Address
      HVA:Host Virtual Address
      GPA:Guest Physical Address
      GVA:Guest Virtual Address
      PDBR:页目录表物理基地址寄存器,X86上叫CR3
      EPT:扩展页表
    3. 传统MMU只负责VA -> PA,虚拟化场景下的MMU要负责GVA -> GPA -> HVA -> HPA
      硬件辅助内存虚拟化出现之前,这个过程是通过软件实现的,即通过VMM来实现的。最典型的实现方式就是影子页表技术。
    4. 影子页表:
      VMM把Guest和Host中的页表合并成一个页表,称为影子页表,来实现GVA->HPA映射。
      影子页表
    5. 影子页表实现 GVA -> HPA的转换:
      • GVA->GPA:VMM层的软件会将guest Page Table本身使用的物理机的物理页面设为write protected的,Guest在进行GVA->GPA写入内容时,由于是只读的,导致 VM exit, traps to VMM。(关于VM exit的过程我们在CPU虚拟化时再详解)。
      • GPA -> HVA:这一过程由VMM软件实现的,这个很容易理解,就是通用的malloc
      • HVA->HPA,这一过程就是我们已知的使用物理MMU完成VMM进程的虚拟内存到物理内存的转换。
      • 把GVA -> HPA,这一路的映射关系记录到页表中,这个页表就是影子页表
  • Intel对敏感指令的硬件处理:

    1. Intel没有将非特权的敏感指令修改为特权指令,毕竟并非所有的特权指令都需要拦截处理
      比如内核态下的进程切换时,需要更改CR3寄存器,保存当前进程的页目录
    2. 原本VMM需要捕获每一次guest kernel对cr3寄存器的修改,使之指向影子页表。
      使用硬件的EPT支持后,cr3寄存器不需要指向影子页表,指向guest kernel的进程的页表即可
      所以VMM无须再捕捉guest对cr3寄存器的操作,这条敏感指令不需要陷入VMM
    3. 将宿主机运行的模式称为VMX Root Mode
      虚拟机运行的模式称为VMX Non Root Mode
      cr3寄存器可以明确当前是guest还是host
      由宿主机到虚拟机的转变称为VM entry
      由虚拟机到宿主机的转变称为VM exit
    4. VMM运行在VMX Root Mode,
      有了Ept硬件支持后,虚拟机可以摆脱内核态和用户态都在物理机的用户态运行,即特权级压缩的方式,直接运行在Non Root Mode的两个状态下面
    5. Root Mode的VMM可以通过执行CPU的虚拟化指令VMLaunch切换进入Non-Root_Mode,称为VM entry
      当出现敏感指令后,CPU从Root Mode切换到Noe Root Mode,称为VM exit,之后VMM通过特权指令模拟敏感操作
    6. 有了硬件支持的VMX模式CPU有3点不同
      • guest用户态可以直接到内核态,不经过Host的内核态
      • guest的cpu收到中断后,CPU从guest退出到host模式,由host内核态处理中断,处理完毕后再回到guest模式。IO也可以直接在guest模式下处理
      • 原本所有特权指令都发生VM exit回到host的内核态由VMM处理,现在不需要VMM介入的特权指令直接在guest运行。敏感指令还是会exit回到host内核态由特权指令模拟敏感指令。
    7. VMX系统设计了保存上下文的数据结构:VMCS,其中一类用于保存host和guest的运行状态,一类用于控制guest行为。
  • ❌A:

    • 特权级压缩,guest的kernel和user都在host的user
  • ❌B:

    • 中断发生后,先由host kernel给出中断处理程序,再由host kernel保存栈指针和寄存器信息,最后由guest kernel执行中断处理程序
  • ✅C:

    • guest kernel 中断处理完成后,想要直接iret回到guest user,发生CPU异常,返回host kernel,再由host kernel 返回到guest user
  • ✅D:

    • host kernel自己修改页表自己当然知道
    • guest kernel尝试修改页表就会触发只读错误,退回到host kernel,由VMM改写影子页表和OS页表

20:

(单选)下面哪个操作可能不会导致用户态切换到内核态?
a. 缺页异常 Page fault
b. 调用 libc 中的字符串函数
c. 除零
d. 用户程序打开磁盘上的一个文件
  • ❌A:
    • 缺页异常 会引发 缺页异常处理程序,是中断处理,需要进入内核态
  • ✅B:
    • libc库属于系统调用库,大多数API都对应一个系统调用,比如:应用程序中使用的接口open(),就对应同名的系统调用open()。
    • 但函数库中的API和系统调用并没有一一对应的关系。应用程序借助系统调用可以获得内核所提供的服务,但是一些像字符串操作这样的函数并不需要借助内核来实现,因此也就不必与某个系统调用关联。
    • 用户态进入内核态的方式有中断、异常、系统调用,所有系统调用都会使得进入内核态
  • ❌C:
    • 除0发生软中断,需要进入内核态进行除0处理
  • ❌D:
    • 磁盘IO属于文件读写,有着open()和close()和write()的系统调用,需要进入内核态

21:

下列会立刻导致 TLB 内容改变的操作是
a. 增加页表项中的映射
b. 修改页表项中映射的物理页地址
c. 同一个进程中的线程 A 切换至线程 B
d. 用户态进程 A 切换至用户态进程 B
  • ☀️TLB作为地址缓存,需要和页表PTE维护一致
    • 常见的三种flush TLB情况:
      1. TLB miss
      2. 进程切换(不包括进入内核态,只强调不同进程切换)
      3. 页表项权限更改
      4. 不常见的TLB击落
    • 和页表一致性有关的flush情况:
      1. 页表PTE的内容出现变化时,如page fault时页面被换出,将仅某TLB entry清除
  • ✅A:
    • 由于不确定现在页表中是否存在VA对应PA1,新的映射是VA对应PA2的情况,所以需要flush TLB中一项
  • ✅B:
    • 修改映射物理页地址属于修改页表项,需要和TLB同步,单独flush TLB中一项
  • ✅C:
    • 由于不同进程之间使用的物理地址几乎0交际,所以需要flush
    • 但是进程切换后,内核部分的TLB项不会有大变化
  • ❌D:
    • 如果进入kernel mode的时候flush了整个TLB,那kernel将面对一个空的TLB,之后内核态的指令和数据访问过程将很漫长
    • 从内核态返回用户态后,又flush了整个TLB,访问用户态的指令和数据又需要很漫长过程

后序:

  • 距离上一篇博客发布过去了四个月,进入了2023年,我也马上完成四个月的实习,准备考研了,希望一战上岸。

猜你喜欢

转载自blog.csdn.net/buptsd/article/details/128869779