计算机操作系统(二)进程管理

181 阅读16分钟

第二章、进程管理

1. 为什么需要进程呢?

先从进程的发展过程开始说起:

  1. 很久以前,CPU只能挨个挨个的处理程序,程序A处理完了才能继续处理程序B,此时叫批处理系统
  2. 随着CPU的发胀,运行速度越来越快,出现了程序的执行速度和磁盘的IO速度不匹配的矛盾。当程序A需要从硬盘读取数据的时候,CPU要进行等待,非常浪费。所以当程序A读取数据的时候,先对A的执行现场进行保存,再对程序B进行处理,等程序A读取完毕的时候再通过中断机制使得CPU继续执行他。
  3. 当程序A被再次调度执行的时候,IO操作还没有完成,只能卡住不动。所以程序A内部出现了多个线程,一个线程去读取IO操作,另外的线程去处理其他的事,比如响应用户等等
  4. 每个线程又可以执行多个协程,操作系统内核完全不用参与,相当于用户态线程了,开销非常小

总而言之:

  1. 进程是系统进行资源分配和调度的基本单位。
  2. 进程存在的作用就是合理的隔离资源、运行环境、提升资源利用率

2. 进程和程序的区别?

  1. 程序是指指令和数据的有序集合,是一个静态的概念。

  2. 进程是程序的一次运行,是一个具有独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度的基本单位。

3. 进程的实体内容包含哪些部分?

进程主要包括一下三个部分:

程序段:程序的代码

数据段:程序运行时使用、产生的运算数据,如全局变量、局部变量、宏定义的常量。

PCB(process control): 操作系统通过PCB来管理进程

4. 什么是进程控制块?包含哪些内容?

  1. PCB是进程存在的唯一标志,当系统创建一个新的进程时,就为他创建一个PCB,结束时又会回收其PCB,用于描述和控制进程运行的通用数据结构
  2. 能实现间断性运行方式,当进程阻塞暂停运行的时候,必须保留自己运行的时候CPU的信息,当再次被调度运行的时候还需要回复。所有系统会将CPU现场信息保存在被中断进程的PCB中
  3. 提高进程管理所需要的信息。当调度到某进程运行时,只能根据该进程的PCB中记录的程序和数据在内存或外存中的地址指针,找到程序和数据,需要访问文件或IO设备时,也需要借助PCB中的信息,所有OS总是通过PCB来控制和管理进程
  4. 提供进程调度所需要的信息,只有就绪的进程才能被调度执行。
  5. 实现和其他进程的同步和通信,在采用信号量机制时,要求在每个进程中都设置有相应的用于同步的信号量。
  6. PCB是操作系统进行调度经常会被读取的信息,所以PCB是常驻内存的,存放在系统专门开辟的PCB区域内

PCB中的信息:

  1. 进程描述信息:

    进程描述符PID:标识各个进程(类似于身份证号)

    用户标识符UID:进程归属的用户

  2. 进程控制和管理信息:

    进程状态

    进程的优先级

  3. 资源分配清单:有关内存地址空间和虚拟地址空间的信息,比如说文件的列表和所使用的I/O设备信息

  4. CPU相关信息: 如下都是: 程序计数器:进程即将被执行的下一条指令的地址

内存指针:程序代码或者进程数据相关指针。内存指针可能有多个,分别指向程序具体的逻辑代码,或者执行进程数据相关的地址

上下文数据:这个是进程控制块中比较重要的区域,这个区域存储的是进程执行时处理器存储的数据(在处理器中有寄存器以及高速缓存,那么这些数据就是进程的上下文数据)

IO状态信息:被进程IO操作所占用的文件列表(在Linux当中,所有的信息都是以文件的形势存在的,比如我们操作的磁盘、操作的内存或者文件,都是以文件的形式存储在IO状态信息中)

5. 进程的状态有哪几种?

七状态模型:

  • 创建状态:一个新进程被创建的第一个状态

  • 就绪状态(ready):线程创建完成初始化之后,变为就绪状态,等待CPU资源(万事具备只欠CPU)

  • 运行状态(running):z获得CPU资源正式运行

  • 阻塞状态(waiting):进程请求某个事件且必须等待时,比如请求I/O事件

  • 挂起状态:通常将阻塞状态的进程的物理空间换出到硬盘,等需要再次运行的时候再从硬盘换入到物理内存中。这个状态描述了进程没有占用实际的物理内存空间的情况。包括:

    通过 sleep 让进程间歇性挂起,其工作原理是设置一个定时器,到期后唤醒进程。

    用户希望挂起一个程序的执行,比如在 Linux 中用 Ctrl+Z 挂起进程;

  • 结束状态:进程已经运行完成或出错时,会被操作系统作结束状态处理

6. 进程和线程的区别?

我们编写的代码只是一个存储在硬盘的静态文件,通过编译之后生成二进制的可执行文件,当运行这个可执行文件的时候,会被装载到内存中,接着CPU会执行程序的每一条指令,那么这个运行中的程序就称为进程。

Ⅰ 拥有资源进程是操作系统资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源

Ⅱ 调度线程是任务调度和执行的基本单位,一个进程中可以有多个线程,它们共享进程资源

在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。

QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。

Ⅲ 系统开销:由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。

Ⅳ 通信方面:线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。

7. 进程通信的方式有哪些?

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)

1.管道/匿名管道(Pipes)

  1. 管道是半双工的,也就是单向传输的,只能够支持父子进程或兄弟进程之间的通信。

  2. 由于普通管道文件没有文件名,所以进程无法使用open函数打开文件,从而得到文件描述符,所以只有一种办法。那就是父进程先调用pipe创建出管道,并得到管道的文件描述符号。然后fork出子进程,让子进程继承父进程打开的文件描述符,父子进程就能通过同一管道,从而实现通信。

  3. 管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据,管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据。

2.有名管道

有名管道不同于匿名管道之处在于它提供了一个路径名与之关联,以有名管道的文件形式存在于文件系统中,这样,即使与有名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过有名管道相互通信,因此,通过有名管道不相关的进程也能交换数据。

3.信号(Signal)

信号来源 信号是软件层次上对中断机制的一种模拟,是一种异步通信方式,,信号可以在用户空间进程和内核之间直接交互,内核可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件主要有两个来源:

  • 硬件来源:用户按键输入Ctrl+C退出、硬件异常如无效的存储访问等。
  • 软件终止:终止进程信号、其他进程调用kill函数、软件异常产生信号。

信号生命周期和处理流程 (1)信号被某个进程产生,并设置此信号传递的对象(一般为对应进程的pid),然后传递给操作系统; (2)操作系统根据接收进程的设置(是否阻塞)而选择性的发送给接收者,如果接收者阻塞该信号(且该信号是可以阻塞的),操作系统将暂时保留该信号,而不传递,直到该进程解除了对此信号的阻塞(如果对应进程已经退出,则丢弃此信号),如果对应进程没有阻塞,操作系统将传递此信号。 (3)目的进程接收到此信号后,将根据当前进程对此信号设置的预处理方式,暂时终止当前代码的执行,保护上下文(主要包括临时寄存器数据,当前程序位置以及当前CPU的状态)、转而执行中断服务程序,执行完成后在回复到中断的位置。当然,对于抢占式内核,在中断返回时还将引发新的调度。

4.消息队列(Message Queuing)

消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。

与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。

另外与管道不同的是,消息队列在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。

消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。

5.信号量(Semaphores)

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

6.共享内存(Shared memory)

使得多个进程可以可以直接读写同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。

为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。

由于多个进程共享一段内存,可能会有并发安全问题,因此需要依靠某种同步机制(如信号量)来达到进程间的同步及互斥。

7.套接字Socket

此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

套接字的特性由3个属性确定,它们分别是:域、端口号、协议类型。

8. 进程调度算法有哪些?

  1. FCFS:先来先服务,调度的顺序就是任务到达就绪队列的顺序。对短作业不利
  2. SJF:最短的作业(CPU区间长度最小)最先调度。对长作业不利,可能导致饥饿
  3. HRN:最高响应比优先法,(等待时间+要求服务时间)/要求服务时间
  4. 时间片轮转:设置一个时间片,按时间片来轮转调度。每隔固定的时间产生一次中断,激活进程调度程序进行调度
  5. 优先权调度:每个任务关联一个优先权,调度优先权最高的任务。
  6. 多级队列调度:按照一定的规则建立多个进程队列;不同的队列有固定的优先级(高优先级有抢占权);不同的队列可以给不同的时间片和采用不同的调度方法;同一个队列使用FCFS。
  7. 多级反馈队列:在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务。可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”。

多级反馈队列:设置多个就绪队列,分别有不同的优先级。队列1的优先级最高,每个队列执行时间片的长度页不同,优先级越高则时间片越短新进程进入内存中,先放入队列1的末尾,按FCFS算法调度,若按队列1一个时间片没有执行完则放入队列2的末尾,同意FCFS,这样依次下去,降到最后则按时间片轮转算法调度。仅当较高优先级的队列为空,才调度较低优先级的队列中的进程执行。如果进程执行时有新进程进入较高优先级的队列,则抢先执行新进程,并把被抢先的进程投入原队列的末尾

I/O型进程:让其进入最高优先级队列,以及时响应I/O交互。通常执行一个小时间片,要求可处理完一次I/O请求的数据,然后转入到阻塞队列。

计算型进程:每次都执行完时间片,进入更低级队列。最终采用最大时间片来执行,减少调度次数。

9. 为什么会出现线程?进程的切换为什么慢?

  1. 可以把线程理解为轻量级进程,引入线程是为了使得一个进程内部也可以并发处理各种任务。
  2. 如果单纯的用多进程的话,进程是一个重量级的单位,切换的开销比较大,而且进程之间的 数据是隔离的,如果要共享的话需要借助IPC,比较麻烦。如果是同一进程内的线程切换,开销就比较小。
  3. 线程是CPU调度和执行的基本单位,而进程是系统分配资源的基本单位

进程是由内核管理和调度的,所以进程的切换只能发生在内核态。

所以,进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。

因为进程拥有自己独立的地址空间,所以进程切换的时候会切换页目录到新的地址空间,需要切换页表,相对应的页表缓存(TLB)等都会全部刷新,这就导致内存的访问在一段时间内会相当低效,反应出来就是程序运行变慢。 现在我们已经知道了进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个cache就是TLB,Translation Lookaside Buffer,我们不需要关心这个名字只需要知道TLB本质上就是一个cache,是用来加速页表查找的。由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后TLB就失效了,cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢,而线程切换则不会导致TLB失效,因为线程线程无需切换地址空间,因此我们通常说线程切换要比较进程切换块,原因就在这里。