单核CPU采取的是并发技术,多核CPU采取的是并发和并行两种技术。
并发:在一个宏观时间内,同时运行多个程序。在微观尺度下,其实是不同程序交替运行。
并行:只有多核CPU才能实现,就是同一时间内运行多个程序。
拓展:并发是让不用用户程序交替运行,操作系统中程序可以分为内核程序和应用程序两种,也就对应CPU的两种状态“内核态”和“外核态”,并发的实现需要CPU在两种状态中进行转换,内核态转换到用户态,需要执行一跳特权指令、用户态转换到内核态,需要由“中断”引发。
RAM:Random Assess Memory随机存取存储器,速度比硬盘快得多,也就是电脑中的内存,保存在其中的数据在电脑断电后就会消失。
ROM:Read-Only Memory只读存储器,一般集成在电脑主板上。被用于存储固化的程序和数据,例如BIOS,固件。
BIOS:Basic Input/Output System基本输入输出系统。存储在ROM(只读存储器)中。
程序:是静态的,就是存储在磁盘中的可执行文件,二进制。
进程:是动态的,是程序的一次执行过程。(如果一个程序运行了多个,那么此时操作系统通过PID“Process ID”对不同进程进行区分)
拓展:程序是如何运行的
进程由:PCB(进程控制块,是进程存在的唯一标识,当进程被创建时,操作系统为其创建PCB,结束时,操作系统回收)、程序段(程序的代码:指令序列)、数据段(运行过程中产生的各种数据,如,程序中定义的变量)组成。
拓展:当通过三个相同的程序被执行时,会对应三个不同的PCB和数据段,但是程序段的内容是相同的。
原语:首先了解一下原子操作,就是不能再划分的操作,可以理解为操作的最小单元。那么原语就可以理解为一个代码执行逻辑的最小单元,不会被中断。
进程通信是指两个进程之间产生数据交互(例如,把微博的文章分享给微信程序)。
进程之间通信有以下几种方式:
进程向操作系统申请一个共享存储区,同时为避免出错,各个进程对共享存储器的访问应该是互斥的。
两种共享方式的优缺:
管道通信和共享存储的方法很像,只不过管道通信的数据传送是有顺序的,按照队列的顺序读写FIFO。就和水流一样,下游接受上游的水是有严格顺序的。First In First Out
知识总览:
如果没有并发技术,那么单核CPU电脑一次只能运行一个进程。
如果没有线程技术,那么一个进程不能同时实现多个功能(例如,QQ程序要同时开启视频聊天,文字聊天,传输数据等功能。)
可以把线程理解为“轻量级进程”,线程是一个基本的CPU执行单元,也是程序执行流的最小单位。
线程实现方式有:用户级线程、内核级线程
(1)用户级线程(User-Level Thread, ULT)
早期操作系统不支持线程,所以很多编程语言提供了强大的线程库,可以实现线程的创建、销毁、调度等功能。
思考以下几个问题:
- 线程的管理工作有谁来完成? 应用程序通过线程库来完成,而不是操作系统。
- 线程切换是否需要CPU转换工作状态? 不需要应该,线程库就是运行在用户态上。
- 操作系统是否能意识到用户级线程的存在? 不能意识到,应为这些工作都是在用户层级运行
- 用户级线程的优点: 线程的切换不需要CPU转换到内核态,线程管理的系统开销小,效率高。
- 用户级线程的缺点:当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行。
(2)内核级线程(Kernel-Level Thread, KLT,又称“内核支持的线程”)
(3)多线程模型
上面介绍了用户级线程还有内核级线程,那么这两种线程能不能同时使用呢?答案是可以同时使用!
所以根据用户级线程和内核级线程的映射关系,就可以划分为几种多线程模型。
- 一对一线程模型
- 多对一线程模型
- 多对多模型
与进程的状态与转换几乎一样
进程是由进程PCB(Process Control Block)控制、线程是由TCB(Thread Control Block)控制。
知识总览图:
当有一堆任务要处理,但由于资源有限,这些事情没法同时处理。这就需要确定某种规则来决定处理这些任务的顺序,这就是“调度”研究的问题。
(1)高级调度
当计算机内存不足,无法把所有需要启动的程序都装入内存时。这时候就需要高级调度(作业调度),就是按一定的原则从外存的作业后备队列中挑选一个作业调入内存,并创建进程。每个作业只调入一次,调出一次。作业调入时会建立PCB,调出时才撤销PCB。
(2)低级调度
与高级调度不同,**低级调度(进程调度/处理机调度)**是对已经加载到内存中的进程进行操作。就是按照某种策略从就绪队列中选取一个进程,将处理机(CPU)分配给他。(这也是实现并发的关键)
(3)中级调度
内存不够时,可将某些进程的数据调出外存。等内存空闲或者进程需要运行时再重新调入内存。
暂时调到外存等待的进程状态为挂起状态。被挂起的进程PCB会被组织成挂起队列。
拓展
可以发现进程又多了一种状态,那就是挂起状态。所以之前的进程的五状态模型可以拓展为七状态模型:
自我总结的三者区别
- 高级调度:外存到内存,创建一个新的进程。
- 低级调度:内存中的进程调度
- 中级调度:也是外存到内存,只不过是将挂起状态的进程重新调入内存。
进程调度的时机
进程的调度方式
进程的切换与过程
进程同步讨论的就是如何解决进程异步性的问题(因为某些应用场景就需要某个进程比另一个进程先执行)
我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。
对临界资源的访问,必须互斥地进行。互斥,亦称间接制约关系。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。
1)软件实现方法
2)硬件实现方法
1.互斥锁
基本概念:
解决临界区最简单的工具就是互斥锁(mutex lock)。一个进程在进入临界区时应获得锁;在退出临界区时释放锁。函数acquire()获得锁,而函数release()释放锁。
每个互斥锁有一个布尔变量available,表示锁是否可用。如果锁是可用的,调用acquire()会成功,且锁不再可用。当一个进程试图获取不可用的锁时,会被阻塞,直到锁被释放。
acquire()
{
while(!available)
; //忙等待
available = false; //获得锁
}
release()
{
available = true; //释放锁
}
原理机制与特性:
原理:acquire()或release()的执行必须是原子操作,因此互斥锁通常采用硬件机制来实现。
缺点:互斥锁的主要缺点是忙等待,当有一个进程在临界区中,任何其他进程在进入临界区时必须连续循环调用acquire()。当多个进程共享统一CPU时,就浪费了CPU周期。因此,互斥锁通常用于处理器系统,一个线程可以在一个处理器上等待,不影响其他线程的执行。
拓展:需要连续循环忙等的互斥锁,都可控称为自旋锁(spin lock),如TSL指令、swap指令、单标志法;
特性:
由来:1965年,荷兰学者Dijkstra提出了一种卓有成效的实现进程互斥、同步的方法----信号量机制
概念:用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。
信号量:其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为1的信号量。
一对原语:wait(S)原语和signal(S)原语,可以把原语理解为我们自己写的函数,函数名分别为wait和signal,括号里的信号量S其实就是函数调用时传入的一个参数。
1)整型信号量
2)记录型信号量(十分重要)
整型信号量的 缺陷是存在“忙等”问题,因此人们又提出了“记录型信号量”,即用记录型数据结构表示的信号量。
记录型信号量机制遵循了“让权等待”原则,不会出现忙等现象。
临界区的资源需要互斥访问
问题:为什么需要进程同步???
进程同步:要让合并发进程按要求有序地推进。
因为程序的异步特性,两个进程在运行时的先后是不确定的,但是有些时候需要两个进程按照先后顺序执行,此时就需要进行进程同步。
如何实现同步
前驱关系就是一个进程同步关系
这里的生产者生产多种类别商品,消费者消费不用类型的商品。
拓展:用管程解决巨额生产者消费者问题
各个进程都在等待别的进程手里的资源,同时自己手里也拥有着别人的资源。然后出现一直等待锁死。
- 死锁:各进程相互等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。
- 饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。比如:在短进程优先(SPF)算法中,若有源源不断的短进程到来,则长进程将一直得不到处理机,从而发生长进程“饥饿”。
- 死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑bug导致的,有时是程序员故意设计的。
产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。
- 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子、打印机设备等)。像内存、扬声器这样可以同时让多个进程使用的资源是不会导致死锁的(因为进程不用阻塞等待这种资源)。
- 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
- 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。
注意:发送死锁时一定有循环等待,但是发生循环等待时未必死锁(循环等待是死锁的必要不充分条件)。
如果同类资源数大于1,则即使有循环等待,也未必发送死锁。但如果系统中每类资源都只有一个,那循环等待就是死锁的充分必要条件了。
这是一种静态策略。这种策略的思想就是破坏产生死锁的四个必要条件的一个或多个,死锁就不会发生。
破坏互斥条件
破坏不剥夺条件
破坏请求和保持条件
破坏循环等待条件
核心算法:银行家算法
安全序列:就是指系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个。(如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了一些资源,那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况)
如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态)
** 因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这也是“ 银行家算法”的核心思想