目录
一.进程的概念
进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位
一个程序的一次执行可以产生多个不同的进程,例如产生子进程; |
一个程序的多次运行可形成多个不同的进程; |
一个进程可以顺序地执行一个或者多个程序。 |
二.进程实体的组成
程序段、数据段、PCB三部分组成了进程实体(进程映像)引入进程实体的概念后,可把进程定义为:
进程是进程实体的运行过程,是系统进行资源分配和调度(一个进程被“调度”,就是指操作系统决定让这个进程上CPU运行)的一个独立单位。
一个进程实体(进程映像)由PCB、程序段、数据段组成。
程序段、数据段是给进程自己用的,与进程自身的运行逻辑有关
PCB 是给操作系统用的
进程是动态的,进程实体(进程映像)是静态的。
同时挂三个QQ号,会对应三个QQ进程,它们的PCB、数据段各不相同,但程序段的内容都是相同的(都是运行着相同的QQ程序)
1.PCB(PCB是进程存在的唯一标志!)
Linux0.11中的PCB的原码,其实就是一个数据结构:
struct task_struct {
/* these are hardcoded - don't touch */
long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
long signal;
struct sigaction sigaction[32];
long blocked; /* bitmap of masked signals */
/* various fields */
int exit_code;
unsigned long start_code,end_code,end_data,brk,start_stack;
long pid,father,pgrp,session,leader;
unsigned short uid,euid,suid;
unsigned short gid,egid,sgid;
long alarm;
long utime,stime,cutime,cstime,start_time;
unsigned short used_math;
/* file system info */
int tty; /* -1 if no tty, so it must be signed */
unsigned short umask;
struct m_inode * pwd;
struct m_inode * root;
struct m_inode * executable;
unsigned long close_on_exec;
struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
struct desc_struct ldt[3];
/* tss for this task */
struct tss_struct tss;
};
进程描述信息 | 进程控制和管理信息 | 资源分配清单 | 处理机相关信息 |
进程标识符(PID) | 进程当前状态 | 代码段指针 | 通用寄存器值 |
用户标识符(UID) | 进程优先级 | 数据段指针 | 地址寄存器值 |
代码运行入口地址 | 堆栈段指针 | 控制寄存器值 | |
程序的外存地址 | 文件描述符 | 标志寄存器值 | |
进入内存时间 | 键盘 | 状态字 | |
CPU 占用时间 | 鼠标 | ||
信号量使用 |
进程描述信息
进程标识符:标志各个进程,每个进程都有一个唯一的标识号。
用户标识符:进程所归属的用户,用户标识符主要为共享和保护服务。
进程控制和管理信息
进程当前状态:描述进程的状态信息,作为CPU分配调度的依据。
进程优先级:描述进程抢占CPU的优先级,优先级高的进程可优先获得CPU。
资源分配清单
用于说明有关内存地址空间或虚拟地址空间的状况,所打开文件的列表和所使用的输入/输出设备信息。
处理机相关信息
也称CPU的上下文,主要指CPU中各寄存器的值。当进程处于执行态时,CPU的许多信息都在寄存器中。当进程被切换时,CPU状态信息都必须保存在相应的 PCB 中,以便在该进程重新执行时,能从断点继续执行。
2.程序段
程序的代码(指令序列)
程序段就是能被进程调度程序调度到CPU执行的程序代码段。注意,程序可被多个进程共享,即多个进程可以运行同一个程序。
3.数据段
运行过程中产生的各种数据(如:程序中定义的变量)
一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行时产生的中间或最终结果。
三.进程的特征
1.动态性(动态性是进程最基本的特征)
进程是程序的一次执行过程,是动态地产生、变化和消亡的
2.并发性
内存中有多个进程实体,各进程可并发执行
3.独立性
进程是能独立运行、独立获得资源、独立接受调度的基本单位
4.异步性
各进程按各自独立的、不可预知的速度向前推进操作系统要提供“进程同步机制"来解决异步问题
5.结构性
每个进程都会配置一个PCB。结构上看,进程由程序段、数据段、PCB组成
四.进程的状态与转换
1.三种基本状态
进程的整个生命周期中,大部分时间都处于三种基本状态
CPU | 其他所需资源 | |
运行状态 | 有 | 有 |
就绪状态 | 无 | 有 |
阻塞状态 | 无 | 无 |
- 运行状态
占有CPU,并在CPU上运行
- 就绪状态
已经具备运行条件,但由于没有空闲CPU,而暂时不能运行
- 阻塞状态
因等待某一事件而暂时不能运行
2.另外两种状态
④创建状态
进程正在被创建,操作系统为进程分配资源、初始化PCB
- 终止状态
进程正在从系统中撤销,操作系统会回收进程拥有的资源、撤销PCB
3.状态间的转换
就绪态->运行态
进程被调度
运行态->就绪态
时间片到,或CPU被其他高优先级的进程抢占
运行态->阻塞态
等待系统资源分配,或等待某事件发生(主动行为)
阻塞态->就绪态
资源分配到位,等待的事件发生(被动行为)
创建态->就绪态
系统完成创建进程相关的工作
运行态->终止态
进程运行结束,或运行过程中遇到不可修复的错误
五.进程的组织方式
进程PCB中,会有一个变量 state 来表示进程的当前状态。如:1表示创建态、2表示就绪态、3表示运行态….
为了对同一个状态下的各个进程进行统一的管理,操作系统会将各个进程的PCB组织起来。
1.链式
按照进程状态将PCB分为多个队列
操作系统持有指向各个队列的指针
2.索引式
根据进程状态的不同,建立几张索引表
操作系统持有指向各个索引表的指针
六.进程的控制
1.什么是进程控制?
进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。
说白了:反正进程控制就是要实现进程状态转换。
2.用原语实现进程控制
内核中一种特殊的程序,具有原子性,执行起来必须一气呵成,不可中断。
如果不能“一气呵成”,就有可能导致操作系统中的某些关键数据结构信息不统一的情况,这会影响操作系统进行别的管理工作
故此需要用“原语”来执行工作。
可以用“关中断指令”和“开中断指令”这两个特权指令实现原子性:
CPU执行了关中断指令之后,就不再例行检查中断信号,直到执行开中断指令之后才会恢复检查
这样,关中断、开中断之间的这些指令序列就是不可被中断的,这就实现了“原子性”。
3.进程控制相关原语
无论哪个进程控制原语,要做的无非三类事情:
1.更新PCB中的信息
2.将PCB插入合适的队列
3.分配/回收资源
①进程的创建
创建原语
创建态→就绪态
申请空白PCB
为新进程分配所需资源
初始化PCB
将PCB插入就绪队列
引起进程创建的事件
用户登录
分时系统中,用户登录成功,系统会建立为其建立一个新的进程
作业调度
多道批处理系统中,有新的作业放入内存时,会为其建立一个新的进程
提供服务
用户向操作系统提出某些请求时,会新建一个进程处理该请求
应用请求
由用户进程主动请求创建一个子进程
②进程的终止
撤销原语
就绪态/阻塞态/运行态>终止态>无
从PCB集合中找到终止进程的PCB
若进程正在运行,立即剥夺CPU,将CPU分配给其他进程
终止其所有子进程(进程间的关系是树形结构)
将该进程拥有的所有资源归还给父进程或操作系统
删除PCB
引起进程终止的事件
正常结束
进程自己请求终止(exit系统调用)
异常结束
整数除以0、非法使用特权指令然后被操作系统强行杀掉
外界干预
Ctrl+Alt+delete,用户选择杀掉进程
③进程的阻塞
阻塞原语唤醒原语必须成对使用
阻塞原语
运行态→阻塞态
找到要阻塞的进程对应的PCB
保护进程运行现场,将PCB状态信息设置为“阻塞态",暂时停止进程运行
将PCB插入相应事件的等待队列
引起进程阻塞的事件
需要等待系统分配某种资源
需要等待相互合作的其他进程完成工作
例如:
读文件 | 磁盘I/O |
PV操作 | 执行信号量的wait()操作,如果信号量的值小于或等于0,则进程进入阻塞态 |
缺页异常 | 当线程检测到缺页异常时,会调用缺页异常处理程序从外存中调入缺失的页面,线程状态由执行转为阻塞 |
④进程的唤醒
唤醒原语
阻塞态→就绪态
在事件等待队列中找到PCB
将PCB从等待队列移除,设置进程为就绪态
将PCB插入就绪队列,等待被调度
引起进程唤醒的事件
等待的事件发生,因何事阻塞,就应由何事唤醒
⑤进程的切换
运行态→就绪态
就绪态→运行态
切换原语
将运行环境信息存入PCB
PCB移入相应队列
选择另一个进程执行,并更新其PCB
根据PCB恢复新进程所需的行环境
引起进程切换的事件
当前进程时间片到
有更高优先级的进程到达
当前进程主动阻塞
当前进程终止
七.进程的通信
进程间通信(Inter-Process Communication,IPC)是指两个进程之间产生数据交互。
进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立。
为了保证安全,一个进程不能直接访问另外一个进程的地址空间。这个时候进程间需要通信的话就需要操作系统来支持了。
1.共享存储
为避免出错,各个进程对共享空间的访问应该是互斥的。
各个进程可使用操作系统内核提供的同步互斥工具(如P、V操作)
基于数据结构的共享
基于数据结构的共享:比如共享空间里只能放一个长度为10的数组。这种共享方式速度慢、限制多,是一种低级通信方式
注意:这里可不是程序中定义的全局变量。
基于存储区的共享
基于存储区的共享:操作系统在内存中划出一块共享存储区,数据的形式、存放位置都由通信进程控制,而不是操作系统。这种共享方式速度很快,是一种高级通信方式。
2.消息传递
进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的“发送消息/接收消息”两个原语进行数据交换。
直接通信方式
消息发送进程要指明接收进在的iD
点名道姓的消息方式
间接通信方式
通过信箱,间接的进行通信。
可以多个进程往同一个信箱send消息,也可以多个进程从同一个信箱中receive消息
间接通信方式,以“信箱”作为中间实体进行消息传递。
3.管道通信
“管道”是一个特殊的共享文件,又名pipe文件。其实就是在内存中开辟一个大小固定的内存缓冲区
1.管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道。
2. 各进程要互斥地访问管道(由操作系统实现)
3当管道写满时,写进程将阻塞,直到读进程将管道中的数据取走,即可唤醒写进程
4当管道读空时,读进程将阻塞,直到写进程往管道中写入数据,即可唤醒读进程。
5管道中的数据一旦被读出,就彻底消失。因此,当多个进程读同一个管道时,可能会错乱。对此,通常有两种解决方案:
- 一个管道允许多个写进程,一个读进程(2014年408真题高教社官方答案);
- 允许有多个写进程,多个读进程,但系统会让各个读进程轮流从管道中读数据(Linux的方案)。
八.线程的概念
1.什么是线程,为什么要引入线程?
例如进程微信下有俩线程,引入线程后调度的基本单位就不是整个进程了,而是更小的线程。
线程(Thread)是操作系统管理下的执行单元,它是一个程序执行过程中最小的调度单位。线程在某种程度上可以看作是一个轻量级的进程。每个进程至少包含一个线程(主线程),并且可以包含多个线程。
线程的引入原因:
- 并发执行:线程允许程序在同一个进程中同时执行多个任务。通过多线程,可以实现任务的并行化或并发化,提高程序的运行效率。
- 资源共享:多个线程共享进程的资源(如内存、文件描述符等),因此在线程间的通信和数据共享上比进程间的通信更加高效。
- 响应更快:多线程程序可以在执行某些任务时保持对用户的响应,比如在文件下载的同时还能继续执行其他操作,这样能提高程序的用户体验。
- 利用多核处理器:现代计算机通常有多个处理器核心,多线程程序可以同时在多个核心上运行,充分利用硬件资源,提升性能。
- 简化设计:一些任务可以被分解为多个独立的子任务,这些子任务可以在多个线程中并行执行,从而简化程序的设计。
引入线程的主要目的就是提高程序的并发能力和执行效率,同时保持对资源的高效利用和及时响应。
2.引入线程带来的变化
引入进程前 | 引入进程后 | |
---|---|---|
资源分配、调度 | 传统进程机制中,进程是资源分配、调度的基本单位 | 引入线程后 进程是资源分配的基本单位, |
线程是调度的基本单位 | ||
并发性 | 传统进程机制中,只能进程间并发 | 引入线程后,各线程旬也能并发,提升了并发度 |
系统开销 | 传统的进程间并发需要切换进程的运行环境,系统开销很大 | 引入线程后,进程下的所有线程共享同一地址空间 代码段,数据段,文件等等都是进程的资源,线程可以共享 只有线程TCB中的例如栈指针是线程独有的,不可共享 |
线程间并发,如果是同一进程内的线程切换,则不需要切换进程环境,系统开销小 引入线程后,并发性带来的系统开销减小 |
3.线程的属性
线程是处理机调度的单位 |
多CPU计算机中,各个线程可占用不同的CPU |
每个线程都有一个线程ID、线程控制块(TCB) |
线程也有就绪、阻塞、运行三种基本状态 |
线程几乎不拥有系统资源 |
同一进程的不同线程间共享进程的资源 |
同一进程中的线程切换,不会引起进程切换 |
由于共享内存地址空间,同一进程中的线程间通信甚至无需系统干预 |
不同进程中的线程切换,会引起进程切换 |
切换同进程内的线程,系统开销很小 |
切换进程,系统开销较大 |
九.线程的实现方式
1.用户级线程
早期的操作系统(如:早期Unix)只支持进程:不支持线程。当时的“线程”是由线程库实现的
1.用户级线程由应用程序通过线程库实现:所有的线程管理工作都由应用程序负责(包括线程切换)
2.用户级线程中,线程切换可以在用户态下由应用程序即可完成,无需操作系统干预。
像:系统调用,I/O请求,异常处理这些涉及内核态的事件都不会导致用户线程切换。
但是像跨进程的线程进行切换时,实际上就是进程的调度了,需要操作系统内核参与。
3.在用户看来,是有多个线程。但是在操作系统内核看来,并感知不到线程的存在。“用户级线程”就是“从用户视角看能看到的线程
4.优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高
5.缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行。
2.内核级线程
内核级线程(Kernel-LevelThread,KLT, 又称“内核支持的线程”) 由操作系统支持的线程
1.内核级线程的管理工作由操作系统内核完成。
2.线程调度、切换等工作都由内核负责,因此内核级线程的切换必然需要在核心态下才能完成。
3.操作系统会为每个内核级线程建立相应的TCB(Thread ControlBlock,线程控制块)通过TCB对线程进行管理。“内核级线程”就是“从操作系统内核视角看能看到的线程”
4.优缺点
优点:当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核处理机上并行执行。
缺点:一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大。
十.多线程模型
在支持内核级线程的系统中,根据用户级线程和内核级线程的映射关系,可以划分为几种多线程模型
1.一对一
思想 | 一对一模型:一个用户级线程映射到一个内核级线程。每个用户进程有与用户级线程同数量的内核级线程。 |
优点: | 当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核处理机上并行执行。 |
缺点: | 一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大。 |
2.多对一
思想 | 多对一模型:多个用户级线程映射到一个内核级线程。且一个进程只被分配一个内核级线程。 |
优点 | 用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高 |
缺点 | 当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行。 |
重点重点重点:操作系统只“看得见”内核级线程,因此只有内核级线程才是处理机分配的单位。
3.多对多
思想 | 多对多模型:n用户及线程映射到m个内核级线程(n>=m)。每个用户进程对应m个内核级线程。 |
优点 | 克服了多对一模型并发度不高的缺点(一个阻塞全体阻塞),又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点 |
理解 | 可以这么理解用户级线程是“代码逻辑”的载体内核级线程是“运行机会”的载体 内核级线程才是处理机分配的单位。 例如:多核CPU环境下,上图这个进程最多能被分配两个核。 一段“代码逻辑”只有获得了“运行机会”才能被CPU执行 内核级线程中可以运行任意一个有映射关系的用级线程代码,只有两个内核级线程中正在运行的代码逻辑都阻塞时,这个进程才会阻塞 |
十一.线程的状态与转换
与进程的切换一样:
十三.线程的组织与控制
与进程的PCB十分类似