1章 温故知新 << 程序员的自我修养 >>
2022-06-28 本文已影响0人
my_passion
1章 温故知新
本书: x86 指令集 32 位 CPU
1. 软件工程师 对硬件只需要关注 3 parts:
Memory
\
\ 频率一致
\ 协调 左右两个速率
bus(总线) - - - I/O 控制芯片 - - -I/O 设备
/
/ 倍频
/
CPU
|
|
| 高速图形设备
|
|/
高速 北桥芯片
+
低速南桥芯片
2. 计算机科学领域 的 任何问题
都可以通过 增加1个间接的 中间层
来解决
接口: 层间 通信协议
接口稳定 & `下层是作 插件 => 可替换`
应用程序 和 开发工具
|
|
|
(1) OS API: (应用程序编程)接口)
|
|
|
Runtime Library
|
|
|
(2) System call: (系统调用) 接口
| `软中断 (Software Interrupt)` 方式提供: Linux ox80 号中断
|
|
OS Kernel
|
| driver program
|
(3) Hardware Specification (硬件规格): (驱动程序 操作 硬件 的) 接口
|
| 虚拟机: Hardware 与 OS 间 增加了1层 `虚拟层` => 1个 computer 运行 多 OS
|
Hardware
3. OS 做什么
2 功能
[1] 提供 抽象的接口
[2] 管理硬件资源
(1) 不让 CPU 打盹
程序 读写 磁盘 时, CPU 空闲
|
|
|
|/
多道 ( MultiProgramming ) 程序
|
| 监控程序: 监控到 某 program 暂时不用 CPU -> 切到 waiting CPU 的 program
|
| 调度粗糙
|/
分时( Time-Sharing ) 系统
|
| program `主动让出 CPU`
|
| `任一 program 死循环 -> OS 死机`
|/
多任务( Multi-tasking ) 系统: OS 接管 所有硬件资源
CPU 分配
1> `进程优先级`
2> `抢占式` : 线程 `时间片耗尽` 后, 被 OS 强制剥夺继续执行的权利, 进入 Ready 态
3> 时间片短 => `CPU 在 多个进程间 快速切换` => `多进程 同时运行的假象`
(2) 设备驱动
1) 硬件抽象
2) 文件系统
扇区
512 字节
ext3 文件系统
8000 字节文件
4096: 扇区 1000-1007 共 8 个扇区
|
| 链状
|/
3094: 扇区 2000-1007 共 8 个扇区, 尾部 192 字节 无效
3) Linux 系统 读 文件 前 4096 字节
read() 系统调用
|
|
|/
文件系统
|
| 1> 收 read 请求
| 2> 判 该文件的前 4096字节 在 逻辑扇区 1000-1007
| 3> 向 硬盘驱动 发 读取请求
|/
硬盘驱动程序
|
| 向 硬盘发 硬件命令:
| 读/写 `I/O 端口寄存器` (x86平台 65536 个 硬件端口寄存器, 分配到 不同 I/O 端口地址)
| CPU 提供 2 条 指令 "in" 和 "out" 实现 对 硬件端口的 读 和 写
|/
硬盘
4. 内存不够 怎么办 ?
1) 地址空间不隔离: 程序 直接访问 物理地址
/
4.1 内存分配 可能的 3 个问题 — 2) 重定位(程序运行地址不确定) 靠 coder 完成
| \
3) 内存使用效率低: memory 不足 & 以 Program 为单位换入换出到磁盘
段映射: 解决1/2
coder 据 虚拟地址空间 (0x00000000 开始 )编程 & `重定位` 靠 linker 完成
页映射: 分页&页权限
磁盘页(DP) : `放 不常用` 数据和代码
物理(内存)页 (PP) : 放常用...
虚拟页 (VP)
进程 用到的 `VP2 不在 内存`
-> 硬件捕获该消息: `页错误 (Page Fault)`
-> `OS 接管 进程`, 将 VP2 从 磁盘 读出并装入 `VP2 新映射的 PP`
页大小典型值 4 KB
`实现: `MMU (Memory Management Unit)
MMU 一般集成于 CPU 内部
地址空间
抽象概念
大字节数组
32位地址空间 2^32 字节
0 - 2^32 - 1 / 0x00000000 - 0xFFFFFFFF
物理地址空间
唯一
内存 + I/O 设备 映射
虚拟地址空间
进程 独立虚拟空间: 地址都是 从 0 开始
4.2 总结
————————————————————————————————————————————————————————————
要解决的问题 | 机制
————————————————————————————————————————————————————————————
`CUP` 共享 于 多进程间 | OS 的 多任务功能
`I/O 设备 共享` + 抽象概念 | OS 的 I/O 抽象模型
`内存分配` | 地址空间隔离 + 段映射 / 页映射
————————————————————————————————————————————————————————————
4.3 `内存共享` 如何实现 ?
`多个进程` 的 `VP 映射到 同一 PP`
5. 多线程
5.1 线程
(1) 什么是 线程 (Thread) ?
线程, 也称 `轻量级进程`
是 程序执行流程 的 最小单元
组成
线程 ID / PC (当前指令指针) / 堆栈 / 寄存器集
(2) 进程内 的 多线程
共享
程序的内存空间: `代码段 / 数据段 / 堆`
进程级资源: 进程空间 / 打开的文件 / 信号
相对于 多进程, 多线程 `数据共享 效率高`
(3) 线程访问权限
——————————————————————————————————————————————————
线程私有 | 线程间共享(进程所有)
——————————————————————————————————————————————————
栈 | 堆
local var | global var
func para | func 内 static 变量
TLS (线程局部存储) 数据 | 程序代码
| 打开的文件
——————————————————————————————————————————————————
(4) 线程调度 与 优先级
1) 线程 3种状态
Running
正在运行
Ready
可立刻运行, 但 CPU 被占
Wait
等待事件: I/O 或 同步
2) 时间片
3) 调度策略
轮转
/
\
优先级调度
`I/O 密集型 线程` -> OS 提升 优先级
`CPU 密集型 线程`: 优先级 应该更低
若 优先级 => `I/O 密集型 线程` `饿死 (Starvation)`
(5) 可抢占线程
`抢占 (Preemption)`: 线程 `用尽时间片` 后, 被 OS 强制剥夺继续执行的权利, 而进入 就绪状态
即 之后执行 的 别的线程 抢占了 当前线程
(6) Linux 多线程
Windows 内核 区分 线程 和 进程
1) Linux 内核 将 `线程 / 进程` 都称为 `Task`
Task 是否 `共享内存空间`- -> 线程 / 进程
2) 创建新任务
fork: copy currentTask's execImage(可执行映像)
exec: newTask 调 exex: newExecImage 覆盖 currentExecImage
clone: copy currentExecImage & 指定位置 开始执行,
本任务 从 fork 返回 新任务 pid
/ |
/ |
fork 后 | `Copy On Write 的 内存空间`
| \ | 两个任务 可 同时读, 但 任一任务 写内存 时 => 内存 copy 一份 让 修改方 使用
\ |
| 新任务 从 fork 返回 0
| fork 只产生 本任务的镜像, `必须 配合 exec 才能启动 新任务`
|/
exec: `新任务调 exec`, 用 `新 可执行映像 覆盖` 当前可执行映像
5.2 线程安全
(1) 同步 与 锁
同步: 一个线程 访问数据 未结束时, `其他线程 不得 访问 同一数据`
=> 对 数据的访问 被原子化了
同步 手段:
1) 锁 (Lock)
机制: 线程 访问 数据/资源 前,
先 试图 `获取 (Acquire)` 锁
————————————————————————————————————————————————————————
1) 获取 / 释放锁 的形式
3 种锁 2) scope
3) 其他线程 `是否可 越俎代庖 去 释放`
————————————————————————————————————————————————————————
信号量(Semaphore) 获取 / 释放 信号量
所有线程可见
其他线程 `可 越俎代庖 去 释放` 信号量
————————————————————————————————————————————————————————
互斥量(Mutex) 获取 / 释放 互斥量
所有线程可见
不可 越俎代庖
————————————————————————————————————————————————————————
临界区 进入 / 离开 临界区
Critical Section 仅 本进程 可见
————————————————————————————————————————————————————————
2) cv
等待 cv: 等待线程
唤醒 cv: 通知线程
(2) 可重入 (Reentrant) 与 线程安全
func 没执行完(递归 / 多线程并发执行)就 再次进入执行
可重入: 重入后 无 不良后果
int sqrt(int x) { return x * x; }
特点:
[1] 不使用 local static / global non-const var
[2] 不返回 local static / global non-const var 的 指针
[3] 仅依赖 caller 提供的 para
...
(3) `过度优化`
1) Compiler cache 变量 到 register, 很久后才 写回(可能被other线程改了)
2) Compiler 乱序执行
调整 指令顺序
阻止 1) 和 2) : `volatile` 修饰变量
3) CPU 乱序执行
`单例模式 双检测`
1 条 new 语句 分解为 3 步: malloc + 调 Ctor + 地址强转 -> 3) 可能先于 2)
=> `单例指针 pInst 已经不是 NULL, 但 对象还没构造完成` -> 被 其他线程 检测&use
解决
barrier(阻止 CPU 换序 穿透 barrier)
tempPtr(不会被外界 看到)
pInst = new T
|
|
|/
T* temp = new T; // 临时指针 temp
barrier();
pInst = temp;
5.3 多线程 内部
3 种线程模型: User Thread -> Kernel Thread
(1) 一 对 一
线程间并发是 真正的并发
(2) 多 对 一
1 个 User Thread 阻塞, 所有线程 均阻塞
(3) 多 对 多
6 对称 多处理器 SMP`: CPU 地位 / 功能 相同
|
| 但 `program` 并不能都分解为 `不相干的子问题`
|/
`多核处理器`: 共享 cache(昂贵) + 保留多个核心
段映射.jpg
页映射.jpg
进程内线程.jpg
COW.jpg