操作系统-面试常见问题

本文摘抄于其它相关知识,但是太多太杂了,我忘记了,如有介意请在评论区加上连接,我加上引用。

1 进程管理

1.1 进程的五大状态?

上图中各个状态的意义:

  • 运行状态(Running):该时刻进程占用 CPU;
  • 就绪状态(Ready):可运行,由于其他进程处于运行状态而暂时停止运行;
  • 阻塞状态(Blocked):该进程正在等待某一事件发生(如等待输入/输出操作的完成)而暂时停止运行,这时,即使给它CPU控制权,它也无法运行;

当然,进程还有另外两个基本状态:

  • 创建状态(new):进程正在被创建时的状态;
  • 结束状态(Exit):进程正在从系统中消失时的状态;

1.2 进程的上下文切换?

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

系统内核会存储保持下来的上下文信息,当此任务再次被分配给 CPU 运行时,CPU 会重新加载这些上下文,这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。

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

通常,会把交换的信息保存在进程的 PCB,当要运行另外一个进程的时候,我们需要从这个进程的 PCB 取出上下文,然后恢复到 CPU 中,这使得这个进程可以继续执行。

1.3 为什么需要线程?

对于多进程,会存在以下问题:

  • 进程之间如何通信,共享数据?
  • 维护进程的系统开销较大,如创建进程时,分配资源、建立 PCB;终止进程时,回收资源、撤销 PCB;进程切换时,保存当前进程的状态信息;

那到底如何解决呢?需要有一种新的实体,满足以下特性:

  • 实体之间可以并发运行;
  • 实体之间共享相同的地址空间;

这个新的实体,就是线程( Thread ),线程之间可以并发运行且共享相同的地址空间。

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

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

1.4 进程和线程之间的区别?

首先是进程和线程在linux中没有太大区别,都用同样的数据结构task_struct来描述,他们两个在调度器看来都是一个个调度实体。

线程与进程的比较如下:

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

对于,线程相比进程能减少开销,体现在:

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

所以,不管是时间效率,还是空间效率线程比进程都要高

线程上下文切换的是什么?

当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源,这些资源在上下文切换时是不需要修改的;

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

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

1.5 操作系统的进程调度策略?

如果硬件时钟提供某个频率的周期性中断,那么可以根据如何处理时钟中断 ,把调度算法分为两类:

  • 非抢占式调度算法挑选一个进程,然后让该进程运行直到被阻塞,或者直到该进程退出,才会调用另外一个进程,也就是说不会理时钟中断这个事情。
  • 抢占式调度算法挑选一个进程,然后让该进程只运行某段时间,如果在该时段结束时,该进程仍然在运行时,则会把它挂起,接着调度程序从就绪队列挑选另外一个进程。这种抢占式调度处理,需要在时间间隔的末端发生时钟中断,以便把 CPU 控制返回给调度程序进行调度,也就是常说的时间片机制。

主要的进程调度策略包括以下几种:

  1. 先来先服务(FCFS,First-Come-First-Served):
  • 按照进程到达的顺序进行调度,即先到达的进程先执行。
  • 简单易实现,但可能导致长作业运行时间过长,短作业无法运行,不利于短作业。FCFS 对长作业有利,适用于 CPU 繁忙型作业的系统,而不适用于 I/O 繁忙型作业的系统。
  1. 短作业优先(SJF,Shortest-Job-First):
  • 选择估计运行时间最短的进程进行调度。
  • 可以减少平均等待时间和周转时间,但难以准确预测作业执行时间,可能导致长作业“饥饿”。
  1. 最短剩余时间优先(SRF,Shortest-Remaining-Time-First):
  • 选择当前剩余时间最短的进程进行调度。
  • 是SJF的抢占式版本,适用于作业长度可预知的环境。
  1. 最高响应比优先(HRRN,Highest-Response-Ratio-Next):
  • 选择响应比最高的进程进行调度,响应比 = (等待时间 + 执行时间) / 执行时间。
  • 综合考虑了等待时间和执行时间,旨在平衡长短作业的等待时间。
  1. 时间片轮转(Round Robin):
  • 将所有就绪进程按FCFS的原则排成一个队列,每次调度时,把CPU时间平均分配给进程,该进程可以执行一个时间片。
  • 适用于分时系统,可以公平地分配CPU时间给各个进程,但是需要权衡时间,时间太短可能导致上下文切换开销增大。
  1. 优先级调度(Priority Scheduling):
  • 为每个进程分配一个优先级,并按照优先级高低的顺序进行调度。
  • 可以确保高优先级进程优先执行,但可能导致低优先级进程长时间得不到执行(饥饿现象)。
  1. 非抢占式优先权算法:
  • 系统一旦把处理机分配给就绪队列中优先权最高的进程后,该进程便一直执行下去,直至完成;或因发生某事件使该进程放弃处理机时,系统方可再将处理机重新分配给另一优先权最高的进程。
  • 主要用于批处理系统或某些对实时性要求不严的实时系统中。
  1. 抢占式优先权调度算法:
  • 系统同样是把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高的进程,进程调度程序就立即停止当前进程的执行,重新将处理机分配给新到的优先权最高的进程。

1.6 进程间的通信方式有哪些?IPC?

进程间通信依靠内核空间来通信。在内核空间定义通信结构。但是共享内存不一样。详见下。

进程间的通信方式主要有以下几种:

  1. 匿名管道(Pipe):管道是一种半双工的通信方式,数据只能单向流动。它通常用于具有亲缘关系的进程间的通信,如父子进程或兄弟进程之间。其原理基于操作系统内核提供的缓冲区,将一个进程的输出连接到另一个进程的输入,实现进程间通信。
  2. 命名管道(FIFO):也被称为有名管道,它也是半双工的通信方式,但允许无亲缘关系的进程间进行通信。命名管道的原理与匿名管道类似,也是基于操作系统内核提供的缓冲区来实现进程间通信的。

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则。

  1. 消息队列(Message Queue):消息队列是消息的链接表,存放在内核中。它允许多个进程通过向队列中添加或获取消息来进行通信。消息队列是一种先进先出的队列型数据结构,实际上是系统内核中的一个内部链表。
  2. 信号量(Semaphore):信号量是一个计数器,用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。它可以防止多进程竞争共享资源而造成的数据错乱,保护共享资源在任意时刻只能被一个进程访问。
  3. 共享内存(Shared Memory):共享内存是指两个或多个进程共享一个给定的存储区。多个进程可以同时操作共享内存,因此需要进行同步。共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。

不同于消息队列频繁的系统调用,对于共享内存机制来说,仅在建立共享内存区域时需要系统调用,一旦建立共享内存,所有的访问都可作为常规内存访问,无需借助内核。这样,数据就不需要在进程之间来回拷贝,所以这是最快的一种进程通信方式。

主要同步方法有:互斥锁同步、信号量同步、条件变量同步。

共享内存通信效率最高。

1.7 互斥锁与自旋锁的区别?

mutex和spinlock。

    • 互斥锁加锁失败后,线程会释放 CPU自我阻塞 ,CPU 给其他线程;
    • 自旋锁加锁失败后,线程会忙等待,直到它拿到锁;

线程没有获得互斥锁时会阻塞当前线程,把CPU给别的线程用,直到被唤醒。这个过程会触发线程上下文切换。

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

自旋锁是通过 CPU 提供的 CAS 函数(Compare And Swap),在「用户态」完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁来说,会快一些,开销也小一些。

1.8 线程的同步方式有哪些?

线程同步是确保两个或多个线程按照预期的顺序执行代码的机制,以避免竞态条件和确保数据一致性。以下是几种常见的线程同步方式:

  1. 临界区(Critical Section)
    • 在任意时刻只允许一个线程对共享资源进行访问。
    • 如果有多个线程试图访问公共资源,有一个线程进入后,其他线程将被挂起,直到进入临界区的线程离开。
    • 临界区通常是由操作系统或编程语言的线程库提供的同步原语来实现。
  1. 互斥锁(Mutex)
  • 互斥锁(互斥量)用于保护对共享资源的访问,确保同一时间只有一个线程能够访问被锁定的资源。
  • 互斥锁属于内核对象,由操作系统维护,因此可用于不同进程的线程同步。
  • 当一个线程持有互斥锁时,其他试图获取该锁的线程将被阻塞,直到锁被释放。
  1. 信号量(Semaphore)
    • 信号量是一个用于控制多个线程访问共享资源的计数器。
    • 它允许多个线程同时访问资源,但数量受到信号量初始值的限制。
    • 当线程需要访问资源时,它会尝试减少信号量的值;如果信号量的值大于零,则访问被允许;如果值为零,则线程将被阻塞。
  1. 条件变量(Condition Variable)
    • 条件变量通常与互斥锁一起使用,允许线程在满足特定条件之前等待。
    • 一个线程可以在条件变量上等待,直到另一个线程发出通知(signal)或广播(broadcast),表示条件已经满足。
  1. 读写锁(Read-Write Lock)
    • 读写锁允许多个线程同时读取共享资源,但在写入时只允许一个线程进行。
    • 这对于读操作远多于写操作的场景非常有用,因为它可以提高并发性能。
  1. 原子操作(Atomic Operations)
    • 原子操作是不可中断的操作,即在执行完毕之前不会被其他线程打断。
    • 它们通常用于实现低级别的同步机制,如自增、自减等。
  1. 自旋锁(Spinlock)
    • 自旋锁是一种特殊的互斥锁,当线程尝试获取锁而失败时,它会一直循环检查锁是否可用,而不是进入睡眠状态。
    • 自旋锁适用于锁被持有的时间非常短的场景,以避免线程切换的开销。

在选择线程同步方式时,需要根据具体的应用场景和需求来权衡各种因素,如性能、公平性、复杂性等。不同的同步方式在不同的场景下可能具有不同的优势和劣势。

2 内存管理

2.1 为什么需要虚拟内存?

虚拟内存的发明是为了解决直接使用物理内存所存在的种种问题。以下是发明虚拟内存的主要原因:

  1. 内存碎片问题:直接使用物理内存可能会导致内存碎片问题。因为程序在读取内存时需要连续的内存空间,而物理内存分页后可能会导致多次的内存分割,从而产生内存碎片。
  2. 内存不足问题:物理内存的大小通常是固定的,取决于计算机硬件的配置。当需要运行内存需求较大的程序时,如果物理内存不足以支撑该程序的运行,就会发生内存溢出。虚拟内存通过利用硬盘空间来模拟物理内存,从而扩展了可用的内存空间,使得大程序能在小内存上运行。让所有进程以为自己拥有了所有的内存。
  3. 更好的内存管理:虚拟内存为每个进程提供一个大的、一致的、私有的存储空间,确保每个进程可以顺利运行,并且不同进程不会意外地写入另一个进程的存储空间,从而避免错误。

2.2 虚拟内存地址与物理地址之间的转化是如何转换的?

MMU、TLB、页表。TLB shootdown,缺页中断。

2.3 物理内存不够了怎么办?

swap机制,将内存swap到磁盘上,再读取新的数据。

2.4 说一下内存碎片,外部碎片和内部碎片?

外碎片:外部碎片指的是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。

内碎片:内部碎片就是已经被分配出去(能明确指出属于哪个进程)却不能被利用的内存空间;

2.5 分段和分页,为什么?

主要有两种方式,分别是内存分段和内存分页,分段是比较早提出的,我们先来看看内存分段。

2.5.1 分段

分段的办法很好,解决了程序本身不需要关心具体的物理内存地址的问题,但它也有一些不足之处:

  • 第一个就是内存碎片的问题。
  • 第二个就是内存交换的效率低的问题。

对于多进程的系统来说,用分段的方式,外部内存碎片是很容易产生的,产生了外部内存碎片,那不得不重新 Swap 内存区域,这个过程会产生性能瓶颈。

因为硬盘的访问速度要比内存慢太多了,每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。

所以,如果内存交换的时候,交换的是一个占内存空间很大的程序,这样整个机器都会显得卡顿。

为了解决内存分段的「外部内存碎片和内存交换效率低」的问题,就出现了内存分页。

2.5.2 分页

内存分页由于内存空间都是预先划分好的,也就不会像内存分段一样,在段与段之间会产生间隙非常小的内存,这正是分段会产生外部内存碎片的原因。而采用了分页,页与页之间是紧密排列的,所以不会有外部碎片。

但是,因为内存分页机制分配内存的最小单位是一页,即使程序不足一页大小,我们最少只能分配一个页,所以页内会出现内存浪费,所以针对内存分页机制会有内部内存碎片的现象。

如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为换出(Swap Out)。一旦需要的时候,再加载进来,称为换入(Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。

为了减少单级页表的空间消耗,多级页表

内存分段和内存分页并不是对立的,它们是可以组合起来在同一个系统中使用的,那么组合起来后,通常称为段页式内存管理。

段页式内存管理实现的方式:

  • 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制;
  • 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页;

这样,地址结构就由段号、段内页号和页内位移三部分组成。

用于段页式地址变换的数据结构是每一个程序一张段表,每个段又建立一张页表,段表中的地址是页表的起始地址,而页表中的地址则为某页的物理页号,如图所示:

2.5.3 TLB

多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换的工序,这显然就降低了这俩地址转换的速度,也就是带来了时间上的开销。程序是有局部性的,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。为了加快转换的速度,我们就可以利用这一特性,把最常访问的几个页表项存储到访问速度更快的硬件,于是计算机科学家们就在 CPU 芯片中加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB(Translation Lookaside Buffer) ,通常称为页表缓存、转址旁路缓存、快表等。

3 文件系统

3.1 请简要介绍一下Linux文件系统?

Linux文件系统是对存储设备上的数据和元数据进行组织的机制,旨在实现数据的查询和存取。它的结构为树状,具有一个根目录,其中含有下级子目录或文件的信息,子目录中又可以包含更多的子目录或文件信息,这样一层一层地延伸下去,构成一棵倒置的树。在Linux系统中,一切都被看作是文件,包括硬件设备。这种设计理念体现了UNIX哲学的一个方面,Linux作为UNIX的重写版本,也继承了UNIX这个概念。

3.2 什么是Linux 文件系统的inode?

inode是Linux文件系统中用于储存文件元信息的区域,中文译名为“索引节点”。每个文件都有一个唯一的inode号,相当于文件的身份证,用于在磁盘上查找文件。inode结构包含了多个字段,用于描述文件或目录的属性和状态,如文件类型、文件权限、文件所有者、文件大小、时间戳、硬链接计数以及数据块指针等。通过inode,系统可以方便地找到存储数据的位置,并管理文件的属性。

3.3 文件系统有哪些结构?

文件系统主要由索引节点、目录项、超级块、逻辑块组成。

  • 索引节点:记录了文件的元信息,包括文件权限、文件大小、创建时间、数据的索引位置等等。索引节点是文件的唯一标识,和文件一一对应,它和文件内容一样会持久化到磁盘中保存。
  • 目录项:用来记录文件的名字、索引节点的指针以及与其他目录项的关联关系。多个关联的目录项组成了文件系统的目录结构。目录项对象不会存储到磁盘中,而是被缓存起来,便于快速的解析目录。
  • 超级块:存储了整个文件系统的状态,如索引节点、逻辑块的使用情况。
  • 逻辑块:文件系统用来存储数据的最小单位,大小为 4 KB,一般由 8 个连续的扇区组成(磁盘读写的最小单位是扇区,大小为 512B),多个逻辑块组成了文件系统的数据块区。

3.4 什么是虚拟文件系统,有什么作用?

虚拟文件系统(VFS)是操作系统在用户层与文件系统之间引入的一个抽象层,屏蔽了不同文件系统之间的差异,定义了一组标准的系统调用接口,为用户提供了统一访问文件的方式。

VFS 定义了一组所有文件都支持的数据结构和标准接口,这样,用户和其他内核子系统只需要跟 VFS 提供的统一接口交互就可以了,而不需要关心底层各种文件系统的实现细节。

3.5 磁盘IO的方式?

标准IO、直接IO、mmap。

3.5.1 标准I/O(缓存I/O)

    大多数文件系统的默认I/O操作都是标准I/O。在Linux的缓存I/O机制中,数据先从磁盘复制到内核空间的缓冲区,然后从内核空间缓冲区复制到应用程序的地址空间。

    读操作:操作系统检查内核的缓冲区有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回;否则从磁盘中读取,然后缓存在操作系统的缓存中。

    写操作:将数据从用户空间复制到内核空间的缓存中。这时对用户程序来说写操作就已经完成,至于什么时候再写到磁盘中由操作系统决定,除非显示地调用了sync等同步命令。

    缓存I/O的优点:1)在一定程度上分离了内核空间和用户空间,保护系统本身的运行安全;2)可以减少读盘的次数,从而提高性能。

    缓存I/O的缺点:数据在传输过程中需要在应用程序地址空间和缓存之间进行多次数据拷贝操作,这些数据拷贝操作所带来的CPU以及内存开销是非常大的。

3.5.2 直接I/O

    直接IO就是应用程序直接访问磁盘数据,而不经过内核缓冲区,这样做的目的是减少一次从内核缓冲区到用户程序缓存的数据复制。比如说数据库管理系统这类应用,它们更倾向于选择它们自己的缓存机制,因为数据库管理系统往往比操作系统更了解数据库中存放的数据,数据库管理系统可以提供一种更加有效的缓存机制来提高数据库中数据的存取性能。

    直接IO的缺点:如果访问的数据不在应用程序缓存中,那么每次数据都会直接从磁盘加载,这种直接加载会非常耗时。通常直接IO与异步IO结合使用,会得到比较好的性能。(异步IO:当访问数据的线程发出请求之后,线程会接着去处理其他事,而不是阻塞等待)

从图中你可以看到,直接 I/O 访问文件方式减少了一次数据拷贝和一些系统调用的耗时,很大程度降低了 CPU 的使用率以及内存的占用。

但是直接与磁盘交互非常耗时,所以只有确定标准I/O开销非常巨大才考虑使用直接I/O。

3.5.3 mmap

    mmap是指将硬盘上文件的位置与进程逻辑地址空间中一块大小相同的区域一一对应,当要访问内存中一段数据时,转换为访问文件的某一段数据。这种方式的目的同样是减少数据在用户空间和内核空间之间的拷贝操作。当大量数据需要传输的时候,采用内存映射方式去访问文件会获得比较好的效率。

    使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。

mmap的优点:

    减少系统调用。我们只需要一次 mmap() 系统调用,后续所有的调用像操作内存一样,而不会出现大量的 read/write 系统调用。

    减少数据拷贝。普通的 read() 调用,数据需要经过两次拷贝;而 mmap 只需要从磁盘拷贝一次就可以了,并且由于做过内存映射,也不需要再拷贝回用户空间。

    可靠性高。mmap 把数据写入页缓存后,跟缓存 I/O 的延迟写机制一样,可以依靠内核线程定期写回磁盘。但是需要提的是,mmap 在内核崩溃、突然断电的情况下也一样有可能引起内容丢失,当然我们也可以使用 msync来强制同步写。

 从上面的图看来,我们使用 mmap 仅仅只需要一次数据拷贝。看起来 mmap 的确可以秒杀普通的文件读写,那我们为什么不全都使用 mmap 呢?事实上,它也存在一些缺点:

虚拟内存增大。mmap 会导致虚拟内存增大,我们的 APK、Dex、so 都是通过 mmap 读取。而目前大部分的应用还没支持 64 位,除去内核使用的地址空间,一般我们可以使用的虚拟内存空间只有 3GB 左右。如果 mmap 一个 1GB 的文件,应用很容易会出现虚拟内存不足所导致的 OOM。

磁盘延迟。mmap 通过缺页中断向磁盘发起真正的磁盘 I/O,所以如果我们当前的问题是在于磁盘 I/O 的高延迟,那么用 mmap() 消除小小的系统调用开销是杯水车薪的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值