一、进程与线程
1.进程的基本概念和特点
为了使程序能够并发执行,并能对并发程序加以描述和控制,引入了进程(Process)的概念。进程是系统进行资源分配的基本单位,进程的引入提高了系统的资源利用率和吞吐量。
进程和程序的区别:
1.结构性。一个进程实体是由程序段、数据段和进程控制块(PCB)三部分组成。
2.动态性。进程是程序的执行过程,是和程序的最基本区别。
3.并发性。实现在一个处理机上同时完成多个程序的正常运行,提高系统资源的利用率。
4.独立性。进程是资源分配与调度的基本单位,一个进程实体拥有独立的资源,能够独立地运行。
5.异步性。进程按照各自独立、不可预知的速度向前推进。
2.进程的状态与转换
进程的五种状态:创建态、就绪态、运行态、阻塞态、终止态。
进程的三种基本状态:就绪态、运行态、终止态。
3.孤儿进程和僵尸进程
孤儿进程: 父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init进程(1号进程)所收养,并由init进程对他们完成状态收集工作。
僵尸进程: 进程使用fork创建子进程,如果子进程exit()退出,而父进程并没有调用wait 获取和回收子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这些进程是僵尸进程。
4.进程的通信
进程通信是指进程之间的信息交换。根据信息量及通信效率,进程通信可以分为低级通信和高级通信。
低级通信是指进程之间信息量较少且效率较低,比如进程的互斥与同步就是一种低级通信,主要通过PV原语操作来实现。
高级通信是指以较高的效率传输大量数据的通信方式。主要包括共享内存、消息传递、管道通信。
1.管道
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。
2.FIFO命名管道
去除了管道只能在父子进程中使用的限制。FIFO常用于客户-服务器应用程序中,FIFO用作汇聚点,在客户进程和服务器进程之间传递数据。
3.消息队列
消息队列是在两个进程之间传递二进制块数据的一种简单有效的方式。每个数据块都有一个特定的类型,接收方可以根据类型来有选择地接收数据,而不一定像管道和命名管道那样必须以先进先出的方式接收数据。
4.信号量Semaphore
信号量semaphore是一个整型变量,可以对其执行wait(等待)和signal(信号)操作,也就是常见的P和V操作。
P操作:申请资源操作。V操作:释放资源操作。信号量SV:用来记录资源数量,看是否能满足申请资源的操作。
5.共享内存通信
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
6.信号
一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。
每种信号类型都对应于某种系统事件。低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。
7.套接字socket
创建socket
命名socket:bind()
监听socket:listen()
接收连接:accept()
发起连接:connect()
关闭连接
5.线程的定义
1.线程是进程划分的任务,是一个进程内可调度的实体,是CPU调度的基本单位,用于保证程序的实时性,实现进程内部的并发。
2.线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。
3.每个线程完成不同的任务,但是属于同一个进程的不同线程之间共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
6.线程和进程的区别和联系
1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
2.进程在执行过程中拥有独立的地址空间,而多个线程共享进程的地址空间。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)
3.进程是资源分配的最小单位,线程是CPU调度的最小单位。
4.通信:由于同一进程中的多个线程具有相同的地址空间,使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信(需要一些同步方法,以保证数据的一致性)。
5.进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
6.进程间不会相互影响;一个进程内某个线程挂掉将导致整个进程挂掉。
7.进程适应于多核、多机分布式;线程适用于多核分布式。
7.线程的分类
根据线程的执行任务不同,线程分为两类:用户级线程和内核级线程。
用户级线程是指不依赖操作系统核心,由应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制的线程。
内核级线程是指依赖于内核,由操作系统内核完成创建和撤销工作的线程。
多线程模型:
1.多对一模型。将多个用户级线程映射到一个内核级线程上。该模型下,线程在用户空间进行管理,效率较高。缺点就是一个线程阻塞,整个进程内的所有线程都会阻塞。几乎没有系统继续使用这个模型。
2.一对一模型。将内核线程与用户线程一一对应。优点是一个线程阻塞时,不会影响到其它线程的执行。该模型具有更好的并发性。缺点是内核线程数量一般有上限,会限制用户线程的数量。更多的内核线程数目也给线程切换带来额外的负担。linux和Windows操作系统家族都是使用一对一模型。
3.多对多模型。将多个用户级线程映射到多个内核级线程上。结合了多对一模型和一对一模型的特点。
8.协程
协程,即用户态线程。我们知道,在Linux下,线程有PCB,然后可以占用时间片去调度,但是在用户态线程中,该线程的执行不由内核做调度,由用户自己实现。
可以这么理解,在用户进程A中,再实现了个调度器,调度用户线程,这些线程不像之前的线程,内核是感知不到的,它们只能感知到A的存在,用户态线程之间时间片只能争取内核分给进程A的时间片。
二、处理机调度
1.进程调度的时机
1.当前运行的进程运行结束。
2.当前运行的进程由于某种原因阻塞。
3.执行完系统调用等系统程序后返回用户进程。
4.在使用抢占调度的系统中,具有更高优先级的进程就绪时。
5.分时系统中,分给当前进程的时间片用完。
2.不能进行进程调度的情况
1.在中断处理程序执行时。
2.在操作系统的内核程序临界区内。
3.其它需要完全屏蔽中断的原子操作过程中。
3.进程的调度策略
1.先到先服务调度算法
2.短作业优先调度算法
3.优先级调度算法
4.时间片轮转调度算法
5.高响应比优先调度算法(把CPU分配给就绪队列中响应比最高的进程。 既考虑作业的执行时间也考虑作业的等待时间,综合了 先来先服务 和最短作业优先两种算法的特点。)
6.多级队列调度算法(将不同类型或性质的进程固定分配在不同的就绪队列中,不同的就绪队列采用不同的调度算法。)
7.多级反馈队列调度算法(按时间片轮转算法执行任务,设置n个队列,当第一个队列任务为空,才执行第二个队列,依次类推。 如果在i队列的任务在该时间片执行后没有完成,即插入i+1号队列中。)
8.最短剩余时间优先算法
4.进程调度策略的基本设计指标
1.CPU利用率
2.系统吞吐率,即单位时间内CPU完成的作业的数量。
3.响应时间。自进程就绪至进程第一次获得CPU响应的时间。
4.周转时间。是指作业从提交到完成的时间间隔。从每个作业的角度看,完成每个作业的时间也是很关键。
三、进程同步
1.进程同步和线程同步区别
进程之间地址空间不同,不能感知对方的存在,同步时需要将锁放在多进程共享的空间。而线程之间共享同一地址空间,同步时把锁放在所属的同一进程空间。
2.临界区
对临界资源进行访问的那段代码称为临界区。
3.同步与互斥
同步:多个进程按一定顺序执行。互斥:多个进程在同一时刻只有一个进程能进入临界区。
4.信号量
信号量semaphore是一个整型变量,可以对其执行wait(等待)和signal(信号)操作,也就是常见的P和V操作。P操作:申请资源操作。V操作:释放资源操作。信号量SV:用来记录资源数量,看是否能满足申请资源的操作。
P(SV):如果SV的值大于0,执行-1操作;如果SV的值等于0,挂起进程,等待SV大于0;V(SV):唤醒因为等待SV而挂起的进程,如果没有,则将SV加1;
如果信号量的取值只能为0或1,那么就成了互斥量Mutex,0表示临界区已经加锁,1表示临界区解锁。
5.管程
管程Monitor可以视为一个线程安全的数据结构,其内部提供了互斥与同步操作,向外提供访问共享数据的专用接口(接口被称为管程的过程),通过管程提供的接口即可达成共享数据的保护与线程间同步。
wait(c)表示为进入管程的进程分配某种类型的资源,如果此时这种资源可用,那么进程使用,否则进程被阻塞,进入紧急队列。signal(c)表示进入管程的进程使用的某种资源要释放,此时进程会唤醒由于等待这种资源而进入紧急队列中的第一个进程。
6.经典同步问题
1.生产者-消费者问题
使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。
缓冲区属于临界资源,需要使用一个互斥量mutex来控制对缓冲区的互斥访问。
为了同步生产者和消费者的行为,需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:empty记录空缓冲区的数量,full记录满缓冲区的数量。其中,empty信号量是在生产者进程中使用,当empty不为0时,生产者才可以放入物品;full信号量是在消费者进程中使用,当full信号量不为0时,消费者才可以取走物品。
生产者、消费者的函数中需要先判断信号量,再对缓冲区加锁。
2.读者-写者问题
允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。
一个整型变量count记录在对数据进行读操作 的进程数量,一个互斥量count_mutex用于对count加锁,一个互斥量data_mutex用于对读写的数据加锁。
3.哲学家进餐问题
五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家有两种活动:吃饭和思考。当一个哲学家吃饭时,需要先拿起左右两边的两根筷子,并且每次只能拿一根筷子。
防止死锁的两个条件:必须同时拿起左右两根筷子;只有在两个邻居都没有进餐的情况下才允许进餐。
四、死锁
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的相互等待的现象。
1.产生死锁需要满足四个必要条件
1.互斥:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源。
2.占有并等待:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源。
3.不可抢占:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放。
4.环路等待:进程发生死锁后,必然存在一个进程-资源之间的环路。
2.处理方法
1.鸵鸟策略:当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低时,处理死锁问题的办法就是忽略它。
2.死锁检测
通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到了死锁的发生。
3.死锁恢复
利用抢占恢复、利用回滚恢复、通过杀死进程恢复。
4.死锁预防
(1)资源一次性分配,这样就不会再有请求了(破坏互斥条件)。
(2)只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏占有和等待条件)。
(3)当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可抢占的条件。(破坏不可抢占条件)
(4)系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反。(破坏环路等待)
5.死锁避免
(单个资源的银行家算法、多个资源的银行家算法)
银行家算法的实质就是要设法保证系统动态分配资源后不进入不安全状态,以避免可能产生的死锁。即每当进程提出资源请求且系统的资源能够满足该请求时,系统将判断满足此次资源请求后系统状态是否安全,如果判断结果为安全,则给该进程分配资源,否则不分配资源,申请资源的进程将阻塞。