面经-操作系统-进程和线程

271 阅读13分钟

什么是操作系统

  1. 操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。
  2. 操作系统本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源。  举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
  3. 操作系统存在屏蔽了硬件层的复杂性。  操作系统就像是硬件使用的负责人,统筹着各种相关事项。
  4. 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。

Kernel_Layout.png

系统调用

我们可以把进程在系统上的运行分为两个级别:

  1. 用户态(user mode) : 用户态运行的进程可以直接读取用户程序的数据。
  2. 系统态(kernel mode):系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。

那么什么是系统调用呢?

我们运行的程序基本都是运行在用户态,如果我们调用系统态级别的子功能咋办呢?那就需要系统调用了!

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

进程

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

写了一年golang,来聊聊进程、线程与协程-阿里云开发者社区 (aliyun.com)

进程:把内存划分为多块,不同程序使用各自的内存空间互不干扰,这里单独的程序就是一个进程,CPU 可以在多个进程之间切换执行。。为了实现 CPU 在多个进程之间切换,需要保存进程的上下文(如程序计数器、栈、内核数据结构等等),以便下次切换回来可以恢复执行。见下文

线程:减少上下文切换时的开销进程当中的一条执行流程。 进程切换时需要转换内存地址空间,而线程切换没有这个动作,所以线程切换比进程切换代价更小。

协程:协程是用户视角的一种抽象,操作系统并没有这个概念,其主要思想是在用户态实现调度算法,用少量线程完成大量任务的调度。

image.png

image.png

le0t3hqy8t.jpg 一文读懂什么是进程、线程、协程 - 腾讯云开发者社区-腾讯云 (tencent.com)

goroutine 是 golang 实现的协程,它的核心是MPG调度模型:juejin.cn/post/711753…

从单进程到多进程提高了 CPU 利用率;从进程到线程,降低了上下文切换的开销;从线程到协程,进一步降低了上下文切换的开销,使得高并发的服务可以使用简单的代码写出来,技术的每一步发展都是为了解决实际问题。

进程有哪几种状态

创建就绪运行,堵塞,结束

如果有大量处于阻塞状态的进程,进程可能会占用着物理内存空间,在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出到硬盘,等需要再次运行的时候,再从硬盘换入到物理内存。那么,就需要一个新的状态,来描述进程没有占用实际的物理内存空间的情况,这个状态就是挂起状态

  • 阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现;
  • 就绪挂起状态:进程在外存(硬盘),但只要进入内存,即刻立刻运行;

10-进程七中状态.webp

进程的上下文切换

各个进程之间是共享 CPU 资源的,在不同的时候进程之间需要切换,让不同的进程可以在 CPU 执行,那么这个一个进程切换到另一个进程运行,称为进程的上下文切换

  • cpu上下文:任务是交给 CPU 运行的,那么在每个任务运行前,CPU 需要知道任务从哪里加载,又从哪里开始运行。--- CPU 寄存器和程序计数器

image.png

  • CPU 上下文切换:是先把前一个任务的 CPU 上下文保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

  • 上面说的任务包括:进程,线程,中断。所以cpu上下文切换分为:进程上下文切换,线程上下文切换,中断上下文切换

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

13-进程上下文切换.webp

我们希望他的开销越小越好

image.png

线程

线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程之间可以并发运行且共享相同的地址空间

线程是进程当中的一条执行流程。

同一个进程内多个线程之间可以共享代码段数据段打开的文件等资源,但每个线程各自都有一套独立的寄存器和栈,这样可以确保线程的控制流相对独立的。

16-多线程内存结构.webp 线程的优点

  • 一个进程中可以同时存在多个线程;
  • 各个线程之间可以并发执行;
  • 各个线程之间可以共享地址空间和文件等资源;

缺点

  • 进程的一个线程挂了,会影响其他线程(比如王者荣耀用户设计采用多线程,你的线程挂了,你队友也跟着倒霉

进程和线程比较

  • 进程是资源(包括内存、打开的文件等)分配的单位,线程是 CPU 调度的单位;
  • 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈;
  • 线程同样具有就绪、阻塞、执行三种基本状态,同样具有状态之间的转换关系;
  • 线程能减少并发执行的时间和空间开销;

线程减小开销体现在:

  • 线程的创建时间比进程快,因为进程在创建的过程中,还需要资源管理信息,比如内存管理信息、文件管理信息,而线程在创建的过程中,不会涉及这些资源管理信息,而是共享它们;
  • 线程的终止时间比进程快,因为线程释放的资源相比进程少很多;
  • 同一个进程内的线程切换比进程切换快,因为线程具有相同的地址空间(虚拟内存共享),这意味着同一个进程的线程都具有同一个页表,那么在切换的时候不需要切换页表。而对于进程之间的切换,切换的时候要把页表给切换掉,而页表的切换过程开销是比较大的;
  • 由于同一进程的各线程间共享内存和文件资源,那么在线程之间数据传递的时候,就不需要经过内核了,这就使得线程之间的数据交互效率更高了;

(线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。)

线程上下文切换

线程与进程最大的区别在于:线程是调度的基本单位,而进程则是资源拥有的基本单位

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

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

调度

定义:选择一个进程运行的这一功能

调度时机

  1. 进程从一个状态到另一个状态的时候,会触发一次调度。操作系统考虑是不是要让新的进程给cpu运行,或者把当前的进程换成另一个进程
  2. 根据周期性时钟中断,调度算法也会分为: 2.1 非抢占式调度算法,一个进程运行完或者堵塞,再去调用另一个进程,不理中断这茬 2.2抢占式调度算法,选一个进程运行,如果在规定时间段内没运行完挂起,触发时钟中断,接着调度程序从就绪队列挑选另外一个进程

调度原则

image.png

进程的调度算法

为了确定首先执行哪个进程以及最后执行哪个进程以实现最大 CPU 利用率,计算机科学家已经定义了一些算法:

  • 先到先服务(FCFS)调度算法 : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 短作业优先(SJF)的调度算法 : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
  • 时间片轮转调度算法 : 称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
  • 多级反馈队列调度算法 :前面介绍的几种进程调度的算法都有一定的局限性。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
  • 优先级调度 : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。

进程之间的通信方式?

4-进程空间.webp

每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。

大概有七种:

  1. 管道/匿名管道(Pipes) (效率低,不适合频繁交换数据) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。

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

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

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

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

  6. 信号(Signal)  :异常情况下的工作模式,需要信号量的方式通知进程(硬件来源,与软件来源)

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

线程间的同步方式?-锁和信号量

线程同步是两个或多个共享关键资源的线程的并发执行。

image.png

死锁

多个进程/线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于进程/线程被无限期地阻塞,因此程序不可能正常终止。

举个例子,小林拿了小美房间的钥匙,而小林在自己的房间里,小美拿了小林房间的钥匙,而小美也在自己的房间里。如果小林要从自己的房间里出去,必须拿到小美手中的钥匙,但是小美要出去,又必须拿到小林手中的钥匙,这就形成了死锁。

image.png

5-并发与并行.webp

最简单的解决方式:使用资源有序分配,破坏环路等待