6.15 操作系统面试题 锁 内存管理
自旋锁
自旋锁加锁失败后,线程会忙等待,直到它拿到锁。
需要注意,在单核 CPU 上,需要抢占式的调度器(即不断通过时钟中断一个线程,运行其他线程)。否则,自旋锁在单 CPU 上无法使用,因为一个自旋的线程永远不会放弃 CPU。
当加锁失败时,互斥锁用「线程切换」来应对,自旋锁则用「忙等待」来应对。
如果你能确定被锁住的代码执行时间很短,就不应该用互斥锁,而应该选用自旋锁,否则使用互斥锁。
死锁发生条件是什么?
- 互斥条件
- 持有并等待
- 不可剥夺条件
- 环路等待
资源有序分配法
银行家算法
银行家算法是最有代表性的避免死锁的算法。
分配给进程资源前,首先判断这个进程的安全性,也就是预执行,判断分配后是否产生死锁现象。通过不断检查剩余可用资源是否满足某个进程的最大需求,如果可以则加入安全序列,并把该进程当前持有的资源回收;不断重复这个过程,看最后能否实现让所有进程都加入安全序列。
介绍一下操作系统内存管理
操作系统设计了虚拟内存,每个进程都有自己的独立的虚拟内存,我们所写的程序不会直接与物理内打交道。
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。
虚拟地址与物理地址之间通过页表来映射。
页表是存储在内存里的,内存管理单元 (MMU)就做将虚拟内存地址转换成物理地址的工作。
访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
分页不会有外部碎片,但会有内部碎片
页号+偏移量
讲一下段表?
虚拟地址也可以通过段表与物理地址进行映射的,分段机制会把程序的虚拟地址分成 4 个段,每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量。
虚拟地址是怎么转化到物理地址的?
在虚拟地址转换的过程中,通常会使用页表(Page Table)来进行映射。当程序访问一个虚拟地址时,MMU会将虚拟地址分解为页号和页内偏移量。
程序的内存布局是怎么样的?
- 代码段,包括二进制可执行代码;
- 数据段,包括已初始化的静态常量和全局变量;
- BSS 段,包括未初始化的静态变量和全局变量;
- 堆段,包括动态分配的内存,从低地址开始向上增长;
- 文件映射段,包括动态库、共享内存等;
- 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。
堆和栈的区别?
- 堆是动态分配内存,由程序员手动申请和释放内存,通常用于存储动态数据结构和对象。如果管理不当可能会导致内存泄漏或内存溢出。堆通常比栈大,内存空间较大,动态分配和释放内存需要时间开销。
- 栈是静态分配内存,由编译器自动分配和释放内存,用于存储函数的局部变量和函数调用信息。遵循后进先出的原则,变量的生命周期由其作用域决定,函数调用时分配内存,函数返回时释放内存。栈大小有限,通常比较小,内存分配和释放速度较快,因为是编译器自动管理。
fork()会复制哪些东西?
fork 阶段会复制父进程的页表(虚拟内存)
fork 之后,如果发生了写时复制,就会复制物理内存
当父进程或者子进程在向这个内存发起写操作时,CPU 就会触发写保护中断,这个写保护中断是由于违反权限导致的,然后操作系统会在「写保护中断处理函数」里进行物理内存的复制,并重新设置其内存映射关系,将父子进程的内存读写权限设置为可读写,最后才会对内存进行写操作。
malloc 1KB和1MB 有什么区别?
如果用户分配的内存小于 128 KB,则通过 brk() 申请内存;
如果用户分配的内存大于 128 KB,则通过 mmap() 申请内存;
brk() 函数将「堆顶」指针向高地址移动,获得新的内存空间。
mmap() 系统调用中「私有匿名映射」的方式,在文件映射区分配一块内存
操作系统内存不足的时候会发生什么?
如果没有空闲的物理内存,那么内核就会开始进行回收内存的工作
OOM Killer 机制会根据算法选择一个占用物理内存较高的进程,然后将其杀死,以便释放内存资源
页面置换有哪些算法?
- 最佳页面置换算法(OPT): 置换在「未来」最长时间不访问的页面。
- 先进先出置换算法
- 最近最久未使用的置换算法
- 时钟页面置换算法
- 最不常用置换算法