├── 图片
├── 大页.png
├── 彩票.png
├── 纤程.png
├── DMA.png
├── exec.png
├── fat1.png
├── fat2.png
├── git1.png
├── git2.png
├── mmap.png
├── tlb.png
├── 下半部.png
├── 两级调度.png
├── 内存空间.png
├── 写时拷贝.png
├── 分段机制.png
├── 加速比.png
├── 区段树.png
├── 原子操作.png
├── 影子页表.png
├── 文件复制.png
├── 日志1.png
├── 皮特森.png
├── 直接通信.png
├── 线程模型.png
├── 群组调度.png
├── 解释执行.png
├── 读写锁1.png
├── 读写锁2.png
├── 负载分担.png
├── 远程验证.png
├── 页表项.png
├── AMD SEV.png
├── ARMv8.1.png
├── LFS 前滚.png
├── LFS 段概要.png
├── LFS 段清理.png
├── LL-SC.png
├── LRPC伪代码.png
├── LRPC接口.png
├── RCU修改.png
├── RCU删除.png
├── TOCTOU.png
├── TSO一致性.png
├── arm虚拟化1.png
├── arm虚拟化2.png
├── buddy1.png
├── buddy2.png
├── cpu利用率.png
├── inode.png
├── lockone.png
├── mcs锁例子.png
├── mmap1.png
├── msi例子.png
├── nsp-IPC.png
├── nsp-PID.png
├── ntfs1.png
├── numa1.png
├── sleep.png
├── tlb结构.png
├── type1.png
├── type2.png
├── wakeup.png
├── 上下文切换.png
├── 上下文切换1.png
├── 下陷和模拟.png
├── 两级地址翻译.png
├── 两阶段地址翻译.png
├── 严格一致性.png
├── 二进制翻译.png
├── 公平共享调度.png
├── 共享内存1.png
├── 共享内存2.png
├── 共享内存3.png
├── 冯诺依曼架构.png
├── 半虚拟化-发包.png
├── 半虚拟化-收包.png
├── 多Log写入.png
├── 存储结构与缓存.png
├── 排号锁题目.png
├── 日志文件系统.png
├── 特权级x86.png
├── 管道写操作.png
├── 管道数据结构.png
├── 管道读操作.png
├── 设备模拟-发包.png
├── 设备模拟-收包.png
├── 设置影子页表.png
├── 顺序一致性1.png
├── CPU中断处理流程.png
├── IO虚拟化技术对比.png
├── JBD2事务的状态.png
├── LFS 段使用表.png
├── Linux收包过程.png
├── RCU插入新的节点.png
├── chcoreTCB.png
├── cochort锁.png
├── ext2文件系统.png
├── intel锁总线.png
├── nsp-mount.png
├── risc-cisc.png
├── vt-x执行过程.png
├── 一次性申请所有资源.png
├── 中断异常处理流程.png
├── 偏向读者的读写锁.png
├── 写者友好的读写锁.png
├── 即时优先级置顶协议.png
├── 原生优先级置顶协议.png
├── 第二阶段4级页表.png
├── AARCH64的4级页表.png
├── AArch64中断分类.png
├── ChCore网络架构图.png
├── F2FS的改进1:NAT.png
├── JBD2日志的磁盘结构.png
├── QEMU-KVM的流程.png
├── VT-x和VHE对比.png
├── VT-x的处理器虚拟化.png
├── aarch64中断处理.png
├── aarch64异常向量表.png
├── guestVM的内存.png
├── nsp-network.png
├── vt-x和普通进程对比.png
├── 多核环境中的缓存结构.png
├── 多线程进程的地址空间.png
├── 目录式缓存一致性协议.png
├── 闪存友好的磁盘布局1.png
├── Enclave与进程的关系.png
├── IOVM Exit的处理流程.png
├── JBD2部分接口和使用方法.png
├── LFS的问题1:递归更新问题.png
├── nsp-network虚拟机.png
├── 写时拷贝在文件系统中的应用.png
├── 硬件提供不同粒度的隔离环境.png
├── 设备直通-DMA恶意读写内存.png
├── 设备直通-SR-IOV的使用.png
├── AARCH64的4级页表地址翻译.png
├── F2FS的改进2:多log并行写入.png
├── LinuX Container资源.png
├── QEMU使用KVM的用户态接口.png
├── RISC-V平台的Enclave.png
├── Stride-Scheduling.png
├── WFI指令VM Exit的处理流程.png
├── 出问题再处理-死锁的检测与恢复.png
├── 设备直通-DMA恶意读写内存-解决.png
├── Ext4用JBD2实现的三种日志模式.png
├── 单一缓存行高度竞争导致的可扩展性问题.png
├── ARMv8.1中的Type-2 VMM架构.png
├── TSO一致性模型中四类不同的操作组合行为.png
└── Type-1和Type-2在VT-x和VHE下架构.png
├── README.md
├── 15-网络.md
├── 2-硬件结构.md
├── 4-操作系统内核架构.md
├── 7-调度.md
├── 11-文件系统.md
├── 3-中断异常与系统调用.md
├── 14-设备.md
├── 9-同步原语.md
├── 12-文件系统崩溃一致性.md
├── 6-进程.md
├── 17-轻量级虚拟化.md
├── 13-新型文件系统.md
├── 8-进程间通信.md
├── 5-内存.md
├── 10-多核.md
└── 16-虚拟化.md
/图片/大页.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/大页.png
--------------------------------------------------------------------------------
/图片/彩票.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/彩票.png
--------------------------------------------------------------------------------
/图片/纤程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/纤程.png
--------------------------------------------------------------------------------
/图片/DMA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/DMA.png
--------------------------------------------------------------------------------
/图片/exec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/exec.png
--------------------------------------------------------------------------------
/图片/fat1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/fat1.png
--------------------------------------------------------------------------------
/图片/fat2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/fat2.png
--------------------------------------------------------------------------------
/图片/git1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/git1.png
--------------------------------------------------------------------------------
/图片/git2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/git2.png
--------------------------------------------------------------------------------
/图片/mmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/mmap.png
--------------------------------------------------------------------------------
/图片/tlb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/tlb.png
--------------------------------------------------------------------------------
/图片/下半部.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/下半部.png
--------------------------------------------------------------------------------
/图片/两级调度.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/两级调度.png
--------------------------------------------------------------------------------
/图片/内存空间.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/内存空间.png
--------------------------------------------------------------------------------
/图片/写时拷贝.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/写时拷贝.png
--------------------------------------------------------------------------------
/图片/分段机制.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/分段机制.png
--------------------------------------------------------------------------------
/图片/加速比.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/加速比.png
--------------------------------------------------------------------------------
/图片/区段树.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/区段树.png
--------------------------------------------------------------------------------
/图片/原子操作.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/原子操作.png
--------------------------------------------------------------------------------
/图片/影子页表.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/影子页表.png
--------------------------------------------------------------------------------
/图片/文件复制.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/文件复制.png
--------------------------------------------------------------------------------
/图片/日志1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/日志1.png
--------------------------------------------------------------------------------
/图片/皮特森.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/皮特森.png
--------------------------------------------------------------------------------
/图片/直接通信.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/直接通信.png
--------------------------------------------------------------------------------
/图片/线程模型.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/线程模型.png
--------------------------------------------------------------------------------
/图片/群组调度.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/群组调度.png
--------------------------------------------------------------------------------
/图片/解释执行.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/解释执行.png
--------------------------------------------------------------------------------
/图片/读写锁1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/读写锁1.png
--------------------------------------------------------------------------------
/图片/读写锁2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/读写锁2.png
--------------------------------------------------------------------------------
/图片/负载分担.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/负载分担.png
--------------------------------------------------------------------------------
/图片/远程验证.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/远程验证.png
--------------------------------------------------------------------------------
/图片/页表项.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/页表项.png
--------------------------------------------------------------------------------
/图片/AMD SEV.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/AMD SEV.png
--------------------------------------------------------------------------------
/图片/ARMv8.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/ARMv8.1.png
--------------------------------------------------------------------------------
/图片/LFS 前滚.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/LFS 前滚.png
--------------------------------------------------------------------------------
/图片/LFS 段概要.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/LFS 段概要.png
--------------------------------------------------------------------------------
/图片/LFS 段清理.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/LFS 段清理.png
--------------------------------------------------------------------------------
/图片/LL-SC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/LL-SC.png
--------------------------------------------------------------------------------
/图片/LRPC伪代码.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/LRPC伪代码.png
--------------------------------------------------------------------------------
/图片/LRPC接口.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/LRPC接口.png
--------------------------------------------------------------------------------
/图片/RCU修改.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/RCU修改.png
--------------------------------------------------------------------------------
/图片/RCU删除.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/RCU删除.png
--------------------------------------------------------------------------------
/图片/TOCTOU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/TOCTOU.png
--------------------------------------------------------------------------------
/图片/TSO一致性.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/TSO一致性.png
--------------------------------------------------------------------------------
/图片/arm虚拟化1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/arm虚拟化1.png
--------------------------------------------------------------------------------
/图片/arm虚拟化2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/arm虚拟化2.png
--------------------------------------------------------------------------------
/图片/buddy1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/buddy1.png
--------------------------------------------------------------------------------
/图片/buddy2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/buddy2.png
--------------------------------------------------------------------------------
/图片/cpu利用率.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/cpu利用率.png
--------------------------------------------------------------------------------
/图片/inode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/inode.png
--------------------------------------------------------------------------------
/图片/lockone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/lockone.png
--------------------------------------------------------------------------------
/图片/mcs锁例子.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/mcs锁例子.png
--------------------------------------------------------------------------------
/图片/mmap1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/mmap1.png
--------------------------------------------------------------------------------
/图片/msi例子.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/msi例子.png
--------------------------------------------------------------------------------
/图片/nsp-IPC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/nsp-IPC.png
--------------------------------------------------------------------------------
/图片/nsp-PID.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/nsp-PID.png
--------------------------------------------------------------------------------
/图片/ntfs1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/ntfs1.png
--------------------------------------------------------------------------------
/图片/numa1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/numa1.png
--------------------------------------------------------------------------------
/图片/sleep.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/sleep.png
--------------------------------------------------------------------------------
/图片/tlb结构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/tlb结构.png
--------------------------------------------------------------------------------
/图片/type1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/type1.png
--------------------------------------------------------------------------------
/图片/type2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/type2.png
--------------------------------------------------------------------------------
/图片/wakeup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/wakeup.png
--------------------------------------------------------------------------------
/图片/上下文切换.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/上下文切换.png
--------------------------------------------------------------------------------
/图片/上下文切换1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/上下文切换1.png
--------------------------------------------------------------------------------
/图片/下陷和模拟.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/下陷和模拟.png
--------------------------------------------------------------------------------
/图片/两级地址翻译.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/两级地址翻译.png
--------------------------------------------------------------------------------
/图片/两阶段地址翻译.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/两阶段地址翻译.png
--------------------------------------------------------------------------------
/图片/严格一致性.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/严格一致性.png
--------------------------------------------------------------------------------
/图片/二进制翻译.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/二进制翻译.png
--------------------------------------------------------------------------------
/图片/公平共享调度.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/公平共享调度.png
--------------------------------------------------------------------------------
/图片/共享内存1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/共享内存1.png
--------------------------------------------------------------------------------
/图片/共享内存2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/共享内存2.png
--------------------------------------------------------------------------------
/图片/共享内存3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/共享内存3.png
--------------------------------------------------------------------------------
/图片/冯诺依曼架构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/冯诺依曼架构.png
--------------------------------------------------------------------------------
/图片/半虚拟化-发包.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/半虚拟化-发包.png
--------------------------------------------------------------------------------
/图片/半虚拟化-收包.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/半虚拟化-收包.png
--------------------------------------------------------------------------------
/图片/多Log写入.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/多Log写入.png
--------------------------------------------------------------------------------
/图片/存储结构与缓存.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/存储结构与缓存.png
--------------------------------------------------------------------------------
/图片/排号锁题目.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/排号锁题目.png
--------------------------------------------------------------------------------
/图片/日志文件系统.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/日志文件系统.png
--------------------------------------------------------------------------------
/图片/特权级x86.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/特权级x86.png
--------------------------------------------------------------------------------
/图片/管道写操作.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/管道写操作.png
--------------------------------------------------------------------------------
/图片/管道数据结构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/管道数据结构.png
--------------------------------------------------------------------------------
/图片/管道读操作.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/管道读操作.png
--------------------------------------------------------------------------------
/图片/设备模拟-发包.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/设备模拟-发包.png
--------------------------------------------------------------------------------
/图片/设备模拟-收包.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/设备模拟-收包.png
--------------------------------------------------------------------------------
/图片/设置影子页表.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/设置影子页表.png
--------------------------------------------------------------------------------
/图片/顺序一致性1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/顺序一致性1.png
--------------------------------------------------------------------------------
/图片/CPU中断处理流程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/CPU中断处理流程.png
--------------------------------------------------------------------------------
/图片/IO虚拟化技术对比.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/IO虚拟化技术对比.png
--------------------------------------------------------------------------------
/图片/JBD2事务的状态.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/JBD2事务的状态.png
--------------------------------------------------------------------------------
/图片/LFS 段使用表.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/LFS 段使用表.png
--------------------------------------------------------------------------------
/图片/Linux收包过程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/Linux收包过程.png
--------------------------------------------------------------------------------
/图片/RCU插入新的节点.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/RCU插入新的节点.png
--------------------------------------------------------------------------------
/图片/chcoreTCB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/chcoreTCB.png
--------------------------------------------------------------------------------
/图片/cochort锁.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/cochort锁.png
--------------------------------------------------------------------------------
/图片/ext2文件系统.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/ext2文件系统.png
--------------------------------------------------------------------------------
/图片/intel锁总线.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/intel锁总线.png
--------------------------------------------------------------------------------
/图片/nsp-mount.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/nsp-mount.png
--------------------------------------------------------------------------------
/图片/risc-cisc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/risc-cisc.png
--------------------------------------------------------------------------------
/图片/vt-x执行过程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/vt-x执行过程.png
--------------------------------------------------------------------------------
/图片/一次性申请所有资源.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/一次性申请所有资源.png
--------------------------------------------------------------------------------
/图片/中断异常处理流程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/中断异常处理流程.png
--------------------------------------------------------------------------------
/图片/偏向读者的读写锁.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/偏向读者的读写锁.png
--------------------------------------------------------------------------------
/图片/写者友好的读写锁.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/写者友好的读写锁.png
--------------------------------------------------------------------------------
/图片/即时优先级置顶协议.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/即时优先级置顶协议.png
--------------------------------------------------------------------------------
/图片/原生优先级置顶协议.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/原生优先级置顶协议.png
--------------------------------------------------------------------------------
/图片/第二阶段4级页表.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/第二阶段4级页表.png
--------------------------------------------------------------------------------
/图片/AARCH64的4级页表.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/AARCH64的4级页表.png
--------------------------------------------------------------------------------
/图片/AArch64中断分类.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/AArch64中断分类.png
--------------------------------------------------------------------------------
/图片/ChCore网络架构图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/ChCore网络架构图.png
--------------------------------------------------------------------------------
/图片/F2FS的改进1:NAT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/F2FS的改进1:NAT.png
--------------------------------------------------------------------------------
/图片/JBD2日志的磁盘结构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/JBD2日志的磁盘结构.png
--------------------------------------------------------------------------------
/图片/QEMU-KVM的流程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/QEMU-KVM的流程.png
--------------------------------------------------------------------------------
/图片/VT-x和VHE对比.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/VT-x和VHE对比.png
--------------------------------------------------------------------------------
/图片/VT-x的处理器虚拟化.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/VT-x的处理器虚拟化.png
--------------------------------------------------------------------------------
/图片/aarch64中断处理.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/aarch64中断处理.png
--------------------------------------------------------------------------------
/图片/aarch64异常向量表.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/aarch64异常向量表.png
--------------------------------------------------------------------------------
/图片/guestVM的内存.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/guestVM的内存.png
--------------------------------------------------------------------------------
/图片/nsp-network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/nsp-network.png
--------------------------------------------------------------------------------
/图片/vt-x和普通进程对比.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/vt-x和普通进程对比.png
--------------------------------------------------------------------------------
/图片/多核环境中的缓存结构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/多核环境中的缓存结构.png
--------------------------------------------------------------------------------
/图片/多线程进程的地址空间.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/多线程进程的地址空间.png
--------------------------------------------------------------------------------
/图片/目录式缓存一致性协议.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/目录式缓存一致性协议.png
--------------------------------------------------------------------------------
/图片/闪存友好的磁盘布局1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/闪存友好的磁盘布局1.png
--------------------------------------------------------------------------------
/图片/Enclave与进程的关系.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/Enclave与进程的关系.png
--------------------------------------------------------------------------------
/图片/IOVM Exit的处理流程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/IOVM Exit的处理流程.png
--------------------------------------------------------------------------------
/图片/JBD2部分接口和使用方法.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/JBD2部分接口和使用方法.png
--------------------------------------------------------------------------------
/图片/LFS的问题1:递归更新问题.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/LFS的问题1:递归更新问题.png
--------------------------------------------------------------------------------
/图片/nsp-network虚拟机.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/nsp-network虚拟机.png
--------------------------------------------------------------------------------
/图片/写时拷贝在文件系统中的应用.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/写时拷贝在文件系统中的应用.png
--------------------------------------------------------------------------------
/图片/硬件提供不同粒度的隔离环境.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/硬件提供不同粒度的隔离环境.png
--------------------------------------------------------------------------------
/图片/设备直通-DMA恶意读写内存.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/设备直通-DMA恶意读写内存.png
--------------------------------------------------------------------------------
/图片/设备直通-SR-IOV的使用.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/设备直通-SR-IOV的使用.png
--------------------------------------------------------------------------------
/图片/AARCH64的4级页表地址翻译.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/AARCH64的4级页表地址翻译.png
--------------------------------------------------------------------------------
/图片/F2FS的改进2:多log并行写入.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/F2FS的改进2:多log并行写入.png
--------------------------------------------------------------------------------
/图片/LinuX Container资源.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/LinuX Container资源.png
--------------------------------------------------------------------------------
/图片/QEMU使用KVM的用户态接口.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/QEMU使用KVM的用户态接口.png
--------------------------------------------------------------------------------
/图片/RISC-V平台的Enclave.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/RISC-V平台的Enclave.png
--------------------------------------------------------------------------------
/图片/Stride-Scheduling.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/Stride-Scheduling.png
--------------------------------------------------------------------------------
/图片/WFI指令VM Exit的处理流程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/WFI指令VM Exit的处理流程.png
--------------------------------------------------------------------------------
/图片/出问题再处理-死锁的检测与恢复.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/出问题再处理-死锁的检测与恢复.png
--------------------------------------------------------------------------------
/图片/设备直通-DMA恶意读写内存-解决.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/设备直通-DMA恶意读写内存-解决.png
--------------------------------------------------------------------------------
/图片/Ext4用JBD2实现的三种日志模式.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/Ext4用JBD2实现的三种日志模式.png
--------------------------------------------------------------------------------
/图片/单一缓存行高度竞争导致的可扩展性问题.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/单一缓存行高度竞争导致的可扩展性问题.png
--------------------------------------------------------------------------------
/图片/ARMv8.1中的Type-2 VMM架构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/ARMv8.1中的Type-2 VMM架构.png
--------------------------------------------------------------------------------
/图片/TSO一致性模型中四类不同的操作组合行为.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/TSO一致性模型中四类不同的操作组合行为.png
--------------------------------------------------------------------------------
/图片/Type-1和Type-2在VT-x和VHE下架构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akangakang/OS-Study-Note/HEAD/图片/Type-1和Type-2在VT-x和VHE下架构.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OS STUDY NOTE
2 |
3 | * 硬件结构
4 | * 中断异常与系统调用
5 | * 操作系统内核架构
6 | * 内存
7 | * 进程
8 | * 调度
9 | * 进程间通信
10 | * 同步原语
11 | * 多核
12 | * 文件系统
13 | * 文件系统崩溃一致性
14 | * 新型文件系统
15 | * 设备
16 |
17 |
18 |
19 | > 学习资料来自上海交通大学软件工程学院操作系统课程
20 | >
21 | > https://ipads.se.sjtu.edu.cn/courses/os/schedule.shtml
22 |
23 |
--------------------------------------------------------------------------------
/15-网络.md:
--------------------------------------------------------------------------------
1 | # 网络
2 |
3 | > 在数据传输的场景中,如果接受者(receiver)无法跟上发送者(sender)发送数据包的速度,接受者需要如何处理?(提示选择轮询或者中断的方式)
4 |
5 | 轮询方式:后续的数据可能会覆盖之前的数据如果接受者没有及时的接受数据包
6 |
7 | 中断方式:前面的数据包会被保留,并且能够阻塞后续的数据包进入设备的缓存中
8 |
9 |
10 |
11 | > 简单描述当接受者(receiver)从网卡接受到了网络包之后的数据传输流程。在真实情况下,Linux中会发生几次上下文的切换,ChCore中呢?
12 |
13 | **数据流**:
14 |
15 | a) ISR:DMA环形缓存->skb缓存
16 |
17 | b) Softirq:通知IP层
18 |
19 | c) IP->TCP->内核中接受socket的缓存区(通过转移skb的指针)
20 |
21 | d) 内核中的缓存区->用户态的缓存区
22 |
23 | (例如:Linux中过的copy_to_user())
24 |
25 | 
26 |
27 |
28 |
29 | **上下文切换**:
30 |
31 | Linux 1次:(内核的网络栈/用户态应用)
32 |
33 | ChCore 4次:(网络驱动/内核/网络栈服务/内核/用户态应用)
34 |
35 | 
36 |
37 |
38 |
39 | > Linux是如何使用skb机制在内核中实现零拷贝的?skb拷贝在什么时候会发生?请同时考虑浅拷贝(skb_clone)以及深拷贝(skb_copy)
40 |
41 | 零拷贝:sk_buff拥有四个指针:head,data,tail,end。Linux网络栈实现零拷贝通过更新data的指针
42 |
43 | 浅拷贝:捕获网络包(通过监视器或者分析)而不会损害性能
44 |
45 | 深拷贝:修改数据包的内容,例如Network Address Translation
46 |
47 |
48 |
49 | > Intel DPDK使用了哪一种技术来提升网络性能?如果你是ChCore团队的网络设计者,你会选者什么方式来优化ChCore为内核的网络服务,你可以通过学习"Snap: a microkernel approach to host networking", Marty et al., SOSP’19来回答该问题
50 |
51 | 相应技术:
52 |
53 | a) 绕过内核,全部实现在用户态
54 |
55 | b) 使用轮训的机制而不是使用中断
56 |
57 | c) 使用大页
58 |
59 | d) 绑核(CPU 亲和力)或者使用无锁的环形缓存的结构以上的技术都能够实现在ChCore中,并且能进一步的优化性能,例如使用中断和轮训的动态切换。
--------------------------------------------------------------------------------
/2-硬件结构.md:
--------------------------------------------------------------------------------
1 | # 硬件结构
2 |
3 | ## 1. 冯诺依曼架构
4 |
5 |
6 |
7 | **冯诺依曼架构**:
8 |
9 | * 中央处理单元CPU :负责运算和逻辑控制
10 | * 存储器:负责存储程序和数据,保存程序执行后的中间结果和最终结果
11 | * 输入输出:与外界交互
12 |
13 | **缺点**:
14 |
15 | 1. CPU与内存的交互引起的内存墙问题
16 | 2. 据与指令不区分,指令等数据或数据等指令
17 | 3. 串行顺序处理,缺乏数据并行能力
18 |
19 |
20 |
21 | ## 2. ARM
22 |
23 | ### 2.1 名词解释
24 |
25 | * `ARM`指的是处理器(与`intel`相对)
26 | * 现在广泛使用的是`ARMv8`的体系结构(不止指处理器还包括指令集什么的)
27 | * `ARMv8`体系结构的主要特点是支持64位虚拟地址 (`ARMv7`只支持32位)
28 | * `ARMv8`支持`AArch64`和`AArch32`(与`x86`,`x86-64`相对)
29 |
30 |
31 |
32 | ### 2.2 `AArch64`实现
33 |
34 | #### 寄存器
35 |
36 | * 31个64位通用寄存器 :`X0`-`X30`
37 | * `x29`用作帧指针(FP),保存函数调用栈顶地址
38 | * `x30`用作链接指针(LP),CPU在执行函数调用指令`bl`的时候会自动把返回地址保存其中
39 | * 1个PC寄存器
40 | * 4个栈寄存器(切换时保存SP) : `SP_EL0`, `SP_EL1`, `SP_EL2`, `SP_EL3`
41 | * 3个异常链接寄存器(保存异常的返回地址) – `ELR_EL1`, `ELR_EL2`, `ELR_EL3 `
42 | * 3个程序状态寄存器(切换时保存`PSTATE`) – `SPSR_EL1`, `SPSR_EL2`, `SPSR_EL3`
43 |
44 | * 页表基地址寄存器:`TTBR0_EL1`,`TTBR1_EL1`
45 | * `TTBR0_EL1`负责$0-2^{48}$
46 |
47 | * `TTBR1_EL1`负责$2^{48}-2^{64}$
48 |
49 |
50 |
51 |
52 |
53 | 与`x86-64`对比:
54 |
55 | * 16个通用寄存器
56 | * 一个`rip`寄存器
57 | * 一个栈寄存器(`rsp`)(切换特权级`rsp`压栈)
58 | * 没有异常链接寄存器,把返回地址压栈
59 | * 用`EFLAG`保存状态
60 |
61 |
62 |
63 | > 问题:
64 | >
65 | > AArch64的TTBR0支持(0~2^48-1)的地址映射, TTBR1支持(2^48~2^64)的地址映射,这样的硬件设 计与x86-64中的CR3相比较,能够如何协助到操作系 统的设计?
66 |
67 |
68 |
69 | #### 指令集
70 |
71 | 
72 |
73 |
74 |
75 | #### 特权级
76 |
77 | ##### `X86-64`:
78 |
79 | 
80 |
81 | ##### `ARM`
82 |
83 | * `EL0`:用户态程序
84 | * `EL1`:内核态,操作系统运行
85 | * `EL2`:hypervisor
86 | * `EL3`:和安全特性`TrustZone`相关
87 |
88 |
89 |
90 | **从`EL0`切换到`EL1`的三种情况:**
91 |
92 | 1. 系统调用,`svc`指令
93 | 2. 应用程序执行到一条指令,该指令触发了**异常**,该异常导致特权级切换(如缺页异常)
94 | 3. CPU收到外设的**中断**
95 |
96 | **从`EL0`切换到`EL1`的基本流程:**
97 |
98 | 1. 正常执行
99 | 2. CPU发生中断或异常
100 | 3. 保存处理器状态和错误信息至寄存器
101 | 4. 查询异常向量表并选择handler
102 | 5. 处理
103 | 6. 从handler中返回
104 | 7. 继续回到EL0执行
105 |
106 | **CPU保存的状态主要有:**
107 |
108 | 1. 把当前`PC`存在`ELR_EL1`(exception link register) (存储触发异常的指令地址)
109 | 2. 异常原因存储在`ESR_EL1`(exception syndrome register)(比如是由于svc还是缺页)
110 | 3. 栈指针:从`SP_EL0`切换到`SP_EL1`
111 | 4. 其他状态:
112 | * 把CPU相关状态保存在`SPSR_EL1` (saved program status register)
113 | * 把引发缺页异常的地址存在`FAR_EL1`
114 |
115 |
116 |
117 | #### 输入输出
118 |
119 | **MMIO (Memory-mapped IO)**
120 |
121 | * 将设备映射到连续的物理内存中,使用相同的指令
122 | * 如,Raspi3映射到0x3F200000
123 | * 行为与内存不完全一样,读写会有副作用(回忆volatile)
124 |
125 | **PIO (Port IO)**
126 |
127 | * IO设备具有独立的地址空间
128 | * 使用特殊的指令(如x86中的in/out指令)
129 |
130 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/4-操作系统内核架构.md:
--------------------------------------------------------------------------------
1 | # 操作系统内核架构
2 |
3 | **策略与机制分离**
4 |
5 | * 策略(Policy):要做什么 —— 相对动态
6 |
7 | * 机制(Mechanism):怎么做 —— 相对静态
8 |
9 | ## 1. 宏内核
10 |
11 | ### 1.1 宏内核的优缺点分析
12 |
13 | **优点**
14 |
15 | * 宏内核拥有丰富的沉淀和积累
16 | * 拥有巨大的统一的社区和生态
17 | * 针对不同场景优化了30年
18 |
19 | **缺点**
20 |
21 | * 宏内核的结构性缺陷
22 | * 安全性与可靠性问题:模块之间没有很强的隔离机制
23 | * 实时性支持:系统太复杂导致无法做最坏情况时延分析
24 | * 系统过于庞大而阻碍了创新:Linux代码行数已经过2千万
25 |
26 |
27 |
28 | ### 1.2 宏内核难以满足的场景
29 |
30 | * 向上向下的扩展
31 | * 很难去剪裁/扩展一个宏内核系统支持从KB级别到TB级别的场景
32 | * 硬件异构性
33 | * 很难长期支持一些定制化的方式去解决一些特定问题
34 | * 功能安全
35 | * 一个广泛共识:Linux无法通过汽车安全完整性认证(ASIL-D)
36 | * 信息安全
37 | * 单点错误会导致整个系统出错,而现在有数百个安全问题(CVE)
38 | * 确定性时延
39 | * Linux花费10+年合并实时补丁,目前依然不确定是否能支持确定性时延
40 |
41 |
42 |
43 | ## 2. 微内核
44 |
45 | ### 2.1 微内核的优缺点分析
46 |
47 | **缺点**
48 |
49 | 1. 性能较差:内核中的模块交互由函数调用变成了进程间通信
50 | 2. 生态欠缺:尚未形成像Linux一样具有广泛开发者的社区
51 | 3. 重用问题:重用宏内核操作系统提供兼容性,带来新问题
52 |
53 |
54 |
55 | ## 3. 外核+库OS(EXOKERNEL + LIBOS)
56 |
57 | **Exokernel 不提供硬件抽象**
58 |
59 | * "只要内核提供抽象,就不能实现性能最大化"
60 | * 只有应用才知道最适合的抽象(end-to-end原则)
61 |
62 | **Exokernel 不管理资源,只管理应用**
63 |
64 | * 负责将计算资源与应用的绑定,以及资源的回收
65 | * 保证多个应用之间的隔离
66 |
67 |
68 |
69 | ### 库OS(LibOS)
70 |
71 | * 策略与机制分离:将对硬件的抽象以库的形式提供
72 |
73 | * 高度定制化:不同应用可使用不同的LibOS,或完全自定义
74 |
75 | * 更高性能:LibOS与应用其他代码之间通过函数调用直接交互
76 |
77 |
78 |
79 | ### Exokernel架构的设计
80 |
81 | * **外核的功能**
82 | * 追踪计算资源的拥有权
83 | * 保证资源的保护
84 | * 回收对资源的访问权
85 |
86 | * **对应的三个技术**
87 | * 安全绑定(Secure binding)
88 | * 显式回收(Visible revocation)
89 | * 中止协议(Abort protocol)
90 |
91 | 管理与保护分离
92 |
93 |
94 |
95 | ### Exokernel架构的优缺点分析
96 |
97 | **优点**
98 |
99 | 1. OS无抽象,能在理论上提供最优性能
100 | 2. 应用对计算有更精确的实时等控制
101 | 3. LibOS在用户态更易调试,调试周期更短
102 | 4. 可以按照应用领域的特点和需求,动态组装成最适合该应用领域的`LibOS`,最小化非必要代码,从而获得更高性能
103 | 5. 处于硬件特权级的操作系统内核可以做到非常小,并且由于多个`LibOS`之间的强隔离性,从而可以提升安全性和可靠性
104 |
105 | **缺点**
106 |
107 | 1. 对计算资源的利用效率主要由应用决定
108 | 2. 定制化过多,导致维护难度增加
109 |
110 |
111 |
112 | ### 外核和微内核的区别
113 |
114 | 1. 外核架构将多个硬件资源切分长一个个切片,每个切片中保护的多个硬件资源由`LibOS`管理并直接服务于一个应用。而微内核架构则是通过让一个操作系统模块独立地运行在一个地址空间上来管理一个具体的硬件资源,为操作系统中的所有应用服务。
115 | 2. 外核架构中,运行在特权级的内核主要为`LibOS`提供硬件的多路复用能力,并管理`LibOS`。而微内核架构中,内核主要提供进程间通信的功能
116 | 3. 外核架构在面向一个功能和生态受限的场景下,可通过定制化`LibOS`获得更高的性能。微内核需要更复杂的优化。
117 |
118 |
119 |
120 | ## 4. 多内核架构(Multikernel)
121 |
122 | ### 背景:多核与异构
123 |
124 | 1. OS内部维护很多共享状态
125 | * Cache一致性的保证越来越难
126 | * 可扩展性非常差,核数增多,性能不升反降
127 | 2. GPU等设备越来越多
128 | * 设备本身越来越智能——设备有自己的CPU
129 | * 通过PCIe连接,主CPU与设备CPU之间通信非常慢
130 | * 通过系统总线连接,异构SoC(System on Chip)
131 |
132 |
133 |
134 | ### Multikernel的设计
135 |
136 | **Multikernel的思路**
137 |
138 | 1. 默认的状态是划分而不是共享
139 | 2. 维持多份状态的copy而不是共享一份状态
140 | 3. 显式的核间通信机制(避免处理器核之间通过共享内存进行隐式的共享)
141 |
142 | **Multikernel的设计**
143 |
144 | 1. 在每个core上运行一个小内核
145 | 2. OS整体是一个分布式系统
146 | 3. 应用程序依然运行在OS之上
--------------------------------------------------------------------------------
/7-调度.md:
--------------------------------------------------------------------------------
1 | # 调度
2 |
3 | > 什么是调度
4 |
5 | 协调请求对于资源的使用,所以有资源的地方就需要调度
6 |
7 | I/O (磁盘)、打印机、内存、网络包
8 |
9 |
10 |
11 | ## 1. 长期、中期和短期调度
12 |
13 | ### 长期调度
14 |
15 | ▲ 用于限制系统中真正被短期调度的进程数量
16 |
17 | 当一个程序尝试运行时,操作系统并不一定会立即为其创建对应进程并设置为就绪。(如果这样,那如果大量进程在短时间内被创建,会造成调度决策需要考虑的进程数量增加,调度开销变大)。要不要立即处理创建进程的决策,由长期调度负责。
18 |
19 | 当长期调度为某个程序创建进程并设置状态为就绪后,交由短期调度管理。
20 |
21 | ▲ 长期调度可以根据当前系统中的CPU、I/O利用率的情况选取合适的计算密集型或I/O密集型进程,交由短期调度管理,有效控制资源利用率。
22 |
23 | ### 中期调度
24 |
25 | ▲中期调度考虑内存,避免内存使用过多
26 |
27 | ▲ 实际是换页机制的一部分
28 |
29 | 当系统中的进程已经占用了大量内存,中期调度会挂起系统中被短期调度管理的进程。
30 |
31 | ### 短期调度
32 |
33 | ▲ 负责进程在就绪状态、运行状态、阻塞状态之间转换
34 |
35 |
36 |
37 | ## 2. 经典调度
38 |
39 | ### 2.1 FIFO(FCFS)
40 |
41 | 优点:简单直观
42 |
43 | 缺点:在长短任务混合的场景下对短任务不友好
44 |
45 | ### 2.2 SJF
46 |
47 | 缺点:
48 |
49 | * 必须预知任务运行时间
50 | * 其表现严重依赖于任务到达时间点
51 | * 不公平
52 | * 平均响应时间长
53 |
54 | ### 2.3 抢占式调度
55 |
56 | * 每次任务执行
57 | * 一定时间后会被切换到下一任务
58 | * 而非执行至终止
59 | * 通过定时触发的时钟中断实现
60 |
61 | ### 2.5 时间片轮转
62 |
63 | 缺点:在任务运行时间相似的场景下平均周转时间高
64 |
65 |
66 |
67 | ## 3. 优先级调度
68 |
69 | ### 3.1 MLQ 多级队列
70 |
71 | 1. 维护多个优先级队列
72 | 2. 高优先级的任务优先执行
73 | 3. 同优先级内使用Round Robin调度
74 |
75 | 适合静态的应用场景,任务信息(任务大致运行时间,资源使用情况)可以在执行前获知。根据任务信息,可以生成对应的调度模型,并计算出每种任务合适的优先级
76 |
77 | #### 问题一:低优先级任务饥饿
78 |
79 | > 什么样的任务应该有高优先级
80 |
81 | * I/O绑定的任务
82 | * 为了更高的资源利用率
83 | * 用户主动设置的重要任务
84 | * 时延要求极高(必须在短时间内完成)的任务
85 | * 等待时间过长的任务
86 | * 为了公平性
87 |
88 | #### 问题二:优先级反转
89 |
90 | 解决方案:**优先级继承**
91 |
92 |
93 |
94 |
95 |
96 | ## 4. 公平共享调度
97 |
98 | 优先级和份额都表示了任务在系统中的重要程度,但目的不同。
99 |
100 | **优先级**:
101 |
102 | 优先级的数值仅仅是用于比较优先级高低而存在,无法反映单位时间内一个任务可以占用的CPU时间比例。
103 |
104 | 优先级调度是为了优化的周转时间、响应时间和资源利用率而设计。不同任务的优先级只能用于相互比较,表明任务执行的先后。
105 |
106 | **份额**:
107 |
108 | 基于份额的公平共享调度是为了让每个任务都能使用它应该获得的系统资源。
109 |
110 | ### 4.1 彩票调度
111 |
112 | 
113 |
114 |
115 |
116 | > 权重 与 优先 的异同
117 |
118 | **权重**:影响任务对CPU的占用比例
119 |
120 | * 永远不会有任务饿死
121 |
122 | **优先级**影响任务对CPU的使用顺序
123 |
124 | * 可能产生饿死
125 |
126 | > 随机的利弊
127 |
128 | 好处:简单
129 |
130 | 问题:
131 |
132 | * 不精确——伪随机非真随机
133 | * 各个任务对CPU时间的占比会有误差
134 |
135 | ### 4.2 Stride Scheduling
136 |
137 | 
138 |
139 | 为了让虚拟时间短的任务能够追赶虚拟时间长的任务,调度策略一般选择虚拟时间最少的任务
140 |
141 | ### 4.3 对比
142 |
143 | 
144 |
145 |
146 |
147 | ## 5. 实时调度
148 |
149 | 实时任务会有一个明确的截止时间,根据**任务超过截止时间的后果**,分类:
150 |
151 | * 硬实时任务
152 | * 必须在截止时间前完成
153 | * 如交通工具:超过截止时间 -> 严重后果
154 | * 软实时任务
155 | * 可以偶尔超过截止时间完成
156 | * 如视频播放,每一帧的渲染:超过截止时间 -> 画质差
157 |
158 | 根据**被触发的时间**,分类:
159 |
160 | * 周期任务
161 | * 到达系统时间遵循一定周期的任务
162 | * 偶发任务
163 | * 不会周期地到达系统
164 | * 连续两个相同偶发任务到达系统的时间间隔有最小值,即系统不会在同一时刻处理两个相同的偶发任务
165 | * 偶发任务通常是硬实时任务
166 | * 如刹车
167 | * 非周期任务
168 | * 到达系统随机的任务
169 | * 非周期任务通常是软实时任务
170 | * 如用户按下键盘
171 |
172 | ### 5.1 调度算法
173 |
174 | 
175 |
176 | ▲ 如果存在一种调度策略,使所有任务可以在截止时间前完成,那么U一定小于等于1
177 |
178 | #### 5.1.1 速率单调 RM策略
179 |
180 | ▲ 静态优先级实时调度策略
181 |
182 | ▲ 在所有基于静态优先级的实时调度策略中,RM策略是最优的
183 |
184 | ▲ 但是当任务的U ≤ 1 时,RM策略也不一定可以满足任务的截止时间要求
185 |
186 | ▲ 1/T 大的任务优先级高
187 |
188 | #### 5.1.2 最早截止优先 EDF
189 |
190 | 根据任务截止时间动态分配任务
191 |
192 | ▲ U ≤ 1的充分必要条件时 EDF可以在任务的截止时间前完成任务
193 |
194 | ▲ 但是在U>1,是会出现多米诺效应
195 |
196 | **多米诺效应**:
197 |
198 | 因为一个任务错过截止时间而导致大量后续任务级联地错过截止时间
199 |
200 |
201 |
202 | ## 6. 多核调度策略
203 |
204 | ### 6.1 负载分担
205 |
206 | 
207 |
208 | **优点**
209 |
210 | * 设计实现简单
211 | * 不会出现CPU资源浪费的情况
212 |
213 | **缺点**
214 |
215 | * 多个核共享全局运行队列 -> 同步开销
216 | * 任务在多个CPU之间来回切换的开销(缓存载入、TLB刷新)
217 |
218 | ### 6.2 协同调度
219 |
220 | **目的**:尽可能让一组任务并行执行
221 |
222 | **适用**:1. 关联任务 2. 任务间有依赖
223 |
224 | **例子**:并行计算,编译
225 |
226 | #### 6.2.1 群组调度
227 |
228 | 将关联任务设置为一组(一个组内的关联任务都需要同时运行),以组为单位调度任务在多个CPU核心上运行
229 |
230 | 
231 |
232 |
233 |
234 | ## 6.3 两级调度
235 |
236 | 当一个任务被全局调度器分配到给定的CPU核心上时,将一直被该核心的本地调度器管理,不会迁移到其他的CPU核心上执行
237 |
238 | 如:linux,chcore
239 |
240 | 
--------------------------------------------------------------------------------
/11-文件系统.md:
--------------------------------------------------------------------------------
1 | # 文件系统
2 |
3 | ## 1. Ext2
4 |
5 |
6 |
7 | 
8 |
9 | **超级块**:记录整个文件系统的元数据,如文件系统类型,版本等
10 |
11 | **块分配信息**:使用`bitmap`的格式记录数据区中各个块的使用情况。一个`bit`代表一个块,表示该块有没有被分配
12 |
13 | **inode分配信息**:记录`inode`的使用情况,一个`bit`表示一个`inode`,表示该`inode`有没有被分配
14 |
15 | **inode表**:用数组的结构保存了整个文件系统所有的`inode`,`inode`号就是数组索引。一个`inode`对应一个文件,记录了这个文件的数据存储在哪些块里
16 |
17 | ### 1.1 inode
18 |
19 |
20 |
21 | **元数据**:文件类型,文件大小 ,链接数,文件权限,拥有用户/组,时间(创建、修改、访问时间)
22 |
23 | **数据块指针**:12个直接指针,1个间接指针,1个二级间接指针,1个三级简介指针
24 |
25 |
26 |
27 | ### 1.2 硬链接
28 |
29 | 创建一个硬链接,不会新建一个`inode`
30 |
31 | 找到目标链接文件的`inode`后,在目标路径的父路径下,创建一个指向次`inode`的新目录项
32 |
33 | ### 1.3 符号链接
34 |
35 | 创建一个符号链接,会创建一个新的`inode`
36 |
37 | 创建一个新的文件,文件的内容是到目标链接文件的文件路径
38 |
39 |
40 |
41 | ## 2. Ext4文件存储 – 区段树(Extent)
42 |
43 | > 问题:在Ext2的设计中,保存一个1GB的视频文件,文件被拆成多少数 据块?需要多少元数据来维护这些数据块?
44 |
45 | $$
46 | \begin{align}
47 | 数据块数:& \frac{10^6 KB}{4} \\
48 | 元数据的大小:& \frac{10^6 KB}{4} * 8Byte(一个指针) = 2M
49 | \end{align}
50 | $$
51 |
52 | ▲ 如果这些数据块物理上连续,只需要保存起始块地址和长度即可!
53 |
54 | **区段(Extent)**:是由物理上连续的多个数据块组成
55 |
56 | * 一个区段内的数据可以连续访问,无需按4KB数据块访问
57 | * 可以减少元数据的数量
58 |
59 | 
60 |
61 | **为什么这样设计**:
62 |
63 | 硬盘耗时在于最后读文件,前面内存操作耗时没关系
64 |
65 |
66 |
67 | ## 3. 文件内存映射:用mmap()来访问文件
68 |
69 | mmap可将文件映射到虚拟内存空间中
70 |
71 | 1. mmap时分配虚拟地址,并标记此段虚拟地址与该文件的inode绑定
72 | 2. 访问mmap返回的虚拟地址时,触发缺页中断(page fault)
73 | 3. 缺页中断处理函数,通过虚拟地址,找到该文件的inode
74 | 4. 从磁盘中将inode中对应的数据读到内存页中
75 | 5. 将内存页映射添加到页表中
76 |
77 | 
78 |
79 | **优势**:
80 |
81 | * 对于随机访问,不用频繁lseek (syscall)
82 | * 减少系统调用次数
83 | * 可以减少数据copy – 如拷贝文件,数据无需经过中间buffer
84 | * 访问的局部性更好
85 | * 可以用madvice为内核提供访问提示,提高性能
86 |
87 |
88 |
89 | 把文件映射到虚拟内存,访问文件就像访问数组一样方便
90 |
91 | 
92 |
93 | ## 4. 基于TABLE的文件系统
94 |
95 | ### 4.1 FAT
96 |
97 | 
98 |
99 | #### FAT
100 |
101 | **FAT**其实就是一个大数组,每一个簇(类似于block)都在FAT里对应了一个数组项
102 |
103 | 该数组项的内容是下一个簇号,也就比如FAT[i]=j,说明簇i后面的数据就是簇j
104 |
105 | 十六进制的全F表示这是最后一个簇
106 |
107 | 构成了链表
108 |
109 | **目录项**
110 |
111 | 目录项里存了:数据的起始簇号,文件名,文件大小,属性(只读,隐藏,子目录,系统文件,卷标等)
112 |
113 | > 为什么FAT在目录项里区分了文件时目录还是普通文件,inode文件系统并没有在目录项里区分
114 |
115 | 因为在inode里面记录了是目录还是普通文件,但是FAT表里不能记录这些信息,所以只能记载目录项里
116 |
117 | **根文件夹**
118 |
119 | 第一个数据块
120 |
121 |
122 |
123 | 
124 |
125 |
126 |
127 | > 为什么FAT不支持4G以上的文件
128 |
129 | 因为目录项里记录了文件大小(单位为KB?),有4个字节
130 | $$
131 | 4 byte=32bit\\
132 | 2^{32} = 4G
133 | $$
134 |
135 | > 为什么U盘一般用FAT
136 |
137 | > 为什么FAT不支持link
138 |
139 | 如果要支持link,需要记录文件的`ref_cnt`
140 |
141 | 放哪呢?
142 |
143 | 放目录项:不行这样会有多个
144 |
145 | 放FAT:没地方->每个FAT都要加,若加上浪费空间
146 |
147 | > 为什么FAT的随机读取文件非常慢
148 |
149 | 因为访问一个文件的中间部分,FAT文件系统不得不逐个簇进行查找,使得访问变慢
150 |
151 |
152 |
153 | ### 4.2 NTFS
154 |
155 | 
156 |
157 | #### MFT
158 |
159 | MFT记录了保存了前十六个元数据文件的位置
160 |
161 | MFT也记录了MFT元数据文件的位置,该文件记录了其他所有文件的位置和信息
162 |
163 | 文件所有的信息都在MFT里面,所以everything找文件超级快
164 |
165 | #### NTFS数据保存位置和目录项
166 |
167 | * 非常驻文件(大文件/目录)
168 | * 数据区的B+树和区段
169 | * 常驻文件(小文件/目录)
170 | * 大小不超过MFT记录的最大值(1KB)
171 | * 内嵌在MFT中保存(在数据属性中)
172 | * 目录项 – 包含文件名、文件ID(在MFT中的序号)
173 |
174 |
175 |
176 | ## 5. 虚拟文件系统(VFS)
177 |
178 | * FAT没有inode,如何挂载到VFS?
179 | * VFS层对上提供的接口,每个文件都有一个inode
180 | * FAT的inode从哪里来?
181 | * FAT的驱动需要提供inode
182 | * 磁盘上的FAT并没有inode:硬盘上的数据结构
183 | * 内存中的VFS需要inode:只在内存中的数据结构
184 |
185 | ## 6. 存储结构与缓存
186 |
187 | 
188 |
189 | ## 7. 文件系统高级功能
190 |
191 | ### 7.1 文件复制
192 |
193 | 只复制inode(COW)
194 |
195 | 
196 |
197 | ### 7.2 快照(Snapshot)
198 |
199 | * 同样使用CoW
200 | * 对于基于inode表的文件系统
201 | * 将inode表拷贝一份作为快照保存
202 | * 标记已用数据区为CoW
203 | * 对于树状结构的文件系统
204 | * 将树根拷贝一份作为快照保存
205 | * 树根以下的节点标记为CoW
206 |
207 |
208 |
209 | ### 7.3 稀疏文件
210 |
211 | * 一个文件大部分数据为0,则为稀疏文件 – 如虚拟机镜像文件
212 | * 稀疏文件中大量的0数据,白白消耗空间
213 |
214 | * 在索引中增加标记
215 | * 删除全0块
216 |
217 |
218 |
219 | ## 8. GIT:内容寻址文件系统
220 |
221 | * 表面上GIT是一个版本控制软件
222 | * 但实际上GIT是一个内容寻址的文件系统!
223 | * 其核心是一个键值存储
224 | * 值:加入GIT的数据
225 | * 键:通过数据内容算出的40个字符SHA-1校验和
226 | * 前2个字符作为子目录名,后38个字符作为文件名
227 | * 所有对象均保存在.git/objects目录中(文件内容会被压缩)
228 | * 是一个“文件系统之上的文件系统
229 |
230 | ”
231 |
232 | ”
--------------------------------------------------------------------------------
/3-中断异常与系统调用.md:
--------------------------------------------------------------------------------
1 | # 中断异常与系统调用
2 |
3 | 注意:从`EL0`到`EL1`的三种方法就是异常中断系统调用
4 |
5 | ## 1. 中断、异常
6 |
7 | ### 1.1 通用概念
8 |
9 | **中断(Interrupt)**
10 |
11 | * 外部硬件设备所产生的信号
12 |
13 | * 异步:产生原因和当前执行指令无关,如程序被磁盘读打断
14 |
15 |
16 |
17 | **异常(Exception)**
18 |
19 | * 软件的程序执行而产生的事件
20 |
21 | * 包括系统调用(System Call
22 | * 用户程序请求操作系统提供服务
23 | * 同步:产生和当前执行或试图执行的指令相关
24 |
25 |
26 |
27 | (中断是被外来的打断的,异常时内部自己发生的,但是arm里所有都叫异常,中断叫异步异常,异常叫同步异常)
28 |
29 |
30 |
31 | ### 1.2 不同体系结构术语的对应关系
32 |
33 | | 通用概念 | 产生 原因 | AArch64 | x86-64 |
34 | | :------: | :-------: | :------------------------: | :-----------------------: |
35 | | 中断 | 硬件 异步 | 异步异常 (重置/中断) | 中断 (可屏蔽/不可屏蔽) |
36 | | 异常 | 软件 同步 | 同步异常 (终止/异常指令) | 异常 (Fault/Trap/Abort) |
37 |
38 |
39 |
40 | #### AArch64
41 |
42 | **异步异常**
43 |
44 | * 重置(Reset)
45 | * 最高级别的异常,用以执行代码初始化CPU核心
46 | * 由系统首次上电或控制软件、Watchdog等触发
47 |
48 | * 中断(Interrupt)
49 | * CPU外部的信号触发,打断当前执行
50 | * 如计时器中断、键盘中断等
51 |
52 | **同步异常**
53 |
54 | * 中止(Abort)
55 | * 失败的指令获取或数据访问
56 | * 如访问不可读的内存地址等
57 | * 异常产生指令(Exception generating instructions)
58 | * SVC:用户程序 -> 操作系统
59 | * HVC:客户系统 -> 虚拟机管理器
60 | * SMC:Normal World -> Secure World
61 |
62 |
63 |
64 | #### x86-64
65 |
66 | * 中断(设备产生、异步)
67 | * 可屏蔽:设备产生的信号,通过中断控制器与处理器相连,可被 暂时屏蔽(如,键盘、网络事件)
68 | * 不可屏蔽:一些关键硬件的崩溃(如,内存校验错误)
69 | * 异常(软件产生、同步)
70 | * 错误(Fault): 如缺页异常(可恢复)、段错误(不可恢复)等
71 | * 陷阱(Trap): 无需恢复,如断点(int 3)、系统调用(int 80)
72 | * 中止(Abort): 严重的错误,不可恢复(机器检查)
73 |
74 |
75 |
76 | ## 1.3 中断注意事项
77 |
78 | * 中断处理没有进程上下文
79 | * 中断(和异常相比)和具体的某条指令无关
80 | * 也和中断时正在跑的进程、用户程序无关
81 | * 中断处理handler不能睡眠
82 |
83 |
84 |
85 | **约束**:
86 |
87 | 1. 不能睡眠,也不能调用可能会睡眠的任务
88 | 2. 不能调用schedule()调
89 | 3. 不能释放信号或调用可能睡眠的操作
90 | 4. 不能和用户地址空间交换数据
91 |
92 |
93 |
94 | ## 1.4 中断和异常的处理
95 |
96 | 中断与异常的处理使用同一套机制,差异仅在选择handler中提现
97 |
98 | 
99 |
100 |
101 |
102 | ### **中断和异常处理必做事项**
103 |
104 | 1. 进入中断或异常时
105 | * 需保存处理器状态,方便之后恢复执行
106 | * 需准备好在高特权级下进行执行的环境
107 | * 需选择合适的异常处理器代码进行执行
108 | * 需保证用户态和内核态之间的隔离
109 | 2. 处理时
110 | * 需获得关于异常的信息,如系统调用参数、错误原因等
111 | 3. 返回时
112 | * 需恢复处理器状态,返回低特权级,继续正常执行流
113 |
114 |
115 |
116 | ### **AArch64的中断和异常处理**
117 |
118 | 1. **发生 – 信息保存**
119 |
120 | * 异常或中断发生后,硬件会将错误码和部分上下文信息存储在寄存器中
121 | * 处理器状态(PSTATE)-> Saved Program Status Register (SPSR_EL1
122 | * 当前指令地址(PC)-> Exception Link Register(ELR_EL1)
123 | * 异常发生原因 ->
124 | * Serror与异常:Exception Syndrome Register(ESR_EL1)
125 | * 中断:GIC中的寄存器(使用MMIO读取)
126 | * 安全性问题
127 | * 上述寄存器均不可在用户态(EL0)中访问
128 |
129 | 2. **发生 – 进入EL1**
130 |
131 | * 硬件会适当修改处理器状态(PSTATE),进入EL1执行
132 | * 问题:栈内存的安全性
133 | * 进入EL1级别后,栈指针(SP)会自动换用SP_EL1
134 | * 从而实现用户栈->内核栈
135 | * 如需在EL1下使用SP_EL0作为栈指针,可配置SPSel寄存器
136 |
137 | 3. **寻找handler的代码**
138 |
139 | 使用异常向量表
140 |
141 | * 每个异常级别存在独立的异常向量表(分级,x86-64不分级)
142 |
143 | * 表项为异常向量(Exception Vector):是处理异常或跳转到异常handler的小段汇编代码
144 |
145 | * 异常向量表的地址位于VBAR_EL1寄存器中
146 |
147 | * 选择表项取决于
148 |
149 | * 异常类型(同步、IRQ、FIQ、Serror)
150 | * 异常发生的特权级
151 | * 异常发生时的处理器状态(使用的栈指针/运行状态)
152 |
153 |
154 |
155 | 4. **返回(Exception Return)**
156 |
157 | * ELR_EL1 -> PC,恢复PC状态
158 | * SPSR_EL1 -> PSTATE,恢复处理器状态
159 | * 降至EL0,硬件自动使用SP_EL0作为栈指针
160 | * 恢复执行
161 |
162 |
163 |
164 | ### x86-64的中断和异常处理
165 |
166 | 1. 进入异常
167 | * 硬件会将上下文信息和错误码存储在内核栈上
168 | 2. 用异常向量表寻找handler
169 | * 不分级
170 | * 异常向量表中存handler的地址
171 | 3. iret返回
172 | * 恢复程序上下文
173 | * 从内核态返回用户态
174 | * 继续执行用户程
175 |
176 |
177 |
178 | **与`aarch64`区别**
179 |
180 | 1. `x86-64`信息都存栈上,而`aarch64`都存在寄存器里
181 | 2. `x86-64`不分级
182 |
183 |
184 |
185 |
186 |
187 | ## 2. 系统调用
188 |
189 | ### 系统调用与安全
190 |
191 | * AArch64使用寄存器传参,个数有限
192 | * 如ChCore的系统调用支持使用寄存器X0-X7最多8个参数
193 | * 若系统调用需要更多参数如何处理?
194 | * 使用结构体打包参数,并将结构体的指针作为参数
195 | * 问题:内存安全性
196 | * 作为参数的指针必须经过检测!
197 | * 指向NULL -> kernel crash
198 | * 指向内核内存 -> 安全漏
199 |
200 | ### 用户指针检测
201 |
202 | * 完备的指针检测十分耗时
203 | * 需要遍历用户进程的所有合法内存区域进行检测
204 | * 合法区域由链表(vma)管理
205 | * Linux解决方法:非全面检查
206 | * Linux仅初步检测用户指针是否属于对应进程的用户内存区域的最大可能 边界
207 | * 即使通过初步检测,用户指针仍然可能非法(如指向尚未分配的栈空间 等)
208 | * 直接将非法的指针交给内核使用会导致内核出现页错误,内核态的页错误通常意味着bug,内核会打印异常信息并中止用户进程
209 | * Linux采用了一些复杂机制来防止这一情况发生
210 |
211 | ### 处理用户指针问题
212 |
213 | * 内核代码仅使用特定代码片段访问用户指针(如copy_from_user)
214 | * 由访问用户指针而导致内核内存错误的代码段是确定的
215 | * 或者可以该页表,设为read-only
216 | * 当内核发生页异常(Page Fault)时,内核会检查异常发生的PC
217 | * 若异常发生的PC属于访问用户指针的代码段,Linux尝试对其进行修复
218 | * 若不属于,则报告问题并终止用户程序
--------------------------------------------------------------------------------
/14-设备.md:
--------------------------------------------------------------------------------
1 | # 设备
2 |
3 | ## 1. 设备抽象
4 |
5 | **Linux系统三种设备抽象**:
6 |
7 | 1. **字符设备**:设备上的信息抽象为连续的字节流,顺序读写,字节粒度
8 |
9 | * 例:LED、键盘、串口、打印机
10 |
11 | * 访问模式: 顺序访问,每次读取一个字节;调用驱动程序和设备直接交互
12 | * 通常使用文件抽象:open(), read(), write(), close()
13 |
14 | 2. **块设备**:随机读写,块粒度
15 |
16 | * 例:磁盘、U盘、闪存等(以存储设备为主)
17 |
18 | * 访问模式:
19 | 1. 随机访问,以块粒度进行读写
20 | 2. 在驱动程序之上增加一层缓冲,避免和慢设备频繁交互
21 | * 通常使用内存抽象:
22 | * 内存映射文件(Memory-Mapped File):直接访问数据
23 | * 同样可以使用文件抽象,但内存抽象更受欢迎(灵活性更好)
24 |
25 | 3. **网络设备**:
26 |
27 | * 例:以太网、WiFi、蓝牙等(以通信设备为主)
28 |
29 | * 访问模式:
30 | * 面向格式化报文的收发
31 | * 在驱动层之上维护多种协议,支持不同策略
32 | * 通常使用套接字抽象:socket(), send(), recv(), close()
33 |
34 |
35 |
36 | > Linux设备驱动的主要抽象是那些?请列举sysfs文件系统中子项,并且指出他们之间的关系
37 |
38 | 主要的抽象:Class,Bus,Device
39 |
40 | Sysfs下的子项:/sys/class, /sys/bus, /sys/devices
41 |
42 | ## 2. CPU与外设的数据交互
43 |
44 | ### 2.1 可编程 I/O(Programmable I/O)
45 |
46 | **PIO (Port IO)**
47 |
48 | * IO设备具有独立的地址空间
49 | * 使用特殊的指令(如x86中的in/out指令)
50 |
51 | **MMIO (Memory-mapped IO)**
52 |
53 | * 将设备映射到连续物理内存中
54 | * 使用内存访问指令
55 | * 行为与内存不完全一样,读写会有副作用
56 |
57 |
58 |
59 | ### 2.2 DMA
60 |
61 | 
62 |
63 |
64 |
65 | ## 3. 中断与中断管理
66 |
67 | **CPU中断处理流程**:
68 |
69 | 
70 |
71 | **AArch64中断分类**:
72 |
73 | 
74 |
75 |
76 |
77 | ### 3.1 GIC
78 |
79 | #### 3.1.1 Distributor
80 |
81 | * 中断分发器:
82 | * 将当前最高优先级中断转发给对应CPU Interface
83 | * 寄存器:GICD
84 |
85 | #### 3.1.2 CPU Interface
86 |
87 | * CPU接口:
88 | * 将GICD发送的中断信息,通过IRQ、FIQ管脚,发送给连接到interface的core
89 | * 寄存器:GICC
90 |
91 | ### 3.2 ARM中断的生命周期
92 |
93 | 1. Generate:外设发起一个中断
94 | 2. Distribute:Distributor对收到的中断源进行仲裁,然后发送 给对应的CPU Interface
95 | 3. Deliver:CPU Interface将中断传给core
96 | 4. Activate:core读 GICC_IAR 寄存器,对中断进行确认
97 | 5. Priority drop: core写 GICC_EOIR 寄存器,实现优先级重置
98 | 6. Deactivate:core写 GICC_DIR 寄存器,来无效该中断
99 |
100 | **中断确认**:
101 |
102 | * CPU开始响应中断:
103 | * IRQ状态:pendingàactive
104 | * 寄存器:GICC_IAR,记录当前等待处理的中断号
105 | * 通过访问GICC_IAR寄存器,来对中断进行确认
106 |
107 | **中断完成**:
108 |
109 | ▲ 只有中断完成后,对应的中断才能重新被响应
110 |
111 | 🔺 为了提高中断响应的实时性,中断完成分两步
112 |
113 | * CPU处理完中断:
114 | * 设置IRQ状态:active -> inactive
115 | * 优先级重置(priority drop):
116 | * 将当前中断屏蔽的最高优先级进行重置,以便能够响应低优先级中断
117 | * 寄存器:GICC_EOIR
118 | * 中断无效(interrupt deactivation):
119 | * 将中断的状态置为inactive状态
120 | * 寄存器: GICC_DIR
121 |
122 |
123 |
124 | ### 3.3 如何设计中断处理函数
125 |
126 | * 中断应该尽快响应 – 提高系统对外部的实时响应能力
127 | * 尽量短 – Linux上半部:马上处理
128 | * 可重入 – 应允许在中断过程的任意时刻被抢占
129 |
130 |
131 |
132 | ### 3.4 中断嵌套
133 |
134 | * 中断也能被“中断”!
135 | * 在处理当前中断(ISR)时:
136 | * 更高优先级的中断产生;或者
137 | * 相同优先级的中断产生
138 | * 那么该如何响应?
139 | * 允许高优先级抢占
140 | * 同级中断无法抢占
141 | * ARM的FIQ能抢占任意IRQ,FIQ不可抢占
142 |
143 |
144 |
145 | 🔺 中断上下文不能睡眠!!!
146 |
147 | * 考虑如下场景:
148 | 1. Process 1进入内核态
149 | 2. Process 1获得 Lock A
150 | 3. 中断发生
151 | 4. ISR 试图拿锁 Lock A
152 | 5. ISR 调用sleep,等待Lock A被释放
153 | * 死锁: – Process 1必须等待ISR返回,但ISR在等待Process 1释放锁LockA
154 | * 在中断上下文中睡眠,内核将被挂起
155 |
156 |
157 |
158 | ## 4. 管理设备
159 |
160 | ### 4.1 Linux设备驱动抽象
161 |
162 | * Device(设备):用于抽象系统中所有的硬件
163 | * 包括CPU和内存
164 | * Bus(总线):CPU连接Device的通道
165 | * 所有的Device都通过bus相连
166 | * Class(分类):具有相似功能或属性的设备集合
167 | * 类似面向对象程序设计中的Class
168 | * 抽象出一套可以在多个设备之间共享的数据结构和接口
169 | * 从属于相同Class的设备驱动程序,直接继承
170 |
171 |
172 |
173 | ## 5. 设备树
174 |
175 | 见PPT
176 |
177 |
178 |
179 | ## 6. LINUX的上下半部
180 |
181 | ### 6.1 上半部
182 |
183 | ▲ 执行上半部期间关闭中断
184 |
185 | ▲ 硬中断处理函数实质是上半部
186 |
187 | * 最小化公共例程:
188 | * 保存寄存器、屏蔽中断
189 | * 恢复寄存器,返回现场
190 | * **最重要**:调用合适的由硬件驱动提供的中断处理handler
191 | * 因为中断被屏蔽,所以不要做太多事情(时间、空间)
192 |
193 |
194 |
195 | ### 6.2 下半部
196 |
197 | ▲ 延迟完成,执行时间由系统调度决定,下半部属于具有较高优先级的内核任务
198 |
199 | * 提供可以推迟完成任务的机制
200 | * softirqs
201 | * tasklets (建立在softirqs之上)
202 | * 工作队列
203 | * 内核线程
204 |
205 | #### 6.2.1 软中断 (softirqs)
206 |
207 | * 静态分配:在内核编译时期确定,数量有限
208 | * 执行时间点:
209 | * 中断之后(上半部之后)
210 | * 系统调用或是异常发生之后
211 | * 调度器显式执行ksoftirqd
212 | * 并发:
213 | * 可以在多核上同时执行
214 | * 必须是可重入的
215 | * 或根据需要加锁
216 | * 可中断:Softirq运行时可再被中断抢占
217 |
218 | **要求**:软中断要求能被重调度(在处理软中断A时,能切换至软中断B(挂起A唤醒B))
219 |
220 | * 问题:在处理软中断A时,软中断产生了B,怎么办?
221 | * 不处理àB响应被延迟
222 | * 总是处理à如果软中断很长 -> 用户程序被饿死? <活锁>
223 | * 方案:配额(quota)+ ksoftirqd
224 | * Softirq调度器每次只运行有限数量的请求
225 | * 剩余请求有内核线程ksoftirqd代为执行,和用户进程抢CPU
226 | * ksoftirqd和用户进程都被调度器调度
227 |
228 |
229 |
230 | #### 6.2.2 Tasklet
231 |
232 | **优势**:
233 |
234 | * 可动态分配,数量不限
235 | * 直接运行在调度它的CPU上(缓存亲和性)
236 | * 避免一个tasklet实例被多个CPU接管的情况
237 | * 同一时间只允许有一个相同类型的tasklet实例存在
238 | * 执行期间不能被其它下半部抢占
239 | * 不存在重入的问题
240 | * 无需加锁
241 | * 编程友好性
242 |
243 | **问题**
244 |
245 | * 难以正确实现
246 | * 要防止休眠代码
247 | * 任务不可抢占性(仍可被中断)
248 | * 比其他任务的优先级都高,影响任务实时性
249 | * 导致不可控的延迟
250 | * Linux社区一直在讨论是否要移除Tasklet
251 |
252 |
253 |
254 | #### 6.2.3 工作队列(Work Queues)
255 |
256 | 🔺 Softirq和Tasklet使用中断上下文,工作队列使用进程上下文,可以睡眠!!!!
257 |
258 | * **方式**:
259 | * 在内核空间维护FIFO队列, workqueue内核进程不断轮询队列
260 | * 中断负责enqueue(fn, args), workqueue负责dequeue并执行fn(args)
261 | * **特点**:
262 | * 只在内核空间,不和任何用户进程关联,没有跨模式切换和数据拷贝
263 |
264 |
265 |
266 | #### 6.2.4 内核线程(Kernel Threads)
267 |
268 | * 始终运行在内核态
269 |
270 |
271 |
272 | 
273 |
274 |
275 |
276 | > 为什么ARM中断完成确认分为两步走?
277 |
278 | Priority dropping:允许低优先级的中断被触发Interrupt
279 |
280 | Deactivation:使制定的IRQ处于未活跃的状态,该中断可以被再次触发
281 |
282 | Linux上半部:使用了priority dropping(GICC_EOIR)防止低优先级中断阻塞
283 |
284 | Linux下半部:使用interrupt deactivation(GICC_DIR)完成该IRQ,并且通知GIC接受后续的IRQs
285 |
286 |
287 |
288 | > 为什么要共享中断
289 |
290 | * IRQ是有限资源
291 | * 可以通过多个设备共享同一中断号来解决需求
292 | * Linux将同一中断的ISR组成链表
293 | * IRQ到来后,内核对每个中断处理程序都要执行
294 | * 所有该中断的“订阅者”都会查询自己的设备寄存器,以确定当前中 断是不是自己的设备发出的
295 | * 对于慢速设备,就会造成很大的开销
--------------------------------------------------------------------------------
/9-同步原语.md:
--------------------------------------------------------------------------------
1 | # 同步原语
2 |
3 | ## 1. 生产者消费者问题
4 |
5 | 多生产者会出现会将新产生的数据放入到同一个缓冲区中,造成数据覆盖的问题
6 |
7 | **竞争条件**
8 |
9 | * 当多个进程同时对共享的数据进行操作
10 | * 该共享数据最后的结果依赖于这些进程特定的执行顺序
11 |
12 | **解决临界区问题的三个要求**
13 |
14 | 1. 互斥访问:在同一时刻,有且仅有一个进程 可以进入临界区
15 | 2. 有限等待:当一个进程申请进入临界区之后 ,必须在有限的时间内获得许可进入临界区 而不能无限等待
16 | 3. 空闲让进:当没有进程在临界区中时,必须 在申请进入临界区的进程中选择一个进入临 界区,保证执行临界区的进展
17 |
18 | ## 2. 软件:皮特森算法
19 |
20 | 
21 |
22 | 书:P210
23 |
24 | **问题**
25 |
26 | 1. 只能应付两个线程的状况
27 | 2. 现代CPU允许访存操作乱序执行,皮特森算法无法正常工作
28 |
29 |
30 |
31 | ## 3. 硬件:关闭中断
32 |
33 | **问题**
34 |
35 | * 只适合单核,不适合多核
36 |
37 |
38 |
39 | ## 4. 软硬件协同:原子操作实现互斥锁
40 |
41 | ### 4.1 原子操作
42 |
43 | 
44 |
45 | **CAS**:比较地址`addr`上的值和期望值`expected`是否相等,相等则置换为`new_value`,返回`addr`存放的旧值
46 |
47 | **FAA**:读取`addr`上的旧值,将其加上`add_value`并存入,返回`addr`上的旧值
48 |
49 | #### 4.1.1 Intel硬件实现
50 |
51 | 🔺 锁总线:对任意地址的修改都要经过总线的,通过锁总线来实现原子操作
52 |
53 | 
54 |
55 | #### 4.1.2 arm
56 |
57 | 🔺 LL/SC : 在`Load-Link`时,CPU使用专门的监视器,记录当前访问的地址,而在`store-conditional`时,当前仅当监视的地址没有被其他核修改时,才执行存储操作,否则失败
58 |
59 | 
60 |
61 |
62 |
63 | ### 4.2 自旋锁(spin lock)
64 |
65 | ▲ 利用CAS
66 |
67 | **实现**
68 |
69 | ```c
70 | void lock(int *lock) {
71 | while(atomic_CAS(lock, 0, 1)!= 0)
72 | /* Busy-looping */ ;
73 | }
74 | void unlock(int *lock) {
75 | *lock = 0;
76 | }
77 | ```
78 |
79 | **条件**
80 |
81 | 1. 互斥访问:√
82 |
83 | 2. 有限等待
84 |
85 | * 🔺 自旋锁不能保证有线等待
86 | * 原子操作的成功与否完全取决于硬件特性。小核的运行频率低,在于大核竞争时,可能永远也无法获取锁
87 |
88 | 3. 空闲让进:?
89 |
90 | 依赖于硬件 => 当多个核同时对一个地址执行原子操作时,能否保证至少有一个能够成功
91 |
92 |
93 |
94 | ### 4.3 排号锁
95 |
96 | ▲ 利用FAA
97 |
98 | ```c
99 | void lock(int *lock) {
100 | volatile unsigned my_ticket =
101 | atomic_FAA(&lock->next, 1);
102 | while(lock->owner != my_ticket)
103 | /* busy waiting */;
104 | }
105 | void unlock(int *lock) {
106 | lock->owner ++;
107 | }
108 | ```
109 |
110 | **条件**
111 |
112 | 1. 互斥访问 ✓
113 |
114 | 2. 有限等待 ?
115 |
116 | 按照顺序,在前序竞争者保证有限 时间释放时,可以达到有限等待
117 |
118 | 3. 空闲让进 ✓
119 |
120 | ## 5. 读写锁
121 |
122 | **互斥锁**:所有的进程均互斥,同一时刻只能有一个进程进入临界区对于部分只读取共享数据的进程过于严厉
123 |
124 | **读写锁**:区分读者与写者,允许读者之间并行,读者与写者之间互斥
125 |
126 | 
127 |
128 | ### 5.1 读写锁的偏向性
129 |
130 | **考虑这种情况**:
131 |
132 | * t0:有读者在临界区
133 | * t1:有新的写者在等待
134 | * t2:另一个读者能否进入临界区?
135 |
136 | **不能**:偏向写者的读写锁
137 |
138 | * 后序读者必须等待写者进入后才进入
139 | * 更加公平
140 |
141 | **能**:偏向读者的读写锁
142 |
143 | * 后序读者可以直接进入临界区
144 | * 更好的并行性
145 |
146 |
147 |
148 | ### 5.2 偏向读者的读写锁
149 |
150 | 
151 |
152 | 具体解析见书P231
153 |
154 | > 为什么`lock_reader`中对reader的加一操作使用互斥锁而非原子指令FAA
155 |
156 | 因为后一句还要检查`lock->reader==1`要再次使用`reader`,要保证这两行操作的原子性
157 |
158 | > 既然读者也要一个读者锁, 那怎么提高读者的效率?
159 |
160 | 额外的读者锁,在临界区开始之前就已经执行了放锁操作,其保护的是对`reader_cnt`的修改
161 |
162 |
163 |
164 | ## 6. RCU
165 |
166 | **读写锁**:虽然允许多个读者同时进入读临界区,但是写会阻塞读者,且读者仍然需要在关键路径上添加读者锁
167 |
168 | **RCU**:读者即使在有写者写的时候也能随意读
169 |
170 | **需求**::需要一种能够类似之前硬件原子操作的方式,让读者要么看到旧的值,要么看到新的值,不会读到任何中间结果
171 |
172 | ▲ 单拷贝原子性:对地址对齐的单一读写操作的原子性保证,其支持尾款往往与CPU位宽一致
173 |
174 | ### 6.1 操作
175 |
176 | #### 6.1.1 插入新的节点
177 |
178 | 
179 |
180 | #### 6.1.2 删除
181 |
182 | 
183 |
184 | #### 6.1.3 修改
185 |
186 | 
187 |
188 | ### 6.2 宽限期
189 |
190 | **需求**:在合适的时间,回收无用的旧拷贝
191 |
192 | * 写者必须区分出有可能观察到旧数据的读者
193 | * 读者必须标记自己的读临界区开始与结束的位置
194 | * 使用接口`rcu_read_lock`和`rcu_read_unlock`标记
195 |
196 | ```c
197 | void rcu_reader() {
198 | RCU_READ_START(); // 通知RCU,读者进临界区了
199 | /* Reader Critical Section */
200 | RCU_READ_STOP(); // 通知RCU,读者出临界区了
201 | }
202 | ```
203 |
204 | (可以使用不同的方式实现:如计数器)
205 |
206 |
207 |
208 | ### 6.3 同x步原语对比:读写锁 vs RCU
209 |
210 | **相同点**:允许读者并行
211 |
212 | **不同点**:
213 |
214 | * 读写锁:
215 | * 读者也需要上读者锁
216 | * 关键路径上有额外开销
217 | * 方便使用
218 | * 可以选择对写者开销不大的读写锁
219 | * RCU
220 | * 读者无需上锁
221 | * 使用较繁琐
222 | * 写者开销大
223 |
224 |
225 |
226 | ## 7. 死锁
227 |
228 | ### 7.1 死锁产生原因
229 |
230 | 死锁是由于资源有限以及线程的交错执行造成的
231 |
232 | **必要条件**
233 |
234 | 1. 互斥访问
235 | 2. 持有并等待
236 | 3. 资源非抢占
237 | 4. 循环等待
238 |
239 | ### 7.2 出问题再处理:死锁的检测与恢复
240 |
241 | 
242 |
243 | 什么时候运行死锁检测:
244 |
245 | 1. 定时监测
246 | 2. 超时等待检测
247 |
248 |
249 |
250 | ### 7.3 设计时避免:死锁预防
251 |
252 | #### 7.3.1 避免互斥访问:通过其他手段(如代理执行)
253 |
254 | 共享数据只能通过代理线程来操作
255 |
256 | **问题**
257 |
258 | 1. 大部分应用不容易修改成此模式
259 | 2. 对于每一个共享资源,都需要一个代理线程,负担大
260 |
261 | #### 7.3.2 不允许持有并等待:一次性申请所有资源
262 |
263 | 在真正开始操作之前,一次性申请所有资源
264 |
265 | 
266 |
267 | **问题**:live lock
268 |
269 | #### 7.3.3 资源允许抢占:需要考虑如何恢复
270 |
271 | 允许一个线程抢占其他线程已经占有的资源
272 |
273 | **问题**
274 |
275 | 只适用于易于保存和恢复的场景
276 |
277 | #### 7.3.4 打破循环等待:按照特定顺序获取资源
278 |
279 | Ø 所有资源进行编号
280 |
281 | Ø 所有进程递增获取
282 |
283 | 任意时刻:获取最大资源号的进程可以继续执行,然后释放资源
284 |
285 | ### 7.4 死锁避免:运行时检查是否会出现死锁
286 |
287 | **银行家算法**
288 |
289 | * 所有进程获取资源需要通过管理者同意
290 | * 管理者预演会不会造成死锁
291 | * 如果会造成:阻塞进程,下次再给
292 | * 如果不会造成:给进程该资源
293 |
294 |
295 |
296 | ## 8. 优先级反转
297 |
298 | **出现原因**:双重调度不协调
299 |
300 | 操作系统:基于优先级调度
301 |
302 | 锁:对于竞争同一个资源的进程按照锁使用的策略进行“调度“
303 |
304 | 如何解决?打通两重调度,给另一个调度hint
305 |
306 | ### 8.1 不可打断临界区协议 NCP
307 |
308 | 进入临界区后不允许其他进程打断:禁止操作系统调度
309 |
310 | ### 8.2 优先级继承协议 PIP
311 |
312 | 高优先级进程被阻塞时,继承给锁持有者自己的优先级
313 |
314 | ### 8.3 即时优先级置顶协议 IPCP
315 |
316 | 获取锁时,给持有者该锁竞争者中最高优先级
317 |
318 | 
319 |
320 | ### 8.4 原生优先级置顶协议
321 |
322 | 高优先级进程被阻塞时,给锁持有者该锁竞争者中最高优先级
323 |
324 | 
325 |
326 | ### 8.5 对比
327 |
328 | **不可打断临界区协议 (Non-preemptive Critical Sections, NCP)**
329 |
330 | * 进入临界区后不允许其他进程打断:禁止操作系统调度
331 | * 易实现,但会阻塞系统正常运行(更高优先级的程序正常执行)
332 |
333 | **优先级继承协议 (Priority Inheritance Protocol, PIP)**
334 |
335 | * 高优先级进程被阻塞时,继承给锁持有者自己的优先级:锁给操作系统调度hint
336 | * 难实现,且每次有更高优先级的竞争者出现时都会被打断然后重新继承
337 |
338 | **即时优先级置顶协议 (Immediate Priority Ceiling Protocols, IPCP)**
339 |
340 | * 获取锁时,给持有者该锁竞争者中最高优先级:锁给操作系统调度hint
341 | * 易实现,但需要知道有哪些竞争者会竞争锁。直接给最高与NCP相同
342 |
343 | **原生优先级置顶协议 (Original Priority Ceiling Protocols, OPCP)**
344 |
345 | * 高优先级进程被阻塞时,给锁持有者该锁竞争者中最高优先级:锁给操作系统调度hint
346 | * 难实现,需要知道有哪些竞争者会竞争锁,一旦发生置顶便不会再被其他竞争者打断
347 |
348 |
349 |
350 | > 在课程中,我们了解了对读者友好的读写锁定的情况。 这种锁定会导致写者饿死,其原因是? 请提供一种对写者友好的读写锁定设计(避免写者饥饿)
351 |
352 | 基本思想是:当写者等待锁时,它将阻止以后想要获取锁的读者。以下为代码样例这是一个代码模板:
353 |
354 | 
355 |
356 |
357 |
358 |
--------------------------------------------------------------------------------
/12-文件系统崩溃一致性.md:
--------------------------------------------------------------------------------
1 | # 文件系统崩溃一致性
2 |
3 | ## 1. 崩溃一致性
4 |
5 | 1. **分配inode**:新的常规文件需要使用一个新的inode 结构进行保存。因此在创建文件时需先为新的文件分配新的inode 结构。
6 |
7 | 这一步骤需要在inode 分配器的位图(bitmap)中查找空闲的inode,并将其对应的比特位标记为1,标记该inode 已被使用。
8 |
9 | 2. **初始化inode**:在分配并得到inode 之后,文件系统需要对该inode 进行初始化操作,即将新文件的信息保存在inode 结构之中。
10 |
11 | 3. **增加目录项**:最后,需要在父目录中添加新的目录项,保存新文件的文件名和inode 号。
12 |
13 |
14 |
15 | **情况1** 只有分配inode 的操作保存在设备中,增加目录项的操作并没有保存在存储设备中
16 |
17 | 新创建的文件无法在文件系统中被观测到。但是由于对应的inode 在分配信息中已经被标记为占用,而实际上该inode 并未被任何文件所使用,该inode 将无法被释放,造成inode空间的泄漏。如果inode 空间被多次泄漏,文件系统中所能保存的文件越来越少。这种情况实际上违反了inode 分配信息与inode 结构的实际使用之间的一致性。
18 |
19 | **情况2** 只有初始化inode 的操作保存在设备中,增加目录项的操作并没有保存在存储设备中
20 |
21 | 新创建的文件无法在文件系统中被观测到。同时,由于inode 的分配信息未被修改,后续的创建文件操作依然可以使用到该inode 结构,因此并未产生空间泄漏。这种情况的出现并不会造成文件系统的一致性问题。
22 |
23 | **情况3** 只有增加目录项的操作保存在设备中
24 |
25 | 由于增加目录项的操作被持久化在存储设备中,当文件系统遍历该目录时,能够看到新文件对应的文件名和inode 号。
26 |
27 | 然而由于该inode 结构中的数据未被初始化,文件系统尝试访问该inode 时,会访问到未初始化的数据从而造成错误。同时,由于该inode 号对应的分配信息未被持久化,在后续的文件操作中,该
28 | inode 可能会被再次分配给其他文件,从而导致该inode 错误地被两个不同的文件共享,造成数据丢失、泄漏和被篡改等问题。
29 |
30 | 这种情况违反了inode 分配信息、inode 结构与目录项中所保存的inode 号之间的一致性,即所有目录项中所保存的inode 号皆应被分配且初始化。
31 |
32 | **情况4** 只有分配inode 的操作未保存在设备中
33 |
34 | 该情况下,由于分配inode 的信息未被持久化,会造成情况3中错误地指向未分配inode 结构的情况,从而造成正确性和安全性的问题。
35 |
36 | **情况5** 只有初始化inode 的操作未保存在设备中
37 |
38 | 与情况3相似,如果只有inode 的初始化操作未被持久化,则文件系统在后续访问该文件时,会访
39 | 问到未初始化的数据,从而产生错误。
40 |
41 |
42 |
43 | ## 2. 文件系统操作所要求的三个属性
44 |
45 | ```
46 | creat(“a”); fd = creat(“b”); write(fd,…); crash
47 | ```
48 |
49 | **持久化/Durable**: 哪些操作可见 a和b都可以
50 |
51 | **原子性/Atomic**: 要不所有操作都可见,要不都不可见 要么a和b都可见,要么都不可见
52 |
53 | **有序性/Ordered**: 按照前缀序(Prefix)的方式可见 如果b可见,那么a也应该可见
54 |
55 |
56 |
57 | ## 3. 崩溃一致性保障方法
58 |
59 | * 原子更新技术
60 | * 日志
61 | * 写时复制
62 | * Soft updates
63 |
64 |
65 |
66 | ## 4. 日志
67 |
68 | ### 4.1 日志的生命周期
69 |
70 | **创建**:在使用日志之前,需要先进行日志的创建和初始化操作。在此过程中,文件系统会为日志分配内存和存储空间,并初始化维护日志所需的元数据。此后,日志进入到写入阶段。
71 |
72 | **写入**:在写入阶段,文件系统可以将要进行的操作或操作影响到的数据及其所在位置写入到日志中。举例来说,如果文件系统想要将位置0x50处的数据A改成B,其需要在日志中记录“位置0x50上的数据从A修改为B”。
73 | 需要注意的是,由于操作数量较多或者操作的数据量较大,在写入阶段记录的操作信息(即日志内容)往往超出存储设备的原子写入大小,因而无法原子地写入到存储设备中。因此,在崩溃后,存储设备中处于写入阶段的日志内容可能是不完整的。为了防止这些不完整的日志内容造
74 | 成一致性问题,在进行恢复时,处于写入阶段的日志的内容会被直接丢弃。换句话说,在写入阶段的日志内容一般不会马上生效。这些日志内容的生效需要等到日志的提交阶段。
75 |
76 | **提交**:在提交阶段中,文件系统需要将此前在日志中记录的操作信息原子地标记为有效。在日志提交成功后,日志中所记录的信息在进行崩溃恢复时才会发挥效用。具体来说,在进行提交前,文件系统需要将日志内容以固定的格式,持久且完整地保存在存储设备中。在保证所有日志内容均已持久化在存储设备中之后,日志系统将在存储设备上原子地标记日志为已提交状态。在日志提交完成后,日志进入完成阶段。
77 |
78 | **完成**:在完成阶段,文件系统可以将实际的修改写回到存储设备之中。由于这些写回的位置及其信息均在日志中有所记录,若在此阶段发生崩溃,在进行恢复时,会使用日志中记录的内容进行恢复,保证日志中所记录操作的原子性。
79 |
80 | **销毁**:日志的记录需要占用一定的内存和存储资源。日志无效后,文件系统可以对日志所占用的这些资源进行回收,即销毁日志。一般情况下,这一过程可以通过批量化的形式延迟完成。
81 |
82 |
83 |
84 | ### 4.2 问题
85 |
86 | 1. 每个操作都要写磁盘,内存缓存的优势被抵消
87 | 2. 每个修改都要拷贝新数据到日志
88 | 3. 相同块的多个修改被记录多次
89 |
90 |
91 |
92 | ### 4.3 优化
93 |
94 | #### 4.3.1 利用内存中的页缓存
95 |
96 | 
97 |
98 | **缺点**:
99 |
100 | 1. 丢的多
101 | 2. 依然要写两次磁盘
102 |
103 |
104 |
105 | #### 4.3.2 批量处理日志以减少磁盘写
106 |
107 |
108 |
109 | ### 4.4 日志提交的触发条件
110 |
111 | * 定期触发
112 | * 每一段时间(如5s)触发一次
113 | * 日志达到一定量(如500MB)时触发一次
114 | * 用户触发
115 | * 例如:应用调用fsync()时触发
116 |
117 |
118 |
119 | ### 4.5 Linux中的日志系统JBD2
120 |
121 | Journal:日志,由文件或设备中某区域组成
122 |
123 | Handle:原子操作,由需要原子完成的多个修改组成
124 |
125 | Transaction:事务,多个批量在一起的原子操作
126 |
127 |
128 |
129 | #### 4.5.1 JBD2事务的状态
130 |
131 | 
132 |
133 |
134 |
135 | #### 4.5.2 JBD2部分接口和使用方法
136 |
137 | 
138 |
139 |
140 |
141 | #### 4.5.3 JBD2日志的磁盘结构
142 |
143 | 
144 |
145 |
146 |
147 | #### 4.5.4 Ext4用JBD2实现的三种日志模式
148 |
149 | 
150 |
151 | **writeback**: 在该模式下,Ext4 对于文件数据部分的修改不进行日志记录,且对文件数据的写回没有特定的要求。在这种模式下,文件的数据块修改可以在任何时候写入到磁盘中进行持久化。这种模式的一致性保证比较差。在发生崩溃的时候,有更大的概率发生不一致的情况。但是
152 | 由于对于写回顺序没有特殊要求,这种模式的性能往往是最优的。
153 |
154 | **ordered**: 在该模式下,Ext4 对于文件数据部分的修改同样不进行日志记录,但是对于一个文件的数据和元数据修改,需要保证文件数据部分的修改在文件元数据被持久化前持久化到设备中。这一顺序的保证,可以减少数据不一致的情况出现,在一定程度上增强文件系统对一致性的保
155 | 证。该模式是Ext4 中的默认模式,也是一种性能和一致性保证之间的权衡。
156 |
157 | **journal**: 在该模式下,Ext4 文件中的数据和元数据修改均使用日志进行保护。这是一种一致性保证很强的模式。但是它要求文件数据在日志中和原位置上进行两次写入。因此,对于产生大量文件数据修改的场景来说,这种模式会带来不小的性能开销和不必要的写入操作。
158 |
159 |
160 |
161 | ### 4.6 小结
162 |
163 | 日志是一种保证原子更新和崩溃一致性的常用方法。通过日志记录,可以保证任意位置任意数量操作的原子完成。然而,使用日志同样会带来一些问题。首先,日志要求所有的修改先在日志中进行记录,再将修改应用到其原有位置上。这样所有的修改都执行了两次:一次在日志中,另外一次在原位置上。因此,对于数据修改较多的场景,使用日志保证原子性可能会带来很大的写入开销。另外,日志的原子性保证依赖于日志恢复,因此在发生崩溃并重启后,需要首先进行日志的恢复。只有日志恢复完毕之后,文件系统才能开始处理新的请求。若日志恢复时间较长,整个文件系统将长时间处于不可用状态,进而影响到操作系统或应用程序的启动时间。
164 |
165 |
166 |
167 | ## 5. 写时拷贝
168 |
169 | ### 5.1 更新传递
170 |
171 | 
172 |
173 | 在树形数据结构中,写时拷贝往往需要更新到根节点才会停止。我们修改节点C 和E 后,需要继续修改节点B、D 和A(根节点)。这种修改一些节点后,还需要进一步修改数据结构中其他节点以保证原子更新的问题,便称为更新传递问题。
174 |
175 | **解决**:
176 |
177 | 解决更新传递问题,一种常见的方法是提前进行原子更新。对于树形数据结构来说,并非只有指向树根的指针才可以被原子更新;在上述例子中,原子更新的粒度为一个节点。若在更新传递的过程中,所有要进行的修改被一个原子更新粒度所覆盖,则可以直接原地进行修改,无需继续传递更新。例如在图13.3中,在节点A 中保存了指向节点B 和D 的指针。由于节点A 可以被原子更新,我们可以通过一次原地更新操作,原子地修改A 中指向节点B 和D的指针,让它们指向节点B’和D’,从而避免继续使用写时拷贝技术,停止更新传递。
178 |
179 |
180 |
181 | ### 5.2 写放大
182 |
183 | 写放大问题是指实际修改数据量大于用户要修改的数据量的情况。如在进行以4KB 内存页为粒度的写时拷贝技术时,若想要修改某个页中的1 个字节,我们不得不拷贝整个4KB 内存页中的所有数据,因此修改量从1 个字节被放大到4KB。写时拷贝技术中的更新传递,会使写放大问题更加严重。
184 |
185 | **解决**:
186 | 当要修改的数据覆盖了完整的原子更新粒度时,可以无需拷贝原有数据,直接写入新的数据,从而在一定程度上缓解写放大问题。例如在图13.3中,由于原子更新粒度为一个节点,且E 节点中所有的数据均需要被修改,在分配E’后,即使我们将E 中的数据拷贝到E’,这些数据也会被新数据全部覆盖掉。因此,在这种情况下,可以省略写时拷贝中的拷贝操作,从而减少写入操作。
187 |
188 |
189 |
190 | ### 5.3 写时拷贝在文件系统中的应用
191 |
192 | 
193 |
194 | 我们会将数据块C 和数据块D进行拷贝,并在拷贝副本上进行修改。由于指向数据块C 的指针保存在inode中,而指向数据块D 的指针保存在索引块中,我们需要继续对索引块进行写时拷贝。最终,需要修改的指向数据块C 的指针和指向索引块的指针均保存在inode 结构中。由于inode 结构通常小于存储设备的块大小,通过原子更新inode 结构,我们可以原子地持久化文件数据块C 和数据块D 上的数据修改。由于所有文件数据访问均从inode 结构出发,因此一个文件中的任何位置上的修改,均可以通过写时拷贝技术进行原子更新和持久化。
195 |
196 |
197 |
198 | ### 5.4 小结
199 |
200 | 写时拷贝技术对数据结构有一定的要求,当所有的修改最终能够变成一个原子修改时,才可以使用写时拷贝技术。此外,写时拷贝技术的使用与原子更新粒度有关。当修改的数据量远大于原子更新粒度时,往往只有数据头部和尾部需要真正进行拷贝操作,而中间部分可以根据前文提到的方法省略拷贝操作,直接使用新数据写入新分配出来的节点。这种情况下,写时拷贝技术产生的写放大相对较小。而当数据修改量小于原子更新粒度时,写时拷贝技术造成的写放大会非常严重,即使每次仅修改一个字节,也需要按照原子更新粒度进行数据拷贝。因此,是否适合使用写时拷贝,需要结合原子更新粒度和数据修改量综合考虑。
201 |
202 |
203 |
204 | ## 6. Soft Updates
205 |
206 | ### 6.1 Soft updates 的三条规则
207 |
208 | **规则1** 不要指向一个未初始化的结构。如:目录项指向一个inode 之前,该inode 结构应该先被初始化。
209 | **规则2** 一个结构被指针指向时,不要重用该结构。如:当一个inode 指向了一个数据块时,这个数据块不应该被重新分配给其他结构。
210 | **规则3** 对于一个仍有用的结构,不要修改最后一个指向它的指针。如:重命名文件时,在写入新的目录项前,不应删除旧的目录项。
211 |
212 |
--------------------------------------------------------------------------------
/6-进程.md:
--------------------------------------------------------------------------------
1 | # 进程
2 |
3 | ## 1. 进程
4 |
5 | ### 1.1 数据结构 Process Control Block
6 |
7 | * 存放进程相关的各种信息
8 |
9 | * 进程的标识符、内存、打开的文件
10 |
11 | * 进程在切换时的状态(▲ 上下文切换会写PCB哦)
12 |
13 | * PCB保存在内核中
14 |
15 | 
16 |
17 |
18 |
19 | ### 1.2 fork()
20 |
21 | **语义**:为调用进程创建一个一模一样的新进程
22 |
23 | * 调用进程为父进程,新进程为子进程
24 | * 接口简单,无需任何参数
25 | * fork后的两个进程均为独立进程
26 | * 拥有不同的进程id
27 | * 可以并行执行,互不干扰(除非使用特定的接口)
28 | * 父进程和子进程会共享部分数据结构(内存、文件等)
29 | * fork的父子进程有相同的PCB
30 | * PCB里存了fd
31 | * 父子进程共用文件的偏移量
32 |
33 | #### 1.2.1 写时拷贝(Copy-On-Write)
34 |
35 | **基本思路**:只拷贝内存映射,不拷贝实际内存
36 |
37 | * 性能较好:一条映射至少对应一个4K的页面
38 | * 调用exec的情况里,减少了无用的拷贝(因为在调用fork之后立即调用exec会重置地址空间,之前内存拷贝毫无意义)
39 |
40 | **fork的优点**
41 |
42 | * 接口非常简洁
43 | * 将进程“创建”和“执行”(exec)解耦,提高了灵活度
44 | * 刻画了进程之间的内在关系(进程树、进程组)
45 |
46 |
47 |
48 | ### 1.3 fork的替代接口
49 |
50 | #### 1.3.1 vfork
51 |
52 | **vfork**:类似于fork,但让父子进程共享同一地址空间。不会为子进程单独创建地址空间。因此父子进程中任一进程对内存的修改都会对另一进程产生影响。为了保证正确性,`vfork`会在结束后阻塞父进程,直到子进程调用`exec`或者退出为止。
53 |
54 | **优点**:
55 |
56 | * 连映射都不需要拷贝,性能更好
57 | * "`vfork`+`exec`"与"`fork`+`exec`"相比省去了一次地址空间拷贝
58 |
59 | **缺点**:
60 |
61 | * 只能用在”fork + exec”的场景中
62 | * 共享地址空间存在安全问题
63 |
64 | #### 1.3.2 posix_spawn
65 |
66 | posix_spawn是POSIX提供的另一种创建进程的方式,最初是为不支持fork的机器设计的
67 |
68 | 相当于fork + exec
69 |
70 | **优点**:
71 |
72 | * 可扩展性、性能较好
73 | * 执行时间与原进程的内存无关
74 |
75 | **缺点**
76 |
77 | * 定制参数表达能力有限,不如fork灵活
78 |
79 | #### 1.3.3 clone
80 |
81 | 类似于fork的精密控制版,允许应用程序通过参数对创建过程进行更多控制。
82 |
83 | ### 1.4 进程的执行 exec()
84 |
85 | 
86 |
87 | `exec`可以执行可执行文件
88 |
89 | 在`fork`后调用`exec`,在载入可执行文件之后会重置地址空间
90 |
91 | `exec`被调用时,操作系统:
92 |
93 | 1. 根据pathname指明的路径,将可执行文件的数据段和代码段载入当前进程的地址空间
94 | 2. 重新初始化堆栈(在这里,操作系统可以进行地址空间随机化ASLR,改变堆栈的起始地址)
95 | 3. 将PC寄存器设置到可执行文件代码段定义的入口点,该入口点最终会调用main
96 |
97 |
98 |
99 | ## 2. 线程
100 |
101 | ▲ 线程是调度的基本单位
102 |
103 | > 为什么需要线程
104 |
105 | 1. 创建进程的开销较大
106 | 2. 进程的隔离性过强(IPC)
107 | 3. 进程内部无法支持并行
108 |
109 | ### 2.1 多线程进程的地址空间
110 |
111 | 
112 |
113 | * 分离的内核栈和用户栈
114 |
115 | * 当线程切换到内核中执行时,它的栈指针就会切换到对应的内核栈
116 | * 一个线程栈对应一个内核栈
117 |
118 | * 共享的其他区域
119 |
120 | * 堆是共享的!(malloc)
121 |
122 | ### 2.2 用户态线程与内核态线程
123 |
124 | 根据线程是用户态应用还是由内核创建管理,可以分成两类:
125 |
126 | * 内核态线程
127 | * 由内核创建,线程相关信息存放在内核中
128 | * 内核可见,受内核管理
129 | * 用户态线程
130 | * 在应用态创建,线程相关信息主要存放在应用数据中
131 | * 内核不可见,不受内核直接管理
132 |
133 | 与内核线程相比,用户态线程更加轻量级,创建开销更小,但功能也较为受限,与内核态相关的操作需要内核态线程协助才能完成。
134 |
135 | #### 2.2.1 线程模型
136 |
137 | 
138 |
139 | **多对一模型**
140 |
141 | 由于只有一个内核态线程,因此每次只有一个用户态线程可以进入内核,其他需要内核服务的用户态线程会被阻塞。
142 |
143 | * 优点:内核管理简单
144 | * 缺点:可扩展性差,无法适应多核机器的发展
145 |
146 | **一对一模型**
147 |
148 | * 优点:解决了多对一模型中的可扩展性问题
149 | * 缺点:内核线程数量大,开销大
150 |
151 | (主流操作系统都采用一对一模型)
152 |
153 | **多对多模型**
154 |
155 | * 优点:解决了可扩展性问题(多对一)和线程过多问题(一对一)
156 | * 缺点:管理更为复杂
157 |
158 | ### 2.3 线程的相关数据结构:TCB
159 |
160 | #### 2.3.1 一对一模型的TCB可以分为两部分
161 |
162 | * 内核态:与PCB结构类似
163 | * – Linux中进程与线程使用的是同一种数据结构(task_struct)
164 | * – 上下文切换中会使用
165 | * 应用态:可以由线程库定义
166 | * Linux:pthread结构体
167 | * Windows:TIB(Thread Information Block)
168 | * 可以认为是内核TCB的扩展
169 |
170 | #### 2.3.2 线程本地存储(TLS)
171 |
172 | 不同线程可能会执行相同的代码(线程不具有独立的地址空间,多线程共享代码段),对于全局变量,不同线程可能需要不同的拷贝(用于标明系统调用错误的errno)。使用TLS可以很方便的实现线程内的全局变量。
173 |
174 | * 线程库允许定义每个线程独有的数据
175 | * __thread int id; 会为每个线程定义一个独有的id变
176 |
177 | * 每个线程的TLS结构相似
178 | * 可通过TCB索引
179 | * TLS寻址模式:基地址+偏移量
180 | * X86: 段页式 (fs寄存器)
181 | * AArch64: 特殊寄存器tpidr_el0
182 |
183 |
184 |
185 | ### 2.4 线程的上下文切换
186 |
187 | #### 2.4.1 线程上下文内容
188 |
189 | 即重要寄存器信息
190 |
191 | * 常规寄存器:x0-x30
192 | * 程序计数器(PC): elr_el1
193 | * 栈指针:sp_el0
194 | * CPU状态(如条件码):spsr_el1
195 |
196 | #### 2.4.2 ChCore的TCB结构
197 |
198 | 内核态TCB:
199 |
200 | 
201 |
202 | * 上半部分:线程的相关信息
203 | * 下半部分:线程上下文
204 | * TCB下面为线程的内核栈
205 | * 刚进入内核时的线程内核栈为空
206 | * sp_el1指向栈顶
207 |
208 | #### 2.4.3 线程上下文切换步骤
209 |
210 | ##### 第一步:进入内核态、保存上下文
211 |
212 | 应用线程可通过异常、中断或系统调用进入内核态
213 |
214 | * 运行状态将切换到内核态(EL1)
215 | * 开始使用sp_el1作为栈指针(用户栈切换到内核栈)
216 | * 保存应用线程的PC(elr_el1)
217 | * 保存应用线程的CPU状态(spsr_el1)
218 | * 以上均由硬件自动完成
219 |
220 | ##### 第二步:切换页表和内核栈
221 |
222 | * 操作系统确定下一个被调度的线程(调度器决定)
223 | * 切换页表
224 | * 将页表相关寄存器的值置为目标线程的页表基地址
225 | * 切换内核栈 – 找到目标内核栈的栈顶指针(目标线程的TCB)
226 | * 修改sp_el1的值至目标内核栈
227 | * 🔺可以认为是线程执行的分界点(切换之后变为目标线程执行)
228 |
229 | ##### 第三步:上下文恢复,返回用户态
230 |
231 | * 上下文恢复:取出栈上的值并存回寄存器
232 | * 返回用户态:调用eret,由硬件执行一系列操作
233 | * 将elr_el1中的返回地址存回PC
234 | * 改为使用sp_el0作为栈指针(内核栈切换到用户栈)
235 | * 将CPU状态设为spsr_el1中的值
236 | * 运行状态切换为用户态(EL0)
237 |
238 | ##### 小结
239 |
240 | 🔺 共涉及两次权限等级切换、三次栈切换
241 |
242 | 🔺 内核栈的切换是线程切换执行的“分界点
243 |
244 | 
245 |
246 |
247 |
248 | ## 3. 纤程
249 |
250 | ### 3.1 一对一线程模型的局限
251 |
252 | * 复杂应用:对调度存在更多需求
253 | * 生产者消费者模型:生产者完成后,消费者最好马上被调度
254 | * 内核调度器的信息不足,无法完成及时调度
255 | * “短命”线程:执行时间亚毫秒级(如处理web请求)
256 | * 内核线程初始化时间较长,造成执行开销
257 | * 线程上下文切换频繁,开销较大
258 |
259 | ### 3.2 纤程(用户态线程)
260 |
261 | **比线程更加轻量级的运行时抽象**
262 |
263 | * 不单独对应内核线程
264 | * 一个内核线程可以对应多个纤程(多对一)
265 |
266 | **优点**
267 |
268 | 1. 不需要创建内核线程,开销小
269 | 2. 上下文切换快(不需要进入内核)
270 | 3. 允许用户态自主调度,有助于做出更优的调度决策
271 |
272 | ### 3.3 例子
273 |
274 | 
275 |
276 | **优势**
277 |
278 | * 纤程切换及时
279 | * 当生产者完成任务后,可直接用户态切换到消费者
280 | * 对该线程来说是最优调度(内核调度器和难做到)
281 | * 高效上下文切换
282 | * 切换不进入内核态,开销小
283 | * 即时频繁切换也不会造成过大开销
284 |
285 |
286 |
287 | > 在Linux中,若在一个多线程进程的某个线程中使用fork创建一个子进程,则在子进程中会存在几个线程?创建出的子进程可能会导致什么问题?Linux为何要如此设计Fork的语义
288 |
289 | (1)只有调用fork的线程会存在于子进程中。
290 |
291 | (2)于此同时,由于fork的调用会拷贝整个内存空间的内容(包括锁、条件变量等)。因此,若调用fork的线程希望获得另一个线程所持有的锁时,会触发死锁。
292 |
293 | (3)(一种可能的解释):Linux如此设计fork是出于性能上的考虑。若希望在fork时对所有线程都进行复制,则需要确保所有线程被冻结在一个可被拷贝的状态,之后才能对所有线程进行状态复制。这一冻结、拷贝过程会消耗大量的时间。对fork的常见用法(首先使用fork创建新进程,之后使用exec执行这一进程)而言,对所有线程进行拷贝是昂贵且无用的,因此在当前的Linux设计中并没有如此实现。
294 |
295 |
296 |
297 | > 对于分配大量内存的应用,为何及时采用了写时拷贝(Copy-on-write,COW)优化后,`fork`的性能仍然比`vfork`要差?请尝试利用`vfork`的思路对`fork`进行优化,从而在不改变`fork`语义的前提下,提升`fork`的性能
298 |
299 | 原因:相较于使用了写时拷贝优化的fork而言,vfork更进一步,消除了对内存映射关系(页表)的拷贝,因此,对于消耗了大量内存的应用而说,fork对页表的拷贝仍然需要消耗一定时间,其性能相较于vfork更慢
300 |
301 | 可能优化方法:在fork时,可以选择仅拷贝初级页表,在后续第一次访问某一内存地址时,再对后续几级的页表进行拷贝。
302 |
303 |
304 |
305 | > 在像`ChCore`一样的单进程多线程的操作系统中,操作系统通常以线程为粒度进行调度。在一些调度的实现中,在从属于同一个进程的两个不同线程之间进行上下文切换所消耗的时间比在从属于不同进程的两个不同线程之间进行切换耗时更低,试解释其原因
306 |
307 | 从属于同一个进程的线程共享同一个地址空间与内存映射,因此,在同一进程内不同线程间进行上下文切换时,无需进行页表切换、TLB刷新等操作,相较而言性能更快
308 |
309 |
310 |
311 | > 对于像协程一样的用户态线程而言,由于所有的协程均从属于同一个内核态线程,因此同一时间仅能同时运行一个协程。在这一情况下,为何协程仍然能够提升应用性能?对于哪类应用,协程能够尽可能高的提升系统性能
312 |
313 | (1)由于频繁的线程创建和线程上下文切换会消耗一些时间,因此,对于需要创建大量运行时间极短的线程的应用(如网络服务器等)而言,使用协程能够减少此类开销,提升性能。
314 |
315 | (2)在应用程序内使用协程后,应用程序能够基于其逻辑进行更加合理的调度,在很多情况下更能够提升性能。如一个生产者、消费者的例子中,可以使用协程,确保消费者在生产者之后进行执行
316 |
317 |
--------------------------------------------------------------------------------
/17-轻量级虚拟化.md:
--------------------------------------------------------------------------------
1 | # 轻量级虚拟化
2 |
3 | ## 1. FaaS与Serverless
4 |
5 | **云厂商普遍用虚拟化来隔离**
6 |
7 | * 优势
8 | * 可以运行完整的软件栈,包括不同的操作系统
9 | * 灵活的整体资源分配(支持动态迁移)
10 | * 方便的添加、删除、备份(只需文件操作)
11 | * 虚拟机之间的强隔离(唯一能抵御 fork bomb 的方法)
12 | * 问题:太重
13 | * 云:性能损失,尤其是I/O虚拟化
14 | * 用户:两层操作系统导致资源浪费
15 |
16 | **函数即服务的特点**
17 |
18 | * orkload特点 –
19 | * 无状态(stateless)
20 | * 运行时间非常短(秒级)
21 | * 两个重要的性能指标
22 | * 启动时间
23 | * 运行密度
24 |
25 |
26 |
27 | ## 2. CHROOT
28 |
29 | **Chroot效果**
30 |
31 | * 控制进程能够访问哪些目录子树
32 | * 改变进程所属的根目录
33 | * 进程只能看到根目录下属的文件
34 |
35 | **Chroot原理**
36 |
37 | * 进程只能从根目录向下开始查找文件
38 | * 操作系统内部修改了根目录的位置
39 | * 一个简单的设计
40 | * 内核为每个用户记录一个根目录路径
41 | * 进程打开文件时内核从该用户的根目录开始查找
42 | * 上述设计有什么问题?
43 | * 遇到类似“..”的路径会发生什么?
44 | * 特殊检查根目录下的“..”
45 | * 使得“/..”与“/”等价
46 | * 无法通过“..”打破隔离
47 | * 一个用户想要使不同进程有不同的根目录怎么办?
48 | * 每个TCB都指向一个root目录
49 | * 一个用户可以对多个进程chroot
50 |
51 |
52 |
53 | ## 3. LinuX Container (LXC)
54 |
55 | * 安全隔离 – 基于namespace机制
56 | * 性能隔离 – Linux cgroup
57 | * 
58 |
59 |
60 |
61 | ### 3.1 Mount Namespace
62 |
63 | * 容器内外可部分共享文件系
64 | * 假设主机操作系统上运行了一个容器
65 | * Step-1:主机OS准备从/mnt目录下的ext4文件系统中读取数据
66 | * Step-2:容器中进程在/mnt目录下挂载了一个xfs文件系统
67 | * Step-3:主机操作系统可能读到错误数据
68 |
69 | **实现**
70 |
71 | * 设计思路
72 | * 在内核中分别记录每个NS中对于挂载 点的修改
73 | * 访问挂载点时,内核根据当前NS的记 录查找文件
74 | * 每个NS有独立的文件系统树
75 | * 新NS会拷贝一份父NS的文件系统树
76 | * 修改挂载点只会反映到自己NS的文件系统树
77 |
78 | 
79 |
80 | ### 3.2 IPC Namespace
81 |
82 | * 假设有两个容器A和B
83 | * A中进程使用名为“ my_mem ”共享内存进行数据共享
84 | * B中进程也使用名为“ my_mem ”共享内存进行通信
85 | * B中进程可能收到A中进程的数据,导致出错以及数据泄露
86 |
87 | **不好的设计**
88 |
89 | * 在内核中创建IPC对象时,贴上对应NS的标签
90 | * 进程访问IPC对象时内核来判断是否允许访问该对象
91 | * **问题**:可能有timing side channel隐患 ;对于同名的IPC对象不好处理
92 |
93 | **IPC Namespace的实现**
94 |
95 | * 使每个IPC对象只能属于一个NS
96 | * 每个NS单独记录属于自己的IPC对象
97 | * 进程只能在当前NS中寻找IPC对象
98 | * 
99 |
100 | ### 3.3 Network Namespace
101 |
102 | * 假设有两个容器均提供网络服务
103 | * 两个容器的外部用户向同一IP发送网络服务请求
104 | * 主机操作系统不知道该将网络包转发给哪个容器
105 |
106 | **虚拟机**
107 |
108 | 
109 |
110 | **Network Namespace的实现**
111 |
112 | * 每个NS拥有一套独立的网络资源
113 | * 包括IP地址、网络设备等
114 | * 新NS默认只有一个loopback设备
115 | * 其余设备需后续分配或从外部加入
116 | * 图例
117 | * 创建相连的veth虚拟设备对
118 | * 一端加入NS即可连通网络
119 | * 分配IP后可分别与外界通信
120 |
121 | 
122 |
123 | ### 3.4 PID Namespace
124 |
125 | * 假设有容器内存在一个恶意进程
126 | * 恶意进程向容器外进程发送SIGKILL信号
127 | * 主机操作系统或其他容器中的正常进程会被杀死
128 |
129 | * 直接的想法
130 | * 将每个NS中的进程放在一起管理,不同NS中的进程相互隔离
131 | * 存在的问题
132 | * 进程间关系如何处理(比如父子进程)?
133 | * 更进一步
134 | * 允许父NS看到子NS中的进程,保留父子关系
135 |
136 | **PID Namespace的实现**
137 |
138 | ****
139 |
140 | ### 3.5 User Namespace
141 |
142 | * 假设一个恶意用户在容器内获取了root权限
143 | * 恶意用户相当于拥有了整个系统的最高权限
144 | * 可以窃取其他容器甚至主机操作系统的隐私信息
145 | * 可以控制或破坏系统内的各种服务
146 |
147 | **User Namespace的实现**
148 |
149 | * 对NS内外的UID和GID进行映射
150 | * 允许普通用户在容器内有更高权限
151 | * 基于Linux Capability机制
152 | * 容器内root用户在容器外无特权
153 | * 只是普通用户
154 | * 图例 – 普通用户在子NS中是root用户
155 |
156 |
157 |
158 | > 如果容器内root要执行特权操作怎么办?
159 |
160 | * insmod?一旦允许在内核中插入驱动,则拥有最高权限
161 | * 关机/重启?整个云服务器会受影响
162 |
163 | 1. 从内核角度来看,仅仅是普通用户
164 |
165 | 2. 限制系统调用 – Seccomp机制
166 |
167 | ### 3.6 UTS Namespace
168 |
169 | * 每个NS拥有独立的hostname等名称
170 | * 便于分辨主机操作系统及其上的多个容器
171 |
172 | ### 3.7 Cgroup Namespace
173 |
174 | * cgroupfs的实现向容器内暴露cgroup根目录
175 | * 增强隔离性:避免向容器内泄露主机操作系统信息
176 | * 增强可移植性:取消cgroup路径名依赖
177 |
178 |
179 |
180 | ## 4. 执行环境间的性能隔离
181 |
182 | ### 4.1 Cgroups
183 |
184 | * Cgroups是Linux内核(从Linux2.6.24开始)提供的一种资源隔离的功能
185 | * Cgroups可以做什么
186 | * 将线程分组
187 | * 对每组线程使用的多种物理资源进行限制和监控
188 | * 怎么用Cgroups
189 | * 名为cgroupfs的伪文件系统提供了用户接口
190 |
191 | ### 4.2 Cgroups的常用术语
192 |
193 |
194 |
195 | ## 5. 基于硬件ENCLAVE的隔离
196 |
197 | **硬件提供不同粒度的隔离环境**
198 |
199 | 
200 |
201 | **Enclave的隔离方法**
202 |
203 | 1. 基于权限控制 – 使操作系统没有权限访问用户的数据
204 | 2. 基于加密 – 操作系统即使访问用户数据,也无法解密
205 | 3. 基于权限控制+加密 – 隔离防御软件攻击,加密防御硬件攻击
206 |
207 | ### 5.1 基于权限隔离的隔离方法
208 |
209 | 1. 基于**预留**的隔离(硬件)
210 | * 例如:PRM(Processor Reserved Memory)
211 | * CPU预留一部分物理内存,不提供给操作系统
212 | 2. 基于**页表**的隔离(操作系统)
213 | * 例如:保证操作系统无法映射应用的物理内存页
214 | * 问题:页表是由操作系统自己管理的,监守自盗?
215 | 3. 基于**插桩**的隔离(编译器)
216 | * 例如:SFI(Software Fault Isolation)
217 | * 在每次访存前插入边界检查,性能损失较大
218 |
219 | **Intel SGX**
220 |
221 | * SGX: Software Guard eXtension
222 | * 2015年首次引入Intel Skylake架构
223 | * 保护程序和代码在运行时的安全(data in-run)
224 | * 其他安全包括:存储时安全和传输时安全
225 | * 关键技术 – Enclave内部与外部的隔离 – 内存加密与完整性保护 – 远程验证
226 |
227 | ### 5.2 硬件内存加密与保护机制
228 |
229 | * 硬件加密保护隐私性
230 | * CPU外皆为密文,包括内存、存储、网络等
231 | * CPU内部为明文,包括各级Cache与寄存器
232 | * 数据进出CPU时,由进行加密和解密操作
233 | * 硬件Merkle Tree保护完整性
234 | * 对内存中数据计算一级hash,对一级hash计算二级hash,形成树
235 | * CPU内部仅保存root hash,其它hash保存在不可信的内存中
236 | * 当内存中的数据被修改时,更新Merkle Tree
237 |
238 | ### 5.3 Enclave与进程的关系
239 |
240 | * Enclave是进程的一部分
241 |
242 | * Enclave内外共享一个虚拟地址空间
243 | * Enclave内部可以访问外部的内存 • 反之则不行
244 |
245 | * 创建Enclave的过程
246 |
247 | 1. OS创建进程
248 |
249 | 2. OS分配虚拟地址空间
250 |
251 | 3. OS将Enclave的code加载到EPC中
252 |
253 | 并将EPC映射到Enclave的虚拟地址
254 |
255 | 循环3,完成所有code加载和映射
256 |
257 | 4. 完成进程创建
258 |
259 | 
260 |
261 | ### 5.4 远程验证(Remote Attestation)
262 |
263 | > 要解决的问题:如何远程判断某个主体是Enclave?
264 |
265 | * 例如,如何判断某个在云端的服务运行环境是安全的
266 | * 必须在认证之后,再进行下一步的操作,例如发送数据
267 | * 
268 |
269 | ### 5.5 AMD SEV
270 |
271 | 
272 |
273 | ### 5.6 RISC-V平台的Enclave
274 |
275 | 
276 |
277 |
278 |
279 | > 假设同一台物理机上运行着20台虚拟机,每台虚拟机内部有20个进程。若我们采用影子页表(Shadow Page Table)的方式实现内存虚拟化,则共需要多少个影子页表?若采用第二阶段页表的方式来实现内存虚拟化,则共需要多少个第二阶段页表?
280 |
281 | 影子页表:20*20=400(每个进程一个影子页表)
282 |
283 | 第二阶段页表:20(每个虚拟机一个第二阶段页表)。
284 |
285 |
286 |
287 | > 第二阶段页表和影子页表的性能表现在不同种类的应用上各有千秋,请列举两者各适用于什么种类的应用并说明原因。
288 |
289 | 对于第二阶段页表而言,由于其内存地址翻译既需要查询第一阶段页表有需要查询第二阶段页表,将GVA转化为HPA的过程要更慢;
290 |
291 | 对于影子页表而言,每当从GVA到GPA的内存映射发生改变时,都需要虚拟机管理器对影子页表进行修改,修改过程开销更高。
292 |
293 | 因此,对于TLB miss经常发生,需要经常查询页表进行地址翻译的应用而言,采用影子页表更加高效;对于内存映射经常改变的应用而言,则更适合采用第二阶段页表
294 |
295 |
296 |
297 | > IOMMU是在IO虚拟化中广泛应用的一类硬件。为何在IO虚拟化中要引入IOMMU这类硬件
298 |
299 | IOMMU用于保护虚拟机的隐私内存不会被其他虚拟机发起的恶意DMA请求所访问。SMMU内存储着GPA与HPA,负责从IOVA->GPA->HPA的地址翻译。
300 |
301 |
302 |
303 | > 容器或虚拟机两种技术通常被用于位于同一物理机上的不同执行环境之间的隔离,此外,近年来新出现的gVisor、AWS Firecracker也已被广泛应用与隔离执行环境中。请尝试分析四种方法在隔离性上的区别
304 |
305 | 通常而言,操作系统提供给用户程序的接口多于虚拟机管理器提供给虚拟机的接口数目,且操作系统内核的代码相较于虚拟机的代码更为复杂,因此,共享同一个操作系统内核的**容器**相较于传统的虚拟机而言隔离性更弱。
306 |
307 | 为了解决共享操作系统内核的问题,**gVisor**通过插桩系统调用等方式,在容器的基础上减少了不同实例间共享的接口的数目,因此,隔离性相对于容器而言更强。
308 |
309 | 而**AWS Firecracker**则采用了在host的用户空间内共享网桥等方式减少虚拟机的启动等开销,作为代价,不同实例间共享了更多接口的Firecracker的隔离性可能弱于传统的虚拟机。
--------------------------------------------------------------------------------
/13-新型文件系统.md:
--------------------------------------------------------------------------------
1 | # 新型文件系统
2 |
3 | ## 1. 日志文件系统 LFS
4 |
5 | 
6 |
7 | LFS 中的创建文件过程。新文件的文件名为“g”,inode 号为7
8 |
9 | 块10 和块11 为在此操作中未做修改的日志
10 |
11 | 块12 到14 为创建操作中被无效化的日志。块15 到18 为创建操作过程中新增的日志
12 |
13 |
14 |
15 | ### 1.1 空间管理
16 |
17 | 直接重用无效空间并不容易:
18 |
19 | 1. 日志中的有效数据和无效数据交织混杂在一起。想要重新利用日志中的空间,必须先识别出日志中哪些数据是有效的,哪些数据是无效的,只有无效数据所占用的空间才能进行重新利用
20 | 2. 日志中无效数据所占据的空间大多是分散而不连续的。如果直接使用这些空间,可能会引入大量随机磁盘写入而造成性能下降,这并不符合LFS 希望顺序写入日志的初衷
21 |
22 | 对于第一个问题,即有效数据识别问题,LFS 可通过**增加新的结构记录有效数据块位置**的方式来解决。现在我们先假设LFS 已经能够快速识别日志空间中的有效数据,并以此为前提来看第二个问题
23 |
24 | 对于第二个问题,即无效数据占据的空闲空间的组织问题,有两种比较简单的解决方法:
25 |
26 | 1. **空闲链表**:一种比较直观的方法,是将空闲的空间使用链表连接起来。当文件系统需要使用新的空间时,可以从链表中拿出一块空闲空间,作为接下来的日志空间进行使用;当一段空闲空间使用完之后,再从链表中找出另一块空间继续使用
27 | 2. **空间整理**:假设数据保存在存储设备A 上,我们可以再找一个同等容量的设备B,然后顺序扫描存储设备A 上的日志空间,将有效数据依次移动到设备B 的日志空间中。整理完成后,有效数据全部都保存在了设备B 的日志空间的前一半部分空间,无效数据所占用的空间便被“移动”到了设备B 的尾部。
28 |
29 | **优劣**:
30 |
31 | 空间链表:
32 |
33 | 1. 操作比较简单
34 | 2. 但是随着文件系统不断被使用,整个空间越来越“碎片化“:磁盘中大段连续的空闲空间越来越少,顺序写入越来越困难,取而代之的是大量的随机写入,这导致LFS 的优势完全消失。此外,虽然空闲链表维护起来非常方便,但是用于存放有效数据的有效空间越来越零碎,这导致有效数据段的维护变得越来越复杂且耗时。
35 |
36 | 空间整理:
37 |
38 | 1. 保证每次整理后有效数据和空闲区域分别都是连续的
39 |
40 | 这保证了LFS 总是能够进行大量的顺序写操作,从而保持了LFS 的优势
41 |
42 | 2. 每次在整理空间时,都需要扫描整个空间;而且,由于此过程中需要整理和移动有效数据,整个过程会非常耗时
43 |
44 | 为了平衡这两种方法,LFS 提出了段(Segment)的概念,以求既能同时拥有这两种方法的优点,又尽可能降低两者的缺点。
45 |
46 | ### 1.2 段
47 |
48 | * 设备被拆分为定长的区域,称为段
49 | * 段大小需要足以发挥出顺序写的优势,512KB、1MB等
50 | * 每段内只能顺序写入
51 | * 只有当段内全都是无效数据之后,才能被重新使用
52 | * 干净段用链表维护(对应串联方法)
53 |
54 |
55 |
56 | #### 1.2.1 段使用表
57 |
58 | 
59 |
60 |
61 |
62 | #### 1.2.2 段清理
63 |
64 | 
65 |
66 |
67 |
68 | #### 1.2.3 识别有效数据 段概要
69 |
70 | 
71 |
72 | ▲ 2份元数据 -> 数据一致性
73 |
74 |
75 |
76 | ### 1.3 检查点
77 |
78 | ▲ 由于LFS 中许多结构并没有固定位置,LFS 在进行挂载和崩溃恢复时,需要扫描存储空间中的所有日志,才能在内存中重构出文件系统结构的缓存。扫描整个存储空间比较耗时,造成文件系统的
79 | 挂载甚至整个系统的启动时间的延长。为了提升挂载和恢复的效率,,LFS 使用了检查点(Checkpoint)和前滚(Roll-forward)两种技术
80 |
81 |
82 |
83 | LFS 创建检查点的过程分为两步:
84 |
85 | 1. 由于文件系统会在内存结构中缓存部分修改,LFS 在创建检查点时,需要先将所有的修改追加到日志中,包括文件数据块、索引块、inode 结构、inode 表和段使用表
86 | 2. 当所有的修改均已写入日志后,LFS 在检查点区域固定的位置写入一个检查点。其内容包括inode 表的地址,段使用表的地址、当前时间和最后一个写入的段的位置
87 |
88 | 一旦检查点创建完毕,在进行挂载和恢复时,LFS 无需扫描整个存储空间。其只需要找到检查点,并根据检查点中记录的结构找出检查点创建时的有效数据即可。检查点避免了LFS 对于无效数据的扫描,从而缩短了挂载和恢复的时间
89 |
90 | 🔺 由于检查点决定了挂载时文件系统中的有效数据,检查点自身的数据完整性也十分重要。如果在创建检查点时发生崩溃,文件系统可能会使用一个不完整的检查点,从而造成文件系统格式损坏和数据丢失。为了保证检查点数据的完整性,LFS 交替使用两个不同的位置创建检查点 -> 不会覆盖
91 |
92 |
93 |
94 | ### 1.4 前滚
95 |
96 | 检查点只能将文件系统恢复到创建检查点时的状态,如果想要恢复那些在创建检查点之后写入日志的修改,还需要进行前滚操作
97 |
98 | 前滚操作在检查点恢复之后进行,其通过扫描在检查点之后写入的日志,尽可能恢复在检查点之后进行的修改。具体来说,前滚操作会找到检查点之后修改过的段,并读取其中的段概要进行恢复。例如,如果在段概要中记录了一个新创建的、不在inode 表中的inode 结构,前滚操作会将新的inode 结构添加到inode 表中。考虑到日志的写入顺序,将inode 结构恢复之后,inode结构所引用的那些数据块和索引块,连带被进行了恢复。当然,此时恢复的inode 结构可能由于目录项的丢失而处于从根开始的文件系统树之外,这就引出了另外一个问题:**目录项和inode 的一致性问题**。
99 |
100 | 情况:inode被持久化,但是指向其的目录项未被持久化
101 |
102 | 
103 |
104 | **解决** : 目录修改日志
105 |
106 | * 目录修改日志
107 | * 记录了每个目录操作的信息
108 | * create、link、rename、unlink
109 | * 以及操作的具体信息
110 | * 目录项位置、内容、inode的链接数
111 | * 目录修改日志的持久化在目录修改之前
112 | * 恢复时根据目录修改日志保证inode的链接数是一致的
113 |
114 |
115 |
116 | ## 2. F2FS
117 |
118 | ### 2.1 闪存盘的性质
119 |
120 | * 非对称的读写与擦除操作
121 | * 页 (page) 是读写单元 (8-16KB)
122 | * 块 (block) 是擦除单元 (4-8MB)
123 | * Program/Erase cycles
124 | * 写入前需要先擦除
125 | * 每个块被擦除的次数是有限的
126 | * 随机访问性能
127 | * 没有寻道时间
128 | * 随机访问的速度提升,但仍与顺序访问有一定差距
129 | * 磨损均衡
130 | * 频繁写入同一个块会造成写穿问题
131 | * 将写入操作均匀的分摊在整个设备
132 | * 多通道
133 | * 高并行性
134 | * 异质Cell
135 | * 存储1到4个比特:SLC 、MLC、TLC、 QLC
136 |
137 | ### 2.2 Flash Translation Layer (FTL)
138 |
139 | * 逻辑地址到物理地址的转换
140 |
141 | * 对外使用逻辑地址
142 | * 内部使用物理地址
143 | * 可软件实现,也可以固件实现
144 | * 用于垃圾回收、数据迁移、磨损均衡(wear-levelling)等
145 |
146 |
147 |
148 | ### 2.3 LFS的问题
149 |
150 | 1. 递归更新问题
151 |
152 | 
153 |
154 | 2. 单一log顺序写入
155 |
156 | 无法利用到现代Flash设备的高并行性
157 |
158 |
159 |
160 | ### 2.4 F2FS的改进
161 |
162 | #### 2.4.1 NAT
163 |
164 | * 引入一层 indirection:NAT(node地址转换表)
165 | * NAT:Node Address Table
166 | * 维护node号到逻辑块号的映射
167 | * Node号需转换成逻辑块号才能使用
168 | * F2FS中的文件结构
169 | * 直接node:保存数据块的逻辑块号
170 | * 间接node:保存node号 (相当于索引块)
171 | * 数据块:保存数据
172 |
173 | ▲NAT 就相当于一个表格,记录了node到逻辑块号的映射
174 |
175 | 本来一个数据块修改了,它的逻辑块号变了,指向它的索引块要修改,二级索引块也要修改,二 级索引块的逻辑块号变了那么,inode也要变,inode map也要变
176 |
177 | 现在inode里面记录了间接node的node号,要通过NAT表翻译得到间接node的逻辑块号来找到 间接node,间接node记录了直接node的node号,也要通过NAT翻译得到直接node块号,直接 node里记录了数据块的逻辑块号。
178 |
179 | 当数据块修改时,相当于逻辑块号变了,所以直接node也要修改,这样直接node的逻辑块号也 变了,但是因为间接node是通过NAT翻译node得到直接node的位置的,所以间接node不用修 改,修改NAT表格就可以了,这样更新就不会一直向上传递了
180 |
181 | 
182 |
183 |
184 |
185 | #### 2.4.2 多log并行写入
186 |
187 | 
188 |
189 |
190 |
191 | ### 2.5 闪存友好的磁盘布局
192 |
193 | #### 2.5.1 闪存盘的组织
194 |
195 | * 通道(Channel) – 控制器可以同时访问的闪存芯片数量
196 | * 多通道(Multi-channel) – 低端盘有2或4个通道 – 高端盘有8或10个通道
197 |
198 | #### 2.5.2 组织层级
199 |
200 | * Block:4KB,最小的读写单位
201 | * Segment:2MB
202 | * Section:多个segment(垃圾回收/GC粒度)
203 | * Zone:多个section
204 |
205 | 
206 |
207 | **系统元数据(随机写)**
208 |
209 | * 存放在一起:局部性更好
210 | * CP:检查点
211 | * SIT:段信息表
212 | * NAT:node地址转换表
213 | * SSA:段概要区域
214 |
215 | **数据区(多Log顺序写入)**
216 |
217 | * 区分冷/温/热数据
218 | * 区分文件数据(data segment) 与元数据(node segment)
219 |
220 | #### 2.5.3 多Log写入
221 |
222 | * 按热度将结构分类
223 | * 每个类型和热度对应一个log
224 | * 默认打开6个log
225 | * 用户可进一步配置
226 |
227 | 
228 |
229 | ### 2.6 清理(Cleaning)
230 |
231 | ▲ 以section为粒度 (与硬件FTL的GC单位是对齐的)
232 |
233 | **过程**
234 |
235 | 1. 选择需要清理的section
236 | * Greedy:选择有效块最少的section
237 | * Cost-effective:同时考虑数据修改时间
238 | 2. 识别有效数据
239 | 3. 有效数据拷贝到干净section
240 | 4. 标记被清理的section为pre-free
241 | * 在下一次checkpoint之后被标记为free
242 |
243 | ▲ 为什么不直接标记为free
244 |
245 | 容错
246 |
247 |
248 |
249 | ### 2.7 自适应日志
250 |
251 | * 文件系统使用一段时间后,干净section不足,需要频繁清理
252 | * F2FS动态调整数据段的日志方法
253 | * 干净section充足时,使用常规方法
254 | * 日志写到干净section中
255 | * 没有干净section时需要进行清理操作
256 | * 干净section不足时,使用 threaded logging
257 | * 使用脏段中无效的块
258 | * 避免清理操作
259 | * 但会产生一些随机写
260 |
261 | ### 2.8 崩溃与恢复
262 |
263 | **回滚**:回滚到最近的检查点
264 |
265 | **前滚**:恢复检查点之后fsync过的数据
266 |
267 | 1. 查找带有fsync标记的直接node
268 | 2. 对于每个直接node,对比其中的数据块指针,识别新旧数据块
269 | 3. 更新SIT,标记旧的数据块为无效
270 | 4. 根据直接node中新数据块的记录,更新NAT和SIT
271 | 5. 创建新的检查点
272 |
273 | **fsync()**
274 |
275 | * 无需创建新的检查点
276 | * 持久化文件数据块和直接node,并在直接node上附带fsync标记
277 | * 前滚:恢复检查点之后fsync过的数据
278 |
279 | ## 3. 瓦式磁盘
280 |
281 | ## 4. 非易失性内存
282 |
283 | 见PPT
284 |
285 |
286 |
287 | > 符号连接和硬链接是访问文件的两种捷径方式,但是它们在使用和实现上有许多不同。请列举出至少4点不同。
288 |
289 | (1)符号链接本身是一个特殊的文件,硬链接只是多了一个指向inode的指针,并且增加引用计数
290 |
291 | (2)符号链接没有文件限制,且可以指向一个空文件。硬链接不能指向目录、也不能指向空文件
292 |
293 | (3)符号链接可以跨文件系统,硬链接不能跨文件系统
294 |
295 | (4)符号链接在目标文件删除后失效,而硬连接则不会
296 |
297 | (5)访问目标文件时,符号链接需要走两次文件系统,而硬连接只需一次。
298 |
299 |
300 |
301 | > 在某些文件系统中,例如ext4,不允许使用硬链接来链接目录。
302 | >
303 | > (1)请举一个例子说明如果文件系统支持到目录的硬链接会出现什么问题。
304 | >
305 | > (2)如果确实需要支持到目录的硬链接,那么如何设计文件系统?并且,请从复杂性和性能等方面分析您的设计。
306 |
307 | (1)仅使用引用计数器不能删除某些文件。例如:
308 |
309 | 路径:/ a / b / c / d /
310 |
311 | 在目录d下,增加到目录c的硬链接e:root-> a-> b-> c-> d-> e-> c
312 |
313 | 移除b-> c后:root-> a-> b,c-> d-> e-> c。
314 |
315 | 第二条路径是无法删除的循环。
316 |
317 | (2)例如,循环检查。会增加每次删除时的性能开销。(其他合理答案均可)
318 |
319 |
320 |
321 | > 请给出一个由于系统崩溃而导致的文件系统不一致的例子。
322 |
323 | 在目录下创建文件。分配了索引节点,但是在索引索引链接到目录之前,系统崩溃了。
324 |
325 |
326 |
327 | > 什么是文件系统的持久性和原子性?如何保证文件系统操作的持久性和原子性?
328 |
329 | **持久性:**如果执行并提交了一个文件操作,它将永久保留在磁盘上。
330 |
331 | **原子性**:操作的效果要么做了,要么没做,不能看到做到一半的中间状态。
332 |
333 | 为了保证持久性,可以使用同步I/O。为了保证原子性,可以将数据拷贝出一个新的版本并在复制后的新数据上执行更新,最后更新使用原子指令将旧指针重定向到新指针。
334 |
335 |
336 |
337 | > 有三种支持一致性的技术:写时复制,journal和log-structured update。
338 | >
339 | > (1)请解释它们之间的区别。
340 | >
341 | > (2)如何在不同的情况下选择这些技术?请给出一些各个技术所适合的场景。
342 |
343 | (1)
344 |
345 | 写时复制:对于文件系统中的更新,递归地从叶复制到根,并在新节点中写入更新,然后切换到新节点。
346 |
347 | Journal:在磁盘上保留一个区域作为日志,首先写入日志,然后更新数据
348 |
349 | Log-structured update:所有文件系统都以只能追加的形式组织,并且每个修改都作为日志附加到末尾,但是读取速度很慢,需要检查是否被提交
350 |
351 | (2)
352 |
353 | 写时复制需要大量空间来重建块(4K或更大)以进行较小的修改。
354 |
355 | Journal的粒度可能很小,但是每次需要写两次(一次日志,一次更新数据)。
356 |
357 |
358 |
359 | > 对于闪存的磨损问题,文件系统如何在短时间内避免其导致闪存容量下降的问题?如果某些块已用完,文件系统如何处理它们? (假设硬件提供了某种方式来报告块是否已磨损。
360 |
361 | 文件系统可以平均使用闪存中的所有块。文件系统可以保留一个表来记录已用完的块,以后不使用它们。
--------------------------------------------------------------------------------
/8-进程间通信.md:
--------------------------------------------------------------------------------
1 | # 进程间通信
2 |
3 | **多进程协作优势**
4 |
5 | 1. 功能模块化,避免重复造轮子
6 | 2. 增强模块间的隔离,提供更强的安全保障
7 | 3. 提高应用的容错能力
8 |
9 | ## 1. 共享内存
10 |
11 | 系统内核为两个进程映射共同的内存区域
12 |
13 | 问题:做好同步
14 |
15 | 
16 |
17 | **发送者**
18 |
19 | 
20 |
21 | **接收者**
22 |
23 | 
24 |
25 | **问题**
26 |
27 | 1. 一直轮询,导致资源浪费
28 | 2. 如果固定一个检查时间,时延长
29 |
30 | ▲ 操作系统在通信过程中不干预数据传输
31 |
32 | ❓ 共享内存和基于共享内存的消息传递有什么区别??
33 |
34 | ## 2. 操作系统系统辅助的消息传递
35 |
36 | 内核对用户态程序提供接口如`Send`和`Receive`。进程可以直接使用这些接口,将消息传递给另一个进程,不需要共享内存和轮询
37 |
38 | **过程**
39 |
40 | 1. 通过特定的内核接口建立一个通信连接
41 | 2. 通过`Send`和`Receive`接口进行消息传递
42 | 3. (这里建立通信连接的过程和通过内核建立共享内存相似,更多的时抽象意义的建立连接)
43 |
44 |
45 |
46 | > 共享内存和操作系统辅助传递的对比
47 |
48 | 共享内存可以实现理论上的零拷贝,而操作系统辅助传递需要两次内存拷贝(用户->内核->用户)
49 |
50 | 操作系统辅助传递的**优势**:
51 |
52 | 1. 抽象更简单,操作系统可以保证每一次通信接口的调用都是一个消息被发送或接收,并且可以较好的支持变长的消息,而内存共享则需要用户态软件封装
53 | 2. 安全性保障更强,不会破坏发送者和接收者进程的内存隔离性
54 | 3. 在多方通讯时,多个进程共享内存区域复杂且不安全,操作系统复制可以避免此问题
55 |
56 |
57 |
58 | ## 3. 通信连接管理
59 |
60 | ### 3.1 直接通信
61 |
62 | **定义**:
63 |
64 | * 通信的进程以放需要显示的标识另一方
65 |
66 | * 进程拥有一个唯一标识
67 | * Send(P, message): 给P进程发送一个消息
68 | * Recv(Q, message): 从Q进程接收一个消息
69 |
70 | **连接**
71 |
72 | * 直接通信下的连接的建立是自动的 (通过标识)
73 | * 一个连接唯一地对应一对进程
74 | * 一对进程之间也只会存在一个连接
75 | * 连接可以是单向的,但是在大部分情况下是双向的
76 |
77 | **例子**
78 |
79 | * 信号
80 |
81 | 
82 |
83 | ### 3.2 间接通信
84 |
85 | **定义**
86 |
87 | * 间接通信需要经过一个中间的信箱完成通信
88 | * 每个信箱有自己唯一的标识符
89 | * 发送者往 “信箱”发送消息,接收者从“信箱”读取消息
90 |
91 | **连接**
92 |
93 | * 进程间连接的建立发生在共享一个信箱时
94 | * 每对进程可以有多个连接 (共享多个信箱)
95 | * 连接同样可以是单向或双向的
96 | * Send(M, message): 给信箱M发送一个消息
97 | * Recv(M, message): 从信箱M接收一个消息
98 |
99 | **间接进程间通信的操作**
100 |
101 | * 创建一个新的信箱
102 | * 通过信箱发送和接收消息
103 | * 销毁一个信箱
104 |
105 | **例子**
106 |
107 | * 管道
108 |
109 | **问题**
110 |
111 | > 信箱共享导致多接收者均收到消息,P1负责发送消息, P2、P3负责接收消息 ,当一个消息发出的时候,谁会接收到最新的消息呢?
112 |
113 | * 让一个连接(信箱)只能被最多两个进程共享,避免该问题
114 | * 同一时间,只允许最多一个进程在执行接收信息的操作
115 | * 让消息系统任意选择一个接收者 (需要通知发送者谁是最终接收者)
116 |
117 | ## 4. 同步异步
118 |
119 | **阻塞**
120 |
121 | * 阻塞通常被认为是同步通信
122 | * 阻塞的发送/接收: 发送者/接收者一直处于阻塞状态,直到消息发 出/到来
123 | * 同步通信通常有着更低时延和易用的编程模型
124 |
125 | **非阻塞**
126 |
127 | * 非阻塞通常被认为是异步通信
128 | * 发送者/接收者不等待操作结果,直接返回
129 | * 异步通信的带宽一般更高
130 |
131 | ## 5. 超时机制
132 |
133 | ▲ Send(A, message, Time-out)
134 |
135 | * 两个特殊的超时选项: ① 一直等待(阻塞);②不等待(非阻塞)
136 | * 避免由通信造成的拒接服务攻击等
137 |
138 | ## 6. UNIX经典IPC
139 |
140 | ### 6.1 管道
141 |
142 | **特点**
143 |
144 | * 单向通信
145 | * 内核中有缓冲区,当缓冲区满时,阻塞
146 | * 一个管道有且只能有两个端口: 一个负责输入 (发送数据),一个负 责输出 (接收数据)
147 | * 传输数据不带类型,即字节流
148 | * 基于Unix的文件描述符使用
149 |
150 | #### 6.1.1 管道数据结构
151 |
152 | 
153 |
154 | 下一次写在`data[nwrite++]`,下一次读在`data[nread++]`
155 |
156 | 要用`lock`,写的人在写的时候要锁住,读的人不能读(spin住)
157 |
158 | #### 6.1.2 管道写操作
159 |
160 | 
161 |
162 | 如果缓冲区满了,就叫醒reader,自己sleep
163 |
164 | 消息写完了也要再叫醒reader一次
165 |
166 | #### 6.1.3 管道读操作
167 |
168 | 
169 |
170 | 读完了也要叫醒写者
171 |
172 | #### 6.1.4 : Sleep/Wakeup通信机制
173 |
174 | * 信道(Channel)是等待和通知的媒介
175 | * 一个进程可以通过sleep接口将自己等待在一个信道上
176 | * 另外一个进程可以通过wakeup将等待在某个信道上的进 程唤醒
177 |
178 | 
179 |
180 | #### 6.1.5 优缺点
181 |
182 | **优点**
183 |
184 | 设计和实现简单,针对简单通信场景十分有效
185 |
186 | **缺点**
187 |
188 | * 缺少消息的类型,接收者需要对消息内容进行解析
189 | * 缓冲区大小预先分配且固定
190 | * 只能支持单向通信
191 | * 只能支持最多两个进程间通信
192 |
193 |
194 |
195 | ### 6.2 消息队列
196 |
197 | **特点**
198 |
199 | * 消息队列: 以链表的方式组织消息
200 | * 任何有权限的进程都可以访问队列,写入或者读取
201 | * 支持异步通信 (非阻塞)
202 | * 消息的格式: 类型 + 数据
203 | * 类型:由一个整型表示,具体的意义由用户决定
204 | * 消息队列是间接消息传递方式
205 | * 通过共享一个队列来建立连接
206 |
207 | **例子**
208 |
209 | 发送者
210 |
211 | ```c
212 | key = ftok("./msgque", 11);
213 | msgid = msgget(key, 0666 | IPC_CREAT);
214 | message.mesg_type = 1;
215 | msgsnd(msgid, &message, sizeof(message), 0);
216 | ```
217 |
218 | 接收者
219 |
220 | ```c
221 | key = ftok(”./msgque", 11);
222 | msgid = msgget(key, 0666 | IPC_CREAT);
223 | msgrcv(msgid, &message, sizeof(message), 1, 0);
224 | msgctl(msgid, IPC_RMID, NULL);
225 | ```
226 |
227 | **消息传递**
228 |
229 | * 基本遵循FIFO (First-In-First-Out)先进先出原则
230 | * 消息队列的写入:增加在队列尾部
231 | * 消息队列的读取:默认从队首获取消息
232 |
233 | **允许按照类型查询**
234 |
235 | * `Recv(A, type, message)`
236 | * 类型为0时返回第一个消息 (FIFO)
237 | * 类型有值时按照类型查询消息 ,如type为正数,则返回第一个类型为type的消息
238 |
239 | ### 6.3 消息队列 VS. 管道
240 |
241 | 1. 缓存区设计
242 | * 消息队列: 链表的组织方式,动态分配资源,可以设置很大的上限
243 | * 管道: 固定的缓冲区间,分配过大资源容易造成浪费
244 | 2. 消息格式
245 | * 消息队列: 带类型的数据
246 | * 管道: 数据 (字节流)
247 | 3. 连接上的通信进程
248 | * 消息队列: 可以有多个发送者和接收者
249 | * 管道: 两个端口,最多对应两个进程
250 | 4. 消息的管理
251 | * 消息队列: FIFO + 基于类型的查询
252 | * 管道: FIFO
253 |
254 | ## 7. LRPC
255 |
256 | **IPC设计问题**
257 |
258 | ▲ IPC设计可以看成将需要处理的数据发送到另一个进程,所以会有以下两个问题
259 |
260 | * 控制流转换: 调用者进程快速通知被调用者进程
261 | * 控制流转换需要下陷到内核
262 | * 内核系统为了保证公平等,会在内核中根据情况进行调度(调用者和被调用者之间可能会执行多个不相关进程)
263 | * 数据传输: 将栈和寄存器参数传递给被调用者进程
264 | * 经过内核的传输有(至少)两次拷贝
265 | * 慢: 拷贝本身的性能就不快 (内存指令)
266 | * 不可扩展: 数据量增大10x,时延增大10x
267 |
268 |
269 |
270 | **LRPC的基本原则**
271 |
272 | ▲ 将另一个进程处理数据的代码拉到当前的进程,避免了控制流切换和数据传输
273 |
274 | 1. 简化控制流切换,让客户端线程执行服务端代码
275 | 2. 简化数据传输,共享参数栈和寄存器
276 | 3. 简化接口,减少序列化开销
277 | 4. 优化并发,避免共享的全局数据结构
278 |
279 | ### 7.1 共享参数栈和寄存器
280 |
281 | **参数栈**
282 |
283 | * 系统内核为每一对LRPC连接预先分配好一个参数栈A-stack
284 | * A-stack被同时映射在调用者进程和被调用者进程地址空间
285 | * 调用者进程只需要将参数准备到A-stack即可
286 | * 不需要额外内存拷贝
287 |
288 | **寄存器**
289 |
290 | * 普通的上下文切换: 保存当前寄存器状态 → 恢复切换到的进程寄存器状态
291 | * LRPC迁移进程: 直接使用当前的通用寄存器
292 | * 类似函数调用中用寄存器传递参数
293 | * 客户端进程会优先使用寄存器,在寄存器不够的情况下用参数栈
294 |
295 | **执行栈**
296 |
297 | * 执行栈不共享哦
298 | * 是执行服务端代码用的 E-stack
299 |
300 | ### 7.2 通信连接的建立
301 |
302 | **服务描述符**
303 |
304 | * 内核为通信的服务端提供一个服务的抽象
305 | * 所有支持客户端调用的服务端进程需要将自己的处理函数等信息注册到服务描述符中
306 | * 在系统内核中为每个服务描述符提供两个资源:参数栈,连接记录
307 | * **参数栈**:被同时映射到调用者和被调用者进程
308 | * **连接记录**:返回地址
309 |
310 | **绑定对象**
311 |
312 | * 当客户端申请和一个服务端建立连接时,内核会分配参数栈和连接记录,并返回给客户进程一个绑定对象
313 | * ▲ 绑定对象的获得意味着客户端和服务端成功建立了连接
314 | * 内核将参数栈交给客户端进程,作为一个绑定成功的标志
315 | * 在通信过程中,通过检查A-stack来判断调用者是否正确发起通信
316 |
317 | 
318 |
319 | ### 7.3 一次调用过程
320 |
321 | 1. 内核验证绑定对象的正确性,并找到正确的服务描述符
322 | 2. 内核验证参数栈和连接记录
323 | 3. 检查是否有并发调用 (可能导致A-stack等异常)
324 | 4. 将调用者的返回地址和栈指针放到连接记录中
325 | 5. 将连接记录放到线程控制结构体中的栈上 (支持嵌套LRPC调用)
326 | 6. 找到被调用者进程的E-stack (执行代码所使用的栈)
327 | 7. 将当前线程的栈指针设置为被调用者进程的运行栈地址
328 | 8. 将地址空间切换到被调用者进程中
329 | 9. 执行被调用者地址空间中的处理函数
330 |
331 | 
332 |
333 | > 为什么需要将栈分成参数栈和运行栈
334 |
335 | > LRPC中控制流转换的主要要开销来自哪?
336 |
337 | 进出内核,切换页表
338 |
339 | > 不考虑多线程的情况下,共享参数栈安全吗
340 |
341 |
342 |
343 | > 对于以下四个场景,请从“使用阻塞的消息传递进行进程间的直接通讯”、“使用非阻塞的消息传递进行进程间的直接通讯”、“使用信箱的方式进行进程间进行间接通讯”、“通过轮询共享内存的方式进行进程间的通讯”中选择最合适的进程间通信方法
344 | >
345 | > a) 电商网站中的反向代理进程希望通过进程间通信的方式将收到的用户请求转发给一系列服务进程,使得某服务进程空闲后即可处理该请求。
346 | >
347 | > b) 电商网站中的服务进程希望通过进程间通信的方式从锁服务(Lock Service)进程中获取一把锁,从而进入临界区(Critical Section)执行商品购买逻辑。
348 | >
349 | > c) 电商网站中的服务进程希望通过进程间通信的方式,将包含用户请求执行结果的网络包通过用户态网络驱动服务进程,以尽可能低的时延发送出去。
350 | >
351 | > d) 电商网站中的服务进程希望通过进程间通信的方式将一条用户购买记录发送给后台推荐分析进程。
352 |
353 | a)使用“信箱”的方式进行进程间进行间接通讯,因为该通信为单一发送者,多接收者,且在发送时接收者并不确定。
354 |
355 | b)使用阻塞的消息传递进行进程间的直接通讯,因为进出临界区执行需要确保已经成功获取到锁,后续操作需要在该进程间通信完成之后才能执行。
356 |
357 | c)通过轮询共享内存的方式进行进程间的通讯,因为该操作希望延迟尽可能低,因此可以让接收者轮询共享内存,确认发送者在发出进程间通信请求后,接收者可以尽可能快的收到这一请求并进行处理。
358 |
359 | d)使用非阻塞的消息传递进行进程间的直接通讯,因为用户请求的继续处理不依赖于后台推荐分析进程的分析结果,所以无需阻塞,可以使用非阻塞的方式进行进程间通信。
360 |
361 |
362 |
363 | > 在`xv6`的管道线(PIPE)实现中,pipe这一结构体中的lock这一属性的作用是什么?为什么在sleep函数中存在放锁与拿锁操作,而在wake函数中却没有?
364 |
365 | struct pipe中的lock是为了确保在不同进程同时访问pipe时,不会产生数据竞争问题
366 |
367 | 在sleep操作时,需要进行放锁与拿锁操作,以确保sleep操作的原子性。
368 |
369 | 对于放锁操作:一方面,当进入sleep函数时,调用者线程应当持有锁,以防止对调用者线程的wakeup操作在调用者线程真正进入sleep状态之前到来所导致的后续无人再次唤醒调用者线程问题;另一方面,调用者线程又不能再持有锁的状态下进入sleep状态,否则会产生死锁(调用者线程需要等待其他线程唤醒才能解锁,而其他线程则需要等调用者线程放锁才能进行唤醒)。
370 |
371 | 对于拿锁操作:由于调用者线程在调用sleep函数之前已经持有锁,因此,在sleep函数执行之后,仍应该将调用者线程恢复至持有锁的状态。
372 |
373 | 在wakeup操作时,由于不存在线程状态转化的问题,即调用wakeup的线程在调用完成后仍然处于可以被执行的状态,因此无需在wakeup函数内进一步进行复杂的锁操作。
374 |
375 |
376 |
377 | > 对于基于共享内存的进程间通信而言,存在一个常见的安全性问题:Time-to-check to time-to-use(`TOCTOU`)。请查阅外部资料,简要说明这一问题的含义。
378 |
379 | TOCTOU指一类由竞争条件所导致的软件bug,通常是由在检查内存状态是否合法和使用检查结果的之间,共享同一内存的恶意线程修改内存内容所导致的。
380 |
381 | 举例而言,假设存在发送者(S)和接受者(R)使用如下结构体通信
382 |
383 | 
384 |
385 | 在接受者端,R会先检查length是否小于MAX_LENGTH,在从buf中依次拷贝length个字节到其本地内存中,以待后续处理。这是,若S在R检查length后,拷贝开始之前对length的值进行了修改,则能够触发一个TOCTOU的攻击,覆写掉R的本地内存
386 |
387 |
388 |
389 | > 对于课程中所介绍的轻量级进程间通信(LIPC),请回答以下问题:
390 | >
391 | > (1)为什么要将栈分成参数栈与执行栈两种?
392 | >
393 | > (2)LRPC中控制流转换的主要要开销来自哪?
394 | >
395 | > (3)不考虑多线程的情况下,共享参数栈安全吗?
396 |
397 | (1)LRPC使用参数栈进行参数的传递,而执行栈则仅用来实际执行接受者的代码,从而防止发送者通过参数对接受者进行攻击。
398 |
399 | (2)参照LRPC的论文,主要的性能开销来自于地址空间的切换。此外,如进入内核态、内核内部的检查等操作也会产生一定的性能开销。
400 |
401 | (3)否,因为LRPC是同步的进程间调用,因此,在不考虑多线程的前提下,只有被调用者进程才能够访问到参数栈,不存在TOCTOU攻击的可能性。
402 |
403 |
404 |
405 | > 在进程间通信的实现中,通常需要采用某种命名机制来确定某个进程间通信的目标进程(如xv6例子中的nread/nwrite指针)。这类命名机制的设计是否会成为影响进程间通信性能的决定性因素?为什么?
406 |
407 | 在大部分情况下,命名机制的选择与实现并不会决定进程间通信的性能。这主要是由于通常而言,命名机制只被用在进程间通信连接的创建过程中,而进程间通信的主要开销则来自于通信所需求的数据拷贝过程。当然,对于如生命周期极短的进程间通信等特殊场景而言,明明机制的实现会主导进程间通信的性能
--------------------------------------------------------------------------------
/5-内存.md:
--------------------------------------------------------------------------------
1 | # 内存
2 |
3 | ## 1. 虚拟地址
4 |
5 | ### 1.1 分段机制
6 |
7 | * 虚拟地址空间分成若干个不同大小的段
8 | * 段表存储着分段信息,可供MMU查询
9 | * 虚拟地址分为:段号 + 段内地址(偏移)
10 | * 物理内存也是以段为单位进行分配
11 | * 虚拟地址空间中相邻的段,对应的物理 内存可以不相邻
12 | * 存在问题
13 | * 分配的粒度太粗(不连续,不等长),外部碎片
14 | * 段与段之间留下碎片空间,降低主存利用率
15 |
16 | ### 1.2 分页机制
17 |
18 | * 更细粒度的内存管理
19 | * 物理内存也被划分成连续的、等长的物理页
20 | * 虚拟页和物理页的页长相等
21 | * 任意虚拟页可以映射到任意物理页
22 | * 大大缓解分段机制中常见的外部碎片
23 | * 虚拟地址分为
24 | * 虚拟页号 + 页内偏移
25 |
26 |
27 |
28 | ## 2. 页表
29 |
30 | ### 2.1 单级页表的问题
31 |
32 | 空间占用大:
33 |
34 | 32位地址空间,页4K,页表项4B:页表大小:232 / 4K * 4 = 4MB
35 |
36 | 64位地址空间,页4K,页表项8B,页表大小:264 / 4K * 8 = 33,554,432 GB
37 |
38 | 因为单级页表可以看成以虚拟地址的虚拟页号作为索引的数组,整个数组的起始地址(物理地址)存储在页表基地址寄存器中。所以整个数组必须连续存在,其中没有被用到的数组项也需要预留。
39 |
40 | ### 2.2 AARCH64的4级页表
41 |
42 |
43 |
44 |
45 | 页表项:
46 |
47 | 最后12位是属性位哦!
48 |
49 | **页表使能**:
50 |
51 | * CPU启动流程
52 | * 上电后默认进入物理寻址模式
53 | * 系统软件配置控制寄存器,使能页表,进入虚拟寻址模式
54 | * AARCH64
55 | * SCTLR_EL1 (System Control Register, EL1)
56 | * 第0位(M位)置1,即在EL0和EL1权限级使能页表
57 | * 对比x86_64
58 | * CR0,第31位(PG位)置1,使能页表
59 |
60 |
61 |
62 | **空间占用**:
63 |
64 | 一个页表页为`4K`
65 |
66 | 假设整个应用程序的虚拟地址空间中只有两个虚拟页被占用,分别对应于最低和最高的两个虚拟地址,那么需要1个0级页表,2个1级页表,2个2级页表,2个3级页表,共7个页表,28k物理内存空间。
67 |
68 | ### 2.3 TLB
69 |
70 | #### 2.3.1 TLB刷新
71 |
72 | 不同的应用程序虚拟地址空间不一样,而TLB是虚拟地址到物理地址的映射,所以切换应用程序的时候,需要刷新TLB
73 |
74 | 🔺 操作系统在进行页表切换的时候,需要主动刷新TLB
75 |
76 | **AARCH64**:
77 |
78 | 应用程序和操作系统使用不同的页表,`TTBR0_EL1`,`TTBR1_EL1`,系统调用过程不需要切换页表
79 |
80 | **x86-64**:
81 |
82 | 内核映射到应用页表的高地址,系统调用也不需要切换页表,不用刷TLB
83 |
84 | **降低TLB刷新的开销**:
85 |
86 | * 为不同的页表打上标签
87 | * TLB缓存项都具有页表标签,切换页表不再需要刷新TLB
88 | * **x86_64**:PCID(Process Context ID)
89 | * PCID存储在CR3的低位中
90 | * 在KPTI使用后变得尤为重要
91 | * KPTI: Kernel Page Table Isolation
92 | * 即内核与应用不共享页表,防御Meltdown攻击 https://meltdownattack.com/
93 | * **AARCH64**:ASID(Address Space ID)
94 | * OS为不同进程分配8/16 ASID,将ASID填写在TTBR0_EL1的高8/16位
95 | * ASID位数由TCR_EL1的第36位(AS位)决定
96 |
97 | 🔺 用了ASID后切换页表不需要刷新TLB,但是修改页表映射后,仍需要刷新TLB
98 |
99 |
100 |
101 | **全局TLB**:
102 |
103 | 第三级页表的Lower attributes的第11位是nG(not Global)位
104 |
105 | * nG == 0: 相应TLB缓存项对所有进程有效
106 | * nG == 1: 仅对特定进程(ASID)有效
107 |
108 | 🔺**为什么需要全局TLB**:
109 |
110 | 内核空间和用户空间是分开的,并且内核空间是所有进程共享。既然内核空间是共享的,进程A切换进程B的时候,如果进程B访问的地址位于内核空间,完全可以使用进程A缓存的TLB。
111 |
112 | 但是现在由于ASID不一样,导致TLB miss。我们针对内核空间这种全局共享的映射关系称之为global映射。针对每个进程的映射称之为non-global映射。所以,我们在最后一级页表中引入一个bit(non-global (nG) bit)代表是不是global映射。
113 |
114 | 当虚拟地址映射物理地址关系缓存到TLB时,将nG bit也存储下来。当判断是否命中TLB时,当比较tag相等时,再判断是不是global映射,如果是的话,直接判断TLB hit,无需比较ASID。当不是global映射时,最后比较ASID判断是否TLB hit。
115 |
116 | (直白的说,内核地址在TLB里都标记为global?)
117 |
118 | 
119 |
120 | #### 2.3.2 TLB与多核
121 |
122 | * 需要刷新其它核的TLB吗?
123 | * 一个进程可能在多个核上运行
124 | * 如何知道需要刷新哪些核?
125 | * 操作系统知道进程调度信息
126 | * 怎么刷新其他核?
127 | * AARCH64: 可在local CPU上刷新其它核TLB
128 | * TLBI ASIDE1IS
129 | * x86_64:
130 | * 发送IPI中断某个核,通知它主动刷新
131 |
132 |
133 |
134 | #### 2.3.3 TLB结构
135 |
136 | 为什么分级?我推测是利用局部性加快查询速度?
137 |
138 | 
139 |
140 |
141 |
142 | ### 2.4 按需分配与换页
143 |
144 | ▲ 被分配使用的虚拟页,在内存中可能也没有相应的物理页映射
145 |
146 | * 情景1:
147 | * 两个应用程序各自需要使用 3GB 的物理内存
148 | * 整个机器实际上总共只有 4GB 的物理内存
149 | * 情景2:
150 | * 一个应用程序申请预先分配足够大的(虚拟)内存
151 | * 实际上其中大部分的虚拟页最终都不会用到
152 |
153 | #### 2.4.1 换页
154 |
155 | **基本思想**
156 |
157 | 将物理内存里面存不下的内容放到磁盘上
158 |
159 | 虚拟内存使用不受物理内存大小限制
160 |
161 | **过程**
162 |
163 | 1. 操作系统希望从物理页A那里回收物理页P(对应A的虚拟页V)
164 | 2. 操作系统把物理页P的内容写到磁盘上,并在A的页表中去掉虚拟页V的映射
165 | 3. 操作系统记录物理页P被放在磁盘上的位置
166 | 4. 回收物理页P分配给其他应用程序
167 | 5. 此时,A的虚拟页V处于已分配但未映射的状态
168 |
169 | **预取**
170 |
171 | 因为换页会涉及耗时的磁盘操作,因此操作系统往往会引入预取机制进行优化
172 |
173 | 当发生换入操作时,预测还有哪些页即将被访问,提前将他们一并换入物理内存
174 |
175 | (以上针对第一个场景)
176 |
177 | * 替换策略的评价标准
178 | * 缺页发生的概率
179 | * 策略本身的性能开销
180 | * 🔺如何高效地记录物理页的使用情况
181 | * Recap:上节课说到的页表项中Access/Dirty Bits
182 |
183 |
184 |
185 | **Thrashing Problem**:
186 |
187 | * 直接原因
188 | * 过于频繁的缺页异常(物理内存总需求过大)
189 | * 大部分 CPU 时间都被用来处理缺页异常
190 | * 等待缓慢的磁盘 I/O 操作
191 | * 仅剩小部分的时间用于执行真正有意义的工作
192 | * 调度器造成问题加剧
193 | * 等待磁盘 I/O导致CPU利用率下降
194 | * 调度器载入更多的进程以期提高CPU利用率
195 | * 触发更多的缺页异常、进一步降低CPU利用率、导致连锁反
196 |
197 | **工作集模型**
198 |
199 | 一个进程在时间t的工作集W(t, x) (Peter Denning):其在时间段(t - x, t)内使用的内存页集合,也被视为其在未来(下一个x时间内)会访问的页集合, 如果希望进程能够顺利进展,则需要讲该集合保持在内存中。
200 |
201 | * 工作集时钟中断固定间隔发生,处理函数扫描内存页
202 | * 访问位为1则说明在此次tick中被访问, 记录上次使用时间为当前时间
203 | * 访问位为0(此次tick中未访问)
204 | * Age = 当前时间 – 上次使用时间
205 | * 若Age大于设置的x,则不在工作集
206 | * 将所有访问位清0
207 | * 注意访问位(access bit)需要硬件支持
208 | * 需要CPU硬件在程序访问某个页的时候自动的将访问位设为1
209 |
210 |
211 |
212 | #### 2.4.2 缺页异常
213 |
214 | 换页机制能正常工作的前提是当应用程序访问已分配但未映射的虚拟页时会触发缺页异常
215 |
216 |
217 |
218 | #### 2.4.3 按需分配
219 |
220 | (针对第二个场景)
221 |
222 | 当应用程序申请内存分配的时候,操作系统可选择将新分配的虚拟页标记位*已分配但未映射*。当应用程序真的要访问这个虚拟页时,再映射到物理页。
223 |
224 |
225 |
226 | ### 2.5 物理内存管理之buddy system
227 |
228 | 
229 |
230 | 
231 |
232 | * 高效地找到伙伴块
233 | * 互为伙伴的两个块的物理地址仅有一位不同
234 | * 一个是0,另一个是1
235 | * 块的大小决定是哪一位
236 |
237 |
238 |
239 | ### 2.6 Rowhammer攻击
240 |
241 | 攻击者利用物理内存缺陷,极频繁访问某一行,其相邻行某些位会发生翻转
242 |
243 | 巧妙地利用位翻转,可以实施包括提权在内的多种攻击
244 |
245 | **安全防御**
246 |
247 | * 为抵御Rowhammer攻击,实际上操作系统需要知道部分硬件细节,从而能够在物理内存分配时主动加入 一些Guard Page
248 | * 为抵御cache Side Channel攻击,操作系统需要知道同样cache映射细节
249 |
250 |
251 |
252 | ## 3. 操作系统内存管理的功能
253 |
254 | ### 3.1 共享内存
255 |
256 | * 节约内存 : 共享库
257 | * 进程通信:传递数据
258 |
259 | ### 3.2 写时拷贝(copy-on-write)
260 |
261 | **实现**
262 |
263 | * 修改页表权限项
264 | * 在缺页时拷贝、恢复
265 |
266 | ### 3.3 内存去重
267 |
268 | * memory deduplication
269 | * 基于写时拷贝机制
270 | * 在内存中扫描发现具有相同内容的物理页面
271 | * 执行去重
272 | * 操作系统发起,对用户态透明
273 |
274 | * 如何发现相同页
275 | * 异或
276 | * 哈系数
277 |
278 | **内存去重潜在安全隐患**
279 |
280 | * 导致新的side channel
281 | * 访问被合并的页会导致访问延迟明显 (写被合并的页的时候会发生COW)
282 | * 潜在攻击 – 攻击者可以确认目标进程中含有构造数据
283 |
284 | ### 3.4 内存压缩
285 |
286 | 当内存资源不充足的时候, 选择将一些“最近不太会 使用”的内存页进行数据压缩,从而释放出空闲内存
287 |
288 | **Linux **
289 |
290 | * swap:换页过程中磁盘的缓存
291 | * 将准备换出的数据压缩并先写入 zswap 区域 (内存)
292 | * 好处:减少甚至避免磁盘I/O;增加设备寿命
293 |
294 |
295 |
296 | ### 3.5 大页
297 |
298 | * 在4级页表中,某些页表项只保留两级或三级页表
299 | * L2页表项的第1位
300 | * 标识着该页表项中存储的物理地址(页号)是指向 L3 页表页(该位是 1)还是指向一个 2M 的物理页(该位 是 0)
301 | * L1页表项的第1位
302 | * 类似地,可以指向一个 1G 的物理页
303 |
304 | 
305 |
306 | **利弊**
307 |
308 | * 好处
309 | * 减少TLB缓存项的使用,提高 TLB 命中率
310 | * 减少页表的级数,提升遍历页表的效率
311 | * 案例
312 | * 提供API允许应用程序进行显示的大页分配
313 | * 透明大页(Transparent Huge Pages) 机制
314 | * 弊端
315 | * 未使用整个大页而造成物理内存资源浪费
316 | * 增加管理内存的复杂度
317 |
318 | **计算**
319 |
320 | > 在32位的机器中,巨页的大小是4M;在64位机器中,巨页的大小是2M,为什么巨页的大小不一致。
321 |
322 | 64位机器使用了4级页表,每一级页表使用9 bit做索引,大页包括了21 bit(48-4 * 9+9),因此大小是2M;在32位的机器中,使用了2级页表,每一级页表使用了10 bit做索引,因此大页大小为4M(22 bit(32-2 * 10+10))
323 |
324 | 在32位机器中,一个页表项占`4 bytes`;而在64位机器中一个页表项占`8 bytes`。
325 |
326 | 两种机器都选`4k`作为最小页大小时,32位机器一个`L3`页表能指向的合计内存区域为64位机器的两倍。所以巨页大小为64位机器的两倍。
327 |
328 | 32位机器:
329 |
330 | 一页为`4k`,所以一个页表页可以存放`1024`个页表项:
331 | $$
332 | 4k \div 4 bytes = 2^{10} = 1024
333 | $$
334 | 也就是说`L3`页表页中有`1024`个页表项,又因为一个页表项可以指向一个`4k`的物理页,所以一个`L3`页表页合计可以指向`4M`的物理页:
335 | $$
336 | 1024 * 4k = 2^{12} k = 4M
337 | $$
338 |
339 | 64位机器:
340 |
341 | 64位机器一个页表项占`8 bytes`,所以一个页表也可以放`512`个页表项。
342 |
343 | 所以`L3`页表页可以指向`2M`的页表页:
344 | $$
345 | 512 * 4k = 2M
346 | $$
347 |
348 |
349 |
350 | > 为什么OS/MMU使用多级页表映射虚拟地址到物理地址中?使用4级页表的最大内存空间消耗是多少?
351 |
352 | 减少页表的空间开销
353 | $$
354 | 4KB+4KB*2^9+4KB*(2^9)^2+4KB*(2^9)^3
355 | $$
356 | **使用多级页表原因**:
357 |
358 | 使用多级页表是为了压缩页表在内存中的占用大小。
359 |
360 | 多级页表允许在整个页表结构中出现空洞,而单级页表需要每一项都实际存在(假设页的大小位`4k`页表项占`8 bytes`,那么要占空间33554432GB)。
361 |
362 | 在实际使用中,应用程序的虚拟地址空间中的绝大部分处于未分配状态,多级页表可以部分创建,能够极大地节约所占空间。
363 |
364 | **最大空间消耗:**
365 |
366 | 使用四级页表最大空间消耗为使用全部虚拟空间时
367 |
368 | 假设使用全部`48`位虚拟空间,即
369 | $$
370 | 2^{48}
371 | $$
372 | 因为一个页表页可以放`512`个页表项
373 |
374 | 则`L1`有`512`个页表页,`L2`有`512*512`个页表,`L3`有`512*512*512`个页表页
375 |
376 | 则共需空间
377 | $$
378 | (1+512+521^{2}+512^{3})*4kB ≈ 513GB
379 | $$
380 |
381 | ###
382 |
383 | > 在ARM-mmu架构中,mmu是如何区分页条目是否被使用
384 |
385 | 检查页表条目中第一个属性位
386 |
387 | 第三级页表页中的页表项中,第0位表示该项是否有效
388 |
389 |
390 |
391 | > 使用巨页的优点和缺点分别是什么
392 |
393 | 优点:TLB miss少,缺页异常少等。缺点:内存分配粒度大,可能造成内存浪费
394 |
395 |
396 |
397 | > 内存的属性位AP和UXN已经能够隔离用户态和内核态,为什么还需要两个ttbr寄存器?
398 |
399 | ▲ AP(第3级页表页中的页表项):读写权限位
400 |
401 | ▲ XN:EL0能不能执行
402 |
403 | ▲ PNX:EL1能不能执行
404 |
405 | 两个基地址寄存器寄存器相对于一个基地址寄存器的好处是:”**不同进程可共用独立的内核页表,不再需要修改每个进程页表的高地址区域来映射内核页,内核的设计和实现更加方便**“。
406 |
407 | 两个ttbr寄存器并不能防住meltdown攻击,某些arm架构的CPU仍然存在meltdown的漏洞。使用软件防御meltdown攻击的方法是使用KPTI(Kernel page-table isolation)。对于arm来说,需要三张页表,两张kernel页表和一张user页表。对于x86来说,需要两张页表,对应kernel mode和user mode的两张页表。在用户态时,页表里面只映射了部分的kernel空间,而进入内核态之后,会切换页表,切换到full kernel space的页表。通过这种方式,我们可以使用纯软件的方法防御meltdown攻击,但是这会带来切换页表以及flush tlb的额外开销。
408 |
409 |
410 |
411 | > TLB能够缓存虚拟地址到物理地址的映射,当发生进程间的上下文切换的时候,需要刷掉所有的TLB条目,这是为什么?你能想出一种解决方式,使得TLB条目在上下文切换的时候不需要被刷掉吗?
412 |
413 | 不同的进程可能使用相同的虚拟地址;使用ASID技术
414 |
415 | **为什么上下文切换要刷`TLB`:**
416 |
417 | 因为TLB是使用虚拟地址进行查询的,不同的进程使用不同的页表,同一个虚拟地址`VA`可能对应不同的物理地址`PA1`和`PA2`。
418 |
419 | 当进程1访问`VA`后,`TLB`会缓存`VA`到`PA1`的映射;在切换到进程2后,页表已经发生了变化,再次访问虚拟地址`VA`,如果没有刷新`TLB`,则会查询到`VA`到`PA1`的映射,而非`PA2`,进而产生错误。
420 |
421 | 所以发生进程间上下文切换时,需要刷掉所有TLB条目。
422 |
423 | **不需要刷`TLB`的解决方式:**
424 |
425 | 可以为`TLB`缓存项打上标签。
426 |
427 | 操作系统为不同的应用程序(进程)分配不同的标签(`ASID`)作为进程的身份标签,将该标签写入进程页表基地址寄存器的空闲位。`TLB`中的缓存项也会包含`ASID`这个标签,从而使`TLB`中属于不同进程的缓存项被区分开。所以切换进程时,不用刷新`TLB`。
428 |
429 |
430 |
431 | > 在ARMv8结构之前,内存属性中没有DBM(Dirty Bit Modifier)位。这意味着硬件不支持脏页。所以OS需要如何模拟并且记录脏页呢?给出一种可行的解决办法
432 |
433 | 软件模拟,初始化的时候将所有的页的可写位都置为0而OS中记录当前页具有写权限,当CPU触发了一个写请求的时候会产生fault,OS能够检察该写请求是否合法,如果合法将对应的页条目中的可写属性位置为1
434 |
435 | 利用读写权限模拟记录脏页
436 |
437 | 增加一个读写权限位。先将所有页的读写权限都设为**只读**,当要一个页时,会触发permission fault,将该位改成**可写**,此时该页也变为脏页。之后的写,因为已经将权限改为可写,所以不会触发permission fault。
438 |
439 | 也就是说当该位位**可写**,则说明被写过,是脏页。如果是**只读**,则不是脏页。
--------------------------------------------------------------------------------
/10-多核.md:
--------------------------------------------------------------------------------
1 | # 多核
2 |
3 | ## 1. 并行理论加速比
4 |
5 | 
6 |
7 | ## 2. 多核环境中的缓存结构
8 |
9 | 
10 |
11 | **多极缓存**
12 |
13 | * 每个核心有自己的私有高 速缓存(L1 Cache)
14 | * 多个核心共享一个二级高 速缓存(L2 Cache)
15 | * 所有核心共享一个最末级 高速缓存(LLC)
16 |
17 |
18 |
19 | ## 3. 缓存一致性
20 |
21 | 由于不同核心均拥有私有的高速缓存(如一级缓存),某一地址上的数据可能同时存在于多个核心的一级缓存中。当这些核心同时使用写回策略修改该地址的数据时,会导致不同核心上一级缓存中该地址数据不一致,违反了共享内存的抽象。为了保证私有缓存之间也能就某一地址的值达成共识,多核硬件提供了缓存一致性协议(Cache Coherence Protocol)。
22 |
23 | 缓存一致性是由硬件保证的
24 |
25 |
26 |
27 | ## 4. MSI状态迁移
28 |
29 | 属于目录式缓存一致性协议
30 |
31 | 
32 |
33 | **状态**
34 |
35 | 1. **独占修改**
36 |
37 | 当前缓存行在全局只有本地高速缓存中这一份拷贝
38 |
39 | 可以直接进行读/写操作,不会触发缓存行的状态变化
40 |
41 | 2. **共享**
42 |
43 | 当前缓存行在全局可能存在多份拷贝,且本地的拷贝是有效的
44 |
45 | 因此当前核心可以直接读该缓存行
46 |
47 | 如果需要写该缓存行:
48 |
49 | 1. 则当前核心需要查找全局共享目录
50 | 2. 找到所有拥有该缓存行拷贝的核心,并通知这些核心将缓存行状态转换为失效
51 | 3. 再设置目录中该项的Dirty Bit 为1,更新拥有者的Bit Vector
52 | 4. 最后,将本地的缓存行状态转换为独占修改,方能对缓存行进行写操作
53 |
54 | 3. **失效**
55 |
56 | 这个状态代表当前缓存行本地的拷贝失效
57 |
58 | 当前核心不能直接读/写该缓存行。
59 |
60 | 如果需要读缓存行:
61 |
62 | 1. 则应当在目录找到拥有该缓存行的核心,向其索要该缓存行的数据
63 | 2. 同时通知该核心将该缓存行状态改为共享
64 | 3. 之后更新目录中的Dirty Bit 为0,并更新拥有者的Bit Vector
65 | 4. 在本核心中将拿到的缓存行设置为共享之后,方能读取该缓存行
66 |
67 | 如果需要写该缓存行:
68 |
69 | 1. 则需要通过目录找到所有拥有缓存行的核心,通知它们将该缓存行状态都改为失效,之后才能拿到该缓存行的数据
70 | 2. 更新目录中的状态
71 | 3. 最后,将本地的缓存行状态设置为独占修改后,方能写该缓存行
72 |
73 | **例子**
74 |
75 |
76 |
77 |
78 |
79 | ## 5. 单一缓存行高度竞争导致的可扩展性问题
80 |
81 | 
82 |
83 | ### 5.1 Back-off 策略
84 |
85 | ```c
86 | void back_off(int time) {
87 | for (volatile int i=0; i