xv6项目八股(不懂的地方记录补充)

bash 复制代码
流水线级数:
IF	Instruction Fetch	从内存中读取指令
ID	Instruction Decode	解析指令,读取寄存器操作数
EX	Execute	执行运算(如加减、逻辑操作)
MEM	Memory Access	访问内存(加载/存储数据)
WB	Write Back	将结果写回寄存器文件

问题记录:

为什么上下文切换耗费时间?

1.上下文切换需要执行用户态到内核态,内核态到用户态的大量无关程序运行的指令代码,耗费程序运行时间

2.TLB快表缓存了用户页表的内容。从内存中多次读取虚拟地址与物理地址的映射很浪费时间,TLB会对这些映射进行缓存,节省后续使用的时间。但是如果频繁切换上下文,切换进程,但导致TLB缓存的内容失效,TLB缓存需要重新从内存中重新读取,比较耗时。

TLB和CPU Cache的区别:

1.TLB 是加速地址转换的缓存:虚拟地址 ➜ 物理地址 的映射会走页表,TLB 缓存这类映射,减少页表查找次数。页表存储在内存中。

2.CPU Cache 是加速数据访问的缓存:物理地址 ➜ 实际数据的读取。L1/L2/L3 缓存内存中经常用到的数据或指令。

TLB 是地址转换的加速器;Cache 是数据访问的加速器。访问内存一定先经过 TLB(地址阶段),再到 Cache(数据阶段)。

内存管理:

1.磁盘交换,缺页故障?

1.缺页故障:当程序访问一个虚拟地址时,CPU 会通过页表将其转换为物理地址。如果这个虚拟页没有对应的物理页(即该页没有加载到内存中),就会产生一个缺页故障。

2.磁盘交换:也叫页面置换,物理内存不足时,把一些暂时不用的页面写到磁盘中,腾出内存空间给更活跃的页面使用。作用:让系统看起来好像有"无限"内存;保证更活跃的页面始终能留在内存中。

bash 复制代码
一、缺页故障的场景:
1.懒惰页面分配:比如使用 sbrk 或 mmap 分配虚拟内存时,操作系统不会立即分配实际物理页,而是等程序第一次**真正访问该地址(store/load)**时再通过缺页中断分配物理内存。
2.mmap文件映射缺页,和懒惰页面分配类似。
3.页面被换出:某些虚拟页之前在内存中,但由于内存不足,被操作系统换出到了 Swap(磁盘)中。当程序再次访问这些页时,就会发生一次 Page Fault,并从磁盘中重新读回。
4.Copy-On-Writefork子进程写共享页触发写时复制,也会发生页面中断。
5.页表项不存在:访问非法地址和已被释放区域,产生段错误.
缺页故障+磁盘交换的详细流程:
假设:程序访问虚拟页 P1;页表中发现 P1 不在内存;内存空间已满。
步骤:
1. CPU 触发 Page Fault 异常;
2. 内核查页表,确认访问合法但页不在内存;
3. 如果物理内存满,操作系统选择一个"牺牲页"(Victim Page),通常使用最近最少使用(LRU)等页面置换算法;
   - 若该页被修改过(脏页),需写回磁盘;
   - 写入 Swap 区域(磁盘中的一个专用区域);
4. 从磁盘读取所需页 P1;
5. 加载到刚腾出来的物理页框中;
6. 更新页表,将 P1 映射到新分配的物理地址;
7. 程序恢复运行,重新执行导致 page fault 的指令。
相关标志位:
1.判断虚拟地址对应的页是否被换出?查看页表项中present标志位
2.Dirty位:页面是否被写过
3.Accessed位 :页面是否被访问过
4.swap信息:如果该页被换出,页表中将记录 Swap 的位置(Swap Entry)

补充:

1.操作系统实际换出的是用户进程的数据页,而不是维护页表本身的页(只有页表本身的页存在,才能够确认是否是合法的虚拟地址)。

2.什么是快表(TLB)?

缓存页表的,用于虚拟地址转物理地址。

TLB应用的局部性原理:

1.时间局部性:如果一个内存地址被访问,那么不久的将来它可能被再次访问。

2.空间局部性:如果一个内存地址被访问,那么与它相邻的内存地址也可能在不久的将来被访问。TLB通常以页为单位存储地址映射。TLB缓存一般以page为单位从内存中读取。

进程管理:

1.进程,线程,协程的概念

2.如何实现用户态线程,调度任务那部分。

3.系统调用的整个流程(用户态到内核态的细节我搞懂了,内核态到用户态我没有仔细了解

4.上下文切换(保存的只是一部分寄存器)

bash 复制代码
对,有一部分寄存器的内容是在栈中存放的,另一部分是在CPU寄存器中,这部分内容要临时保存到进程空间中,以便切换回来后能重载这些地址。

锁:(这个是大问题,疏忽掉了)

1.自旋锁(test_and_set原子指令)

c 复制代码
主要应用在占有锁的时间比较短的情况下
void
acquire(struct spinlock* lk) // does not work!
{
  for(;;) {
    if(lk->locked == 0) {
      lk->locked = 1;//这两行代码,不同CPU线程可能会发生竞态条件
      break;
    }
  }
}
void
release(struct spinlock* lk) // does not work!
{
   if(lk->locked == 1) {
     lk->locked = 0;//释放锁如果不是原子性的话,可能会使数值没有完整写完就中断掉。
  }
}
xv6的代码使用了_sync_lock_test_and_set()和_sync_lock_release()来进行原子释放锁和获取锁。获取和释放都必须使用原子指令

2.睡眠锁

c 复制代码
主要应用在要占有锁的时间非常长的情况下。
void V(struct semaphore* s) {
    acquire(&s->lock);
    s->count += 1;
    wakeup(s);
    release(&s->lock);
}

void P(struct semaphore* s) {
    acquire(&s->lock);

    while (s->count == 0)
        sleep(s, &s->lock);  // !pay attention
    s->count -= 1;
    release(&s->lock);
}
进程A在等待达成条件,选择执行sleep释放CPU.在sleep过程中将进程添加到等待通道上,然后释放锁,另一个进程B获取锁完成任务后执行V操作(条件达成),唤醒进程A,进程A重新获取锁。
主要的案例:pipe读取,每一个管道都有一个锁。进程A负责写,当数据写满之后,需要进程B去读,就会用sleep进行睡眠状态释放锁,此时读端就可以获取锁进行读取,读完之后wakeup写端。
睡眠锁相当于条件变量。一个进程获取了锁,要等待一个条件变量,由于条件未达成,所以进行条件变量等待,释放锁并将自身添加到等待队列,条件达成后机会被唤醒。
上面都应用在不同线程/进程要同时访问共享资源的时候。

3.怎么降低锁的竞争(读写锁,无锁数据结构,降低锁的粒度)

c 复制代码
1.使用读写锁:可以多线程读,但是写的时候只能有一个写,并且读和写互斥,读的时候不能写,写的时候不能读,写锁优先级更高。如果一个共享资源的有被读的需求,可以选择读写锁,这样在读的时候是不会阻塞的。只有写的时候会。
写锁持有时:
  新读请求阻塞 ──→ 等待写锁释放
  新写请求阻塞 ──→ 等待写锁释放
读锁持有时:
  新读请求允许 ──→ 立即获取读锁
  新写请求阻塞 ──→ 等待所有读锁释放
饥饿问题:持续有读锁被获取,导致写锁长期阻塞。解决方案:使用写优先策略;
限制读锁的最大持有时间;
死锁风险:若线程A持有读锁并尝试升级为写锁,而线程B也持有读锁,则A会阻塞等待B释放读锁,导致死锁。先释放读锁,再重新获取写锁。
性能权衡:
​读写锁开销:读写锁的内部计数器维护和状态切换可能比普通互斥锁更耗时。
​适用性:仅在读操作远多于写操作时(如80%以上为读)性能优势明显。
2.减小锁的粒度;(xv6中锁实验就是做这个的)
3.使用无锁数据结构
4.在短时间内的竞争可以使用自旋锁(自旋锁和原子操作很接近)

6.怎么处理死锁问题?(和操作系统八股一致,需要背记)

cpp 复制代码
预防死锁:
1.避免多重锁定:
尽量避免一个线程同时获取多个锁,如果需要多重锁定,确保所有线程以相同的顺序获取锁。
2.使用锁超时:
尝试获取锁时使用超时机制,允许线程等待所超过一定时间后放弃,防止永久阻塞,也就防止了死锁。
3.资源分配顺序:确保所有线程以固定顺序请求资源,这减少死锁的可能性。
避免死锁:
1.银行家算法:预先计算分配资源后是否会导致不安全状态来决定是否分配资源
2.资源分配图分析:通过分析资源分配图检测是否有循环等待条件,从而避免死锁。
检测死锁和恢复:
1.死锁检测:定期检查资源分配图是否有循环等待的情况,通常由操作系统或者特定的监控工具完成。
2.资源剥夺:一旦检测到死锁,剥夺和重新分配资源来打破死锁。例如中断一个线程,回滚其操作并释放持有的资源。
1.使用现代编程语言的并发库:atomic等
2.使用不可重入锁:确保同一线程不会重复获取同一锁,从而减少死锁可能性
3.最小化锁的作用范围:只在必要短时间内持有锁。
4.避免在持有锁时调用可能再次获取锁或长时间运行的外部代码。
测试和审查代码:
1.死锁检测工具:使用工具对代码进行静态分析,以找出潜在死锁风险;
2.代码审查和测试:定期进行代码审查和并发测试识别可能导致的死锁问题。

其他:

1.什么是段错误?如何解决?

bash 复制代码
段错误:非法地址访问,程序会触发SIGSEGV信号,捕获后进入内核态处理。
场景:
1.访问未映射的地址;
2.写入只读地址;
3.解引用空指针和野指针
4.未初始化指针
5.栈溢出(栈空间耗尽)
6.访问已释放内存
7.未初始化指针
如何解决?
1.linux查看错误日志和提示:会有Segmentation fault (core dumped)
使用调试器gdb:(程序崩溃时)
backtrace:查看函数调用顺序,查清楚程序调用路径,排查错误在哪?
list 查看出错代码
2.核查指针是否初始化
3.是否调用了释放的指针(野指针)
4.避免数组越界
5.不能修改常量或者字符串字面量

3.操作系统的最开始前10行一般会做什么事情?

4.你在做mit6.s081遇到的最大难题是什么?

相关推荐
鑫鑫向栄38 分钟前
[蓝桥杯]剪格子
数据结构·c++·算法·职场和发展·蓝桥杯
白总Server39 分钟前
C++语法架构解说
java·网络·c++·网络协议·架构·golang·scala
羊儿~1 小时前
P12592题解
数据结构·c++·算法
.Vcoistnt1 小时前
Codeforces Round 1028 (Div. 2)(A-D)
数据结构·c++·算法·贪心算法·动态规划
PXM的算法星球2 小时前
paoxiaomo的XCPC算法竞赛训练经验
c++·算法
孤独得猿2 小时前
高阶数据结构——并查集
数据结构·c++·经验分享·算法
一只鱼^_2 小时前
力扣第452场周赛
数据结构·c++·算法·leetcode·贪心算法·动态规划·剪枝
虾球xz2 小时前
CppCon 2014 学习:Decomposing a Problem for Parallel Execution
开发语言·c++·学习
apolloyhl2 小时前
C/C++ 面试复习笔记(2)
c语言·c++·算法·面试
不愧是你呀4 小时前
C++中单例模式详解
网络·c++·windows·单例模式