https://juejin.cn/post/6844903957098151950#heading-13
https://juejin.cn/post/6850037269835808782
进程与线程分别是什么?
1、进程是资源分配的基本单位,线程是cpu调度的基本单位
2、一个线程只能属于一个进程,而一个进程可以包含多个线程
3、进程本身就持有系统资源,而线程只含有维持自身活动的必不可少的系统资源,本身不拥有系统资源。
为什么线程的调度开销比进程小?
进程是资源的调度的基本单位,因此在创建或者销毁进程时,系统都要为它分配或者回收内存。而且在进程切换时,系统需要保留当前进程的cpu环境,并重新设置另一个进程的cpu环境。同时对进程的调度也是很复杂,也需要花费大量的时间。
而线程本身不持有资源,而且同一进程中的线程共享进程资源,所以线程之间的通讯也更加便捷。因此相对于进程来说,线程的调度开销比较小的,它也被称为“轻型进程”。
进程的状态
就绪状态:进程已经获得系统分配的资源,等待处理器执行
运行状态:进程正在被处理器执行
阻塞状态:进程不具备运行条件,正在等待某个事件的完成。引起阻塞状态的事件有:等待IO完成,等待信号等。
线程的同步方式
互斥量(加锁):采用互斥对象机制,只有拥有这个互斥对象的线程才能访问共享资源,而且这个互斥对象是唯一的。会造成死锁。
信号量:允许同一时刻多个线程来访问共享资源,需要一个计数器来控制同一时刻来访问的最大线程数量。
事件:即使用通知操作的方式来保证线程同步,某一个线程不执行时可以主动唤醒其他的线程执行。
经典的进程同步问题
生产者-消费者问题:
问题描述 :一组生产者进程和一组消费者进程共享一块初始为空,大小确定的缓冲区,只有当缓冲区不为满的时候,生产者进程才可以将消息放入缓冲区,否则就要等待;只有当缓冲区不为空的时候,消费者进程才可以从中取出消息,否则就要等待;缓冲区一次只能被一个进程所访问。
问题分析 :生产者和消费者进程对缓冲区的访问是互斥关系,而生产者与消费者本身又存在同步关系,即消息必须先被生产,然后才能被消费。因而可以对缓冲区的访问设置一个互斥量,再设置两个信号量,一个记录空闲缓冲区单元,一个记录满缓冲区单元,从而实现生产者与消费者的同步。
哲学家进餐问题:
问题描述 :一张圆桌上坐着五名哲学家,每两个哲学家之间摆一根筷子,哲学家只有同时拿起左右两根筷子才可以用餐,用餐完成后将筷子放回原处。
问题分析 :为防止哲学家各取一根筷子而出现死锁,需要添加一定的限制条件。
几种解决方案如下 :
限制至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用毕时能释放出他用过的筷子,从而使更多的哲学家能够进餐
限制仅当哲学家左右手两边的筷子都可用时,才允许拿起筷子进餐;即使用AND信号量机制解决问题
读者-写者问题:
问题描述 :有读者和写者两个并发进程共享一个数据,多个读进程可以同时访问数据,但写进程的数据访问与其他进程都互斥
问题分析 :读者与写者互斥,写者与写者互斥,读者与读者共享。所以需要一个互斥量实现读写进程与写写进程的互斥,此外还需要引入一个计数器对读线程进行计数,使得读读同步,读写互斥。
几种解决方案如下 :
读者优先 :但只要读者源源不断,写者就得不到资源,容易造成写者饥饿
读写公平 :读者与写者公平竞争资源,但只要之前有已经排好队的读者,就算写者获取到了资源,也要等待所有读者线程结束
写者优先 :虽然之前已经排好队的读者可以优先访问资源,但在这之后的读者则需要等待写者进程结束。只要写者源源不断,后面的读者就得不到资源,容易造成读者饥饿。
进程间的通信方式
1、管道(匿名管道)
2、命名管道
管道是一种半双工的通讯方式,数据只能在进程间单向流动,而且如果读进程(接收数据的一方)来不及接收数据会造成数据丢失。匿名管道与命名管道的区别在于匿名管道只允许在有亲缘关系的进程之间通讯,而命名管道没有这个约束。
3、消息队列
写进程将数据写入到信息缓冲区中,读进程直接从缓存区中拿数据,避免了数据丢失问题。但是数据的复制过程会带来较大的性能消耗
4、信号量
信号量解决了多个进程同时访问多个相同资源的问题。通过对访问进程的计数来保证当前访问进程数小于最大允许进程数。
5、共享内存区
通过开辟一块共享内存区,多个进程都通过虚拟内存映射到这一块共享内存区中,实现进程通信。
6、Socket套接字
上面进程的通讯方式针对的都是本地主机进程间的通讯方式,而Socket可以实现远程进程间的网络通信。
操作系统中的进程调度算法有哪些
https://juejin.cn/post/6931957287040843784
当 CPU 有一堆任务要处理时,由于其资源有限,这些事情就没法同时处理。这就需要确定某种规则来决定处理这些任务的顺序,这就是 “调度” 研究的问题。除了接下来将要说的进程调度,还有作业调度、内存调度等。
从就绪队列中选择一个进程来执行,有如下调度算法。
一、非抢占式进程调度算法:当进程正在运行时,它就会一直运行,直到该进程完成或发生某个事件发生而被阻塞时,才会把 CPU 让给其他进程。可用于对实时性要求不严格的系统中。
1)先来先服务的调度算法:每次选择最先进入队列的进程执行(对短进程不利)
2)最短作业优先的调度算法:每次选择预计执行时间最短的进程来执行(按照预计执行时间对进程进行排序)(对长进程不利)
3)高响应比优先的调度算法:调度时计算所有就绪进程的响应比,先执行响应比最高的进程。响应比 = (进程的等待时间 + 进程需要的运行时间) / 进程需要的运行时间
二、抢占式优先权算法:处理机先分配给高优先权的进程,在它执行过程中如果有更高优先权的进程加入进来,则立即停止当前进程的执行,并重新将处理机让给更高优先权的进程。通常用于要求比较严格的实时系统中。
1)最短剩余时间优先的调度算法:当一个新的进程到达时,把它所需要的整个运行时间与当前进程的剩余运行时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程,否则新的进程等待。
2)轮转调度算法(时间片):就绪队列中的每个进程轮流地运行一个时间片,当时间片耗尽时就强迫当前运行进程让出 CPU 资源,转而排到就绪队列尾部,等待下一轮调度。所以,一个进程一般都需要多次轮转才能完成。
三、最高优先级调度算法:从就绪队列中选择最高优先级的进程进行运行。最高优先级算法并非是固定的抢占式策略或非抢占式,系统可预先规定使用哪种策略。
1)抢占式:当就绪队列中出现优先级高的进程,则立即强制剥夺当前运行进程的 CPU 资源,分配给优先级更高的进程运行。
2)非抢占式:当就绪队列中出现优先级高的进程,则运行完当前进程后,再选择该优先级高的进程。
死锁的四个必要条件
互斥条件:同一份资源同一个时刻只能被同一进程访问
不可剥夺条件:在进程使用资源时,另一进程不能强行占用
请求与保持条件:一个进程已经拿到了资源1,正在请求资源2。同时,另一个进程已经拿到了资源2,正在请求资源1。这种情况下两个进程都拿不到他们想要的资源。
循环等待条件:多个进程形成一种循环等待资源的情况。(类似于哲学家就餐问题)
解决死锁的基本方法有哪些
预防死锁:
1、资源一次性分配:破坏请求与保持条件
2、可剥夺资源:当某个进程迟迟没有拿到新资源时,就释放已经获得的资源。(破坏了不可剥夺条件)
3、资源有序分配:给每个资源赋予一个编号,多个进程按照编号顺序获得资源。(破坏循环等待条件)
避免死锁:
1、银行家算法:按照系统当前剩余资源去逐个检查每个进程要申请的资源,如果该进程还需要的资源小于系统剩余资源就将资源分配给它,并更新剩余资源数量。如果一个队列中的进程都能拿到资源并完成工作,则系统是安全的。
动态连续分配有哪些算法
连续动态分区分配又称为可变分区分配,是一种动态规划的内存分配方法。这种方法不预先分配内存,而是在进程装入内存时,根据进程的需要来分配大小刚好够他用的内存。
主要有以下算法:
1)首次适应算法:空闲分区按照内存地址递增的顺序连接,分配时进行顺序查找,找到第一个大小满足要求的分区。
2)最佳适应算法:空闲分区按照容量递增链接,找到第一个满足要求的空闲分区,即找到第一个刚好够用的空闲分区。
3)最坏适应算法:空闲分区按照容量递减的顺序链接,找到第一个满足要求的空闲分区,即找到最大空闲分区。
分页和分段存储管理以及段页式存储管理
https://juejin.cn/post/6845166890709417998
1)页是一个物理单位,页是为了合理利用空间,减少内存碎片,提高内存的利用率。以页面为单位,把进程空间装进物理内存中分散的物理块中(可能存在内存碎片,取决于页的大小,因此选择一个合适的页的大小很关键,太大了难以分配,太小了内存碎片过多且一个逻辑被分到多个页中严重影响执行效率)。
2)而段是一种逻辑单位,它将进程逻辑空间划分成若干段(非等分),能够更好地满足用户的需要。(例如对于主函数main、子程序段X、子函数Y等,会根据每一个函数的逻辑的长度去分配逻辑空间)
页的大小固定且由系统确定,而段的长度是不确定的,取决于用户编写的程序。
页表信息中记录了页的编号与真实物理块的映射(页的大小和物理块的大小是一样的)。段表信息记录了段的逻辑地址到真实物理地址的映射,即需要给出段的编号和对应真实物理地址的基址和段长(段长是不固定的因此段表中需要记录段长来确定真实物理内存的大小)。
3)段页式存储管理:
分页存储管理可以有效提高内存利用率,分段可以更好满足用户需求。
中和这两种方法的优点先把逻辑空间按段式管理分成若干段,在把段内空间按页式管理分成若干页。
什么是虚拟内存
如果存在一个程序,它需要的内存超出了计算机能够提供的实际内存,那么由于程序无法装入内存,就无法运行,因此提出了虚拟内存。
基于局部性原理,在程序装入时可以将程序的一部分装入内存,剩下的放在外存上。当程序执行时,遇到所需要的信息是在外存中的,操作系统再把这部分信息调入内存中来,再执行程序。另一方面,操作系统会将暂时不用的内容移到外存中,从而移出空间存放将要调入内存的信息。
这样就好像操作系统为用户提供了一个比实际内存容量大得多的存储器,成为虚拟内存。
虚拟内存有什么特点
多次性:一个任务可以分多次被调入内存
对换性:指任务执行过程中存在换进换出的过程(在磁盘和内存中对换,当要使用时就从磁盘调入内存,当暂时不使用就从内存移到磁盘,腾出空间)
虚拟性:指它从逻辑上扩充了内存的容量,它是基于多次性和对换性的,而多次性和对换性又是基于内存离散分配基础上的。
虚拟地址空间
每个进程都有属于自己的虚拟内存,这块空间就叫做虚拟地址空间。
在没有**虚拟地址空间**之前,是根据进程的需要**按需分配**物理内存的。但有了**虚拟地址空间**,分配策略可以变一下,先把**虚拟地址空间**分配的大些,**但不立马建立与物理内存的映射**,而是用到的时候,用多少,建立多少。进程中维护一张页表,用来保存映射关系。
内核空间与用户空间
操作系统虽然为**每个进程**都分配了**虚拟地址空间**,但 **虚拟地址空间**中并不是所有的区域都可以为进程所用。
操作系统将**虚拟地址空间** 分为**用户空间**和**内核空间**,对于 **32 位**的操作系统,在 **Linux** 的**虚拟地址空间**中,**用户空间**和**内核空间**的大小比例为 3:1,而在 **window** 中则为 2:2。
所有的系统调用都要交给内核完成,而内核也要运行在内存中,为了防止用户进程的干扰,于是在虚拟地址空间中划分了内核空间用来映射物理空间上的内核空间。
从用户态切换到内核态的方式
用户态切换到内核态都是通过函数调用的方式执行的,因此到函数执行完成时就会从内核态切换到用户态中。
1)<font color=#41729f>系统调用</font>:用户态进程主动要求切换(实际上也是执行一个中断函数)。用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。
2)异常:在执行用户进程的过程中,发生了某些异常,而这些异常只能切换到内核态才能处理,比如缺页异常。
3)外围设备的中断:当外围设备完成用户请求后,会向CPU发出相应的中断信号,这时CPU就会去执行中断信号对应的程序,如果CPU之前是在执行用户线程,则处理中断的过程就是一次用户态到内核态的切换。
操作系统常见的服务
http://www.vue5.com/operating_system/os_services.html
1)IO操作(操作系统管理用户进程与设备驱动程序进程之间的通信)
2)程序执行
- 将程序加载到内存中。
- 执行程序。
- 处理程序的执行。
- 提供进程同步的机制。
- 提供流程沟通的机制。
- 提供了一种死锁处理机制。
3)文件系统操作
4)进程通信
5)错误处理
6)资源管理和权限保护
常用的页面置换算法有哪些?
https://juejin.cn/post/6982173572873584677
什么是页面置换算法:在进程运行过程中,如果其访问的页面不存在于内存中,则会产生缺页中断。如果此时内存中没有空闲的页面,则操作系统就需要在内存中选择一个页面将其移出,从而可以将需要访问的页面调入内存中。而用来选择淘汰哪一页的算法就叫做页面置换算法。好的页面置换算法应该有较低的页面更换频率。
注意:正常情况下,在产生缺页中断时,是将内存中的空闲页面移出,这里说的是没有空闲页面的情况。
1)最佳置换算法:选择淘汰的页面是以后永久不使用或者很长一段时间内不会再使用的,这样可以保证最低的缺页率(需要用到这页的时候而内存中没有这页的概率)。
2)先进先出算法:淘汰最早进入内存的页面。把调入内存的页面按照先后顺序放入队列中,当需要置换页面时,选择队头的页面即可。
3)最近最久未使用算法(LRU):淘汰最近最久没有被使用的页面。因此需要记录该页面上次被使用以来经历的时间,并选择淘汰一个最大的时间对应的页面。
4)最近最少使用算法(LFU):如果一个页面在最近一段时间内使用频次很少,那么在将来该页面也会使用很少。即淘汰最近使用次数最少的的页面。
注意:LRU的淘汰规则是基于访问时间,而LFU是基于访问次数的。
java中常见的IO模型有哪些?IO多路复用中的select、poll、epoll机制?
https://juejin.cn/post/6939841279329042439#heading-6
https://juejin.cn/post/6844903687626686472#heading-3
为了保证操作系统的安全性与稳定性,一个进程的地址空间可以被划分为用户空间与内核空间。
我们平常运行的应用程序都是工作在用户空间,只有内核空间才能完成系统级别的资源调用,比如IO操作、文件管理、进程通信、内存管理等。而且用户空间是不能直接访问内核空间的,因此在申请操作系统服务时只能发起系统调用请求操作系统来完成。
以IO操作为例,当程序发起IO调用后,会经历两个阶段:操作系统等待IO设备准备好数据;操作系统把数据从内核空间拷贝到用户空间。
java中有3种常见的IO模型:BIO(同步阻塞IO),NIO(同步非阻塞IO),AIO(异步IO)
操作系统层面上有5种常见的IO模型:同步阻塞IO,同步非阻塞IO,IO多路复用,信号驱动IO和异步IO。
同步阻塞IO:在应用程序发起read调用后,会一直阻塞,直到操作系统将数据从内核空间拷贝到用户空间为止。
在并发量高的情况下,BIO模型的效率很低。
同步非阻塞IO:在应用程序发起read调用后,内核空间开始准备数据,之后每过一段时间应用程序就会发起一个read调用来检查内核是否已经将数据准备好了,若检测到数据已经准备好了才进入阻塞状态执行拷贝过程。
对于高并发量的系统应该使用NIO模型,虽然在数据准备过程中应用程序是非阻塞的,但是由于要一直轮询,所以会很消耗CPU资源。
信号驱动IO:当应用程序发起read调用后,操作系统不用马上返回(即用户进程可以去做其他事),当监控到对应资源可用时就通知用户进程进行数据拷贝操作。IO多路复用类似于多个信号驱动IO。
https://juejin.cn/post/6915395956808613902
IO多路复用模型:一次性向操作系统发起所有调用函数,操作系统不用马上返回这些调用函数,当监控到有资源可用时,再返回相应的调用,即操作系统一直帮应用程序监控资源,不用应用程序每次都来询问。而在NIO中每次向操作系统发起调用,如果资源不可用都会直接返回,直到某次调用遇到可用的资源才进行拷贝操作。
IO多路复用减少了许多无效的系统调用,减少了对 CPU 资源的消耗,但它实际上在数据拷贝阶段仍然是同步阻塞的。
在LINUX中IO多路复用有三种机载select、poll、epoll。不过windows中也实现了select、poll机制。
select机制:在select这种I/O多路复用机制下,我们需要把想监控的文件描述集合通过函数参数的形式告诉select,然后select会将这些文件描述符集合拷贝到内核中,我们知道数据拷贝是有性能损耗的,因此为了减少这种数据拷贝带来的性能损耗,Linux内核对集合的大小做了限制,并规定用户监控的文件描述集合不能超过1024个,同时当select返回后我们仅仅能知道有些文件描述符可以读写了,但是我们不知道是哪一个,因此程序员必须再遍历一边找到具体是哪个文件描述符可以读写了。
poll机制:poll和select是非常相似的,poll相对于select的优化仅仅在于解决了文件描述符不能超过1024个的限制,select和poll都会随着监控的文件描述增加而出现性能下降,因此不适合高并发场景。
epoll:相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户相关的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
异步IO:异步IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成(包括数据准备与数据拷贝的操作),操作系统会给相应的进程发送一个信号告诉它read操作完成了。