九、操作系统详解

目录

1. IO模型

①BIO(blocking IO)

阻塞IO,即在读写数据的过程中会发生阻塞现象。

当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,操作系统就会将数据从内核空间拷贝到用户空间,并返回结果给用户线程,用户线程才解除阻塞状态。

(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)

典型的阻塞IO模型的例子为:

data = socket.read();

如果数据没有就绪,就会一直阻塞在read方法。

②NIO(nonblocking IO)

当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时(数据未准备就绪),它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。

但是对于非阻塞IO就有一个非常严重的问题,在while循环中需要不断地去询问内核数据是否就绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取数据。

③AIO (异步IO Asynchronous IO)

【异步IO模型才是最理想的IO模型】

在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。

而另一方面,从内核的角度,当它收到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何阻塞。

然后,内核会等待数据准备完成,再将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。

当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。

(也就说用户线程完全不需要关心实际的整个IO操作是如何进行的,只需要先发起一个请求。)

也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用IO函数进行实际的读写操作。

④IO多路复用(IO multiplexing)

在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。

因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线 程和 进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。

⑤信号驱动IO(signal driven IO)

在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。

2. 进程有哪几种状态

进程大致分为5个状态

创建状态(new):进程正在被创建,尚未到就绪状态。

就绪状态(ready):进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。

运⾏状态(running) :进程正在处理器上上运⾏(单核 CPU 下任意时刻只有⼀个进程处于运⾏状态)。

阻塞状态(waiting) :⼜称为等待状态,进程正在等待某⼀事件⽽暂停运⾏如等待某资源为可⽤或等待 IO 操作完成。即使处理器空闲,该进程也不能运⾏。

结束状态 (terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运⾏。

3. 进程间通信方式

⼤概有 7 种常⻅的进程间的通信⽅式

管道/匿名管道(Pipes) :⽤于具有亲缘关系的⽗⼦进程间或者兄弟进程之间的通信。

有名管道(Names Pipes) : 匿名管道由于没有名字,只能⽤于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘⽂件的⽅式存在,可以实现本机任意两个进程通信。

信号(Signal) :信号是⼀种⽐较复杂的通信⽅式,⽤于通知接收进程某个事件已经发⽣;

消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(⽆名管道:只存在于内存中的⽂件;命名管道:存在于实际的磁盘介质或者⽂件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除⼀个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不⼀定要以先进先出的次序读取,也可以按消息的类型读取.⽐ FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载⽆格式字 节流以及缓冲区⼤⼩受限等缺。

信号量(Semaphores) :信号量是⼀个计数器,⽤于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信⽅式主要⽤于解决与同步相关的问题并避免竞争条件。

共享内存(Shared memory) :使得多个进程可以访问同⼀块内存空间,不同进程可以及时看到对⽅进程中对共享内存中数据的更新。这种⽅式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有⽤的进程间通信⽅式。

套接字(Sockets) : 此⽅法主要⽤于在客户端和服务器之间通过⽹络进⾏通信。套接字是⽀持TCP/IP 的⽹络通信的基本操作单元,可以看做是不同主机之间的进程进⾏双向通信的端点,简单的说就是通信的两⽅的⼀种约定,⽤套接字中的相关函数来完成通信过程。

4. 进程的调度算法

为了确定⾸先执⾏哪个进程以及最后执⾏哪个进程以实现最⼤ CPU 利⽤率,计算机科学家已经定义了⼀些算法,它们是:

先到先服务(FCFS)调度算法 : 从就绪队列中选择⼀个最先进⼊该队列的进程为之分配资源,使它⽴即执⾏并⼀直执⾏到完成或发⽣某事件⽽被阻塞放弃占⽤ CPU 时再重新调度。

短作业优先(SJF)的调度算法 : 从就绪队列中选出⼀个估计运⾏时间最短的进程为之分配资源,使它⽴即执⾏并⼀直执⾏到完成或发⽣某事件⽽被阻塞放弃占⽤ CPU 时再重新调度。

时间⽚轮转调度算法 : 时间⽚轮转调度是⼀种最古⽼,最简单,最公平且使⽤最⼴的算法,⼜称 RR(Round robin)调度。每个进程被分配⼀个时间段,称作它的时间⽚,即该进程允许运⾏的时间。

多级反馈队列调度算法 :前⾯介绍的⼏种进程调度的算法都有⼀定的局限性。如短进程优先的调度算法,仅照顾了短进程⽽忽略了⻓进程 。多级反馈队列调度算法既能使⾼优先级的作业得到响应⼜能使短作业(进程)迅速完成。因⽽它是⽬前被公认的⼀种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法

优先级 调度 :为每个流程分配优先级,⾸先执⾏具有最⾼优先级的进程,依此类推。具有相同优先级的进程以 FCFS ⽅式执⾏。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。

5. 常见的几种内存管理机制

内存管理机制简单分为连续分配管理⽅式⾮连续分配管理⽅式这两种。

连续分配管理⽅式是指为⼀个⽤户程序分配⼀个连续的内存空间,常⻅的如块式管理

⾮连续分配管理⽅式允许⼀个程序使⽤的内存分布在离散或者说不相邻的内存中,常⻅的如⻚式管理段式管理

块式管理 : 远古时代的计算机操系统的内存管理⽅式。将内存分为⼏个固定⼤⼩的块,每个块中只包含⼀个进程。如果程序运⾏需要内存的话,操作系统就分配给它⼀块,如果程序运⾏只需要很⼩的空间的话,分配的这块内存很⼤⼀部分⼏乎被浪费了。这些在每个块中未被利⽤的空间,我们称之为碎⽚。

⻚式管理 :把主存分为⼤⼩相等且固定的⼀⻚⼀⻚的形式,⻚较⼩,相对相⽐于块式管理的划分⼒度更⼤,提⾼了内存利⽤率,减少了碎⽚。⻚式管理通过⻚表对应逻辑地址和物理地址。

段式管理 : ⻚式管理虽然提⾼了内存利⽤率,但是⻚式管理其中的⻚实际并⽆任何实际意义。段式管理把主存分为⼀段段的,每⼀段的空间⼜要⽐⼀⻚的空间⼩很多 。但是,最重要的是段是有实际意义的,每个段定义了⼀组逻辑信息,例如,有主程序段 MAIN、⼦程序段 X、数据段 D及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。

段⻚式管理机制 : 段⻚式管理机制结合了段式管理和⻚式管理的优点。简单来说段⻚式管理机制就是把主存先分成若⼲段,每个段⼜分成若⼲⻚,也就是说 段⻚式管理机制 中段与段之间以及段的内部的都是离散的。

6. 虚拟地址和物理地址

虚拟地址,就是就是一种逻辑意义上的地址,而当我们想要访问这个虚拟地址时,是需要转换到物理地址才能够真实的访问到。

物理地址指的是真实物理内存中地址,更具体⼀点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。

7. 什么是虚拟内存

虚拟内存计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换

虚拟内存也叫虚拟存储器,基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其余部分留在外存,就可以启动程序执行。

在程序执行的过程中,当访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换出到外存上,从而腾出空间存放将要调入内存的信息。这样,系统好像为用户提供了一个比实际内存大得多的存储器,称为虚拟储存器。

8. 什么是共享内存

共享内存是进程间通信的一种方式。

共享内存允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。

不同进程之间共享的内存通常为同一段物理内存。

进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。

如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

9. 页面置换算法

程序在运行时,在请求分页系统中,每当所要访问的页面不在内存时,便产生一个缺页中断,需要将所缺的页调入内存。

如果内存中有空闲块,则分配一个块,将要调入的页装入该块,并修改页表中的相应页表项。

但内存已无空闲空间时,就需要从内存中淘汰某页,而选择淘汰哪个页面的算法就是页面置换算法

①最佳置换算法(OPT)

最佳置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。

但是由于人们目前无法预知进程在内存下的若干页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。

最佳置换算法可以用来评价其它算法

②先进先出页面置换算法(FIFO)

优先淘汰最早进入内存的页面,即在内存中驻留时间最久的页面。

算法实现:把调入内存的页面根据先后次序链接成队列,设置一个指针总指向最早的页面。

但该算法与进程实际运行时的规律不适应,因为在进程中,有的页面经常被访问。

③最近最久未使用置换算法(LRU)

选择最近最长时间未访问过的页面予以淘汰,

它认为过去一段时间未访问过的页面,在最近的将来可能也不会被访问。

算法为每个页面设置一个访问字段,来记录页面自上次访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。

④时钟置换算法(CLOCK)

时间置换算法是给每一帧关联一个附加位,称为使用位。当某一页首次装入主存时,该帧的使用位设置位1;当该页随后再被访问到时,它的使用位也被置为1。

算法要循环扫描缓冲区,像时钟一样转动,所以叫时钟算法

10. 调度算法

1.先来先服务调度算法(FCFS)

从就绪的队列中选择最先进入该队列的进程进行处理。

2.短作业优先调度算法(SJF)

从就绪队列中选择一个估计运行时间最短的进程进行处理。

3.优先级调度算法

从就绪队列中选择优先级最高的进程进行处理。

4.高响应比优先调度算法

先计算队列中每个作业的等待时间和估计运行时间的比值作为响应比,从中选出响应比最高的作业运行。

5.时间片轮转调度算法

按照先来先服务的原则执行队列中的进程,但是只能运行一个时间片,然后再重新排队,等候再次运行。

6.多级反馈队列调度算法

多级反馈队列调度算法是时间片轮转调度算法和优先级调度算法的,通过动态调度调整整个进程优先级和时间片大小,多级反馈队列调度算法可以兼顾多方面的系统目标。

1.操作系统概述

1.1 系统调用 / 用户态和内核态

根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:

  • 用户态(user mode) : 用户态运行的进程可以直接读取用户程序的数据。
  • 系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
1.2 系统调用分类?
  • 我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能咋办呢?

    那就需要系统调用了!

  • 也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。

这些系统调用按功能大致可分为如下几类:

  • 设备管理。完成设备的请求或释放,以及设备启动等功能。
  • 文件管理。完成文件的读、写、创建及删除等功能。
  • 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
  • 进程通信。完成进程之间的消息传递或信号传递等功能。
  • 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
1.3 那么如何从用户态切换到内核态呢
  • 系统调用
    这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如 read 操作,比如前例中 fork() 实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
  • 异常
    当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
  • 外围设备的中断
    当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。

1.4 其它必会知识
1.4.1并行与并发

并发:在操作系统中,某一时间段,几个程序在同一个CPU上运行,但在任意一个时间点上,只有一个程序在CPU上运行。
并行:两个程序在某一时刻同时运行,强调同时发生。

1.4.2 阻塞与非阻塞

阻塞是指调用线程或者进程被操作系统挂起。
非阻塞是指调用线程或者进程不会被操作系统挂起。

1.4.3 同步与异步

同步与异步同步是阻塞模式,异步是非阻塞模式。

同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,知道收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回式系统会通知进程进行处理,这样可以提高执行的效率。

1.4.4 五种IO模型

I/O模型可细分为五种类型:阻塞IO、非阻塞IO、多路复用IO、信号驱动IO、异步IO。

1、BIO:

​ 其是Blocking IO的意思。在类似于网络中进行read, write, connect一类的系统调用时会被卡住。举个例子,当用read去读取网络的数据时,是无法预知对方是否已经发送数据的。因此在收到数据之前,能做的只有等待,直到对方把数据发过来,或者等到网络超时。

阻塞IO是指系统调用时,我们的应用进程会等待内核返回的结果

2、NIO:

​ 是指将IO模式设为“Non-Blocking”模式。在NIO模式下,调用read,如果发现没数据已经到达,就会立刻返回-1, 并且errno被设为EAGAIN。具体来说NIO就是不断的尝试有没有数据到达,有了就处理,没有就等一小会再试。NIO的底层原理主要是依靠缓冲区来实现的,输入通过管道输入到缓冲区,之后OS系统再通知对应的线程从缓冲区中提取数据,从而实现无需等待的过程。

3、IO多路复用:

​ IO多路复用(IO Multiplexing) 是这么一种机制:程序注册一组socket文件描述符给操作系统,表示“我要监视这些fd是否有IO事件发生,有了就告诉程序处理”,这样就可以只需要一个或几个线程就可以完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据,这么做就可以节省出大量的线程资源出来,这个就是IO复用模型的思路。

img

IO复用模型的思路就是系统提供了一种函数可以同时监控多个fd的操作,这个函数就是我们常说到的select、poll、epoll函数,有了这个函数后,应用线程通过调用select函数就可以同时监控多个fd,select函数监控的fd中只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时询问线程再去通知处理数据的线程,对应线程此时再发起recvfrom请求去读取数据。

select,poll,epoll

(1)select==>时间复杂度O(n)

它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

(2)poll==>时间复杂度O(n)

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.

(3)epoll==>时间复杂度O(1)

epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

4、信号驱动IO模型:

复用IO模型解决了一个线程可以监控多个fd的问题,但是select是采用轮询的方式来监控多个fd的,通过不断的轮询fd的可读状态来知道是否就可读的数据,而无脑的轮询就显得有点暴力,因为大部分情况下的轮询都是无效的,所以有人就想,能不能不要我总是去问你是否数据准备就绪,能不能我发出请求后等你数据准备好了就通知我,所以就衍生了信号驱动IO模型

于是信号驱动IO不是用循环请求询问的方式去监控数据就绪状态,而是在调用sigaction时候建立一个SIGIO的信号联系当内核数据准备好之后再通过SIGIO信号通知线程数据准备好后的可读状态,当线程收到可读状态的信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下应用线程在发出信号监控后即可返回,不会阻塞,所以这样的方式下,一个应用线程也可以同时监控多个fd。

img

5、异步IO模型:

其实经过了上面两个模型的优化,我们的效率有了很大的提升,但是我们当然不会就这样满足了,有没有更好的办法,通过观察我们发现,不管是IO复用还是信号驱动,我们要读取一个数据总是要发起两阶段的请求,第一次发送select请求,询问数据状态是否准备好,第二次发送recevform请求读取数据。应用只需要向内核发送一个read 请求,告诉内核它要读取数据后即刻返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用,我们称这种一劳永逸的模式为异步IO模型

这种模型与信号驱动模型的主要区别在于,信号驱动IO只是由内核通知我们合适可以开始下一个IO操作,而异步IO模型是由内核通知我们操作什么时候完成。

img

**系统调用:**Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统调用命令在自己的应用程序中调用它们。从某种角度来看,系统调用和普通的函数调用非常相似。区别仅仅在于,系统调用由操作系统核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。

2、进程管理

2.1 程序, 进程,线程,协程

程序:计算机程序是一组计算机能识别和执行的指令,运行于计算机上,满足人们某种需求的信息化工具。

  • 进程是资源分配的最基本的单位,运行一个程序会创建一个或多个进程,进程就是运行起来的可执行程序。
  • 线程是程序执行的最基本的单位,是轻量级的进程,每个进程里都有一个主线程,且只能有一个,和进程是相互依存的关系,生命周期和进程一样。
  • 协程是用户态的轻量级线程,是线程内部的基本单位。无需线程上下文切换的开销、无需原子操作锁定及同步的开销、方便切换控制流,简化编程模型。

进程和线程的区别的话

1、调度,线程是独立调度的基本单位,进程是资源分配的基本单位。

2、拥有资源,进程是拥有资源的基本单位,而线程则不拥有系统的资源,但是线程可以访问其所属进程的资源。

3、系统开销,系统创建和回收进程是需要对进程进行资源回收的。因此创建和回收进程的损耗要大于创建和回收线程的损耗。

4、地址空间和其他资源,进程间各自的地址空间是独立的,线程间的地址空间是共享的。

5、通信方面,进程间通信主要通过进程同步和互斥手段,而线程间可以直接读写进程数据段(如全局变量)进行通信。

2.2 PCB是什么?

PCB主要包含下面几部分的内容:

  • 进程的描述信息,比如进程的名称,标识符,
  • 处理机的状态信息,当程序中断是保留此时的信息,以便 CPU 返回时能从断点执行
  • 进程调度信息,比如阻塞原因,状态,优先级等等
  • 进程控制和资源占用,同步通信机制,链接指针(指向队列中下一个进程的 PCB 地址)

PCB是进程实体的一部分,是操作系统中最重要的数据结构

  • 由于它的存在,使得多道程序环境下,不能独立运行的程序成为一个能独立运行的基本单位,使得程序可以并发执行
  • 系统通过 PCB 来感知进程的存在。(换句话说,PCB 是进程存在的唯一标识)
  • 进程的组成可以用下图来表示,PCB 就是他唯一标识符。
2.3 进程和线程创建和撤销的过程中发生了什么事情?

进程允许创建和控制另一个进程,前者称为父进程,后者称为子进程,子进程又可以创建孙进程,如此下去进而形成一个进程的家族树,这样子进程就可以从父进程那里继承所有的资源,当子进程撤销时,便将从父进程处获得的所有资源归还,此外,撤销父进程,则必须撤销所有的子进程。(撤销的过程实际上就是对这棵家族树进行后序遍历的过程)
在应用中创建一个子进程的过程如下:

  • 申请空白的PCB
  • 初始化进程描述信息
  • 为进程分配资源以及地址空间
  • 将其插入就绪队列中

当进程完成后,系统会回收占用的资源,撤销进程,而引发进程撤销的情况有:进程正常结束或者异常结束,外界的干预(比如我们在任务管理器中强制停止某个进程的运行)。

  • 查找需要撤销的进程的 PCB
  • 如果进程处于运行状态,终止进程并进行调度
  • 终止子孙进程 - 归还资源
  • 将它从所在的队列中移除
2.4 进程的5种状态

面试在答的时候这么答:有创建状态、就绪状态、运行状态、阻塞状态、结束状态。

  • 其中只有就绪状态和运行状态能互相转化,当进程为就绪态时,等待 CPU 分配时间片,得到时间片后就进入 运行状态
  • 运行状态在使用完 CPU 时间片后,又重回就绪态。
  • 阻塞状态是进程在运行状态时,需要等待某个资源比如打印机资源,而进入一个挂起的状态,等资源拿到后会回到就绪状态,等待 CPU 时间片。
2.5 进程调度算法

1、先来先服务(FCFS)

处于就绪态的进程按先后顺序链入到就绪队列中,而FCFS调度算法按就绪进程进入就绪队列的先后次序选择当前最先进入就绪队列的进程来执行,直到此进程阻塞或结束,才进行下一次的进程选择调度。FCFS调度算法采用的是****不可抢占的调度方式****,一旦一个进程占有处理机,就一直运行下去,直到该进程完成其工作,或因等待某一事件而不能继续执行时,才释放处理机。操作系统如果采用这种进程调度方式,则一个运行时间长且正在运行的进程会使很多晚到的且运行时间短的进程的等待时间过长。

2、短作业优先(SJF)

其实目前作业的提法越来越少,我们姑且把“作业”用“进程”来替换,改称为短进程优先调度算法,此算法选择就绪队列中确切(或估计)运行时间最短的进程进入执行。它既可采用可抢占调度方式,也可采用不可抢占调度方式。可抢占的短进程优先调度算法通常也叫做最短剩余时间优先(Shortest Remaining Time First,SRTF)调度算法。短进程优先调度算法能有效地缩短进程的平均周转时间,提高系统的吞吐量,但不利于长进程的运行。而且如果进程的运行时间是“估计”出来的话,会导致由于估计的运行时间不一定准确,而不能实际做到短作业优先。

3、时间片轮转(RR)

RR 调度算法与FCFS 调度算法在选择进程上类似,但在调度的时机选择上不同。RR调度算法定义了一个的时间单元,称为时间片(或时间量)。一个时间片通常在1~100 ms之间。当正在运行的进程用完了时间片后,即使此进程还要运行,操作系统也不让它继续运行,而是从就绪队列依次选择下一个处于就绪态的进程执行,而被剥夺CPU使用的进程返回到就绪队列的末尾,等待再次被调度。时间片的大小可调整,如果时间片大到让一个进程足以完成其全部工作,这种算法就退化为FCFS调度算法;若时间片设置得很小,那么处理机在进程之间的进程上下文切换工作过于频繁,使得真正用于运行用户程序的时间减少。时间片可以静态设置好,也可根据系统当前负载状况和运行情况动态调整,时间片大小的动态调整需要考虑就绪态进程个数、进程上下文切换开销、系统吞吐量、系统响应时间等多方面因素。

4、高响应比优先(Highest Response Ratio First,HRRF)

HRRF调度算法是介于先来先服务算法与最短进程优先算法之间的一种折中算法

2.6 进程同步方式

临界区
首先对临界资源的访问那段代码被称为临界区,为了互斥的访问临界区,每个进程在进入临界区时,都需要先进行检查,也就是查看锁。(临界资源:临界资源是一次仅允许一个进程使用的共享资源)

同步与互斥
同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后顺序。
互斥:多个进程在同一时刻只有一个进程能进入临界区。

信号量
信号量是一个整型变量,可以对其执行 P 和 V 操作。
P:如果信号量大于零,就对其进行减 1 操作;如果信号量等于 0,进程进入 waiting 状态,等待信号量大于零。
V:对信号量执行加 1 操作,并唤醒正在 waiting 的进程
如果信号量只能取 0 或者 1,那么就变成了互斥量,其实也可以理解成加锁解锁操作,0 表示已经加锁,1 表示解锁。

管程
使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。
管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。
管程引入了 条件变量 以及相关的操作:wait() 和 signal() 来实现同步操作。对 条件变量 执行 wait() 操作会导致调用进程阻塞,把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。

2.7 进程间通讯方式

进程通信和进程同步很容易混淆,其实可以把进程通信当成一种手段,进程同步是一种目的,为了实现进程同步,可传输一些进程同步所需要的信息。

  • 管道
    • 匿名管道:举个例子:linux 里的竖线,就是管道的意思,比如 ps -aux|grep mysql 这句话的意思是把前一个进程查询的结果作为 grep mysql 的输入,如果两个进程要进行通信的话,就可以用这种管道来进行通信。
      这种通信的方式是半双工通信的,只能单向交替传输
      并且只能在具有亲属关系的进程之间通信使用。
      可以看成是一种特殊的文件,但是这种文件只能存在于内存之中。
    • 命名管道:可以用 mkfifo 命令创建一个命名管道,可以用一个进程向管道里写数据,然后可以让另一个进程把里面的数据读出来。命名管道的优点是去除了只能在父子进程中使用的限制,并且命名管道有路径名和它相关联,是以一种特殊设备文件形式存在于文件系统中的。
  • 消息队列
    • 消息队列的通信模式是这样的:a 进程要给 b 进程发消息,只需要把消息挂在消息队列(可以是中介邮局,也可以是进程自己的信箱)里就行了,b 进程需要的时候再去取消息队列里的消息。
    • 消息队列可以独立于读写进程存在,就算进程终止时,消息队列的内容也不会被删除。
    • 读进程可以根据消息类型有选择的接收消息,而不像 FIFO 那样只能默认接收。
      如果进程发送的数据较大,并且两个进程通信非常频繁的话,消息队列模型就不太合适了,因为如果发送的数据很大的话,意味着发送消息(拷贝)这个过程就需要很多时间来读写内存。
  • 共享内存
    • 共享内存的方式就可以解决拷贝耗时很长的问题了。
    • 共享内存是最快的一种进程通信的方式,因为进程是直接对内存进行存取的。因为可以多个进程对共享内存同时操作,所以对共享空间的访问必须要求进程对共享内存的访问是互斥的。所以我们经常把信号量和共享内存一起使用来实现进程通信。
    • (这里补个知识!!!系统加载一个进程的时候,分配给进程的内存并不是实际的物理内存,而是虚拟内存空间。那么我们可以让两个进程各自拿出一块儿虚拟地址空间来,映射到同一个物理内存中。这样两个进程虽然有独立的虚拟内存空间,但有一部分是映射到相同的物理内存,这样就完成共享机制了。)
  • 信号量
    • 共享内存最大的问题就是多进程竞争内存的问题,就像平时所说的线程安全的问题,那么就需要靠信号量来保证进程间的操作的同步与互斥。
    • 信号量其实就是个计数器,例如信号量的初始值是 1,然后 a 进程访问临界资源的时候,把信号量设置为 0,然后进程 b 也要访问临界资源的时候,发现信号量是 0,就知道已有进程在访问临界资源了,这时进程 b 就访问不了了,所以说信号量也是进程间的一种通信方式。
  • 套接字
    套接字可以实现两个不同的机器之间的进程通信,比如 socket 使用。
2.7.1 有名管道和无名管道的区别

1,匿名管道:

概念:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,一般使用fork函数实现父子进程的通信

2,命名管道:

概念:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,没有血缘关系的进程也可以进程间通信。

2.7.2 消息队列和管道区别

消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。这两点是管道以及有名管道所不能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管道很相似;但消息队列是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大。

2.8 线程通信/同步方式:

1、wait()/notify()/notifyAll();2、Synchronized关键字;3、通过Condition的awiat和signal;4、 ReentrantLock 结合 Condition;5、Volatile关键字修饰的全局变量;

2.9 线程,进程切换

进程切换的是什么:

回答:进程是由内核管理和调度的,所以进程的切换只能发生在内核态。所以,进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。

线程切换的是什么:

这还得看线程是不是属于同一个进程:

当两个线程不是属于同一个进程,则切换的过程就跟进程上下文切换同样;

当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据;

2.10 死锁
2.8.1讲讲死锁发生的条件是什么?

互斥条件:是资源分配是互斥的,资源要么处于被分配给一个进程的状态,要么就是可用状态。

等待和占有条件:进程在请求资源得不到满足的时候,进入阻塞等待状态,且不释放已占有的资源。

不剥夺条件:已经分配给一个进程的资源不能强制性地被抢占,只能等待占有他的进程释放。

环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程释放所占有的资源。

2.8.2 如何避免死锁的发生

预防策略:从形成死锁的条件入手,基本思想就是打破形成死锁的四个条件中的一个或多个,保证系统不会进入死锁状态。

  • 破坏互斥条件:比如只读文件、磁盘等软硬件资源可采用这种办法处理。
  • 破坏占有和等待条件:在进程开始执行之前,就把其要申请的所有资源全部分配给他,直到所有资源都满足,才开始执行。
  • 破坏不剥夺条件:允许进程强行从资源占有者那里夺取某些资源
  • 破坏环路等待条件:给系统的所有资源编号,规定进程请求所需资源的顺序必须按照资源的编号依次执行。

银行家算法

2.8.3 发生死锁怎么办

死锁检测:发生死锁之前总归需要先检测到死锁吧,不然怎么进行接下来的操作?可以通过检测有向图中是否存在环来检测,从一个节点出发进行 dfs,对访问过的节点进行标记,如果访问到了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。

死锁恢复:从下到上逐渐变态。。。

撤销进程法:

  1. 撤消陷于死锁的全部进程;

  2. 逐个撤消陷于死锁的进程,直到死锁不存在;

资源剥夺法:

  1. 从陷于死锁的进程中逐个强迫放弃所占用的资源,直至死锁消失;

  2. 从另外的进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态。

鸵鸟算法,直接不管!

3、内存管理

3.1 内存管理到底是干什么的?
  • 内存分配
  • 内存回收
  • 地址转换
  • 内存保护功能
3.2 内存管理的几种机制

内存的分配主要有两种:连续分配非连续分配

1、连续分配主要指为一个用户程序分配一个连续的内存空间,连续分配的方式包括单一连续分配、固定分区分配、动态分区分配

​ (1)单一连续分配,只用于单用户单进程的操作系统,

​ (2)固定分区分配,其将内存划分为多个固定大小的分区,当有空闲分区的时候,再从后续的作业队列中选择合适大小的作业装入。

​ (3)可变分区分配,os根据程序需要,分给每个程序所需要的内存大小,但会出现内存碎片的问题。通常需要采用紧凑技术进行解决。可变分区的分配策略有以下几种:

  • 首次适应算法,空闲分区以地址递增次序连接,找到大小能满足要求的第一个分区。
  • 最佳适应算法,空闲分区按容量递增次序连接,找到第一个能满足要求的空闲分区。
  • 最坏适应算法,空闲分区以容量递减次序连接,挑选第一个最大的分区。
  • 邻近适应算法,与首次适应类似,只不过每次都从上一次的位置开始查找。

2、非连续分配算法主要有三种实现方式:分页式存储、分段式存储、段页式存储

分页管理
把内存分为若干个很小的页面,相对比分块的划分力度更大一些。提高内存利用率。减少碎片,页式管理通过页表对应逻辑地址和物理地址。

分段管理
把内存分为几个大小不定的有实际意义的段,比如 main 函数段,局部变量段,通过管理段表来把逻辑地址转为物理地址。

段页式管理
结合了段式管理和页面管理的优点,把主存先分为若干个段,每个段又分为若干个页,也就是说段页式管理的段与段以及段的内部都是离散的。

3.3 分页和分段有什么区别呢?

共同点:

首先都是离散分配的,单每个页和每个段的内存是连续的。

都是为了提高内存利用率,减少内存碎片。

不同点:

分页式管理的页面大小是固定的,由操作系统决定;分段式管理的页面是由用户程序所决定的。

分页是为了满足操作系统内存管理的需求,每一页是没有实际的意义的;而段是有逻辑意义的,在程序中可认为是代码段、数据段。

分页的内存利用率高,不会产生外部碎片;而分段如果单段长度过大,为其分配很大的连续空间不方便,会产生外部碎片。

3.4 讲讲分页管理的快表和多级页表(按照why how的方式来回答,即为什么出现快表,是如何解决痛点的)
3.4.1. 快表
  • why? 首先快表的引入是为了加快逻辑地址到物理地址的访问速度的,在引入快表之前,由逻辑地址访问到内存的过程是这样的:
    a) 首先根据逻辑地址的高位拿到页号
    b) 根据页号访问内存中页表,根据页表的映射拿到实际的内存块儿号。(一次访问)
    c) 把内存块儿号和逻辑地址的低位拼接,得到物理地址
    d) 访问对应的内存物理地址。(二次访问)
    这样是需要有两次直接访问内存的过程的,所以为了加快这个速度,引入了快表,快表可以认为是一个 Cache,内容是页表的一部分或者全部内容。和页表的功能是一样的,只不过比在内存中的页表的访问速度要快很多。
  • how? 根据局部性原理,被访问后的内存块儿很可能在短时间内再次被访问,可能程序在一段时间内会多次访问同一个页表项。所以在每次访问页表项时,先在快表里查询是否有该页表项,如果没有再去页表中查询,并把查到的页表项放入快表。如果快表满了,就根据一些策略把里面的页表项淘汰掉,再把新查询的页表加入进去。
3.4.2 多级页表
  • why? 多级页表主要是为了解决页表在内存中占用空间太大的问题的,典型的时间换空间。
  • how?讲个例子即可:在引入多级页表之前,我们使用单级页表来进行存储页表项,假如虚拟内存为 4GB,每个页大小为 4KB,那么需要的页表项就为 4GB / 4KB = 1M 个!每个页表项一般为 4B,那么就需要 4MB 的空间,大概需要占用 1000 个页来存页表项。
    所以如果引入两级页表,让一级页表的每个页表项不再映射 4KB,而是映射 4MB,那么需要的一级页表项的个数为 4GB / 4MB = 1K 个,再让每个一级的页表项映射 1K 个二级页表项。当一级页表的某个页表项被用到时,再把该一级页表项对应的所有 1K 个二级页表项加载到内存中,这样可以节省大量的空间!
3.5 讲讲虚拟地址和物理地址?为什么要有虚拟地址空间?

们在写程序的时候打交道的都是虚拟地址,比如 C 语言的指针,这个虚拟地址由操作系统决定,而物理地址指的是真实内存地址寄存器的地址。现代处理器通常使用虚拟寻址,用 MMU 把虚拟地址翻译成物理地址才能访问到真正的物理地址。

那么为什么要有虚拟地址呢?

  • 如果没有虚拟地址空间的话,我们操作的都是直接的物理地址,这样用户程序可以直接访问到底层的物理地址,很容易破坏操作系统,造成操作系统崩溃。
  • 想要同时运行多个程序特别困难,多个程序可能对同一个寄存器进行操作,会发生崩溃。

通过虚拟地址就会得到如下优势(记不住也无所谓这一段)

  • 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的内存缓冲区。
  • 程序可以使用一系列虚拟地址访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小的时候,内存管理器会将物理页面保存到磁盘里。数据或代码页会根据需要在物理内存和磁盘之间移动。
  • 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一个进程使用的物理内存
3.5.1 虚拟地址转换

(1)CPU生成一个虚拟地址传递给MMU (2)MMU会根据虚拟地址生成PTE地址,然后到内存中查询PTE(物理页) (3)内存返回PTE给MMU (4)MMU解析PTE,得到物理地址,传送回内存 (5)最后内存将数据传给CPU。

MMU是 MemoryManagementUnit 的缩写即,内存管理单元. 针对各种CPU, MMU是个可选的配件. MMU负责的是虚拟地址与物理地址的转换. 提供硬件机制的内存访问授权.(现代 CPU 的应用中,基本上都选择了使用 MMU)

3.6 虚拟内存

按照 why and how 来回答!

  • why? 传统的内存管理必须把作业一次性的 load 到内存中,并且一直驻留到其作业运行结束,当作业很大时,是没有办法一次性装入内存的。
  • how? 而在一段时间内,只需要访问小部分数据就可以保证程序的正常运行。所以基于局部性原理,在程序加载的时候,把很快就会用到的部分放入内存中,暂时用不到的部分留在磁盘上。在程序执行的过程中,当信息不在内存时,再从外存把信息加载到内存里。当内存不够的时候,根据一些策略把用不到的内存换出到外存中,从而腾出空间给要调入内存的信息。而在 os 的管理下,让应用程序认为自己拥有一连续可用的内存,产生独享主存的错觉,这就是虚拟内存。

其实虚拟内存的基础是局部性原理,也正是因为有局部性原理,程序运行时才可以做到只装入部分到内存就可以运行。

3.7 虚拟内存的三种实现技术
  1. 请求分页式存储管理
    建立在分页管理的基础之上,为了支持虚拟内存实现了请求调页和页面置换功能。其具体流程是这样的:
    首先作业运行时,仅装入当前要执行的部分页面即可。
    假如在运行的过程中,发现要请求的页面不在内存中,那么处理器通知操作系统按照对应的页面置换算法把相应的页面调入到内存中。
    如果发现在把页面调入内存时,内存已满,同时也可以把不用的页面置换出去,以便腾出空间装入新的页面。
  2. 请求分段式存储管理
    和分页是一样的,把页换成段即可。
  3. 请求段页式存储管理
  4. 总结
    需要一定量的内存和外存,在刚开始运行的时候,只把部分要执行的页面加载到内存,就可以运行了。
    缺页中断,如果指令或数据不在内存,则处理器通知操作系统把页面段调入到内存。
    虚拟地址空间,都需要把虚拟地址转换为物理地址。
  5. 这和内存管理的机制有什么不同呢?
    请求分页式存储管理建立在分页管理之上,他们的根本区别是用不用把程序所需的全部地址空间 load 到内存里。请求分页式不需要全部 load 到内存中,而分页式管理需要,前者能够提供虚拟内存,后者不可以!
3.8 页面置换算法

答:当使用请求分页存储来管理内存时,发生缺页中断,就是要访问的页面不在内存中,这时就需要操作系统把其调入主存后再进行访问。

而在发生缺页中断时,内存中没有空闲的页面,就必须在内存中根据一定的策略挪出一些不用的页面,可以把页面置换算法看成是淘汰机制。

  • OPT 页面置换法,最佳页面置换:不可实现,不可预测哪个是不用的。
  • FIFO 先到先出算法,把在内存中停留时间最长的页面置换出去
  • LRU 最近最久未使用页面置换算法:LRU 算法赋予每一个页面一个访问字段,来记录一个页面最近一次访问到现在所经历的时间 T,需要淘汰一个页面时,把最久没有使用的页面淘汰掉就可以了。
  • LFU 最少使用算法:把使用最少的页面淘汰掉。
后端专属技术群
构建高质量的技术交流社群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!
文明发言,以交流技术、职位内推、行业探讨为主
广告人士勿入,切勿轻信私聊,防止被骗

图片

关注公众号,拉你进群
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值