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
上一篇下一篇

猜你喜欢

热点阅读