主页 程序员的自我修养 ——链接、装载与库读书笔记(一)
Post
Cancel

程序员的自我修养 ——链接、装载与库读书笔记(一)

此系列为我学习经典的读书笔记,目的是理清知识脉络帮助理解记忆,内容深度、质量远远不及原书。如果您对相关知识感兴趣,强烈建议阅读原书程序员的自我修养

带着问题学习

  • 计算机的软硬件基本结构是什么
  • 计算机如何充分利用 CPU 以及内存
  • 线程是什么,什么是线程安全,如何保证线程安全

计算机的软硬件基本结构

对于软件开发者来说,计算机硬件抓住三个关键部位 CPU、内存和 I/O 控制芯片

硬件框架

早期计算机 CPU、内存和 I/O 设备等都是直接连接在总线(BUS)上。

后来随着 CPU 运行速度的不断提高,慢速的 IO 总线已经无法满足功能需求。人们就把总线拆分成了连接低速设备的南桥键与连接高速芯片的北桥。键盘、鼠标之类都连接在南桥。CPU、内存连接在北桥。

虽然设计更加复杂,但是基本架构没有离开CPU、内存和 I/O 控制芯片这三个关键硬件部位。

软件体系结构

计算机软件体系按层分级

层次之间的通信协议称为接口,每个中间层都是对下层的包装与扩展

这种做法的好处就是各层之间相对独立,只要保证遵循接口协议,那么整个层级都是方便替换的。

操作系统做什么

两个主要功能一个是提供抽象接口,另一个就是管理硬件资源

CPU 管理

早期由于 CPU 资源的紧张,系统往往倾向于最大限度提高 CPU 使用率。基于不让 CPU 停止工作的策略,初期的解决方法是当某个程序暂时不使用 CPU 后,系统就让另一个等待的程序使用 CPU,这就是多道程序(Multiprogramming)

这种做法在当时大大提升了 CPU 效率,但是程序运行部分优先级,可能涉及用户交互的程序要等待很久,这是不能接受的。后来又进行了改进,每个程序运行一会儿后,都是主动交出 CPU 供其他程序运行,这就极大的优化了交互式任务的体验。这种协作模式就是分时系统(Time-Sharing System)

但是这还是存在问题,如果一个程序陷入死循环无法交出 CPU,那么整个系统都死机了,这也是不能接受的。

再后来,操作系统接管了硬件操作。所有程序以进程的形式运行在比操作系统权限低的级别,每个进程有独立的地址空间,相互隔离。CPU 由操作系统统一分配,每个进程根据优先级高低都会被分配到 CPU,当运行时间超过一定时间,系统会暂停该进程,把 CPU 分配给它认为更应该使用 CPU 的进程。这种方式就是抢占式(Preemptive)。现代几乎所有操作系统都是这样管理 CPU 的。

内存不够怎么办

早期的计算机内存有限,如何把有限的物理内存分配给多个程序就是一个很大的问题。

程序直接运行在物理内存的主要问题有三个

  • 地址空间不隔离 所有程序都在物理内存,恶意程序非常容易篡改数据
  • 内存使用效率低 程序间的切换会导致内存大量的换入换出数据
  • 程序运行地址的不确定 程序每次装入运行都需要分配一块儿足够大的空闲区域,但是这个位置又是不确定的,而程序跳转往往是固定的,这就涉及程序的重定位问题。

解决的办法就是引入新的中间层虚拟地址(Virtual Address),通过某种映射,把虚拟地址转换为物理地址。

分段

分段的基本解决思路就是把一段程序所需要的内存空间大小的虚拟地址映射到某个地址空间,这样就解决了问题1和问题3

分页

事实上,当一个程序运行时,某一定时间,它只是频繁用到很小的一块内存区域。由于分段是粗粒度的分配内存,为了解决内存使用率低的问题,引入了分页的方法。

就是把空间大小人为的分为固定大小的页,页的大小由硬件或者操作系统决定,通常为 4KB 或者 4MB。

众人拾柴火焰高

什么是线程

线程是程序执行流的最小单元,由线程ID、当前执行指针、寄存器集合和堆栈组成。

访问权限

私有共享
局部变量全局变量
函数的参数堆上的数据
TLS数据函数里的静态变量
 程序代码,任何线程都有权限读取并执行任何代码
 打开的文件,A线程打开的文件可以由B线程读取

线程调度

线程通常有三种状态:

  • 运行(Running):此时线程正在执行
  • 就绪(Ready):此时线程可以立即执行,但是 CPU 已经被占用
  • 等待(Waiting):此时线程正在等待某一事件(通常是 IO)发生,无法执行

处于运行中线程有一段可以执行的时间,叫做时间片,当时间片用尽时候,线程将处于就绪状态。 如何时间片用尽之前,开始等待某事件,线程将进入等待状态,等待状态的线程,事件发生后也将进入就绪状态。 当一个线程离开运行中状态后,调度系统会选择一个就绪中状态的线程进行执行。

在优先级调度的环境下,线程的优先级改变一般有三种方式:

  • 用户指定优先级
  • 根据进入等待状态的频繁程度提升或降低优先级
  • 长时间得不到执行而被提升优先级

同步与锁

多线程在一个复杂多变的环境当中,全局变量以及堆数据随时会被其他线程改变。多线程在并发时的数据一致就是线程安全。

信号量

一个初始值为 N 的信号量,允许 N 个线程同时访问。

互斥量

与信号量类似,区别在于。信号量可以由 A 线程获取,B 线程释放。但是互斥量获取与释放必须是同一线程。

临界区

控制更加严格,与互斥量和信号量的区别在于。互斥量和信号量在系统中所有进程可见。而临界区要求必须是本进程的线程去获取锁。

读写锁

由三种状态:自由、共享、独占 两种获取方式:共享、独占

读写锁状态共享方式获取独占方式获取
自由成功成功
共享成功等待
独占等待等待

条件变量

作用类似 barrier,可以让许多线程一同等待某一条件发生,条件发生时,所有线程恢复执行。

总结

经过刚才的梳理,可以回答开头的问题了吗,如果还不能,或者想了解更多,欢迎阅读原书。如果发现了我的错误与纰漏,也请不吝赐教,十分感谢。

该博客文章由作者通过 CC BY 4.0 进行授权。

Github Pages 博客迁移阿里云

程序员的自我修养 ——链接、装载与库读书笔记(二)