p3 多处理器编程:从入门到放弃 (线程库;现代处理器和宽松内存模型)#
- 
并发程序的三个麻烦
- 原子性
 - 顺序
 - 可见性
 
 - 
gcc 编译
- 不优化,并查看汇编代码
gcc -c -O1 sum.c && objdump -d sum.o asm volatile("" : : "memory"); // compiler barrier
 - 不优化,并查看汇编代码
 - 
统计次数
./a.out | head -n 1000 | sort | uniq -c - 
现代处理器
- 也是动态编译器:汇编指令也是由多个 uop 所组成的。
 - 维护一个 uop 的 “池子” 指令的有向无环图
 - 乱序执行,顺序提交
 
 
p4 理解并发程序执行 (Peterson 算法、模型检验与软件自动化工具)#
- C 语言的形式语义
- 全局变量加多个栈帧;每个栈帧有其局部变量和 pc
 
 - Peterson 算法
- 看上去是谦让的,但其实是自私的
 - 证明正确性:画出状态机
- 困境:不敢不画,不敢乱画
 - 解决: model-checker
 - 把程序的问题变成图论的问题
- safety 红色状态不可达
 - liveness : 从任意状态出发,都能到达绿 / 蓝色状态 强连通分量
 
 
 - 许多重要的想法,凝练以后就是概念
 
 - 并发程序 = 状态机
 - Python generator
- e.g.
def numbers(init=0,step=1): n = init while Trye: n += step yield n g = numbers() g.__next__() 
 - e.g.
 
p5 并发控制:互斥 (自旋锁、互斥锁和 futex)#
- 不能解决问题的时候,可以找到所依赖的假设,并大胆地打破它
 - spin 线程直接共享 locked
 - mutex 通过系统调用访问 locked
 - futex(Fast Userspace muTexes)
- Fast path: 一条原子指令,上锁成功立即返回
 - Slow path: 上锁失败,执行系统调用睡眠
 
 
p6 并发控制:同步 (条件变量、信号量、生产者 - 消费者和哲♂学家吃饭问题)#
- 思考: 有一堆任务,平均切分成 n 堆。有 x 个线程负责完成该任务 (x < n) 一个线程一次只能完成一个任务,完成后会自动去做下一个任务。要怎么实现?
 - 有万能的方法,就要用万能的方法。
- 他是这样诠释的。当项目代码量不大(一千行以内),项目还是比较好维护的,这时候用写聪明的写法没问题。但当项目到了几万行甚至几百万行时,这时候就需要多个人来进行协作。而人和人之间最大的障碍就是无法完全沟通,理解对方的心意。
 - 不要试图用聪明的办法解决并发问题
 - 个人想法:第一次听这种说法,有一定道理。
 
 - 万能同步方法 —— 条件变量 (Conditional Variables)
- API
- wait(cv, mutex) 💤
调用时必须保证已经获得 mutex
释放 mutex、进入睡眠状态 - signal/notify (cv) 💬 私信:走起
如果有线程正在等待 cv,则唤醒其中一个线程 - broadcast/notifyAll (cv) 📣 所有人:走起
唤醒全部正在等待 cv 的线程 
 - wait(cv, mutex) 💤
 - 
// 需要等待条件满足时 mutex_lock(&mutex); while (!cond) { wait(&cv, &mutex); } assert(cond); // ... // 互斥锁保证了在此期间条件 cond 总是成立 // ... mutex_unlock(&mutex); // 其他线程条件可能被满足时 broadcast(&cv); - debug -> 隔离出 bug 触发的最小条件
 
 - API
 
p7 真实世界的并发编程 (高性能计算 / 数据中心 / 人机交互中的并发编程)#
- 谈 block chain > 是个很好的技术。但觉得不太对。因为造成了相当大的资源浪费。
 - What's so special about the Mandelbrot Set? - Numberphile
 - atanunq/viu
 - 搜索降低了知识的获取成本,ChatGPT 等再一次降低了成本。
 - go 语言,编程友好、性能优化
 - 博客是 web2.0 的第一步
 - Ajax (Asynchronous JavaScript + XML)
 - 这次课中讲了三种并发编程,根据不同的需要,实现并发的方式也不同。
 
p8 并发 bug 和应对 (死锁 / 数据竞争 / 原子性违反;防御性编程和动态分析)#
- 软件是需求在计算机数字世界的投影。
 - assert 的使用
 - 没有工具不做系统
 - premature optimization is root of all evil
 - 编程语言的缺陷 —— 对程序员的完全信任:因为计算资源的宝贵
 - 动态分析工具 
-fsanitize - Canary msvc 中 debug mode 的 canary  
(b'\xcc' * 80).decode('gb2312') 
p9 操作系统的状态机模型 (操作系统的加载;thread-os 代码讲解)#
- 大学的真正意义将已有的知识和方法重新消化,为大家建立好 “台阶”,在有限的时间里迅速赶上数十年来建立起的学科体系。
 
p10 状态机模型的应用 (细胞自动机;gdb/rr/perf; 代码验证工具)#
- 分布式系统也是一种并发程序,但要更复杂。因为并发程序假设了每个 thread 都能正常运行,而分布式系统则要考虑节点丢失的情况。
 
p11 操作系统上的进程 (最小 Linux; fork, execve 和 exit)#
- Linux 操作系统启动流程
CPU Reset → Firmware → Loader → Kernel _start () → 第一个程序 /bin/init → 程序 (状态机) 执行 + 系统调用 - Fork Bomb:
:(){:|:&};::() { # 格式化一下 : | : & }; : - stdout:
终端: line buffer
pipe , file buffer (除非显示地调用 fflush) - fork
- 程序就是状态机,正在执行的程序也是状态机,fork 创建状态机的副本;
 - 创建的进程返回 + 1,子进程返回为 0
 - 把所有的寄存器和内存都复制
 
 - execve
- 将当前运行的状态机重置成成另一个程序的初始状态
 
 - _exit
 
p12 进程的地址空间 (pmap; vdso; mmap; 游戏修改器 / 外挂)#
- 端序
- 大端 (big endian): 低地址存放高有效字节
 - 小端 (little endian): 低字节存放低有效字节
 
 - 工具使用
- gdb
 - readelf
 - pmap
 
 - 计算机世界没有魔法。因为程序就是状态机。
 - vdso: 不进入操作系统内核,实现系统调用
 - mmap:
 - 文件 = 字节序列;内存 = 字节序列; everything is a file
 
p13 系统调用和 Shell (freestanding shell, 终端和 job control)#
- cd 是内部命令:改变当前目录是用系统调用实现的
 strace -f gcc a.c 2>&1 | vim -This will pipe both stdout and stderr to vim. The-argument tells vim to read from stdin.strace pmap 152 |& vim -
|&: This is a shorthand for2>&1 |in bash and zsh. It passes both standard output and standard error of one command as input to another.- fish, zsh 和 bash 都是常用的命令行 shell; sh 是比较原始的
 - clear 清屏
 ,./a.out &后台执行./a.out
p14 C 标准库的实现 (系统调用的封装;内存空间管理)#
- 文件描述符还是不理解。印象中这是第二次谈到了 "everything is a file"
- os 的对象和对象的访问
 
 - gdn 的使用
No symbol table is loaded. Use the "file" command。可能是编译选项未包含 debug 信息,如 gcc 没有添加 - g 选项。
 - premature optimization is the root of all evil.
 - 脱离 workload 谈优化就是耍流氓
 - 经典的设计:
- fast path
 - slow path
 
 
p15 fork 的应用 (文件描述符的复制;写时复制;创建平行宇宙的魔法)#
- fork 状态机复制包括持有的所有操作系统对象
 - 包括持有的所有操作系统对象
 - 文件描述符(file discriptor)
- 一个指向操作系统内对象的 “指针”
 - dup () 的两个文件描述符是共享 offset
 
 - 访问空指针也会造成缺页中断
 - “Copy-on-write” 只有被写入的页面才会复制一份
- 被复制后,整个地址空间都被标记为 “只读”
 - 操作系统捕获 Page Fault 后酌情复制页面
 - fork-execve 效率得到提升
 
 - 操作系统会维护每个页面的引用计数
 - 定义进程所占用的内存
 - page 是归 os 所有的,而非进程
 - 使用 fork 来搜索并行化。
 
p16 什么是可执行文件 (调试信息;Stack Unwinding;静态链接中的重定位)#
- 可执行文件描述了状态机,是一个描述了状态机的初始状态 + 迁移的数据结构
 - os 没有魔法,所有东西都有解释
 She-bang#! interpreter [optional-arg]- GNU binutils
- 生成可执行文件
- ld (linker), as (assembler)
 - ar, ranlib
 
 - 分析可执行文件
- objcopy/objdump/readelf
 - addr2line, size, nm
 
 
 - 生成可执行文件
 objdump -d a.out | lessdisasmaddr2line 401122 a.out- elf: 小精灵;dwarf:矮人
 - 将一个 assembly (机器) 状态映射到 “C 世界” 状态很难
 - gcc 等仍存在着许多不完美
 - 编译器,汇编器,链接器
 
p17 动态链接和加载 (静态 ELF 加载器实现;调试 Linux 内核;动态链接和加载)#
- 自定义了一个二进制格式文件
 - GOT : global offset table
 - PLT : procedure linkage table
 
p23 1-Bit 数据的存储 (延迟线 / 磁芯 / DRAM/SRAM/ 磁带 / 磁盘 / 光盘 / Flash SSD)#
- volatile: 确保该变量的实际值与内存中的值一致,每次读取都是最新值,也禁止编译器对其进行优化。
 - core dumped 磁性内存年代开始的概念。
 - 局部性原理 -> 可以按照大块来读写
 
p24 输入输出设备模型 (串口 / 键盘 / 磁盘 / 打印机 / 总线 / 中断控制器 / DMA 和 GPU#
- DMA: direct memory access : 一个专门执行 "memcpy" 程序的 cpu
 - IPC: Instruction per second
 - GPU:
- 一个通用计算设备
 - 大量并行相似的任务
 
 - 异构计算:都能做,但选那个最合适的。(jjy 在 22 年说的现在已经能感觉到有相关的趋势了。不过倒不是里面举例的挖矿,而是 llm 模型)
 
p25 设备驱动程序 (Linux 设备驱动;GPU 和 CUDA; 存储设备抽象)#
- 设备抽象成 支持各类操作的对象 (文件)
- read - 从设备某个指定的位置读出数据
 - write - 向设备某个指定位置写入数据
 - ioctl - 读取 / 设置设备的状态
 
 stty -a- GPU
- Single Instruction, Multiple Thread
 
 - 读优先的正确性
 
p26 文件系统 API (设备在应用间的共享;目录和文件 API)#
- 信息的局部性
 - Windows 从 c 盘开始时是受其前身 Dos 系统的影响,那个有 a、b
 mount disk.img /mntumount /mnt- 硬(hard)链接
ln /usr/local/python3 python- 目录中仅存储指向文件数据的指针
 - 允许一个文件被多个目录引用.
 - 无法用来链接目录,也不能跨文件系统
 - 通过
ls -i查看是否为硬链接 
 - 软 (symbolic) 链接
- “快捷方式”
 ln -s ../p24 p24- 目录从 “树” 变为了 “图”,还是有环图
 
 cd的特殊性- 每个进程都有一个对应的工作目录(pwd),而这个目录只有系统调用才能够修改
 
p27 FAT 和 UNIX 文件系统 (数据结构视角的文件系统;FAT 手册导读和目录树遍历)#
- 数据结构的假设:数据是以字节来存储的。
 - RAM 和 block 的区别
 - FAT(File Allocation Table)
- 将指针集中存放在文件系统的某个区域
 - 适合小文件
 - 会产生碎片(fragmentation)
 - 基本假设
- 链表无环且长度和文件大小一致
 - FREE 的 cluster 不能有入边
 
 
 - cluster
 - sector
 - ext2
- 大文件的随机读写性能提升明显 (O (1))
 - 支持链接 (一定程度减少空间浪费)
 - inode 在磁盘上连续存储,便于缓存 / 预取
 - 碎片
 
 
p28 持久数据的可靠性 (RAID; 崩溃一致性;FSCK 和日志)#
- 虚拟化
- cpu 的虚拟化:通过分时等技术让多个进程并行,相当于虚拟出了多个 cpu
 - 内存的虚拟化:一份内存通过 mmu,虚拟成每个进程的地址空间
 - RAID:反向的虚拟化:多个磁盘虚拟化一个磁盘
 
 - RAID
- RAID0 : 交错排列: 提升容量和带宽
 - RAID1 : 提升容错和读带宽
 - RAID4 : 额外的一块校验盘
- 致命缺陷:随机写的性能只能有校验盘性能的一半
 
 - RAID5 : Rotating Parity
 
 - RAID 带来的联想:
多个磁盘虚拟化为一个又大又快又可靠的磁盘,多台电脑虚拟化为一个又大又快又可靠的电脑
那能不能多个神经网络虚拟化为一个更好的神经网络 - 崩溃一致性  (Crash Consistency)
- 场景:写入的时候突然断电了怎么办?
 - 方法 1:按照一定顺序来写,且 “all or nothing”
- 困难:磁盘不提供多块读写 “all or nothing” 的支持,甚至为了性能,没有顺序保证。
 
 - 方法 2: File System Checking (FSCK)
- 根据磁盘上已有的信息,恢复出 “最可能” 的数据结构
 - 困难:难;如果修复的时候再掉一次电?
 
 - 方法 3: 日志
 - 具体:
- 数据结构操作发生时,用 (2) append-only 记录日志
 - 日志落盘后,用 (1) 更新数据结构
 - 崩溃后,重放日志并清除 (称为 redo log;相应也可以 undo log)
 
 - 优化: journaling (jdb2)
 
 
p30 现代存储系统 (关系数据库和分布式存储系统)#
- 数据库
- 关键
- 索引
 - 查询优化
 
 - magic:你只管写 sql 语句,相应的搜索优化它来做
 - 要求:acid
- Atoming
 - Consistency
 - Isolation
 - Durability
 
 
 - 关键
 - 图灵奖
- 这门课听下来,听到了好多知识点背后都是获得过图灵奖的研究,甚至开创了一整个产业。
 
 - 关系型数据库跟不上社交网络的需求
 - cap theorem
- Consistency
 - Availability
 - Partition Tolerance
 
 - 分布式存储系统
 
感想#
蒋炎炎这门课还是别人推荐的。第一次看到还不以为意,但出现的次数多了就觉得有必要去看看。发现是一大惊喜。
收获#
- 原版书能看得下来了。大段的英文,之前看着有点怕,现在觉得也能看下来,并且速度还可以。