1.1 前言
进程:本质上来说就是一个正在执行的程序
1.2 进程介绍
1、现代计算机可同时做几件事情,CPU 不断在进程间切换
2、在一个 CPU 中,在某一个瞬间,其实只能运行一道程序,因切换较快,给人错觉并行运行,其实是伪并行
3、在多个 CPU 中,可以达到真正的并行,在享受便利的同时,也有一个不好的地方,因为是并行,所以我们就很难追踪执行程序间的活动,为此,操作系统设计者发展了一种进程模型 :顺序进程
1.3 进程模型
1、计算机上所有可运行的软件(也包括操作系统),都被归类为若干个顺序进程
2、一个进程就是一个正在执行的程序,包括程序计数器,寄存器,变量值
3、拥有自己的虚拟 CPU ,但不是真正的 CPU,真正的 CPU 是一直在进程间来回切换的
例如下图,多个进程 CPU 切换
单个 CPU 中,每个进程在某一时刻的活跃状态
3、进程与程序的关系:
举个栗子
有个人叫大袖,他在照着菜谱做菜,一不小心被烫伤了,他就马上停止做菜,去打开烫伤处理手册,按照该手册处理烫伤,当处理伤口完毕后,他又继续去做菜
分析:
大袖:CPU
菜谱和烫伤处理手册:程序
一不小心被烫伤了:某种 CPU 调度算法,决定 CPU 什么时候发生进程间切换
大袖看着菜谱,拿锅拿铲,放油放盐等等一系列事情活动的总和:进程
大袖看着烫伤处理手册,放药,包扎等等一系列事情活动的总和:进程
1.4 进程的创建
1、进程被创建的几个原因
1> 系统初始化时
操作系统启动时,通常会被创建若干个进程
分为:前台进程(用户交换) 和 后台进程(守护进程)
2> 进程创建进程
进程需要其他进程协助完成工作,会创建新的进程
3> 用户请求创建
4> 批处理作业初始化
当操作系统任务是新的资源请求,它会自动创建一个新的进程去执行
2、如何创建,使用 fork()函数
3、创建进程后,父进程和子进程各自拥有独立的空间,互不干扰,但可能存在共享某些资源,例如打开文件之类的
1.5 进程的终止
1、终止原因
1> 正常退出(自愿)
2> 出错退出(自愿)
3> 严重错误(被迫)
4> 被其他进程杀死(被迫)
1.6 进程的层次结构
1、父进程创建子进程后,会以某种方式保持联系
2、子进程又创建子进程,进而组成一个进程的层次结构
3、父进程只存在一个,旗下可以有很多个子进程
4、父进程和其后裔众多的子进程共同组成一个进程组
5、一个信号发送到进程组,组内所有成员都可以捕获该信号
举个栗子,
在引导映像中,有2个特殊的进程:再生服务器 和 init
再生服务器的任务是启动或重启驱动和服务器,它初始时是阻塞的
init 会向再生服务器发送启动驱动或服务器的消息,因此,再生服务器进程就是所有驱动进程和服务器进程的父进程
所以但某个驱动的子进程终止了,再生服务器将会收到消息并重新启动它,所以子进程崩溃了不打紧。
6、层次结构类似一个进程树
1.7 进程的状态
1、为什么要关注进程状态
因每个进程都是一个独立的实体,但有时候我们就需要一种情形就是:进程间要交换数据,通信或同步亦或是一个进程间结果是另一个进程的输入等等
2、进程状态有哪些
1、运行态(Running): 独占 CPU
2、就绪态(Ready):准备运行,处于暂时挂起状态,因其他进程正在运行
3、阻塞态(Blocked):阻塞中,不能运行,除非外部唤醒后就进入就绪态
如下图
1.8 进程的实现
1、为了实现进程模型,操作系统维护了一张表格(结构体数组),即进程表
2、每个进程占用一个表项,每个表项的信息都保存,所以当进程再次占用 CPU 时,能继续接着上一次的操作,就像没有被中断一样,包含的信息如下
1、进程状态
2、程序计数器
3、栈指针
4、内存分配状况
5、打开文件状态、统计和调度信息
6、定时器和其他信号
7、进程由运行态到就绪态切换时所保存的必要信息
3、每类的 I/O 设备(软盘,硬盘,定时器,终端)都有一个靠近内存底部的位置,这个称为中断向量,它包含中断服务器的入口地址
举个栗子:
用户进程正在运行着,当一个磁盘发生中断后,则中断硬件将程序计数器,程序状态字以及可能的每个或多个寄存器压入堆栈,CPU 随即跳转到磁盘中断向量所指向的地址处,当然,这是硬件的操作,随后交给中断子程序
4、中断服务过程
从把当前进程的全部寄存器存储到进程表开始,当前进程号和指向其表项的指针被保存在全局变量中,随后,将中断服务存入进程表的那部分信息移除,并将栈指针指向一个被进程处理程序所使用的临时堆栈。
接着下一步,构造一条唤醒磁盘进程且消息类型为中断类型的消息,然后磁盘进程就进入了就绪态,等待被调度执行,如存在有更高优先级的,则需要等待一段时间,否则立即执行。
大致一般过程如下,各系统在细节上略有不同
1> 硬件压栈程序计数器等等...
2> 硬件按中断向量下载新的程序计数器
3> 汇编语言》》存储寄存器值
4> 汇编语言》》设置新的栈
5> 执行 C 语言中断服务
6> 发送唤醒进程消息并标识为就绪态
7> 调度器根据实际情况决定调度哪个进程
8> C 程序段结束并返回汇编代码执行处
9> 运行当前进程(汇编代码)
5、进程表的某些域,主要分布在内核,进程管理器,文件系统中
内核 | 进程管理器 | 文件系统 |
寄存器 | 正文段指针 | UMASK 掩码 |
程序计数器 | 数据段指针 | 根目录 |
程序状态字 | bss 段指针 | 工作目录 |
栈指针 | 退出状态 | 文件描述符 |
进程状态 | 信号状态 | 真实 ID |
当前调度优先权 | 进程标识符 | 有效 UID |
最大调度优先权 | 父进程 | 真实 GID |
Scheduling ticks left | 进程组 | 有效 GID |
配额大小 | 子进程占用 CPU 时间 | 控制 tty |
CPU 占用时间 | 真实 UID | 用于 read/write 的保存区 |
消息队列指针 | 有效 UID | 系统调用参数 |
挂起的信号位 | 真实 GID | 各种标志位 |
各种标志位 | 有效 GID | |
进程名字 | 代码段共享所需文件信息 | |
信号位图 | ||
各种标志位 | ||
进程名字 |
1.9 线程
每个进程中只存在一个地址空间和一个控制流运行
有时候需要在一个地址空间有多个控制流并行运行
就好像是跟单独进程一样,只是它们共享一个地址空间
这玩意儿就叫线程(控制流),也叫轻量进程
1、线程依赖于进程而存在
2、其实 线程(执行) 才是 CPU 调度的实体,进程用来整合资源的
3、线程拥有什么?
1、有程序计数器:用于记录下一条要执行的指令
2、有寄存器:存储当前变量
3、有堆栈:存储执行历史
4、使用线程的好处
举个简单例子
比如一个传感器软件的进程,每次启动时它需要连接上多个传感器,每个传感器需要通过各种校验和握手才能连接上,这里将花费大量的时间,而通过多线程,可以同时请求一起连接传感器,这将大大减少启动时间
5、当一个进程中存在多个线程时,就会存在一张 线程表(和进程表类似),而上边的进程表的某些域此时就针对的线程了
6、线程也有运行态,就绪态,阻塞态,线程切换比进程切换速度快很多
7、线程是完全在用户空间进行管理的
每个进程(所有线程共享) | 每个线程(私有) |
地址空间 | 程序计数器 |
全局变量 | 寄存器 |
文件状态 | 堆栈 |
子进程 | 状态 |
定时器 | |
信号和信号处理程序 | |
统计信息 |