进程的概念
- 定义
- 是程序的一次执行过程
- 是一个程序及其数据在处理机上顺序执行时所发生的活动
- 进程是一个具有独立功能的程序在一个数据集合上运行的过程,他是系统进行资源分配和调度的一个基本单位
- 为了使参与并发执行的程序能够独立的运行,必须为之配置一个专门的数据结构(PCB,Process Control Block).
- 系统利用PCB来描述进程的基本信息和运行状态,进而控制进和管理进程
- 相应的由程序段,相关数据段和PCB三部分构成了进程映像(进程实体)。dyld->mmap
- 所谓的创建进程就是创建进程映像中的PCB,而撤销进程则是撤销进程的PCB
- PCB是进程存在的唯一标志
- 在引入进程实体的之后,它可以重新定义为
进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位(标准答案)
进程的特征
- 动态性: 进程是程序的一次运行过程,它有着创建,活动,暂停,终止等过程,具有一定的生命周期,是动态产生,变化和消亡的,动态性是进程最基本的特征。 类比
MainActivity/AppDelegate
,他们的部分状态也是基于进程的状态得来的。 - 并发性: 多个进程实体同时存在于内存并发执行
- 独立性: 进程实体是一个能独立运行,独立获得资源和独立接受调度的基本单位,需要创建PCB。
- 异步性: 由于进程之间是互相制约的,使得进程的执行具有间断性,进程按各自独立的不可预知的速度向前推进,异步会导致执行结果的不可预见性,为此操作系统必须配置相应的进程同步机制。
- 结构性:每个进程都有一块PCB对齐进行描述,从结构上来看进程是有程序段数据段和进程控制块三部分组成
- 通过Windows任务管理器或者Mac的活动监视器可以看到进程信息
进程的状态与转换
- 运行态: 进程正在运行,在单处理机环境下,每个时刻最多只有一个进程处于运行状态
- 就绪态: 进程获得了除处理机意外的一切所需资源,一旦得到了处理机,便可以立即运行.系统中通常会有多个进程处理就绪态,将他们拍成一个队列,称作为就绪队列。
- 阻塞态: 又称等待态,进程正在等待某一件事情而暂停运行,如果等待某资源为可以用(不包括处理机)或等待输入/输出完成,即使在空闲时间,该进程也不能运行。
- 创建态: 进程正在被创建,尚未赚到就绪状态,创建线程通常需要多个步骤,首先申请一个空白的PCB,并向PCB中写一些控制和管理进程的信息,然后由系统分配运行时所必须的资源,最后把进程设置为就绪状态。
结束态: 进程正在从系统中消息,可以是正常结束或者是其它原因中断退出运行。进程需要结束时,系统首先必须设置该进程为结束状态,然后在进一步处理资源释放和回收工作。
注意区分就绪态和等待态,就绪态只是缺少处理机,等待态可能是缺少资源
进程控制
- 主要功能: 对系统中的进程实施有效的管理,是它具有创建新进程,撤销已有进程,实现进程状态转换功能,在操作系统中一般把进程控制用的程序段成为原语。
- 进程的创建
- 为新进程分配一个唯一的进程标示号,并申请一个空白的PCB(PCB是有限的)。失败则返回创建失败。
- 为进程申请资源,为新进程的程序和数据以及用户栈分配必要的内存空间(在PCB中体现),若资源不足(如内存空间)则返回阻塞状态,而不是就绪状态,等待内存资源
- 初始化PCB:主要包括初始化标志信息,初始化处理机状态信息和初始化的处理机控制信息(标记,状态,处理机控制信息),以及进程的优先级等。
- 若进程就绪队列能够接纳新的进程,则将进程插入就绪队列,等待被调度运行。
- 进程终止
- 引起进程终止的原因有很多,常见的有 数组越界,保护错,非法指令,特权指令,运行超时,算数运算错,I/O故障等,外界干预,父亲进程请求终止,用户关闭
- 终止过程如下
- 根据被终止的进程标示符,检索PCB,从中读取该进程的状态。
- 若被终止的进程处于执行状态,立刻终止该进程的执行,并剥夺其处理机资源分配给其它进程。
- 若该进程还有子进程,则终止其所有的子进程
- 将该进程所拥有的全部资源,或归还给其父进程,或归还给操作系统
- 将该PCB从所在队列(链表)中删除。
- 进程的阻塞和唤醒
阻塞原语- 进程标示号 -> PCB
- 若进程处于运行(类似上一篇文章提到的中断)保护现场,将其转化为阻塞状态,停止运行
- 把该PCB插入相应的事件队列,将处理机资源调度给其它的就绪进程
唤醒原语 - 进程标示号 -> 事件等待队列 -> PCB
- 将该PCB移除事件等待队列,并设置其状态为就绪状态
- 将PCB再插入到就绪对垒等待调度程序调度
阻塞和唤醒原语的最大区别: 阻塞是立即清除,唤醒是插入到队列后等待调度
- 进程的切换
- 保存处理机上下文(Context),包括程序计数器和其它寄存器
- 更新PCB信息
- 把PCB移动到相应的队列,如在就绪,在某事件阻塞等待队列
- 选择另一个进程执行,并更新其PCB,
- 更新内存管理的数据结构
- 恢复处理机上下文
> 关键字: 上下文,数据管理结构,计数器,寄存器
进程的组织
- 进程控制块
进程的描述信息 | 进程控制和管理信息 | 资源分配清端 | 处理机相关信息 |
---|---|---|---|
进程标示符 | 进程当前状态 | 代码段指针 | 通用寄存器值 |
用户标示符 | 进程优先级 | 数据段指针 | 地址寄存器值 |
代码运行入口地址 | 堆栈指针 | 控制寄存器值 | |
程序的外存地址 | 文件描述符 | 标志寄存器 | |
进入内存的事件 | 键盘 | 状态字 | |
处理机占用的事件 | 鼠标 | ||
信号量 |
- mac上的进程可视化界面,除了 寄存器没能很直观的看出,其它的基本都能找到对应关系,另外这里多了挂起队列比书上的状态多了一些,应该是苹果系统对进程的控制做了更多的优化和改进的缘故。
![](media/16026041703877/16026080890448.jpg)
kernel_liteos中PCB的定义如下
typedef struct ProcessCB { CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */ 进程名称
UINT32 processID; /**< Process ID */ 进程id
UINT16 processStatus; /**< [15:4] Process Status; 进程状态[3:0] The number of threads currently
running in the process */
UINT16 priority; /**< Process priority */ 进程优先级
UINT16 policy; /**< Process policy */ 进程管理策略
UINT16 timeSlice; /**< Remaining time slice */ 进程剩余 时间片
UINT16 consoleID; /**< The console id of task belongs */ 日志输出相关,便于区分进程的日志信息
UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */ 区分内核调用还是用户调用
UINT32 parentProcessID; /**< Parent process ID */ 父进程id
UINT32 exitCode; /**< Process exit status */ 进程退出的code
LOS_DL_LIST pendList; /**< Block list to which the process belongs */ PCB当前所在的队列,如等待队列,就绪队列
LOS_DL_LIST childrenList; /**< Children process list */ 它所关联的子进程
LOS_DL_LIST exitChildList; /**< Exit children process list */
LOS_DL_LIST siblingList; /**< Linkage in parent's children list */
ProcessGroup *group; /**< Process group to which a process belongs */ 进程组
LOS_DL_LIST subordinateGroupList; /**< Linkage in group list */
UINT32 threadGroupID; /**< Which thread group , is the main thread ID of the process */
UINT32 threadScheduleMap; /**< The scheduling bitmap table for the thread group of the
process */
LOS_DL_LIST threadSiblingList; /**< List of threads under this process */
LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
priority hash table */
volatile UINT32 threadNumber; /**< Number of threads alive under this process */ 当前进程所拥有的线程
UINT32 threadCount; /**< Total number of threads created under this process */
LOS_DL_LIST waitList; /**< The process holds the waitLits to support wait/waitpid */
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 timerCpu; /**< CPU core number of this task is delayed or pended */
#endif
UINTPTR sigHandler; /**< Signal handler */ 捕获进程成义仓你用
sigset_t sigShare; /**< Signal share bit */
#if (LOSCFG_KERNEL_LITEIPC == YES)
ProcIpcInfo ipcInfo; /**< Memory pool for lite ipc */
#endif
LosVmSpace *vmSpace; /**< VMM space for processes */
#ifdef LOSCFG_FS_VFS
struct files_struct *files; /**< Files held by the process */
#endif
timer_t timerID; /**< ITimer */
#ifdef LOSCFG_SECURITY_CAPABILITY
User *user;
UINT32 capability;
#endif
#ifdef LOSCFG_SECURITY_VID
TimerIdMap timerIdMap;
#endif
#ifdef LOSCFG_DRIVERS_TZDRIVER
struct file *execFile; /**< Exec bin of the process */ 执行文件
#endif
mode_t umask;
} LosProcessCB;- 程序段: 能被进程调度程序调度到CPU执行的程序段代码,程序可以被多个进程共享,及多个进程可以运行同一个程序.
- 数据段: 一个进程的数据段,可以是进程对应用程序加工处理的原始数据,也可以是程序执行时产生的中间或最终结果
进程通信
- 共享存储: 通过共享空间来实现,比如共享内存中某个字典,共享磁盘空间
- 消息传递: 进程与进程之间通过各自的端口来收发消息,(从表现形式来看Flutter的Isolate的实现方式也比较像是一种消息传递)
- 管道通信: 管道(pipe)通信是消息通信的一种特殊传递方式,链接一个读进程和一个写进程
- 在Lunix中管道通信是一种非常频繁的通信机制,管道也是一种文件,他和一般的文件略有不通,是一个固定大小的缓冲区
- 管道传递数据需要注意数据的读写同步问题,数据一段读取他就会从管道中释放,
- 管道只能采用半双工通信,某一个时刻只能单向传输,要实现父子进程双方互动通信,则需要两个管道。
线程和多线程模型
- 概念
线程是一个轻量级级进程,它是一个基本的CPU执行单元,也是程序执行流的最小单元,由线程id,程序计数器,寄存器集合,堆栈组成,线程是进程中的一个实体,是被系统独立调度和派发的基本单位
- 目的
- 引入线程的目的是为了更好地使用多道程序并发执行,提高资源利用率和系统吞吐量,而引入线程的目的则是为了减小程序在并发执行时候所付出的开销,提高操作系统的并发性能。
- 效果
- 引入线程之后,进程的内涵发生了改变,进程除了作为CPU外的系统资源的分配单元,而线程作为处理机的分配单元,由于一个进程内部有多个线程,若线程切换发生通一个进程内部,则只需要很少的时间开销。
- 下面是一个线程的基本结构
C typedef __darwin_pthread_t pthread_t;
struct _opaque_pthread_t {
long __sig;
struct __darwin_pthread_handler_rec *__cleanup_stack;
char __opaque[__PTHREAD_SIZE__];
};
struct __darwin_pthread_handler_rec {
void (*__routine)(void *); // Routine to call
void *__arg; // Argument to pass
struct __darwin_pthread_handler_rec *__next;
};
- 线程与进程比较
- 调度: 在传统的操作系统中,拥有资源和独立调度的基本单位都是进程,而在引入线程的操作系统中,线程是独立的调度单位,进程是拥有资源的基本单位,同一进程中,线程的切换不会引起进程的切换; 在不同的进程中进行切换线程,会引起继承的切换
- 拥有资源: 不论在传统的操作系统还是设置有线程的操作系统,进程都是拥有资源的基本单位,而线程不拥有系统资源,它只是能共享所在进程的资源
- 并发性: 都拥有
- 地址空间和其它资源(如打开文件): 同一进程下的线程共享资源,不同进程的线程不能共享
- 系统开销: 切换线程只需要保存和设置少量的寄存器内容,开销很小,同一个进程內的线程通信开销小,甚至无需操作系统的干预
- 通信方面: 进程通信(IPC)需要进程同步和同步手段辅助,保证数据的一致性,而线程可以直接读写数据段来进行通信
- 线程状态
- 创建,阻塞,就绪,运行,销毁
- 线程的实现方式
- 用户级别线程(User-Level Thread, ULT)
- 内核级别线程(Kernel-Level)
- 多线程模型
- 多对一: 将多个用户级线程映射到一个内核级线程, 容易阻塞
- 一对一: 创建线程开销大,影响程序性能
- 多对多: 综合方案
处理机调度
- 概念
- 多道程序操作系统的基础,操作系统设计的核心问题
调度层次
- 作业调度: 内存与辅存之间的调度
- 中级调度: 内存调度,其作作用是提高系统的吞吐量,暂时不用而不运行的进程调至室外等待,把此时的进程挂起,当他们具有已经运行的条件的时候,同时内存有空闲的时候再调度回来,修改PCB状态为就绪状态,挂在等待队列上
- 进程调度: 按照某种方法和策略,从就绪队列汇总选取一个进程,将处理机分配给他。
- 图解
三级调度的联系
- 作业调度为进程的活动做准备,例如启动时mainThread的各种初始化加载; 中级调度将暂时用能运行的进程挂起,中级调度处于之间,所有有个
中
字 - 作业调度次数少,中级调度次数多,进程调度频率对高(进程多了阻塞,就绪挂起态,状态多了切换也就频繁了)
- 进程调度时最基本的,不可缺少
- 作业调度为进程的活动做准备,例如启动时mainThread的各种初始化加载; 中级调度将暂时用能运行的进程挂起,中级调度处于之间,所有有个
调度时机,切换与过程
- 以下几种情况不能处理进程切换
- 在处理中断的过程中,中断操作处于核心态比较重要,原语操作,不能切换
- 进程在操作系统内核的临界区: 进入临界区需要枷锁占有共享数据,为了更快的释放所,不应该切换
- 其他需要完全屏蔽中断的饿原子操作过程,如加锁,解锁,中断现场保护,恢复
- 应该进行调度的情况
- 发生引起调度条件(如CPU时间片时间到了),当前进程无法继续执行下去
- 中断处理结束或自陷处理结束后,返回被中断进程的用户程序执行现场信息,恢复被调度进程的现场信息。
- 以下几种情况不能处理进程切换
调度的方式
- 剥夺式,又称抢占式,处理机分配给任务更为紧迫和重要的进程
- 非剥夺式,均衡执行
- 对比: 采用剥夺时调用,有助于提高系统整体的吞吐量和响应效率,但必须遵守一定的原则,如优先权,短进程优先和时间片原则
## 调度的基本准则
不同的调度算法具有不同的特性,在选择调度算法式,必须要考量调度算法的特性,
- CPU利用率,尽可能让CPU处于忙碌状态
- 系统吞吐量: 单位时间内CPU完成的作业数量
- 周转时间: 作业从提交到完成锁经历的十斤啊,是作业等待, 在就绪队列,在处理机以及输入/输出操作锁花费的总时间之河
周转时间 = 作业完成时间 - 作业提交时间
平均周转时间 = (作业1完成时间 + 作业2完成时间 + ... 作业n完成时间)/n- 带权周转时间 = 作业周转时间/作业实际运行时间
- 等待时间: 进程处于等待处理机状态的时间
- 响应时间: 用户从提交请求到首次系统产生响应所用的时间
- 典型的调度算法:
- FCFS: 先来先服务, 作业时间公平,不可剥夺算法,先来先服务,对长作业有利,对长CPU繁忙型作业有利,不利于I/O繁忙型作业
- SJF: 短作业优先算法, 对长作业不友好,没有考虑优先级,容易导致饥饿
- 优先级调度算法:
剥夺式,非剥夺式
静态优先级, 动态修仙记
系统进程>用户进程
交互型进程> 非交互型进程(前台>后台)
I/O进程优先于CPU进程(I/O)比较慢,先执行,让I/O设备更早工作 高响应比优先算法(HRRN):
响应时间 = (等待时间 + 要求服务时间)/ 要求服务时间- 作业等待时间相同时,要求服务的时间越短,响应比越高
- 在要求服务时间相同时,作业的响应比由他的服务时间决定,因此他实现的是先来先服务
- 时间片轮转调度算法: 如果时间片足够大,所有进程的任务都能在一个时间片中完成,则退化为先来先服务;如果时间片小,则系统需要频繁的切换进程,处理机开销大,时间片的长短通常由系统响应时间,就绪对垒中进程树木和系统处理能力决定
多级反馈队列
- 设置多个就绪队列,每个队列都设置不同的优先级
- 赋予各队列中各进程执行的时间片大小各不相同,队列优先级越高则时间片越短
- 一个新的进程进入内存后它首先放入1级队列的末尾,如果他在一个时间片未执行完则放入到2级对列
- 仅当前以及的调度队列为空的时候才去执行下一级别的调度队列
- 特点:
- 终端作业型用户,短作业优先, 这就意味着如果一直插入一级队列的短作业则会饿死
- 短批处理作业用户,周转时间短
- 长批处理作业用户,经过前面几个步骤的分布执行,不会长期得不到处理
进程同步
- 为甚么引入进程同步: 在多道处理机下面,进程是并发执行的,不同的进程之间存在着不同的相互制约的关系,为了协调进程之间相互制约的关系引入了进程同步概念。
- 进程同步的几种实现机制:
- 临界资源: 受限制资源,一次只能允许一个进程使用的资源,通过互斥访问实现
do {
entry section; //进入区 进入时执行 wait源语
critical section //临界区
exit section: //退出区, 退出时执行signal源语
} - 同步: 进程协调完成某种任务,部分任务需要控制其先后
- 互斥:
- 空闲让进
- 忙着等待
- 有限等待
- 让权等待
- 临界区互斥软件实现
- 单一标志法, trun = 0,多线程同时串改的风险,违背空闲让进
- 双标志法 flag[n] = true; 同样存在多线程同时串改的风险,违背忙着等待
- 双标志检查法: 会再检测时让权,过 (书本解释有歧义,实际能work,不是最优解)
- Perternson‘Algorithm: 正向标记,反向标记
flag[i] = true, turn = j;
while(flag[j] && trun ==j );
...
- 硬件实现:
- 中断屏蔽法: 关中断 -> 临界区 -> 开中断 (CUP无法进行进程切换,所以不会有问题),不能对突发事件处理
- 硬件指令法:
- TestAndSet, 原子指令操作 对 *lock树枝控制
- swap: 对 *a和 *b数据互换实现中断
- 信号量
- 基于系统的原语, signal和wait实现, 可以实现同步,互斥,前驱关系(多个进程按照一定的顺序执行)
- 进程同步互斥的处理方式
- 关系梳理, 同步,互斥 设置pv的个数和放置顺序
- 资源确定: 设置信号量
- 临界资源: 受限制资源,一次只能允许一个进程使用的资源,通过互斥访问实现
管程
- 作用: 简化pv操作,管理进程
- 结构:
- 名称
- 共享数据结构说明
- 对数据结构进行操作的一组过程
- 对局部管程内部的共享数据设置初始值的语句
- 本质就是对数据的插入和获取进行 源语 wait和signal的管理
经典例子
生产者消费者问题
吃苹果和橘子
吸烟者问题
读者学者
哲学家进餐问题
死锁
- 定义: 进程资源互享等待无法继续向前推进
- 原因:
- 系统资源竞争
- 进程推进顺序非法,程序猿bug,信号量设置错误
- 产生条件
- 进程对所分配的资源独占,其他进程无法访问
- 不可剥夺,其他进程无法剥夺它的资源
- 请求和保持条件,进程保持了至少一个资源,又提出了新的资源请求
- 循环等待条件: 每个进程获得资源的同时被下一个进程所请求,存在一个闭环的等待集合 { p1, p2, ..pn, p1}
- 处理策略
- 预防死锁
- 破坏互斥条件,如允许资源共享使用, 但对于资源少的无法满足,如打印机,而且有些场合需要保持这种互斥性
- 破坏不可剥夺条件: 当一个进程已经保持了某些不可剥夺资源的而请求新的资源得不到满足,它必须释放已经保持的资源.这意味着一个进程已占有的资源会被释放,从二破坏了不可剥夺条件. (如 电脑卡,等待浏览器响应or关闭, 超时设置等), 推到重来,开销大
- 破坏请求并保持条件: 采用预先进程分配法,进程在运行前一次申请完它所需要的所有资源,在资源未满足前先不要把它投入进程,一旦投入使用,资源只归它所有,其它进程无法使用。 响应时间慢,利用率低
- 破坏循环等待条件: 顺序分配,给资源边缘,每次请求都按照后面的编号分配, 编号需稳定,对新的设备加入不友好,资源之间的依赖关系不容易处理
- 避免死锁
- 系统安全状态确认: 在分配系统资源时,需要保证有一个安全序列(银行家算法)
allocation -> need -> avaliable
## 死锁的检测和解除
- 系统安全状态确认: 在分配系统资源时,需要保证有一个安全序列(银行家算法)
- 预防死锁
- 资源分配图,(类比OC对象引用关系图)
- 请求边和分配边拆解法,逐步剥离是否可以释放
- 死锁定理
- 当且仅当S状态的资源分配是不可完全简化,则为死锁
- 死锁解除
- 系统强行介入
- 资源剥夺
- 撤销进程
- 进程回退
- 系统强行介入