├── 图片 ├── 大页.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 | ![Linux收包过程](图片\Linux收包过程.png) 26 | 27 | 28 | 29 | **上下文切换**: 30 | 31 | Linux 1次:(内核的网络栈/用户态应用) 32 | 33 | ChCore 4次:(网络驱动/内核/网络栈服务/内核/用户态应用) 34 | 35 | ![ChCore网络架构图](图片\ChCore网络架构图.png) 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 | ![risc-cisc](图片\risc-cisc.png) 72 | 73 | 74 | 75 | #### 特权级 76 | 77 | ##### `X86-64`: 78 | 79 | ![特权级x86](图片\特权级x86.png) 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 | ![彩票](图片\彩票.png) 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 | ![Stride-Scheduling](图片\Stride-Scheduling.png) 138 | 139 | 为了让虚拟时间短的任务能够追赶虚拟时间长的任务,调度策略一般选择虚拟时间最少的任务 140 | 141 | ### 4.3 对比 142 | 143 | ![公平共享调度](图片\公平共享调度.png) 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 | ![cpu利用率](图片\cpu利用率.png) 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 | ![负载分担](图片\负载分担.png) 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 | ![群组调度](图片\群组调度.png) 231 | 232 | 233 | 234 | ## 6.3 两级调度 235 | 236 | 当一个任务被全局调度器分配到给定的CPU核心上时,将一直被该核心的本地调度器管理,不会迁移到其他的CPU核心上执行 237 | 238 | 如:linux,chcore 239 | 240 | ![两级调度](图片\两级调度.png) -------------------------------------------------------------------------------- /11-文件系统.md: -------------------------------------------------------------------------------- 1 | # 文件系统 2 | 3 | ## 1. Ext2 4 | 5 | 6 | 7 | ![ext2文件系统](图片\ext2文件系统.png) 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 | inode 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 | ![区段树](图片\区段树.png) 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 | ![mmap](图片\mmap.png) 78 | 79 | **优势**: 80 | 81 | * 对于随机访问,不用频繁lseek (syscall) 82 | * 减少系统调用次数 83 | * 可以减少数据copy – 如拷贝文件,数据无需经过中间buffer 84 | * 访问的局部性更好 85 | * 可以用madvice为内核提供访问提示,提高性能 86 | 87 | 88 | 89 | 把文件映射到虚拟内存,访问文件就像访问数组一样方便 90 | 91 | ![mmap](图片\mmap1.png) 92 | 93 | ## 4. 基于TABLE的文件系统 94 | 95 | ### 4.1 FAT 96 | 97 | ![fat1](图片\fat1.png) 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 | ![fat2](图片\fat2.png) 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 | ![ntfs1](图片\ntfs1.png) 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 | ![存储结构与缓存](图片\存储结构与缓存.png) 188 | 189 | ## 7. 文件系统高级功能 190 | 191 | ### 7.1 文件复制 192 | 193 | 只复制inode(COW) 194 | 195 | ![文件复制](图片\文件复制.png) 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 | ”![git1](图片\git1.png) 231 | 232 | ”![git2](图片\git2.png) -------------------------------------------------------------------------------- /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 | 中断异常处理流程aarch64中断处理 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 | aarch64异常向量表 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 | ![DMA](图片\DMA.png) 62 | 63 | 64 | 65 | ## 3. 中断与中断管理 66 | 67 | **CPU中断处理流程**: 68 | 69 | ![CPU中断处理流程](图片\CPU中断处理流程.png) 70 | 71 | **AArch64中断分类**: 72 | 73 | ![AArch64中断分类](图片\AArch64中断分类.png) 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 | ![下半部](图片\下半部.png) 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 | ![皮特森](图片\皮特森.png) 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 | ![原子操作](图片\原子操作.png) 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 | ![intel锁总线](图片\intel锁总线.png) 54 | 55 | #### 4.1.2 arm 56 | 57 | 🔺 LL/SC : 在`Load-Link`时,CPU使用专门的监视器,记录当前访问的地址,而在`store-conditional`时,当前仅当监视的地址没有被其他核修改时,才执行存储操作,否则失败 58 | 59 | ![LL-SC](图片\LL-SC.png) 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 | ![读写锁2](图片\读写锁2.png)![读写锁1](图片\读写锁1.png) 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 | ![偏向读者的读写锁](图片\偏向读者的读写锁.png) 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 | ![RCU插入新的节点](图片\RCU插入新的节点.png) 179 | 180 | #### 6.1.2 删除 181 | 182 | ![RCU删除](图片\RCU删除.png) 183 | 184 | #### 6.1.3 修改 185 | 186 | ![RCU修改](图片\RCU修改.png) 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 | ![出问题再处理-死锁的检测与恢复](图片\出问题再处理-死锁的检测与恢复.png) 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 | ![一次性申请所有资源](图片\一次性申请所有资源.png) 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 | ![即时优先级置顶协议](图片\即时优先级置顶协议.png) 319 | 320 | ### 8.4 原生优先级置顶协议 321 | 322 | 高优先级进程被阻塞时,给锁持有者该锁竞争者中最高优先级 323 | 324 | ![原生优先级置顶协议](图片\原生优先级置顶协议.png) 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 | ![写者友好的读写锁](图片\写者友好的读写锁.png) 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 | ![日志1](图片\日志1.png) 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 | ![JBD2事务的状态](图片\JBD2事务的状态.png) 132 | 133 | 134 | 135 | #### 4.5.2 JBD2部分接口和使用方法 136 | 137 | ![JBD2部分接口和使用方法](图片\JBD2部分接口和使用方法.png) 138 | 139 | 140 | 141 | #### 4.5.3 JBD2日志的磁盘结构 142 | 143 | ![JBD2日志的磁盘结构](图片\JBD2日志的磁盘结构.png) 144 | 145 | 146 | 147 | #### 4.5.4 Ext4用JBD2实现的三种日志模式 148 | 149 | ![Ext4用JBD2实现的三种日志模式](图片\Ext4用JBD2实现的三种日志模式.png) 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 | ![写时拷贝](图片\写时拷贝.png) 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 | ![写时拷贝在文件系统中的应用](图片\写时拷贝在文件系统中的应用.png) 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 | ![上下文切换](图片\上下文切换.png) 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 | ![exec](图片\exec.png) 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 | ![多线程进程的地址空间](图片\多线程进程的地址空间.png) 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 | ![线程模型](图片\线程模型.png) 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 | ![chcoreTCB](图片\chcoreTCB.png) 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 | ![上下文切换1](图片\上下文切换1.png) 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 | ![纤程](图片\纤程.png) 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 | * ![LinuX Container资源](图片\LinuX Container资源.png) 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 | ![nsp-mount](图片\nsp-mount.png) 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 | * ![nsp-IPC](图片\nsp-IPC.png) 99 | 100 | ### 3.3 Network Namespace 101 | 102 | * 假设有两个容器均提供网络服务 103 | * 两个容器的外部用户向同一IP发送网络服务请求 104 | * 主机操作系统不知道该将网络包转发给哪个容器 105 | 106 | **虚拟机** 107 | 108 | ![nsp-network虚拟机](图片\nsp-network虚拟机.png) 109 | 110 | **Network Namespace的实现** 111 | 112 | * 每个NS拥有一套独立的网络资源 113 | * 包括IP地址、网络设备等 114 | * 新NS默认只有一个loopback设备 115 | * 其余设备需后续分配或从外部加入 116 | * 图例 117 | * 创建相连的veth虚拟设备对 118 | * 一端加入NS即可连通网络 119 | * 分配IP后可分别与外界通信 120 | 121 | ![nsp-network](图片\nsp-network.png) 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 | **![nsp-PID](图片\nsp-PID.png)** 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 | ![硬件提供不同粒度的隔离环境](图片\硬件提供不同粒度的隔离环境.png) 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 | ![Enclave与进程的关系](图片\Enclave与进程的关系.png) 260 | 261 | ### 5.4 远程验证(Remote Attestation) 262 | 263 | > 要解决的问题:如何远程判断某个主体是Enclave? 264 | 265 | * 例如,如何判断某个在云端的服务运行环境是安全的 266 | * 必须在认证之后,再进行下一步的操作,例如发送数据 267 | * ![远程验证](图片\远程验证.png) 268 | 269 | ### 5.5 AMD SEV 270 | 271 | ![AMD SEV](图片\AMD SEV.png) 272 | 273 | ### 5.6 RISC-V平台的Enclave 274 | 275 | ![RISC-V平台的Enclave](图片\RISC-V平台的Enclave.png) 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 | ![日志文件系统](图片\日志文件系统.png) 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 | ![LFS 段使用表](图片\LFS 段使用表.png) 59 | 60 | 61 | 62 | #### 1.2.2 段清理 63 | 64 | ![LFS 段清理](图片\LFS 段清理.png) 65 | 66 | 67 | 68 | #### 1.2.3 识别有效数据 段概要 69 | 70 | ![LFS 段概要](图片\LFS 段概要.png) 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 | ![LFS 前滚](图片\LFS 前滚.png) 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 | ![LFS的问题1:递归更新问题](图片\LFS的问题1:递归更新问题.png) 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 | ![F2FS的改进1:NAT](图片\F2FS的改进1:NAT.png) 182 | 183 | 184 | 185 | #### 2.4.2 多log并行写入 186 | 187 | ![F2FS的改进2:多log并行写入](图片\F2FS的改进2:多log并行写入.png) 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 | ![闪存友好的磁盘布局1](\图片\闪存友好的磁盘布局1.png) 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 | ![多Log写入](图片\多Log写入.png) 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 | ![共享内存1](图片\共享内存1.png) 16 | 17 | **发送者** 18 | 19 | ![共享内存2](图片\共享内存2.png) 20 | 21 | **接收者** 22 | 23 | ![共享内存3](图片\共享内存3.png) 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 | ![直接通信](图片\直接通信.png) 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 | ![管道数据结构](图片\管道数据结构.png) 153 | 154 | 下一次写在`data[nwrite++]`,下一次读在`data[nread++]` 155 | 156 | 要用`lock`,写的人在写的时候要锁住,读的人不能读(spin住) 157 | 158 | #### 6.1.2 管道写操作 159 | 160 | ![管道写操作](图片\管道写操作.png) 161 | 162 | 如果缓冲区满了,就叫醒reader,自己sleep 163 | 164 | 消息写完了也要再叫醒reader一次 165 | 166 | #### 6.1.3 管道读操作 167 | 168 | ![管道读操作](图片\管道读操作.png) 169 | 170 | 读完了也要叫醒写者 171 | 172 | #### 6.1.4 : Sleep/Wakeup通信机制 173 | 174 | * 信道(Channel)是等待和通知的媒介 175 | * 一个进程可以通过sleep接口将自己等待在一个信道上 176 | * 另外一个进程可以通过wakeup将等待在某个信道上的进 程唤醒 177 | 178 | ![wakeup](图片\wakeup.png)![sleep](图片\sleep.png) 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 | ![LRPC接口](图片\LRPC接口.png) 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 | ![LRPC伪代码](图片\LRPC伪代码.png) 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 | ![TOCTOU](图片\TOCTOU.png) 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 | AARCH64的4级页表地址翻译 43 | AARCH64的4级页表 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 | ![tlb结构](图片\tlb结构.png) 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 | ![tlb](图片\tlb.png) 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 | ![buddy1](图片\buddy1.png) 229 | 230 | ![buddy2](图片\buddy2.png) 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 | ![大页](图片\大页.png) 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 | 软件模拟,初始化的时候将所有的页的可写位都置为0OS中记录当前页具有写权限,当CPU触发了一个写请求的时候会产生fault,OS能够检察该写请求是否合法,如果合法将对应的页条目中的可写属性位置为1 434 | 435 | 利用读写权限模拟记录脏页 436 | 437 | 增加一个读写权限位。先将所有页的读写权限都设为**只读**,当要一个页时,会触发permission fault,将该位改成**可写**,此时该页也变为脏页。之后的写,因为已经将权限改为可写,所以不会触发permission fault。 438 | 439 | 也就是说当该位位**可写**,则说明被写过,是脏页。如果是**只读**,则不是脏页。 -------------------------------------------------------------------------------- /10-多核.md: -------------------------------------------------------------------------------- 1 | # 多核 2 | 3 | ## 1. 并行理论加速比 4 | 5 | ![加速比](图片\加速比.png) 6 | 7 | ## 2. 多核环境中的缓存结构 8 | 9 | ![多核环境中的缓存结构](图片\多核环境中的缓存结构.png) 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 | ![目录式缓存一致性协议](图片\目录式缓存一致性协议.png) 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 | msi例子 76 | 77 | 78 | 79 | ## 5. 单一缓存行高度竞争导致的可扩展性问题 80 | 81 | ![单一缓存行高度竞争导致的可扩展性问题](图片\单一缓存行高度竞争导致的可扩展性问题.png) 82 | 83 | ### 5.1 Back-off 策略 84 | 85 | ```c 86 | void back_off(int time) { 87 | for (volatile int i=0; inext = NULL; 129 | 14 me->flag = WAITING; 130 | 15 tail = atomic_XCHG(&lock->tail, me); 131 | 16 if (tail) { 132 | 17 tail->next = me; 133 | 18 while (me->flag != GRANTED) 134 | 19 ; 135 | 20 } 136 | 21 } 137 | 22 138 | 23 void unlock(struct MCS_lock *lock, 139 | 24 struct MCS_node *me) 140 | 25 { 141 | 26 if (!me->next) { 142 | 27 if (atomic_CAS(&lock->tail, me, 0) == me) 143 | 28 return; 144 | 29 while (!me->next) 145 | 30 ; 146 | 31 } 147 | 32 me->next->flag = GRANTED; 148 | 33 } 149 | ``` 150 | 151 | #### 5.2.2 操作 152 | 153 | **拿锁** 154 | 155 | 1. 当一个线程调用lock尝试获取MCS 锁时,其先初始化自己的等待队列节点 156 | 2. 其后,该线程利用atomic_XCHG操作将队尾指针lock-tail交换为指向自己的指针 157 | 3. 并将原来的队尾指针的值存入tail 158 | 4. 若原来的指针为空,代表该锁为空闲状态,此时该线程可以直接进入临界区执行 159 | 5. 否则,该线程将链入自己的节点,并等待在自己的MCS节点上 160 | 161 | **放锁** 162 | 163 | 1. 先检查等待队列中是否还有其他线程等待在该锁上 164 | 2. 如果有其他线程等待,则依照链表顺序,通过修改后序等待者节点中的标记位为GRANTED来通知该节点进入临界区 165 | 3. 若当前线程已经是等待队列里的最后一个,该线程则需要原子地将等待队列的队尾指针置为空,表示该锁已经释放 166 | 4. 如果原子操作失败,说明此时有新的线程已经交换了队尾指针,该线程需要等待新竞争者链入 167 | 并将锁传递给新的线程 168 | 169 | **例子** 170 | 171 | ![mcs锁例子](图片\mcs锁例子.png) 172 | 173 | > 为何这样设计能够为MCS 锁带来更好的可扩展性 174 | 175 | 如果一个锁要具有良好的可扩展性,锁竞争的开销(如缓存行失效的次数)不应随着同一 176 | 时刻竞争者数量增多而加大。 177 | 178 | 在自旋锁中,随着竞争者数量增多,单一缓存行的竞争加剧,最终导致关键路径上平均每次获取锁造成的缓存失效数量急剧上升。 179 | 180 | 而对MCS 锁而言,当同一时刻竞争者数量增多时,理想情况下关键路径上锁传递都只涉及两次缓存失效:分别为被继任者修改的me->next所在缓存行与继任者初始化的me->next->flag所在缓存行。 181 | 182 | 自旋锁获取锁与释放锁所需的操作均非常精简,在只有少数核心竞争时,其性能较MCS 锁更好。因此这两个锁并不存在好坏之分,需要依据具体场景下的竞争程度来判断到底选用哪一种锁 183 | 184 | #### 5.2.3 硬件特性 185 | 186 | 1. 多个核心对于**同一缓存行的高频修改**将会导致严重的性能开销 187 | 188 | 1. 当多个核心需要修改同一缓存行时,需要缓存一致性协议来保证一致性。由于 189 | 缓存一致性协议同一时刻只允许一个核心独占修改该缓存行,会造成多核执行流串行化,无法充分发挥出多核的性能优势 190 | 2. 多个核心对于同一缓存行的高频修改还会导致高速互联总线中产生大量缓存一致性流量,从而造成性能瓶颈 191 | 192 | 2. **伪共享** 193 | 194 | 伪共享是指本身无需在多核之间共享的内容被错误地划分到同一个缓存行中,并引 195 | 起了多核环境下对于单一缓存行的竞争,从而导致无谓的性能开销 196 | 197 | 如在软件中直接使用整型数组(如int cnt[CORE_NUM])为每个核心提供一个独占的计数器,线程按照所在核心更新这些计数器。由于不同核心将更新不同的计数器,这些计数器本身不是共享的。但如果直接分配一个整型数组,这些计数器很可能落入到同一个缓存行中 198 | 199 | 3. 多核环境下**局部性**同样重要 200 | 201 | 202 | 203 | ## 6. 非一致内存访问(NUMA) 204 | 205 | ### 6.1 NUMA 206 | 207 | 处理器中核心数量增多以及多处理器系统的出现 -> 单一的内存控制器逐渐成为了性能瓶颈 -> 多个内存控制器分布在不同的核心或处理器上 -> 不同的核心有可以快速访问的本地内存,其也可以访问其他处理器上的远程内存 208 | 209 | ![numa1](图片\numa1.png) 210 | 211 | * 一个节点中的任意核心能够快速地访问本地的内存。一旦其需要访问远端其他节点的内存时,其需要通过互联总线(Interconnect)与远端节点通讯 212 | * NUMA 架构有多种组成方式,一个NUMA 节点可以是一个物理处理器,也可以是处理器中一部分核心 213 | * 由于节点数量众多,有些节点之间没有直接相连,请求需要通过两跳才能到达目标节点 214 | * 如果应用所用内存随机分布在不同节点上,与只访问本地内存的应用相比,该应用将面临严重的远程内存访问开销 215 | * NUMA 架构对于操作系统不是透明的 216 | 217 | ## 6.2 NUMA-aware 218 | 219 | **测试**:临界区将访问并修改10个共享缓存行中的数据,而非之前的1 个。另外,测试使用的核心数进一步从32 核扩展至64 220 | 221 | 现的性能下降主要是由于线程临界区访问了更多的缓存行。当核心数超过16 个核心时,锁的竞争者就可能出现在不同的NUMA 节点。当锁持有者在不同节点上时,临界区内访问的缓存行也需要在不同的NUMA 节点之间迁移,导致巨大的开销。 222 | 223 | ### 6.2.1 Cohort 锁 224 | 225 | **问题核心**:用于增强访存操作的局部性,减少发生跨NUMA 节点的缓存一致性。在保证正确性的同时尽可能保证一段时间内访问都能在本地命中(包括本地的缓存行和内存),从而避免在关键路径上出现太多远程内存访问(包括通过缓存一致性访问在其他节点高速缓存中的缓存行)**** 226 | 227 | **cohort锁核心**:在一段时间内限制互斥锁在单个NUMA 节点内部传递,从而在关键路径上剔除耗时的跨节点缓存一致性 228 | 229 | **设计**:采取了两层锁的设计,第一层是唯一的全局锁,第二层是对应每个NUMA 节点的本地锁 230 | 231 | 当某一个NUMA 节点上的核心持有Cohort 锁时,其需要同时持有全局锁以及本地锁。而当该核心执行解锁操作时,如果本地节点有其他竞争者,则该核心不会释放全局锁,而是释放本地锁并将全局锁的拥有权转给下一个本节点的竞争者,从而保证Cohort 锁在一段时间内在本地的线程之间传递。![cochort锁](图片\cochort锁.png) 232 | 233 | * Cohort 锁牺牲了一定的公平性 234 | 235 | > 从操作系统的角度出发,我们如何提供NUMA 感知的能力 236 | 237 | 首先我们需要为上层应用提供NUMA 感知的内存分配接口,让上层应用显式地指定分配内存所在位置。 238 | 239 | 其次,对于没有使用这些接口的应用,操作系统需要尽可能地将内存分配在本地,避免远程内存访问。 240 | 241 | 最后,操作系统调度时需要尽可能避免跨NUMA 节点的线程迁移。 242 | 243 | 244 | 245 | ## 7. 内存模型 246 | 247 | ### 7.1 LockOne算法 248 | 249 | ![lockone](图片\lockone.png) 250 | 251 | * 只保证了互斥访问,不保证有限等待和空闲让进 252 | * 如果两个线程同时希望进入临界区,且在互相读对方的标记位之前,都已经设置了自己的标记位。此时,两个线程都不能进入临界区,陷入了无限等待。 253 | * 在现代处理器上,由于会出现访存乱序的情况,因此LockOne算法的互斥访问无法保证。 254 | * 假设在线程0 与线程1 中,第2 行的写操作与第3 行的读操作发生了乱序 255 | * 导致检查标记位的操作在自己的写标记全局可见(即对方的读操作可以读到其结果)之前发生 256 | * 此时如果线程0 与线程1 同时申请进入临界区,它们乱序后的读操作可能读到对方的标记位均为false,导致两个线程同时进入临界区,打破了LockOne的互斥访问的保证。 257 | 258 | ### 7.2 内存一致性模型 259 | 260 | 内存一致性模型明确定义了不同核心对于共享内存操作需要遵循的顺序。读写操作之间共有四类先后顺序需要保证,即读操作与读操作的顺序、读操作与写操作的顺序、写操作与读操作的顺序、写操作与写操作的顺序 261 | 262 | #### 7.2.1 严格一致性模型(Strict Consistency) 263 | 264 | 🔺 有核心对一个地址的任意读操作都能读到这个地址最近一次写的数据 265 | 266 | * 有访存操作都是严格按照程序编写的顺序可见 267 | * 所有的线程看到的访存操作顺序都与其发生的时间顺序完全一致 268 | * 在这种模型下实现的LockOne算法一定能保证互斥访问 269 | 270 | **问题**:要求使用全局一致的时钟5 以判定不同核心上执行的访存指令的时间先后顺序,增加了系统的实现难度 271 | 272 | **例子** 273 | 274 | ![严格一致性](图片\严格一致性.png) 275 | 276 | #### 7.2.2 顺序一致性模型(Sequential Consistency) 277 | 278 | 🔺 不要求操作按照真实发生的时间顺序(全局时钟)全局可见。首先,不同核心看到的访存操作顺序完全一致,这个顺序称为全局顺序;其次,在这个全局顺序中,每个核心自己的读写操作可见顺序必须与其程序顺序保持一致。 279 | 280 | * 此其中的读操作不一定能读到其他核上最新的修改 281 | * 序一致性模型能够保证LockOne算法的互斥访问 282 | 283 | **例子** 284 | 285 | ![顺序一致性1](图片\顺序一致性1.png) 286 | 287 | #### 7.2.3 TSO一致性模型 288 | 289 | 🔺 针对**不同地址且无依赖**的读-读、读-写、写-写顺序都能得到保证,只有写-读的顺序不能够得到保证 290 | 291 | * TSO 一致性模型通过加入一个写缓冲区达成优化性能的目的 292 | * 该写缓冲区能够保证写操作按照顺序全局可见 293 | 294 | ##### **排序缓冲区(ROB)** 295 | 296 | * 现代处理器允许访存操作乱序执行,单核可以通过ROB保证顺序 297 | 298 | * 让指令按照程序顺序退役 299 | * **退役**对应顺序执行中的执行结束,其意味着该条指令对系统的影响终将全局可见 300 | * 有的指令由于分支预测得到提前执行,但最终分支预测错误时,由于分支判断语句还未退役,这些指令不会提前退役,处理器就可以通过ROB 追踪到这些被错误执行的指令,舍弃 301 | * 只在单核上运行的应用程序并不会受到影响,这些程序展现出的行为与严格按照程序顺序执行的行为相同 302 | 303 | 但是在多核环境下,由于该系统中其它核心也可以观测到当前核心的运行结果,上述这些措施无法保证被其他核心观测的结果与严格按照程序顺序执行一致。其次,访存指令要完成缓存一致性流程后,才能顺利地被其它核心观测到。因此,如果访存指令等待缓存一致性流程结束后再退役,则会阻塞后续指令进入重排序缓冲区,导致性能受损。 304 | 305 | ##### **存取单元LSU** 306 | 307 | * 处理器在每个核心的存取单元(Load/Store Unit,简称为LSU)中预留了读缓冲区与写缓冲区,这个缓冲区用于暂存还没有满足缓存一致性的访存指令 308 | * 访存指令不再需要等待耗时的缓存一致性流程结束后再退役,而是放入对应的读缓冲区或写缓冲区后,就可以认为该指令已经完成 309 | * **退役**只代表该指令一定可以执行,并一定在未来可以被其他核心观测到 310 | * 将一个访存操作完成缓存一致性流程、真正变得全局可见的过程称为**提交(Commit)**。提交与退役并不相等,一个访存操作需要等到其从重排序缓冲区退役后才会提交 311 | 312 | ##### **写缓冲区** 313 | 314 | * 写缓冲区用于缓存已经执行结束,但还没有变得全局可见的写操作(包括刚刚将指令提交到LSU,还未通过重排序缓冲区退役的指令;以及已经退役但是还未能变得全局可见的写操作) 315 | * 规定:如果一个写操作满足了缓存一致性条件,真正离开写缓冲区到达L1 cache,则代表该写操作的结果全局可见,称该写操作成功提交 316 | 317 | **写操作离开写缓冲区需要满足以下几个前提条件:** 318 | 319 | 1. 写操作必须要退役。一旦退役,当前核心将会认为这个写指令已经完成,该指令不可撤销 320 | 2. 所需的缓存一致性流程必须结束 321 | 3. 架构的写缓冲区按照**先入先出**的顺序提交写操作。也即一个写操作必须等到前序写操作离开写缓冲区之后才能离开。因此,x86 架构使用的TSO 模型中写写操作之间的顺序能够得到保证 322 | 323 | ##### 读缓冲区 324 | 325 | 读操作并不存在提交的概念,但仍需要按照先入先出的顺序离开读缓冲区(即退役) 326 | 327 | 但是,如果某个在读缓冲区中的读操作还未退役,且此时该操作目标缓存行被其他核心修改,其会受到缓存一致性中的“失效”命令影响,舍弃当前读操作读到的结果,重新执行该读操作。所以在TSO 架构中不会出现读读乱序(乱序执行的读操作读到的结果与顺序执行一致,否则会被要求重做) 328 | 329 | **读操作离开写缓冲区需要满足以下几个前提条件:** 330 | 331 | 1. 首先,该读操作需要读取的值必须已经被读取到该核心 332 | 2. 其次,程序顺序中该操作之前的所有操作必须已经退役 333 | 3. 最后,读操作从读到值开始到退役之间没有收到目标缓存行“失效”的命令。若在此之间收到了目标缓存行“失效”的命令,则需要重新执行读操作 334 | 335 | ![TSO一致性模型中四类不同的操作组合行为](图片\TSO一致性模型中四类不同的操作组合行为.png) 336 | 337 | **写写**: 338 | 339 | 正如在介绍写缓冲区时讨论,由于写缓冲区保证了写操作会按照程序顺序提交,因此它们会按程序顺序全局可见,从而保证了写写顺序,即图中STR0与STR1将依照顺序全局可见。 340 | **读读**: 341 | 342 | 以代码片段12.1为例,假设乱序执行使得proc_B的读data操作(第13 行,图中LD1)在读flag操作(第11 行,图中LD0)之前发生,且读到的是初始数据0而非proc_A修改的666。而此时如果roc_B读flag操作读到了proc_A修改的READY,那么由于proc_A可以保证写data到写READY的提交顺序(即保证写写的可见顺序),因此此时对data的写操作一定也已经提交完成。而根据缓存一致性协议,此时对于data缓存行的缓存失效通知一定已经到达了proc_B所在核心。由于读缓冲区按程序顺序退役,此时proc_B读flag操作刚刚完成,还未退役,因此读data缓存行操作也没有完成退役。所以,对于data缓存行的缓存失效通知会使得proc_B重做该读操作(图中T2 时刻), 343 | 并读到修改值666。通过以上策略,x86 处理器就实现了对于读读操作的顺序保证。 344 | **读写**: 345 | 346 | 在x86 处理器中,保证读写操作的顺序较为简单。由于重排序缓冲区已经保证了所有指令会按照程序顺序退役,而写操作的提交操作一定发生在退役之后,因此当一个写操作全局可见时(即提交),它之前的读操作一定已经完成并退役了(通过ROB 保证,图中T2 时刻)。所以,TSO 架构能保证对于读写操作之间的顺序 347 | **写读**: 348 | 349 | 最后对于写读操作,由于当写操作退役时,其值可能还没有变得全局可见(图中T1 时刻,STR0退役但还在写缓冲区中),但若其后续的读操作已经满足了退役的条件,造成读操作在写操作真正变得全局可见之前退役(图中T2 时刻),最终破坏了写读之间的顺序。如果此时有使得读操作目标缓存行失效的命令到达,写读乱序将会被其他核心所观测到。 350 | 351 | 为了避免写读乱序,x86 处理器提供了**mfence指令**用于避免写读乱序。在我们上述的这种实现中,可以简单认为mfence指令会阻塞后续指令发送到LSU,直到前序所有访存操作完成。以图12.5中的LockOne算法为例,如果在两个线程的第2-3 行之间添加了mfence指令,读对方flag的操作必须 352 | 要等到写自己的flag操作提交之后才能发送到LSU 并执行。因此能够保证LockOne算法的互斥访问。 353 | 354 | #### 7.2.4 弱序一致性模型 355 | 356 | 🔺 弱序一致性模型不保证任何无依赖且针对不同地址的读写操作之间的顺序 357 | 358 | **写写** 359 | 360 | 在ARM 架构中,写缓冲区中的写操作可以不按照“先入先出”的顺序离开写缓冲区,当对应的写指令退役且目标缓存行缓存一致性流程结束后,该写操作便可以离开写缓冲区,变得全局可见。因此,对于任意的无依赖且针对不同地址的两个写操作,其全局可见的顺序不再受到约束。 361 | 362 | **读读** 363 | 364 | ARM 架构下的读操作的退役机制与x86 架构也存在很大差异。 365 | 366 | x86 架构下必须等到真正的值读入处理器内部(寄存器)且前序指令退役后才能退役。如果在等待过程中被缓存一致性协议标记为失效,则需要重新执行。因此,x86架构能保证读读的顺序。 367 | 368 | 在ARM 架构下,处理器可以在确保该读操作一定会发生(如分支预测成功)且前序指令退役时,就将该读操作退役。此时,这个读操作所需要读到的值可能还没有被读到处理器中。所以读操作虽然还是会按照程序顺序退役,但读操作发生的时机可能会违背程序顺序,造成读读乱序。 369 | 370 | **读写**和**写读** 371 | 372 | 由于读操作与写操作退役之后都不要求其真正执行完毕(需要读的值到达处理器或写的值全 373 | 局可见),因此它们之间真实发生的顺序均没有严格要求,很可能会发生乱序 374 | 375 | **内存屏障指令** 376 | 377 | 最常用的一类内存屏障指令是dmb,其全称为Data Memory Barrier。dmb指令能够保证其之前的访存指令与其之后的访存指令之间的顺序 378 | 379 | 380 | 381 | > 请说明为什么自旋锁不适用于单核系统,但在多核系统下可以正常工作? 对该问题有何种解决方案? 382 | 383 | (1).因为等待自旋锁的线程会不断自旋,通常需要其他线程来释放锁。如果只有一个处理器,则该处理器在等待线程耗尽其时间片前无法调度等待线程(如果调度程序不支持抢占式调度,则等待线程将陷入死循环并且永远不会停止)。 在多处理器中时,其他线程可以释放对其他处理器的锁定。 384 | 385 | (2).使用条件变量,一旦线程等待锁,它将进入休眠状态,直到锁被其他线程释放为止。 386 | 387 | 388 | 389 | > 假设有一个包含四个处理器和五个相同资源的系统。 每个处理器在有限的持续时间内最多使用两个资源。 请说明为什么该系统没有死锁(所有处理器最终都将使用两个资源) 390 | 391 | (1).我们可以通过矛盾证明。 392 | 393 | (2).假设系统死锁。 唯一的情况是,每个进程都持有一个资源,正在等待另一个资源。 394 | 395 | (3).由于有四个进程和五个资源,一个进程必须有两个资源。 396 | 397 | (4). (2)和(3)有矛盾。(5)Q.E.D. 398 | 399 | 400 | 401 | > 排号锁可能会导致在高竞争情况下性能降低。下图描述了使用排号锁和MCS锁的open()系统调用的性能。请指出使得排号锁的吞吐量下降的代码行。 另外,MCS锁是如何达到高可扩展性的呢? (左图为排号锁代码) 402 | 403 | ![排号锁题目](C:/Users/97537/STUDY/OS/复习/图片/排号锁题目.png) 404 | 405 | (1).while(lock-> owner!=my_ticket)使多个处理器在lock->owner的缓存行上竞争。 406 | 407 | (2).MCS锁具有更好的可扩展性,因为使用MCS锁的处理器不需要在同一高速缓存行上争用。 408 | 409 | 410 | 411 | > 在课程中,我们学习了`NUMA-aware cohort lock`。 尽管它可以提高锁在`NUMA`架构中的性能,但它会引入了公平性问题。请给出对此问题的问题的合理解决方案 412 | 413 | 每个NUMA节点获得锁后,为其设置一个最大的本地锁转移次数。 414 | 415 | > 假设小明在ARM 处理器上实现自旋锁,并且发现关键部分没有通过锁定得到很好的保护。 在了解了弱顺序一致性后,他知道要在代码中添加内存屏障。 请在适当的位置帮助小明添加barrier()。 416 | 417 | ```cc 418 | void lock(struct spinlock* lock) { 419 | 420 | /* locking */ 421 | 422 | barrier(); 423 | 424 | } 425 | 426 | void unlock(struct spinlock* lock) { 427 | 428 | barrier(); 429 | 430 | /* unlocking */ 431 | 432 | } 433 | ``` 434 | 435 | -------------------------------------------------------------------------------- /16-虚拟化.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 系统虚拟化 4 | 5 | **系统虚拟化的优势** (P330) 6 | 7 | 1. 服务器整合 8 | 2. 虚拟机管理 9 | 3. 虚拟机热迁移 10 | 4. 虚拟机安全自省 11 | 5. 方便程序开发 12 | 13 | **虚拟化标准** 14 | 15 | 1. 为虚拟机内程序提供与该程序原先执行的硬件完全一样的接口 16 | 2. 虚拟机只比在无虚拟化的情况下性能略差一点 17 | 3. 虚拟机监控器控制所有物理资源 18 | 19 | 20 | 21 | ## 1. 虚拟机分类 22 | 23 | ### 1.1 Type-1虚拟机监控器 24 | 25 | * 直接运行在硬件之上 26 | * 充当操作系统的角色 27 | * 直接管理所有物理资源 28 | * 实现调度、内存管理、驱动等功能 29 | * 性能损失较少 30 | * 例如Xen, VMware ESX Server 31 | 32 | ![type1](图片\type1.png)![type2](图片\type2.png) 33 | 34 | ### 1.2 Type-2虚拟机监控器 35 | 36 | * 依托于主机操作系统 37 | * 主机操作系统管理物理资源 38 | * 虚拟机监控器以进程/内核模块的形态运行 39 | * 易于实现和安装 40 | * 例如QEMU/KVM 41 | 42 | **优势** 43 | 44 | 1. 在已有的操作系统之上将虚拟机当做应用运行 45 | 46 | 2. 复用主机操作系统的大部分功能 47 | 48 | 文件系统 – 驱动程序 – 处理器调度 – 物理内存管理 49 | 50 | 51 | 52 | ## 2. 实现虚拟化 53 | 54 | **系统虚拟化的流程** 55 | 56 | 1. 第一步 :捕捉所有系统ISA并陷入(Trap) 57 | 58 | 2. 第二步 :由具体指令实现相应虚拟化 59 | 60 | • 控制虚拟处理器行为 • 控制虚拟内存行为 • 控制虚拟设备行为 61 | 62 | 3. 第三步 : 回到虚拟机继续执行 63 | 64 | 65 | 66 | ## 3. 处理器虚拟化 67 | 68 | ### 下陷和模拟 69 | 70 | 🔺 将虚拟机监控器运行在EL1, 将客户操作系统和其上的进程都运行在EL0 ,当操作系统执行系统ISA指令时下陷 71 | 72 | ▲ **trap** : 在用户态EL0执行特权指令将陷入EL1的VMM中 73 | 74 | ▲ **emulate **: 这些指令的功能都由VMM内的函数实现 75 | 76 | ![下陷和模拟](图片\下陷和模拟.png) 77 | 78 | 🔺 VMM看到特权指令后修改PC,直接修改寄存器等等,模拟硬件的执行效果->是纯软件模拟哦 79 | 80 | 81 | 82 | **敏感指令** 83 | 84 | 敏感指令指,管理系统物理资源更改CPU状态的指令 85 | 86 | 有以下分类: 87 | 88 | 1. 读写特殊寄存器或更改处理器状态 89 | 90 | 2. 读写敏感内存:例如访问未映射内存、写入只读内存 91 | 92 | 3. I/O指令 93 | 94 | **特权指令** 95 | 96 | * 在用户态执行会**触发异常**,并陷入内核态 97 | 98 | 🔺 **可虚拟化架构**特征: 所有敏感指令都是特权指令,即都会下陷被VMM捕捉 99 | 100 | 101 | 102 | 在下陷和模拟的虚拟化办法中,有五个方法可以弥补不可虚拟化的方法: 103 | 104 | * 解释执行 105 | * 动态二进制翻译 106 | * 扫描和翻译 107 | * 半虚拟化技术 108 | * 硬件虚拟化技术 109 | 110 | ### 3.1 解释执行 111 | 112 | * 使用软件方法对每一条虚拟机代码进行模拟 113 | * 不区分敏感指令还是其他指令 114 | * 没有虚拟机指令直接在硬件上执行 115 | * 使用内存维护虚拟机状态 116 | * 例如:使用uint64_t x[30]数组保存所有通用寄存器的值 117 | 118 | 🔺 所有虚拟机的指令都是VMM软件模拟的,并不是硬件直接执行的 119 | 120 | 121 | 122 | **优点** 123 | 124 | 1. 解决了敏感函数不下陷的问题 (因为根本不区分敏感指令和普通指令了) 125 | 2. 可以模拟不同ISA的虚拟机 126 | 3. 易于实现、复杂度低 127 | 128 | **缺点** 129 | 130 | 1. 非常慢:任何一条虚拟机指令都会转换成多条模拟指令 131 | 132 | 133 | 134 | ### 3.2 二进制翻译 135 | 136 | 扫一遍二进制,如果扫描到敏感指令,则替换,不是敏感指令则不动。二进制就是硬件真的执行的二进制。不用VMM模拟执行。 137 | 138 | ![二进制翻译](图片\二进制翻译.png) 139 | 140 | * 提出两个加速技术 141 | * 执行前**批量翻译**虚拟机指令 142 | * **缓存**已翻译完成的指令 143 | * 使用基本块(Basic Block)的翻译粒度(为什么?) 144 | * 每一个基本块被翻译完后叫代码补丁 145 | 146 | **优点** 147 | 148 | 因为1. 批量翻译 2. 只针对翻译敏感指令 3. 最终是硬件直接执行 4. 缓存命中 149 | 150 | 加快了速度 151 | 152 | **缺点** 153 | 154 | 1. 不能处理自修改的代码(Self-modifying Code) 155 | 156 | 2. 中断插入粒度变大 157 | 158 | – 模拟执行可以在任意指令位置插入虚拟中断 159 | 160 | – 二进制翻译时只能在基本块边界插入虚拟中断 161 | 162 | ​ (因为只能在每个基本块结束的时候检查有没有中断来,如果是正常系统,是引脚控制,粒度是每个指令) 163 | 164 | 165 | 166 | ### 3.3 半虚拟化 167 | 168 | * 让VMM提供接口给虚拟机,称为Hypercall 169 | * 修改操作系统源码,替换不可虚拟化的指令,将其更改为Hypercall 170 | 171 | ▲ 就是将所有不引起下陷的敏感指令替换成超级调用 172 | 173 | **优点** 174 | 175 | 1. 解决了敏感函数不下陷的问题 176 | 2. 协同设计的思想可以提升某些场景下的系统性能 177 | * I/O等场景(反正改了操作系统,系统调用可以进行batch优化) 178 | 179 | **缺点** 180 | 181 | 1. 需要修改操作系统代码,难以用于闭源系统 182 | 2. 即使是开源系统,也难以同时在不同版本中实现 183 | 184 | 185 | 186 | ### 3.4 硬件虚拟化 187 | 188 | #### 3.4.1 VT-x的处理器虚拟化 189 | 190 | ![VT-x的处理器虚拟化](图片\VT-x的处理器虚拟化.png) 191 | 192 | **VMM的进程对应虚拟机** 193 | 194 | **VMM的线程对应vcpu** 195 | 196 | * 因为线程的引入是为了并行,是调度的基本单位 197 | 198 | * 如果给虚拟机配了n个vcpu,那么无论虚拟机里有多少个线程,最终在VMM里真正运行的只有vcpu个 199 | 200 | * **spin-lock**:对于虚拟机来说,他以为自己占用了所有的CPU,所以这个线程没锁,会认为锁被别的CPU上的线程拿着,那个人的关键路径肯定很短,会马上放掉,然后我就可以用了 201 | 202 | 但事实上,自己只占用vcpu个cpu,所以可能会被调度走,spin很久 203 | 204 | ##### **3.4.1.1 VMCS** 205 | 206 | * VMM提供给硬件的内存页(4KB) 207 | * 记录与当前VM运行相关的所有状态 208 | * **VM Entry** 209 | * 硬件自动将当前CPU中的VMM状态保存至VMCS 210 | * 硬件硬件自动从VMCS中加载VM状态至CPU中 211 | * **VM Exit** 212 | * 硬件自动将当前CPU中的VM状态保存至VMCS 213 | * 硬件自动从VMCS加载VMM状态至CPU中 214 | 215 | **包含6个部分** 216 | 217 | 1. Guest-state area: 发生VM exit时,CPU的状态会被硬件自动保存至 该区域;发生VM Entry时,硬件自动从该区域加载状态至CPU中 218 | 2. Host-state area:发生VM exit时,硬件自动从该区域加载状态至 CPU中;发生VM Entry时,CPU的状态会被自动保存至该区域 219 | 3. VM-execution control fields: 控制Non-root模式中虚拟机的行为 220 | 4. VM-exit control fields:控制VM exit的行为 (▲ 可配置系统调用是guest os直接处理还是vm exit给VMM处理) 221 | 5. VM-entry control fields:控制VM entry的行为 222 | 6. VM-exit information fields:VM Exit的原因和相关信息(只读区域) 223 | 224 | ![vt-x执行过程](图片\vt-x执行过程.png) 225 | 226 | * 有几个虚拟机就有几个VMCS 227 | * 但是真正加载到CPU里面的VMCS的数量不超过系统的总核数 228 | 229 | 230 | 231 | ##### 3.4.2 VM Entry 232 | 233 | * 从VMM进入VM 234 | * 从Root模式切换到Non-root模式 235 | * 第一次启动虚拟机时使用VMLAUNCH指令 236 | * 后续的VM Entry使用VMRESUME指令 237 | 238 | 239 | 240 | ##### 3.4.3 VM Exit 241 | 242 | * 从VM回到VMM 243 | * 从Non-root模式切换到Root模式 244 | * 虚拟机执行敏感指令或发生事件(如外部中断) 245 | 246 | ![vt-x和普通进程对比](图片\vt-x和普通进程对比.png) 247 | 248 | #### 3.4.2 ARM的虚拟化技术1 249 | 250 | arm虚拟化1arm虚拟化2 251 | 252 | **VM Entry** 253 | 254 | * 使用ERET指令从VMM进入VM 255 | * 在进入VM之前,VMM需要主动加载VM状态 256 | * VM内状态:通用寄存器、系统寄存器 257 | * VM的控制状态:HCR_EL2、VTTBR_EL2等 258 | 259 | **VM Exit** 260 | 261 | * 虚拟机执行敏感指令或收到中断等 262 | * 以Exception、IRQ、FIQ的形式回到VMM 263 | * 调用VMM记录在vbar_el2中的相关处理函数 264 | * 下陷第一步:VMM主动保存所有VM的状态 265 | 266 | 267 | 268 | **ARM硬件虚拟化的新功能** 269 | 270 | * ARM中没有VMCS 271 | * VM能直接控制EL1和EL0的状态 272 | * 自由地修改PSTATE(VMM不需要捕捉CPS指令) 273 | * 可以读写TTBR0_EL1/SCTRL_EL1/TCR_EL1等寄存器 274 | * VM Exit时VMM仍然可以直接访问VM的EL0和EL1寄存器 275 | 276 | ▲ 因为ARM的寄存器有两套,所以切换不用放到VMCS里保存 277 | 278 | **问题** 279 | 280 | * 增加EL21特权级 281 | * EL2只能运行VMM,不能运行一般操作系统内核 282 | * OS一般只使用EL1的寄存器,在EL2中不存在对应的寄存器 283 | * EL1:TTBR0_EL1、TTBR1_EL1 284 | * EL2:TTBR_EL2 285 | * EL2不能与EL0共享内存 286 | * 因此:无法在EL2中运行Type-2虚拟机监控器的Host OS 287 | * 或者说,Host OS需要大量修改才能运行在EL2 288 | 289 | 290 | 291 | #### 3.4.3 ARM的虚拟化技术2 292 | 293 | **ARMv8.1** 294 | 295 | * 推出Virtualization Host Extensions(**VHE**),在HCR_EL2.E2H打开 296 | * 寄存器映射 297 | * 允许与EL0共享内存 298 | * 使EL2中可直接运行未修改的操作系统内核(Host OS) 299 | 300 | ![ARMv8.1](图片\ARMv8.1.png) 301 | 302 | ![ARMv8.1中的Type-2 VMM架构](图片\ARMv8.1中的Type-2 VMM架构.png) 303 | 304 | #### 3.4.4 VT-x和VHE对比 305 | 306 | ![VT-x和VHE对比](图片\VT-x和VHE对比.png) 307 | 308 | #### 3.4.5 Type-1和Type-2在VT-x和VHE下架构 309 | 310 | ![Type-1和Type-2在VT-x和VHE下架构](图片\Type-1和Type-2在VT-x和VHE下架构.png) 311 | 312 | 313 | 314 | ## 4. QEMU/KVM 315 | 316 | **QUME** 317 | 318 | * 运行在用户态,负责实现策略 319 | * 也提供虚拟设备的支持 320 | 321 | **KVM** 322 | 323 | * 以Linux内核模块运行,负责实现机制 324 | * 可以直接使用Linux的功能 325 | * 例如内存管理、进程调度 326 | * 使用硬件虚拟化功能 327 | 328 | **两部分合作** 329 | 330 | * KVM捕捉所有敏感指令和事件(trap),传递给QEMU(emulate) 331 | * KVM不提供设备的虚拟化,需要使用QEMU的虚拟设备 332 | 333 | ### 4.1 QEMU使用KVM的用户态接口 334 | 335 | ![QEMU使用KVM的用户态接口](图片\QEMU使用KVM的用户态接口.png) 336 | 337 | **ioctl(KVM_RUN)时发生了什么** 338 | 339 | * x86中 340 | 341 | 1. KVM找到此VCPU对应的VMCS 342 | 2. 使用指令加载VMCS 343 | 3. VMLAUNCH/VMRESUME进入Non-root模式 344 | 4. 硬件自动同步状态 (更新VMCS) 345 | 5. PC切换成VMCS->GUEST_RIP,开始执行 346 | 347 | * ARM中 348 | 349 | 1. KVM主动加载VCPU对应的所有状态 350 | 351 | 2. 使用eret指令进入EL2 352 | 353 | PC切换成ELR_EL2的值,开始执行 354 | 355 | 356 | 357 | ### 4.2 QEMU/KVM的流程 358 | 359 | ![QEMU-KVM的流程](图片\QEMU-KVM的流程.png) 360 | 361 | ▲ KVM 有两个switch case,一个是等待QEMU调ioctl(对参数switch),另一个等待虚拟机的VM exit 362 | 363 | ```c 364 | open("/dev/kvm") 365 | ioctl(KVM_CREATE_VM) 366 | ioctl(KVM_CREATE_VCPU) 367 | while (true) { 368 | ioctl(KVM_RUN) // 执行这句后就进入了KVM后进入虚拟机 369 | // 一旦这句话返回,说明发生了VM exit 370 | exit_reason = get_exit_reason(); 371 | switch (exit_reason) { 372 | case KVM_EXIT_IO: /* ... */ 373 | break; 374 | case KVM_EXIT_MMIO: /* ... */ 375 | break; 376 | } 377 | } 378 | ``` 379 | 380 | **WFI指令**VM Exit的处理流程 381 | 382 | ![WFI指令VM Exit的处理流程](图片\WFI指令VM Exit的处理流程.png) 383 | 384 | **I/O指令**VM Exit的处理流程 385 | 386 | IOVM Exit的处理流程 387 | 388 | 389 | 390 | ## 5. 内存虚拟化 391 | 392 | **内存虚拟化的目标** 393 | 394 | 1. 为虚拟机提供虚拟的物理地址空间 395 | 396 | 物理地址从0开始连续增长 397 | 398 | 2. 隔离不同虚拟机的物理地址空间 399 | 400 | VM-1无法访问其他的内存 401 | 402 | **内存虚拟化特点** :P342 403 | 404 | ### 5.1 影子页表 405 | 406 | 影子页表是在没有硬件虚拟化的条件下 407 | 408 | **影子页表的创建**:(具体见P344) 409 | 410 | ```c 411 | // 构造影子页表 412 | set_cr3 (guest_page_table): 413 | for GVA in 0 to 220 414 | if guest_page_table[GVA] & PTE_P: 415 | // P位被设上说明是已经映射的地址 416 | GPA = guest_page_table[GVA] >> 12 // 后12位是属性 417 | HPA = host_page_table[GPA] >> 12 418 | 419 | if GVA 被用来做页表,则标记位只读 420 | shadow_page_table[GVA] = (HPA<<12)|PTE_P 421 | else 422 | shadow_page_table[GVA] = 0 423 | CR3 = PHYSICAL_ADDR(shadow_page_table) 424 | ``` 425 | 426 | **▲ shadow_page_table**保存在host的内存(安全性) 427 | 428 | ▲ guest只能操作GPT,看不到影子页表 429 | 430 | ![影子页表](图片\影子页表.png) 431 | 432 | 🔺 把GPT页表内存页的地址(就是那个虚拟地址)在影子页表里标记为**只读**,这样客户操作系统想改GPT的时候,会触发page fault,并下陷到VMM。VMM帮助更新GPT和SPT 433 | 434 | 🔺 Guest Os 看自己的页表,也就是GPT,是可读可写的 435 | 436 | 🔺 虚拟化的关键:**TRAP** 437 | 438 | 439 | 440 | 现在虚拟机跑在ring3(低权限),host跑在ring0。SPT应给给GPT的内存必须是ring3,这样就打破了虚拟机里面的用户态和内核的隔离,(现在虚拟机里的用户态和内核态的内存都是ring3,那用户就可以随意访问内核的内存了) 441 | 442 | ![guestVM的内存](图片\guestVM的内存.png) 443 | 444 | 把一个页表拆成两个页表,都是ring3。给应用的页表,不映射内核的数据。 445 | 446 | 如何在两个页表中切换? 447 | 448 | guest 的APP 调syscall 进入host OS (ring3调syscall进入ring0),host OS 拿到syscall,切到guest OS。 449 | 450 | ▲ host OS本质上是trap了guest OS 和 guest APP 的切换,给他们换页表 451 | 452 | 453 | 454 | **优点** 455 | 456 | 1. 地址翻译速度快 457 | 458 | MMU只需遍历一个页表就可完成翻译,即使TLB未命中,最多只需要四次访存操作 459 | 460 | **缺点** 461 | 462 | 1. 影子页表的建立和后续的每次更新都需要VMM介入 463 | 2. 影子页表与页表一一对应,一个进程需要一个影子页表 464 | 465 | ### 5.2 直接页表映射 466 | 467 | ▲ 修改guest OS,guest OS的页表直接记录GVA到HPA的映射 468 | 469 | ▲ VMM需要告知VM允许使用的HPA的范围 470 | 471 | ▲ 为了安全性(VM不能乱写HPA,写到别人那边去),所以修改页表需要hypercall。将虚拟机的页表页设置为**只读**,虚拟机修改页表需要hypercall,VMM在接收请求后,修改页表,检查是否合法。 472 | 473 | **优点** 474 | 475 | 1. 实现复杂度降低 476 | 2. 影子页表将虚拟机对客户页表的修改透明的同步到影子页表,有较大性能开销,直接页表映射虚拟机可以将对页表的多次修改batch 477 | 478 | **缺点** 479 | 480 | 1. 要修改虚拟机OS 481 | 2. guest知道的太多了(安全问题roll hammer) 482 | 483 | ### 5.3 两阶段地址翻译 484 | 485 | 硬件虚拟化❗ 486 | 487 | ![第二阶段4级页表](图片\第二阶段4级页表.png) 488 | 489 | ![两阶段地址翻译](图片\两阶段地址翻译.png) 490 | 491 | 492 | 493 | 与影子页表不同,影子页表虽然也会维护GPA到HPA的映射,但是这个转换表在MMU翻译时除了维护信息没有其他作用。而这里EL2的页表可以直接被MMU识别并参与翻译 494 | 495 | 在虚拟机执行过程中,任何的GPA都会被硬件MMU通过`VTTBR_EL2`指向的第二级页表翻译成对应的HPA,整个过程无需VMM介入 496 | 497 | **地址翻译过程** 498 | 499 | 1. 先把`TTBR0_EL1`(GPA)(也就是第一级页表的L0页表地址),翻译成HPA_L0(这个HPA就是第一级页表的L0页表页的宿主机上的物理地址) 500 | 2. 然后拿着GVA的在L0页的offset,通过HPA_L0 + offset 得到L1页表的GPA 501 | 3. 用L1页表的GPA通过第二级页表翻译成HPA_L1 502 | 4. 然后拿着GVA的在L1页的offset,通过HPA_L1 + offset 得到L2页表的GPA 503 | 5. 用L2页表的GPA通过第二级页表翻译成HPA_L2 504 | 6. 然后拿着GVA的在L2页的offset,通过HPA_L2+ offset 得到L3页表的GPA 505 | 7. 用L3页表的GPA通过第二级页表翻译成HPA_L3 506 | 8. 然后拿着GVA的在L3页的offset,通过HPA_L3 + offset 得到GVA对应的HPA 507 | 508 | **VMID** (Virtual Machine IDentifier) 509 | 510 | * VMM为不同进程分配8/16 VMID,将VMID填写在VTTBR_EL2的高8/16位 511 | * VMID位数由VTCR_EL2的第19位(VS位)决定 512 | * 避免刷新上个VM的TLB 513 | 514 | **优点** 515 | 516 | 1. 客户操作系统在更新页表时不会引起任何虚拟机下陷,性能好 517 | 2. 减少内存开销,不需要为每一个进程单独配置一个表,一个虚拟机一个第二阶段页表 518 | 3. 第一阶段页表和第二阶段页表引起的异常将分别唤醒虚拟机的虚拟机监控器,大大提升了缺页异常的处理性能 519 | 4. 不需要捕捉Guest Page Table的更新 520 | 5. 可以为一个虚拟机,配不同的第二阶段页表->可以防硬件漏洞 521 | 522 | **缺点** 523 | 524 | 1. 在发生TLB未命中的时候,最差情况下,需要经过24次访存 525 | 526 | ### 5.4 memory ballooning 527 | 528 | 🔺 提升性能的关键:打破层次 529 | 530 | 为了解决虚拟机的内存超售,可以使用内存气球 531 | 532 | 1. VMM需要回收虚拟机1的内存 533 | 2. VMM通知虚拟机1里的气球驱动 534 | 3. 气球驱动调用客户操作系统提供的内存分配接口(kmalloc),分配大量内存 535 | 4. 气球驱动在得到这些内存后,将内存对应的GPA发送给VMM 536 | 5. VMM翻译为HPA,并把对应的内存数据保存到存储设备 537 | 538 | 539 | 540 | ## 6. I/O虚拟化 541 | 542 | > 为什么要I/O虚拟化 543 | 544 | 如果VM直接管理物理网卡: 545 | 546 | 1. **正确性**问题:所有VM都直接访问网卡 547 | 548 | – 所有VM都有相同的MAC地址、IP地址,无法正常收发网络包 549 | 550 | 2. **安全性**问题:恶意VM可以直接读取其他VM的数据 551 | 552 | 除了直接读取所有网络包,还可能通过DMA访问其他内存 553 | 554 | 555 | 556 | **IO虚拟化目标** 557 | 558 | 1. 为虚拟机提供虚拟的外部设备 559 | 2. 隔离不同虚拟机对外部设备的直接访问 560 | 3. 提高物理设备的利用资源 561 | 562 | 563 | 564 | **实现IO虚拟化** 565 | 566 | 1. 设备模拟(Emulation)(纯软件) 567 | 2. 半虚拟化方式(Para-virtualization) 568 | 3. 设备直通(Pass-through) 569 | 570 | 571 | 572 | ### 6.1 软件模拟 573 | 574 | 关键:捕捉原生驱动的硬件指令 -> trap and emulate 575 | 576 | **发包** 577 | 578 | 1. VM进行MMIO操作,被trap,导致VM exit 579 | 2. 被KVM捕捉后,KVM发现自己处理不了,进QEMU 580 | 3. QEMU从VM的memory里读出数据包,调用syscall 581 | 4. 进去EL2 ,发给真正的网卡 582 | 583 | ![设备模拟-发包](图片\设备模拟-发包.png) 584 | 585 | **收包** 586 | 587 | ![设备模拟-收包](图片\设备模拟-收包.png) 588 | 589 | **优点** 590 | 591 | 1. 可以模拟任意设备 592 | 593 | 选择流行设备,支持较“久远”的OS(如e1000网卡) 594 | 595 | 2. 允许在中间拦截(Interposition) 596 | 597 | 例如在QEMU层面检查网络内容 598 | 599 | 3. 不需要硬件修改 600 | 601 | **缺点** 602 | 603 | 性能不佳 604 | 605 | 606 | 607 | ### 6.2 半虚拟化方式 608 | 609 | **协同设计** 610 | 611 | * 虚拟机“知道”自己运行在虚拟化环境 612 | * 虚拟机内运行前端(front-end)驱动 613 | * VMM内运行后端(back-end)驱动 614 | * 通过共享内存传递指令和命令 615 | 616 | **发包** 617 | 618 | ![半虚拟化-发包](图片\半虚拟化-发包.png) 619 | 620 | **收包** 621 | 622 | ![半虚拟化-收包](图片\半虚拟化-收包.png) 623 | 624 | **优点** 625 | 626 | 1. 性能优越 627 | 628 | 多个MMIO/PIO指令可以整合成一次Hypercall,减少VM exit 629 | 630 | 2. VMM实现简单,不再需要理解物理设备接口 631 | 632 | 3. QEMU不用实现模拟网卡了 633 | 634 | **缺点** 635 | 636 | 需要修改虚拟机操作系统内核 637 | 638 | 639 | 640 | ### 6.3 设备直通 641 | 642 | 虚拟机直接管理物理设备 643 | 644 | #### **问题1** 645 | 646 | DMA恶意读写内存 647 | 648 | ![设备直通-DMA恶意读写内存](图片\设备直通-DMA恶意读写内存.png) 649 | 650 | 做DMA的地址是HPA,会绕过页表,就不能做权限控制了。 651 | 652 | **解决问题1** 653 | 654 | 加页表(IO的页表) 655 | 656 | ![设备直通-DMA恶意读写内存-解决](图片\设备直通-DMA恶意读写内存-解决.png) 657 | 658 | IO VA就是GPA(IO的位置是guest OS 设的所以是GPA) 659 | 660 | **IOMMU**把GPA翻译成HPA(因为真的设备在HPA) 661 | 662 | 663 | 664 | #### 问题2 665 | 666 | 设备独占 667 | 668 | * Scalability不够 669 | * 设备被VM-1独占后,就无法被VM-2使用 670 | * 如果一台物理机上运行16个虚拟机 671 | * 必须为这些虚拟机安装16个物理网卡 672 | 673 | **解决-问题2** 674 | 675 | ▲ SR-IOV : 在设备层实现虚拟化(实现在设备上) 676 | 677 | * SR-IOV是PCI-SIG组织确定的标准 678 | * 满足SRIOV标准的设备,在设备层实现设备复用 679 | * 能够创建多个Virtual Function(VF),每一个VF分配给一个VM 680 | * 负责进行数据传输,属于**数据面**(Data-plane) 681 | * 物理设备被称为Physical Function(PF),由Host管理 682 | * 负责进行配置和管理,属于**控制面**(Control-plane)、 683 | * 一个PF控制多个VF 684 | * 设备的功能 685 | * 确保VF之间的数据流和控制流彼此不影响 686 | 687 | ![设备直通-SR-IOV的使用](图片\设备直通-SR-IOV的使用.png) 688 | 689 | **优点** 690 | 691 | 1. 性能优越 692 | 2. 简化VMM的设计与实现 693 | 694 | **缺点** 695 | 696 | 1. 需要特定硬件功能的支持(IOMMU、SRIOV等) 697 | 2. 不能实现Interposition:难以支持虚拟机热迁移 698 | 699 | 700 | 701 | ### 6.4 I/O虚拟化技术对比 702 | 703 | ![IO虚拟化技术对比](图片\IO虚拟化技术对比.png) --------------------------------------------------------------------------------