进程管理
1、进程和线程的区别?
进程:一个正在运行中的程序就是一个进程,进程包括程序段、数据段、PCB三部分。
- 程序段就是程序的代码;
- 数据段就是程序运行时产生的数据(比如全局变量、局部变量等);
- PCB中包含操作系统对其进行管理的各种信息(如进程标识符PID,进程当前状态,进程优先级)
线程:线程是属于进程的,是一个基本的 CPU 执行单元,是程序执行流的最小单元。
主要区别如下:
- 调度:进程是资源分配的基本单位,线程是程序执行的基本单位。
- 开销:线程上下文切换开销小,进程线程间切换开销大。
- 切换: 同一进程间线程切换不会引起进程切换,不同进程间线程切换会引起进程切换
- 拥有资源: 进程是拥有资源的一个独立单位,线程不拥有系统资源,但是可以访问隶属于进程的资源。
- 通信: 线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助系统内核的帮助。
2、协程与线程的区别?
协程是一种用户态的,不被操作系统内核所管理,完全由用户控制的,比线程更小的一种执行单元,可以认为是轻量级的线程。
区别如下:
- 线程和进程都是同步机制,而协程是异步机制。
- 线程是抢占式,而协程是非抢占式的。需要用户释放使用权切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
- 一个线程可以有多个协程,一个进程也可以有多个协程。
- 协程不被操作系统内核管理,而完全是由程序控制。线程是被分割的CPU资源,协程是组织好的代码流程,线程是协程的资源。但协程不会直接使用线程,协程直接利用的是执行器关联任意线程或线程池。
- 协程能保留上一次调用时的状态。
3、线程切换开销为啥比进程小
- 线程切换不需要更换页表,而进程切换需要。页表切换会导致缓存失效,所以在进行地址转化的时候需要重新去查找页表,这就造成了程序运行的效率低下。
- 进程切换需要切换上下文环境比线程上下文环境大,需要保存现场,切换,然后再恢复现场。
进程上下文环境: 程序计数器,通用寄存器、数据、用户栈、页表、PCB等。
4、线程的分类?
从线程的运行空间来说,分为用户级线程(user-level thread, ULT)和内核级线程(kernel-level, KLT)
- 内核级线程:
这类线程依赖于内核,又称为内核支持的线程或轻量级进程。无论是在用户程序中的线程还是系统进程中的线程,它们的创建、撤销和切换都由内核实现。比如英特尔i5-8250U是4核8线程,这里的线程就是内核级线程。
- 用户级线程:
它仅存在于用户级中,这种线程是不依赖于操作系统核心的。应用进程利用线程库来完成其创建和管理,速度比较快,操作系统内核无法感知用户级线程的存在。
5、PCB是什么?
PCB(进程控制块)是进程存在的唯一标志,当进程被创建时,操作系统为其创建PCB,当进程结束时,会回收其PCB。
PCB主要包含下面几部分的内容:
- 进程的描述信息,比如进程的名称,标识符;
- 进程控制和资源占用 ,如CPU、磁盘、网络流量使用情况统计,进程当前状态:就绪态/阻塞态/运行态;
- 资源分配清单,正在使用哪些文件,正在使用哪些内存区域,正在使用哪些I/O设备;
- 处理机相关信息,如PSW、PC等等各种寄存器的值(用于实现进程切换)。
PCB 的作用:
- PCB是进程实体的一部分,是操作系统中最重要的数据结构 ;
- PCB 是进程存在的唯一标识 , 系统通过 PCB 来感知进程的存在;
- 由于它的存在,使得多道程序环境下,不能独立运行的程序成为一个能独立运行的基本单位,使得程序可以并发执行 。
6、进程有哪些状态?
进程一共有5种状态,分别是创建、就绪、运行(执行)、终止、阻塞。
- 创建态:进程正在被创建,操作系统为进程分配资源、初始化PCB。
- 就绪态:已经具备运行条件,但由于没有空闲CPU,而暂时不能运行。
- 运行态:占有CPU,并在CPU上运行。
- 阻塞态:因等待某一事件而暂时不能运行,如:等待操作系统分配打印机、等待读磁盘操作的结果。
- 终止态:进程正在从系统中撤销,操作系统会回收进程拥有的资源、撤销PCB。
进程状态的转换:
- 创建态→就绪态:系统完成创建进程的系列工作,但是还未获得CPU资源。
- 就绪态→运行态:系统按某种策略选中就绪队列中的一个进程占用处理器,此时就变成了运行态。
- 运行态→就绪态:不是由于自身原因,而是由外界原因使运行状态的进程让出处理器,这时候就变成就绪态。例如时间片用完,或有更高优先级的进程来抢占处理器等。
- 运行态→阻塞态:进程用“系统调用”的方式申请某种系统资源,或者请求等待某个事件发生。
- 阻塞态→就绪态:则是等待的条件已满足,只需分配到处理器后就能运行。
7、什么是僵尸进程?
概念:
- 僵尸进程是已完成且处于终止状态,但在进程表中却仍然存在的进程。
产生原因:
- 僵尸进程一般发生在有父子关系的进程中,一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中。
如何避免僵尸进程?
- 让僵尸进程的父进程来回收,父进程每隔一段时间来查询子进程是否结束并回收,调用wait()或者waitpid(),通知内核释放僵尸进程;
- 让僵尸进程变成孤儿进程,由init回收,换句话说,就是先杀死父进程;
- 采用信号SIGCHLD通知处理,并在信号处理程序中调用wait函数。
8、什么是孤儿进程?
- 一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。
孤儿进程将被 init 进程 (进程 ID 为 1 的进程) 所收养,并由 init 进程对它们完成状态收集工作。因为孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。
9、什么是守护进程?
概念:
- 一种长期运行的进程,这种进程在后台运行,并且不跟任何的控制终端关联。
创建守护进程的步骤:
- 调用 fork(),创建新进程,它会是将来的守护进程。
- 在父进程中调用 exit,保证子进程不是进程组长。
- 调用 setsid() 创建新的会话区。
- 将当前目录改成跟目录(如果把当前目录作为守护进程的目录,当前目录不能被卸载他作为守护进程的工作目录)。
- 将标准输入,标注输出,标准错误重定向到 /dev/null。
10、进程调度算法有哪几种?
- 先来先服务(FCFS)
- 非抢占式的调度算法,按照请求的顺序进行调度。
- 有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。
- 短作业优先(SJF)
- 非抢占式的调度算法,按估计运行时间最短的顺序进行调度。
- 长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
- 最短剩余时间优先(SRTN)
- 短作业优先的抢占式版本,按剩余运行时间的顺序进行调度。
- 当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程,否则新的进程等待。
- 时间片轮转
- 将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。
- 当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。
- 优先级调度
- 为每个进程分配一个优先级,按优先级进行调度。
- 为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。
11、线程同步的方式有哪些?
- 临界区:
- 当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止,以此达到用原子方式操作共享资源的目的。
- 优点:保证在某一时刻只有一个线程能访问数据的简便办法。
- 缺点:虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
- 事件:
- 事件机制,允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。
- 优点:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作。
- 互斥量:
- 为协调共同对一个共享资源的单独访问而设计的。
- 互斥量跟临界区很相似,比临界区复杂,互斥对象只有一个,只有拥有互斥对象的线程才具有访问资源的权限 。
- 信号量:
- 信号量其实就是一个变量 ,可以用一个信号量来表示系统中某种资源的数量, 可以对其执行 P 和 V 操作。
- P 操作意味着进程请求一个单位的该类资源,信号量减一。
- V 操作意味着进程释放一个单位的该类资源,信号量加一。
- 如果信号量只能取 0 或者 1,那么就变成了互斥量,其实也可以理解成加锁解锁操作,0 表示已经加锁,1 表示解锁。
12、进程间通信方式有哪些?
- 管道:
- "管道"是指用于连接读写进程的一个共享文件,又名pipe文件。其实就是在内存中开辟一个大小固定的缓冲区;
- 管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道;
- 如果没写满,就不允许读。如果没读空,就不允许写。
- 共享内存:
- 共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
- 两个进程对共享空间的访问必须是互斥的(互斥访问通过操作系统提供的工具实现)。
- 消息队列:
- 消息队列是消息的链接表。
- 有写权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。
- 消息队列可以独立于读写进程存在,就算进程终止时,消息队列的内容也不会被删除。
- 信号:
- 信号是一种比较复杂的通信方式,信号可以在任何时候发给某一进程,而无需知道该进程的状态。
- 信号量:
- 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
- 例如信号量的初始值是 1,然后 a 进程访问临界资源的时候,把信号量设置为 0,然后进程 b 也要访问临界资源的时候,发现信号量是 0,就知道已有进程在访问临界资源了,这时进程 b 就访问不了了,所以说信号量也是进程间的一种通信方式。
- 套接字
- 套接字(socket)为通信的端点,每个套接字由一个 IP 地址和一个端口号组成。通过网络通信的每对进程需要使用一对套接字,即每个进程各有一个。
- 与其他通信机制不同的是,它可用于不同机器间的进程通信。
优缺点:
- 管道:简单,速度慢,容量有限;
- Socket:任何进程间都能通讯,但速度慢;
- 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题;
- 信号量:不能传递复杂消息,只能用来同步;
- 共享内存区:能够很容易控制容量,速度快,但需要注意不同进程的同步问题。
13、进程与线程的切换流程?
进程切换分两步:
- 切换页表以使用新的地址空间,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。
- 切换内核栈和硬件上下文。
对线程来说:
- 切换内核栈和硬件上下文。
对linux来说:
- 线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2步是进程和线程切换都要做的。
- 因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。
14、为什么虚拟地址空间切换会比较耗时?
- 进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个Cache就是TLB(本质是一个缓存,是用来加速页表查找的)
- 由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后TLB就失效了,Cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢
- 而线程切换则不会导致TLB失效,因为线程无需切换地址空间,因此我们通常说线程切换要比较进程切换块,原因就在这里。
15、什么是临界区,如何解决冲突?
每个进程中访问临界资源的那段程序称为临界区,一次仅允许一个进程使用的资源称为临界资源。
解决冲突的办法:
- 如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入,如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待;
- 进入临界区的进程要在有限时间内退出。
- 如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。