头图

1. 进程和线程的区别是什么 ?

进程是资源分配的基本单位,线程是 CPU 调度的基本单位。进程拥有独立的地址空间,线程是共享内存地址的。进程切换的开销比线程要大。

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

  • 管道(Pipe):在缓存中开辟处输出和输入文件流空间,只能适用于父子进程通信。
  • 命名管道(FIFO):不同进程的管道通信,通过打开同一个 FIFO 文件进行数据传输。
  • 消息队列(MessageQueue):存放在内核中的消息链表,简单高效。
  • 共享内存(SharedMemory):允许两个不相关的进程访问同一个逻辑内存,通过映射同一段物理内存来实现,往往需要其他机制辅助,比如信号量。
  • 信号量(Semaphore):类似于一个计数器,是一个特殊变量,常用于进程间的同步控制。
  • 套接字(Socket):可用于同一台机子的不同进程通信,也可以不同机子的不同进程通信。
  • 信号(sinal): 用于通知接收进程某个事件已经发生。

3. 进程/线程的同步方式有哪些?

  • 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
  • 信号量:为控制一个具有有限数量用户资源而设计。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
  • 互斥量:协调共同对一个共享资源的单独访问而设计的
  • 事件:通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作总结下事件 Event

4. 进程的调度策略有哪些?

进程调度的主要目标是让 CPU 始终处于忙碌状态,并为所有程序提供最短的响应时间。为了实现这一点,必须要适当的中断程序的运行以及唤起其他程序运行。当前的调度分为了两大类:

  • 抢先调度:让程序按一定的时间去占有这些资源,时间到了就被迫让出现有资源,给其他的程序轮流使用。
  • 非抢占式调度:让程序顺利的完成自己的任务,再把资源腾出来给其他程序使用。

当前的调度算法也有很多,总的可以分为下面几类:

  • 先来先服务(FCFS):按先后顺序进行调度。
  • 最短作业优先:按执行时间长度来调度。
  • 优先级调度:给每个进程定义一个优先级,每次需要进程切换时,找优先级最高的进程进行调度。
  • 循环调度(Round Robin):让程序再一个时间片内占有 CPU 资源,时间到了就换下一个,平等对待程序,没有特殊性。
  • 多级队列调度:将进程存储到不同的队列中,每个队列都有自己的调度算法,比如按先来先服务,优先级调度等。
  • 多级反馈队列:设置多个不同优先级的队列,动态调整进程所在的队列,如果进程使用过多的 CPU 时间,那么它会被移到更低的优先级队列。

5. 进程的状态有哪些?

当进程开始运行时,通常会涉及到 5 个状态:

  • 创建状态: 当程序启动时,需要到系统创建进程管理块(PCB:Process Control Block)完成资源分配,进入正在创建的状态。
  • 就绪状态: 创建状态完成之后,进程已经准备好,此时还没被 CPU 调度到,需等待分配。
  • 运行状态: 被 CPU 调度,分配到一定的时间片,开始进入运行状态。
  • 阻塞状态: 正在等待某个事件完成(比如 IO 操作),当操作完成后则进入就绪状态,等待 CPU 调度进入运行状态。
  • 终止状态: 进程结束或被终止,无法再被执行。

进程状态

在进程运行期间主要是就绪、运行、阻塞状态这几种状态轮流转换。

6. 什么是僵尸进程

fork 出来的子进程在结束释放资源时,会残留着一些状态信息在 PCB 里,只有等到父进程调用 wait 或 waitpid 函数来取走这些信息,才会真正的结束。

当父进程比子进程先结束,那么此时子进程会交给 init 进程管理。当子进程结束时,即使没有原来的父进程去收走那些残留信息也没关系,因为 init 进程会接手管理。

如果子进程先结束,此时父进程还没结束,又没有调用 wait/waitpid 来取走相关信息,导致子进程的进程描述符一直残留在系统里,那么就会一直占用着资源。当父进程也结束后,就会变为僵尸进程,此时的僵尸进程还是得交给 init 进程管理,由 init 进程统一清理。

7. 内核态和用户态区别是什么?

在内核态下,操作系统可以访问任意数据以及进行任意操作,而用户态下是有限制的,只能访问指定的内存。内核态相当于拥有了全部权利,用户态让用户程序对系统的操作是有边界的,只能使用系统开放的能力。

8.上下文切换什么时候会发生?

当发生了程序调度的时候,势必涉及到当前进程状态的保存,以及另一个即将运行的进程的状态加载。这一过程被称之为上下文切换。进程的上下文信息是保存在 PCB,也就是进程控制块里的,保存的信息包括了 CPU 寄存器的值、进程状态和内存管理信息。

上下文切换是纯粹的性能开销,因为在此过程中,操作系统不做任何有用的工作,仅仅只是为了切换而切换。所以这一块会很容易就变成瓶颈,导致我们会经常 new 一个新的进程来提高执行效率。

9.为什么需要内存?内存的分配方式是怎么样的?

现代计算机体系是一个为数据处理而设计的结构(冯·诺依曼体系结构),执行指令和数据会同时存放在存储器里。这里的存储器包括了靠近 CPU 的高速缓存(cache),也包括了我们熟悉的内存条这种主存储器(RAM);当然,还有像磁盘这种廉价但速度较慢的外存储器。通过这些存储器的分工合作,使得程序具备长期记忆、快速运算的特点。

在早期的操作系统里,物理内存都是裸奔在 CPU 面前的,也就是程序可以直接操作物理内存。而内存的分配方式采用的是连续分配管理,也就是用户程序将会得到一段连续的地址空间,主要有单一连续和分区式分配方式:

  • 单一连续分配:分为系统区和用户区,系统区是操作系统使用,用户区给用户程序使用。用户程序加载到内存时会一次性分配所需内存,并一直占用,直到程序结束。
  • 固定分区分配:将内存空间划分为若干个固定大小的连续区域,当程序需要分配内存时,会从一张分区说明表中查找未使用且大小合适的区域来分配。
  • 动态分区分配:上面划分的区域大小将不再固定,是动态划分的,当程序释放后比较容易出现外部碎片,需要采用紧缩技术合并这些碎片。

10. 虚拟地址是什么?

上面这种直接访问物理内存的方式会让程序变得很脆弱,很容易就出现访问冲突和内存碎片。为此,操作系统提供了一种机制,将程序要访问的地址和真实的物理地址进行了隔离,抽象出了面向程序的虚拟地址空间。

当程序运行起来后,每个程序都有属于自己的虚拟地址空间,这种虚拟地址空间的好处就在于每个程序所看到的内存地址都是对自己负责的,跟其他程序互不干扰,不用担心冲突的问题。而且一开始并不会分配真正的物理内存,只有当 CPU 需要操作这个虚拟地址的时候,才会通过 一个内存管理单元(MMU)来映射真正的物理地址。

上面这种抽象出虚拟地址的方式,使得程序看到的地址空间将不再需要和底层的物理内存地址空间一一对应,也就是说,一个程序的真实物理地址将可以是离散的,不需要一直连续的,而这更有利于物理内存的高效利用,只需要有一个类似映射关系机制即可。而这种设计主要有分段管理和分页管理。

11. 分段管理是什么的?

分段管理将程序的虚拟地址空间划分成多个段,这些段的划分依据是根据程序自身的逻辑关系来分配的,例如 main 函数的划分为一个段,库函数的划分一个段,数据划分为一个段。总之,这些段是有逻辑含义,可以由用户自己来指定段名。

为了能建立跟物理内存的映射关系,每当创建出一个段的时候,就会在一个段表里维护当前段的信息,段表里的段信息包括了当前段的索引号:段号;当前段的最大长度:段长以及当前段在物理内存里的起始地址:段基地址。

这样的话,只要虚拟地址是由段号和段内偏移量组成的话,那么就可以推算出物理内存地址了:即根据段号在段表里找到当前段基地址,段基地址加上段内偏移量就可以得到真实的物理地址了:

分段管理

有上面可以看出,段地址其实是二维的,因为需要我们给出段名(段号)以及段内偏移量来标识一个虚拟地址。

12. 分页管理是怎么样的?

分段管理虽然建立起一套映射的机制,但是它包含了逻辑含义,需要用户去指定段名(段号)和段内偏移量。这种管理方式太过于灵活了,如果分配某一个段的段长很大,那么就很容易产生外部内存碎片了。

为此,操作系统提供了分页管理方式,并且对用户是不可见的。它将虚拟地址空间和物理地址空间切割成了一个个固定大小的页(例如在 Linux 里页的大小为 4k),并且它们的映射关系会交由一张页表来维护。

页表里包含了虚拟地址页号和物理地址页号的关联关系,只要我们的虚拟地址包含了页号和页偏移量,那么就可以通过页表找到物理地址页号,根据物理地址页号找到物理内存地址,再加上页偏移量,就得到物理地址了。

分页管理

13. 多级页表指的是什么?

页的大小是固定的,而虚拟地址空间大小也是固定的,比如在 32 位的 Linux 系统里,一个虚拟地址空间大小将会分配到 4G,如果按每页为 4k 计算,那么对于一个程序来讲,操作系统就要管理 100 多万个页了。如果有多个程序同时运行,那对于操作系统来讲,压力将会很大,效率也提不上去。

所以,操作系统进行了多级管理,例如,将这 100 多万个页先拆分到 1024 个页表里,每个页表管理着 1024 个页项。这样就缩小了管理页数,而且很多时候程序可能也就需要 100 多 M 的地址空间,并不会真正的用上所有的地址空间,所以后面的页表也并不需要去创建出具体的页项,可以等到使用的时候再创建。

14. 段页式管理是怎么样的?

分段管理让程序的内存分配有了逻辑含义,能更好的满足用户需求,而分页管理提高了内存的利用率,减少了外部内存碎片。因此将两者结合,也就是现在操作系统常用的段页式内存管理了。

段页式管理会先将程序划分为多个有逻辑意义的段,比如代码段、数据段等。然后在这些段里进行了按页管理的方式。段页式管理的虚拟地址是由段号、段内页号和页内位移组成。当要根据这些信息查找物理地址时,将会经历下面三个过程:

  • 根据段号找到该段的页表起始地址
  • 根据页表起始地址找到物理页号
  • 根据物理页号找到物理内存的基地址,再结合页内位移就可以得到物理地址了。

段页式管理

15. 内存置换是什么?算法有哪些?

尽管操作系统为内存管理进行了很多优秀的设计,但对于物理内存来讲,它的上限就是固定的,比如内存条大小为 4G,那再怎么优化,也只能使用 4G 的上限。所以,一旦操作系统检测到没有足够的空闲内存分配时,此时就需要启动“交换”机制了。将那些近期不再使用或不会再用的内存交换到硬盘上,这样就能暂时的空闲出更多的物理内存来使用了。如果有些物理内存加载进来后一直没有被修改过,那么就会直接删除,等到下次触发缺页中断,重新加载。

因此,怎么合适的去交换这些空闲内存,也是需要用决策的,当前流行操作系统都是采用段页式管理内存的,所以主要有三种“页面置换”算法:

  • 最佳置换算法:选择未来长时间不被访问的或者以后永不使用的页面进行淘汰,这种是一种理想化算法,需要去预估使用时间,所以很难分析得到。
  • 先进先出页面置换算法:最先进入内存的页面最先被淘汰,性能较差,因为有些频繁使用的页面会被频繁交换出去,影响性能。
  • 最近最少使用置换算法:选择最近且最少使用的页面进行淘汰,性能较好,符合局部性原理,Linux 系统采用的就是这种算法

如果页面交换频繁,那么操作系统势必要花更多的时间来执行这些动作,这在操作系统里称之为抖动颠簸,当产生这种现象的时候,CPU 的利用率将会很低,因为内存不能及时反馈获取。

16. 死锁是什么?怎么避免?

死锁是因为多个进程并发争夺系统资源而互相等待的现象。死锁要满足四个必要条件才会产生,分别是:

  • 互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
  • 占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
  • 不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
  • 循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

处理死锁有四种方法:

  • 死锁预防:通过确保死锁的一个必要条件不会满足,保证不会发生死锁
  • 死锁检测:允许死锁的发生,但是可以通过系统设置的检测结构及时的检测出死锁的发生,采取一些措施,将死锁清除掉
  • 死锁避免:在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁
  • 死锁解除:与死锁检测相配套的一种措施。当检测到系统中已发生死锁,需将进程从死锁状态中解脱出来。

常用方法:撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程。

17. 同步 IO、异步 IO、IO 多路复用、select、poll、epoll 的相关概念?

操作硬盘上的文件时,先将文件用一个叫文件描述符来表示,然后将文件中的数据加载到内核的缓冲区,再将缓冲区的数据映射到用户空间,告诉用户可以通过文件描述符来操作某个文件了。

缓冲区的文件数据映射到用户空间这个动作可以让用户进程自己来,即站在用户进程来讲,读写操作都是由用户进程来执行的,这个叫同步 IO。也可以将映射的过程交由操作系统的内核来完成,即所谓的异步 IO

其中,同步 IO 里,又可以分为阻塞和不阻塞 IO。所谓的阻塞 IO 即用户进程在询问文件数据是否加载到缓冲区时,可以阻塞的等待,直到缓冲区的数据都加载完毕;不阻塞 IO 即用户进程通过不断的询问操作系统,来获取加载结果。

在不阻塞 IO 模式里,如果要监控的文件描述比较多的话。可以使用 select 模型。select 将监控多个文件描述符,再将结果统一的返回给处理程序。

由于 select 能监视的文件描述符有限,一般为 1024 个,为了处理这个瓶颈,后面实现了 poll 这种结构来监控文件描述符的数据到达情况。 即 poll 没有最大数量的限制。

select 和 poll 本质上都需要遍历的去操作文件描述符,效率不高。为此, linux 后面有设计了 epoll 模型,通过在内核里使用一个红黑树的结构来管理注册进来的文件描述符,同时使用了一个就绪链表维护了就绪事件,一旦基于某个文件描述符就绪时,内核会采用类似 callback 的回调机制,将事件通知给用户进程。

18. 软连接和硬链接区别?

创建一个文件时,会有一个 inode 值指向这个文件的具体存储信息,可以理解为这个文件的指针。每当创建硬链接时,就会将文件的引用计数 + 1,直到没有引用时,那么这个文件就会被删除。硬链接不可以在不同文件系统建立链接,而且只有超级用户才可以为目录创建硬链接。

软链接则没有文件系统的限制,它和原来的文件具有不一样的 node 值,并且 inode 里保存了原来文件绝对路径。因此,在删除了原来文件后,软链接会访问无效的。


感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。
可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!
阅新技术,阅读更多的新知识。
阅新技术


lincoln
57 声望12 粉丝

分享有深度、有启示的技术文章;