美文网首页
1章 温故知新 << 程序员的自我修养 >>

1章 温故知新 << 程序员的自我修养 >>

作者: my_passion | 来源:发表于2022-06-28 23:56 被阅读0次

    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

    相关文章

      网友评论

          本文标题:1章 温故知新 << 程序员的自我修养 >>

          本文链接:https://www.haomeiwen.com/subject/epxtbrtx.html