程序员的自我修养(第一章)

《程序员的自我修养》第一章阶段性总结,内容包括计算机软件结构定义,操作系统的作用,线程及进程

计算机软件体系结构

每一层通过接口通信
机构图

应用程序编程接口

应用程序编程接口(Application Programming Interface)的提供者是运行库,也就是说运行库决定了API的内容,比如linux下的Glibc提供POSIX的API,Windows的运行库提供 Windows API,

系统调用接口

系统调用接口(System cal Interface) 运行时库使用操作系统提供的接口,系统接口往往以软件中断的方式提供,比如Linux使用0X80号中断作为系统调用接口,Windows 使用0X2E

中断

中断指当出现需要时,CPU暂时停止当前程序的执行转而执行处理新情况的程序和执行过程。即在程序运行过程中,系统出现了一个必须由CPU立即处理的情况,此时,CPU暂时中止程序的执行转而处理这个新的情况的过程就叫做中断。

硬件中断:

是由与系统相连的外设(比如网卡 硬盘 键盘等)自动产生的. 每个设备或设备集都有他自己的IRQ(中断请求), 基于IRQ, CPU可以将相应的请求分发到相应的硬件驱动上(注: 硬件驱动通常是内核中的一个子程序, 而不是一个独立的进程).处理中断的驱动是需要运行在CPU上的, 因此, 当中断产生时, CPU会暂时停止当前程序的程序转而执行中断请求.

软件中断:

它是在通信进程之间通过模拟硬中断而实现的一种通信方式。软中断不会直接中断CPU, 也只有当前正在运行的代码(或进程)才会产生软中断. 软中断是一种需要内核为正在运行的进程去做一些事情(通常为I/O)的请求.

操作系统的作用

一个功能是提供抽象的接口,另一个主要功能是管理硬件资源,因为硬件的能力是有限的,无论是否使用,资源总是那么多:1GB的内存最多同时存储1GB的数据,计算机的资源:CPU,存储器(内存和磁盘)和I/O设备

CPU

多道程序(Mulit programming)(监控程序):

最初的实现方法,当某个程序暂时无需使用CPU时,监控程序就把另外等待的程序启动,使得CPU充分的利用起来,
缺点:程序之间的调度策略太粗糙,程序之间部分轻重缓急。对于有急需响应任务(交互事件)的程序是致命。可能一个点击事件要很久才被响应

分时系统:

一种稍加改进的协作模式:每个程序运行一段时间后都主动让出CPU给其他程序,windows的早期版本,Mac OS X 之前的Mac OS 都是采用这种分时调度程序的
缺点:当一个程序进行一个很耗时的计算,一直使用CPU,此时其他程序只能等待,看上去像死机一样

多任务系统:

所有程序都以进程的方式运行在比操作系统权限更低的级别,每个进程有自己独立的空间。

抢占式

CPU由操作系统统一进行分配,如果程序运行超出了一定时间,操作系统会停止该进程,将CPU 分配给其他等待的程序

内存

1.地址不隔离:程序直接访问物理地址。可访问程序的数据。
2.使用效率低:没有有效的内存管理机制,
3.地址不确定性:由于在物理地址上,每次运行时装入的地址不同

虚拟地址空间

MMU:页映射部件
虚拟地址到物理地址的转换

分段

把一段与程序所需要的内存空间大小的虚拟空间映射到某个地址空间,如图
分段+虚拟地址,解决了1和3的问题,因为不同的程序被映射不同的物理空间区域,且没有任何重叠,所以在访问超过本身区域的内存时就可以采用拒绝手段(segment faut),而且分段之后,地址都只需从0x0开始编写,不需要考虑重定向的问题

分页

更小粒度的内存分割和映射方法,使程序的局部性原理得到充分的发挥,大大的提高了内存的使用率,同时并不会把所有的代码和数据转载到内存中,而是把部分不常用的代码和数据留在磁盘中,等需要是通过页错误来加载

page fault:

在访问的页不存在时,会触发页错误,在去加载对应的虚拟页

线程与进程

线程

线程的至少拥有3种状态
运行(Running):此时线程正在执行
就绪(Waiting):此时线程可以立刻执行,等待CPU调度(当前CPU被其他线程占用)
等待(Ready):此时线程正在等待某一事件(通常是I/O或者同步)发生,无法执行
如图

优先级调度
  • 用户指定优先
  • 级根据进入等待状态的频繁程度提升或降低优先级
  • 长时间得不到执行而被提升优先级
    轮转法
    每个线程执行一小段(通常是几十到几百毫秒),线程之间是交错执行的特点。这样看起来就像是同时执行
可抢占线程:

当时间片用尽后会被强制剥夺继续执行的权利,而进入就绪状态,这个过程 就叫抢占

不可抢占线程:

必须手动发出一放弃执行的命令,才能让其他线程的得到执行,这样的模式下,线程必须主动进入就绪状态,并且也不进行任何等待操作,那么其他线程将永远无法执行。
在不可抢占线程中线程主动放弃执行的两种情况:
1.当线程试图等待某事件时(I/O等)
2.线程主动放弃时间片

线程安全

原子性:

单指令的执行不会被打断,可满足简单场景下。
对于复杂的场合下,要保证更改的原子性使用:锁

二元信号量

通过对信号量+1-1的操作判断是否执行,任何线程都可以对信号量操作

互斥量

和信号量类似。但是要求哪个线程获取了互斥量,哪个线程就要负责释放这个锁,其他线程释放是无效的,但是可以获取

临界区

条件被互斥量更严格,其他线程无法获取

读写锁:

被使用一种特定的场合的同步,对于数据读取频繁,但仅仅是偶尔写入。读写锁有两种获取方式:共享和独占,
如图

条件变量

类似一个栅栏,对于条件变量,线程有两种操作,首先线程可以等待条件变量,一个条件变量可以被多个线程等待,其次线程可以换下条件变量,此时某个或所有等待次条件变量的线程都会被唤醒并继续支持
也就是说,使用条件变量可以让许多个线程一起等待某一个事件的发生,当事件发生时,所有线程都可以一起恢复执行。

进程

windows:

进程:CreateProcess
线程:CreateThread

linux 的多线程:

linux下所有的执行实体都成为任务(Task),概念上类似一个单独的进程
API:
如图