目录

操作系统之进程与线程(一)

进程

进程是什么

进程就是一个正在执行程序的实例 进程是独立的实体 有自己的程序计数器 和状态 它存放着程序正文 数据 以及其他资源的i地址空间 同时还有一个执行线程 该线程有PC 寄存器 堆栈等

进程的创建和终止

创建

  1. 系统初始化
  2. 执行了正在运行的进程所调用
  3. 用户请求创建一个新进程
  4. 一个批处理作业的初始化

新进程由老进程执行系统调用 创建

  • unix中创建进程的唯一个方式是fork函数 但分两步 fork 然后再 execve 为了fork后进行文件描述符的修改 子进程的地址空间是父进程的副本
  • windows 则含多个 createProcess同时做两件事 子进程地址空间和父进程不同

终止

  1. 正常退出
  2. 出错退出
  3. 严重错误
  4. kill

进程的层次结构

  • unix: 进程只有一个父进程 隶属于一个进程(递归也是)的包括该进程一起组成一个进程组 发送一个信号给进程组 组内成员可以捕获 忽略 或被信号杀死

OS启动时 由init特殊进程启动 读入终端 为终端依次创建进程

  • windows: 进程创建时 父进程得到一个句柄 拥有句柄就可以控制子进程

    进程的状态

  1. 运行态
  2. 就绪态
  3. 阻塞态

3种状态有4种转换关系 其中只有运行态和就绪态之间能够相互转换 其他的都是单向转换

进程的实现

OS内部维护一个进程表 每一个进程占一个表项(也叫进程控制块PCB) 表项中包含了 程序计数器 堆栈指针 内存分配状况 锁打开文件的状态 账号 和 调度信息

中断发生后OS的底层的工作步骤

  1. 硬件压入堆栈PC
  2. 硬件从中断向量装入新的PC
  3. 汇编保存寄存器值
  4. 汇编语言设置新的堆栈
  5. C中断服务例程启动
  6. 调度决定下一个进程
  7. C返回到汇编
  8. 汇编开始执行新线程

多道程序设计模型

CPU利用率 = 1 - p^n
  • p: 等待I/O操作时间和停留于内存的时间比
  • n: 内存中有n个进程

进程间的通信IPC

3个主要问题

  1. 一个进程如何把信息传递给另一个 shell 中一个进程的输出传给第二个进程
  2. 确保两个或更多的进程在关键活动中不会出现交叉
  3. 正确的顺序 A必须在B之前打印

竞争条件

多个进程读取公用存储区(共享数据) 最后结果取决于进程运行的精确时序

Murphy法则: 任何可能出错的地儿都会出错

临界区

共享内存进行访问的程序片段 就叫临界区域

  1. 任何两个进程不能同时在临界区
  2. 不应对CPU的速度和数量进行任何假设
  3. 临界区外运行的进程不得阻塞其他进程
  4. 不得使进程无限期等待进入临界区

忙等待的互斥

  1. 屏蔽中断

进程进入临界区后立即屏蔽所有中断 包括时钟中断

但若屏蔽不打开 则很有影响 对于用户进程 屏蔽并不是一个合适的机制

  1. 锁变量

0 可进入 1 不可进入

但当一个进程发现为0 到准备设置为1的时候 进行了时钟中断 被切换了线程 仍会造成这样的问题

  1. 严格轮转法

用于忙等待的锁是自旋锁

  1. TSL指令

测试并加锁 将内存字读到寄存器RX 然后在该内存地址上存一个非零值 读写不可分割 指令结束前其他处理器无法访问内存字 执行TSL的CPU锁住内存总线

锁总线 不同于屏蔽中断 锁总线能够防止其他处理器的影响 屏蔽中断 只能影响当前的处理器 对其他处理器没有影响

睡眠与唤醒

信号量

整型变量累计唤醒次数 正值 表示有一个或多个唤醒操作

down 和 up 操作 都为不可分割的原子操作

down 检查是否大于0 大于0 -1 为0 睡眠 检查数值 修改数值 睡眠 都为原子操作

若一个或多个进程在信号量上睡眠 不能完成一个先前的down 则随机挑选来完成down

使用信号量解决生产者-消费者问题

使用3个信号量

  • full
  • empty
  • mutex

信号量也可以用于同步

互斥量

信号量的简化版本 仅仅适用于管理共享资源或一小段代码

数值0 表示解锁 其他的值表示加锁

管程

高级同步原语 由过程 变量和数据结构组成的集合 并且任意时刻 只能有一个活跃进程调用管程进程

如何让生产者在发现缓冲区满的时候阻塞?

引入条件变量 有wait 和 signal2个 且 sigal语句只能作为一个管程过程的最后一条语句 执行signal的进程必须立即退出管程

消息传递

进程间通信的方法使用2条原语 send receive 为系统调用

  1. 消息传递的要点
    • 防止网络丢失
    • 身份认证 确认是与文件服务器
    • 正确接收与确认正确接收
  2. 用消息传递解决生产者–消费者问题

消费者发送消息 生产者发送包含实际数据的消息

对消息进行编址

  1. 为每个进程分配唯一一个地址
  2. 引进信箱这个数据结构 一个是有缓冲区的 一个是无缓冲区的

屏障

这个同步机制用于进程组 只有所有进程到达的时候才能够进行下一阶段

线程

为什么要有线程?

  1. 许多应用在同时发生着多种活动,分解为准并行的多个顺序线程可以让程序设计模型简单化
  2. 线程比进程更轻量级
  3. 性能方面 cpu密集型 不能得到性能的加强 但有大量计算和I/O处理的则会加快执行速度

进程模型的抽象使得不用考虑中断 定时器和上下文切换 有了多线程概念 就可以让并行实体共享同一个地址空间 和数据

像是3个线程程进行PDF的编辑 一个用于编辑 一个用于备份 一个用于监听 更加的方便与快捷

构造服务器的3个方法

模型 特性
多线程 并行 阻塞系统调用
单线程进程 无并行 阻塞系统调用
有限状态机 并行性 非阻塞系统调用 中断

有限状态机 > 请求到来时 判断是否在高速缓存中能找到请求对应的吗 能则返回 不能则启动一个非阻塞的磁盘操作 服务器记录当前请求的状态 处理下一个事件 可能是新的请求 可能是磁盘操作结束的回答(一般以信号 或中断方式出现)

经典的线程模型

进程基于两个独立的概念:

  1. 资源分组处理
  2. 资源分组执行

进程可以理解为用某种方法把相关的资源集合于一起

每个进程存放了

  1. 程序正文和数据
  2. 全局变量
  3. 其他资源地址空间
    1. 打开文件
    2. 子进程
    3. 即将发生的报警
    4. 信号与信号处理程序
    5. 账户信息

每个线程存放了

  1. PC
  2. 寄存器
  3. 堆栈
  4. 状态

进程用于把资源集中到一起 线程则是在CPU上被调度执行的实体

用户空间实现线程

线程包放入用户空间

  • 优点:可以在不支持线程的OS中使用 允许每个线程有自己的定制的调度算法 拥有较好的扩展性
  • 缺点:
    • 难实现阻塞系统调用 另一个相似的问题是(页面故障问题 并不是所有的程序都一次性放入内存 某个程序调用或跳转到一条不存在内存的指令会引起这个问题)
    • 一个线程开始运行 其他线程不能运行 单独进程因无时钟中断 无法用轮转调用

OS内核空间存放进程表 用户空间都运行时系统存放 线程表

内核中实现进程

能够阻塞线程的调用 都以系统调用的形式实现 一个线程阻塞时 内核可以运行该线程所属的进程的其他线程 也可以运行其他进程中的线程

某个进程的线程引起页面故障 内核可以很快检查进程是否有任何其他可运行的线程 有的话 在等需要的页面读入过程 就可以让其他线程运行

  • 缺点: 很难判断多线程进程创建新的进程时 创一个线程还是同等数量级的线程

混合实现

将一个内核级线程对应多个用户级进程

调度程序激活机制

模拟内核线程的功能

弹出式线程

传统处理服务请求是 请求来之前就准备线程

弹出式线程 是服务请求来之后 创建一个线程进行处理

试图将单线程转为多线程的问题是:有些库函数是不可重入的 不能在没调用完上一个的情况下 就调用下一个 (malloc也是类似的 ) 可以使用二进制标志位的包装器来解决上面的问题

对于在多线程中的信号问题 一个信号可能在不同线程表达的意思不同 也许是中断 亦或是捕捉信号

调度

OS为多道程序设计系统 有多个进程竞争CPU 多个进程处于就绪状态 如果只有一个CPU可用 则必须选择下一个要运行的进程

进行选择工作的则为调度程序

进程行为

  • CPU密集型进程
  • I/O密集型进程 如今要越来越偏重这个

何时调度

  1. 创建一个进程后 运行父进程还是子进程
  2. 进程退出
  3. 进程阻塞在I/O 或信号量
  4. I/O中断 根据硬件时钟的中断 进行调度
    1. 非抢占式调度算法 选一个进程直到进程自动释放CPU
    2. 抢占式调度算法 让其运行固定时段最大值 然后挂起 选择别的进程

调度算法分类

  1. 批处理 账单
  2. 交互式 防止一个进程霸占CPU
  3. 实时

调度算法的目标

基本目标: 公平 策略强制执行 平衡

批处理系统的调度 吞吐量 周转时间 CPU利用率

  1. 先来先服务
  2. 最短作业优先 非抢占式 只有当所有作业可运行情况下 才是最优化
  3. 最短剩余时间优先 上面的抢占式版本 运行时间需要掌握

交互式系统: 响应时间 均衡性

  1. 轮转调度 进程分配时间片 维护进程列表 时间片太短 进程切换过快 设置太长对交互的请求响应时间长
  2. 优先级调度 静态和动态赋予 随时间降低优先级 动态是使用完优先级的进程优先级越高
  3. 多级队列 为cpu密集型设置较长时间片比较耗时 进行递增的添加 不是1->1
  4. 最短进程优先
  5. 保证调度
  6. 彩票调度
  7. 公平分享调度

实时系统: 满足截止时间 可预测性

可静态(系统开始前做出调度,且要掌握所有信息) 可动态(运行过程中做出调度) 讲究实时性 迟到的应答还不如不来

  • 硬实时: 绝对的截止时间
  • 软实时: 不希望延时 但能容忍

按响应方式 又有周期性非周期性事件

m Σ(i = 1)ci/pi <=1
 pi: 事件i以周期pi发生
 ci: 需要ci秒cpu时间处理

则是可调度的

策略和机制

上面的算法都是写死了的调度决策信息 无法接收用户进程的自定义调度决策信息 使用调度机制和调度策略分离 提供用户进程填写参数

线程调度

多个进程中有多个线程 调度处理取决于是用户级进程还是内核级进程

  • 用户级进程

多道线程不存在时钟中断 可任意运行多长时间 线程用完了所属进程的时间片 内核切换到别的进程运行 缺陷是缺乏一个时钟将运行过长的线程中断

  • 内核级进程

内核随意选线程 不考虑线程所属哪个进程

内核的线程切换比用户线程消耗资源要多 内核级进程要切换上下文修改内存镜像 清除高速缓存 但内核级线程时 线程阻塞在I/O上 不会像用户级线程样将整个进程挂起