嵌入式经典面试题之操作系统(三)

文章目录

    • [01 进程、线程、协程分别是什么?](#01 进程、线程、协程分别是什么?)
    • [02 描述一下进程的空间模型?](#02 描述一下进程的空间模型?)
    • [03 多进程多线程多协程的优缺点](#03 多进程多线程多协程的优缺点)
    • [04 多进程、多线程同步通信方式](#04 多进程、多线程同步通信方式)
      • [4.1 多进程](#4.1 多进程)
      • [4.2 多线程](#4.2 多线程)
    • [05 进程线程的状态转换?](#05 进程线程的状态转换?)
    • [06 进程上下文是什么?](#06 进程上下文是什么?)
    • [07 中断上下文是什么?](#07 中断上下文是什么?)
    • [08 父进程、子进程及其关系和区别](#08 父进程、子进程及其关系和区别)

01 进程、线程、协程分别是什么?

**进程是程序在计算机中执行的实例,是操作系统进行资源分配和调度的基本单位。**每个进程都有自己的虚拟地址空间、代码段、数据段以及系统资源(如文件描述符、IO资源等)。

**线程是进程中的一个执行单元,线程在进程内共享资源(如内存、文件描述符等),但每个线程有独立的执行栈和程序计数器。**线程是 CPU 调度的最小单位,多个线程可以并发地执行。

协程是一种用户级的线程,它是轻量级的、由程序员控制调度的并发单元。协程通常由程序员主动切换,而不像线程那样由操作系统进行调度。

进程、线程、协程的区别如下:

调度单位 操作系统调度 操作系统调度 用户调度(程序员控制)
内存隔离 每个进程有独立的内存空间 线程共享进程的内存空间 协程共享进程的内存空间
资源消耗 开销大(包括创建和上下文切换) 较小(比进程小,但比协程大) 开销最小(无操作系统介入)
创建销毁开销 较大 较小 非常小(仅为函数调用)
并发性能 并发能力受限于操作系统调度 多线程能提高并发能力 高并发,适合I/O密集型任务
通信方式 进程间通信(IPC) 线程间通信(共享内存、锁等) 协程间直接通过共享内存、变量等
使用场景 适用于需要高度隔离的任务 适用于需要高并发的CPU密集型任务 适用于大量I/O密集型操作

注意:

  • 一个进程可以包含多个线程,一个线程从属于一个进程。一个线程可以有多个协程。
  • 一个进程的消失不会影响其他进程,但是一个线程的消失会使相对应的进程消失。
  • 协程不需要多线程的锁机机制,因为多个协程从属于一个线程,不存在同时写变量冲突。并且效率上比线程高。

02 描述一下进程的空间模型?

通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间具体如图,内核空间是受保护的,用户不能对该空间进行读写操作。

栈的空间有限,堆是很大的自由存储区,程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的传递也是在栈上进行。

程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。

初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。

未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。

栈 (Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。

堆 (Heap):存储[动态内存分配,需要程序员手工分配,手工释放。注意它与数据结构中的堆是两回事,分配方式类似于链表。

注意:栈空间有向下的箭头,代表数据地址增加的空间是往下的,新的数据的地址的值反而更小,堆空间则是往上。

03 多进程多线程多协程的优缺点

内存开销 高,每个进程有独立内存空间 较小,线程共享进程的内存 非常小,多个协程共享线程内存
创建/销毁开销 高,进程创建和销毁较慢 较低,线程创建和销毁较快 极低,协程创建和销毁非常快
上下文切换效率 低,进程切换涉及更多资源 较高,线程切换相对较轻 非常高,由用户级调度控制
并行性 高,支持多核并行处理 高,支持多核并行处理 仅限于单线程,不支持多核并行
稳定性 高,进程独立,一个崩溃不会影响其他进程 较低,线程共享内存,一个线程崩溃可能影响进程 较低,协程在同一线程中,崩溃会影响整个进程
线程/进程间通信 复杂,需要使用IPC(如管道、消息队列等) 简单,线程共享内存,易于通信 极其简单,通过共享变量或异步IO等
适用场景 适用于CPU密集型任务,需要高独立性和并行性 适用于I/O密集型任务,尤其需要多任务并发时 适用于I/O密集型任务,极高并发且资源使用少
开发难度 较高,进程间通信和同步复杂 中等,需要处理线程同步和死锁等问题 低,协程的开发较简单,但调度和控制需要注意
调试难度 中等,进程间隔离相对容易调试 较高,线程安全问题导致调试较困难 高,调试协程中的并发和状态控制较复杂

04 多进程、多线程同步通信方式

4.1 多进程

(1)管道

  • 匿名管道:用于父子进程之间的通信,通常为单向通信。
  • 命名管道:允许不同的进程通过文件系统中的命名管道进行通信,支持双向通信。
  • 优点:简单、轻量。
  • 缺点:只能用于父子进程或具有相关关系的进程。

(2)消息队列

  • 允许进程通过消息队列发送和接收消息,消息按顺序排队。
  • 优点:提供了进程间异步通信,支持多个生产者和消费者。
  • 缺点:性能受限,特别是消息队列长度较长时。

(3)共享内存

  • 允许多个进程访问同一块内存区域,速度较快,但需要显式的同步机制(如信号量或互斥锁)。
  • 优点:速度快,适合大数据量的共享。
  • 缺点:需要额外的同步机制来保证数据一致性。

(4)信号量

  • 信号量用于控制对共享资源的访问,通过计数来实现资源的限制,防止多个进程同时访问共享资源。
  • 优点:可以有效地防止竞争条件,控制并发。
  • 缺点:实现复杂,容易产生死锁。

(5)互斥锁

  • 互斥锁是一种同步原语,确保同一时间只有一个进程能够访问某个资源。
  • 优点:确保数据的一致性,防止竞争条件。
  • 缺点:可能会产生死锁,需要小心设计。

4.2 多线程

(1)互斥锁/锁

  • 互斥锁是最常用的同步工具,确保只有一个线程能访问共享资源,其他线程必须等待锁被释放。
  • 优点:简单直接,保证了线程安全。
  • 缺点:容易产生死锁,锁粒度过大会影响性能。

(2)条件变量

  • 条件变量用于线程间的通知机制。当某个条件满足时,线程可以通过条件变量通知其他线程继续执行。
  • 适合的场景是,线程在某个条件不满足时,需要等待一段时间或直到其他线程改变状态。

(3)读写锁

  • 读写锁允许多个线程同时读取共享资源,但在写操作时会排它性地独占资源。适用于读操作频繁、写操作较少的场景。
  • 优点:提高了读操作的并发性。
  • 缺点:写操作时必须获得独占锁,可能会阻塞读操作。

(4)信号量

  • 信号量是一种用于限制同时访问共享资源的线程数量的同步机制。它维护一个计数器,线程在进入临界区之前需要获取信号量,计数器为0时线程会被阻塞。
  • 适用于有固定数量资源的情况。

(5)队列

  • 使用队列进行线程间的通信
  • 适用于生产者-消费者模式,一个线程向队列放入数据,另一个线程从队列中取出数据。
  • 优点:队列是线程安全的,不需要显式的锁。
  • 缺点:可能会影响性能,尤其是高并发的情况下。

(6)事件

  • 事件对象用于线程间同步,当一个线程设置事件,其他线程可以等待该事件的发生来执行某些操作。
  • 适用于需要线程等待某个特定事件发生的场景。

(7)共享变量

  • 可以直接通过共享全局变量来进行通信,但这种方法需要加锁保证线程安全。
  • 适用于少量数据交换的场景。

05 进程线程的状态转换?

创建态:一个进程正在被创建,还没到转到就绪状态之前的状态。操作系统为新进程分配资源、创建PCB

运行态:占有CPU,并在CPU上运行;

就绪态:已经具备运行条件,但是由于没有空闲的CPU,而暂时不能运行;一旦得到CPU时间片调度时即可运行。

阻塞态:一个进程正在等待某一事件而暂停运行时,已经具备运行条件,但是由于没有空闲的CPU,而暂时不能运行;

终止态:操作系统回收进程的资源,撤销PCB。

转换:

就绪 → 运行:当操作系统的调度器选择一个就绪状态的进程并将 CPU 分配给它时,进程进入运行状态。

运行 → 阻塞:进程执行过程中,如果需要等待某些资源(如 I/O 操作、网络响应等),它会进入阻塞状态。

运行 → 就绪:如果进程的时间片用完,或者调度器决定中断当前进程(如时间片轮转),进程会进入就绪状态,等待下一次调度。

阻塞 → 就绪:当进程等待的事件发生(如 I/O 完成、资源释放等),进程会从阻塞状态转变为就绪状态,准备重新调度执行。

运行 → 终止:进程执行完毕,或发生异常终止,进程进入终止状态。

阻塞 → 终止:如果进程在阻塞状态时发生了崩溃或终止,也会直接进入终止状态。

注意:

不能由阻塞态直接转换运行态,也不能由就绪态直接到阻塞态。(原因:因为进入阻塞态是进程主动请求的,必然需要进程在运行态时才能发出这种请求。)

06 进程上下文是什么?

**进程上下文是指在进程执行过程中,操作系统需要保存和恢复的关于该进程的状态信息。**当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。

**进程切换:**当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。

进程上下文包括:

  • 程序计数器:指示进程下一条指令的地址。
  • 寄存器的值:包括通用寄存器、浮点寄存器等,保存了进程在执行时的数据。
  • 堆栈指针:指向当前进程的栈帧。
  • 内存管理信息:如页表、段表等,用于管理该进程的虚拟内存。
  • 进程状态:如进程是否处于运行、就绪、阻塞等状态。
  • 文件描述符:进程当前打开的文件、套接字等的标识符。
  • 信号状态:进程的信号掩码,表示哪些信号处于屏蔽状态。

**当发生进程调度时,进行进程切换就是上下文切换。**操作系统必须对上面提到的全部信息进行切换,新调度的进程才能运行。

  • 保存当前进程的上下文,包括所有寄存器、程序计数器、堆栈指针等。
  • 恢复目标进程的上下文,让目标进程从上次中断的地方继续执行。

进程上下文切换的场景:

  • 时间片耗尽:调度器决定切换到另一个进程。
  • 阻塞事件:当前进程因为等待 I/O 操作或其他资源而被阻塞,操作系统切换到其他进程。
  • 进程退出:进程执行完毕,操作系统切换到其他就绪进程。

07 中断上下文是什么?

**中断上下文是指当中断发生时,操作系统需要保存和恢复的执行状态。**当硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。

中断上下文包括:

  • 程序计数器:指示中断发生前的指令位置。
  • 中断触发前的寄存器值:中断发生前 CPU 寄存器的内容,包括状态寄存器、通用寄存器等。
  • 栈信息:中断发生时当前栈的状态。
  • 标志寄存器:表示中断状态和 CPU 特权级(如中断屏蔽等)。

**中断上下文切换:**在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中继服务结束时能恢复被中断进程 的执行。

中断上下文的场景:

  • 定时器中断:操作系统的时钟中断用于实现多任务调度。
  • 外部设备中断:例如磁盘 I/O 完成、网络包到达等。
  • 系统调用:当进程发出系统调用时,通常会触发一个软件中断。

注意:

  • 不能-睡眠或者放弃CPU。因为内核在进入中断之前会关闭进程调度,一旦睡眠或者放弃CPU,这时内核无法调度别的进程来执行,系统就会死掉。牢记:中断服务子程序一定不能睡眠(或者阻塞)。

  • 不能-尝试获得信号量。如果获得不到信号量,代码就会睡眠,导致(1)中的结果。

  • 不能-执行耗时的任务。中断处理应该尽可能快,因为如果一个处理程序是IRQF_DISABLED类型,他执行的时候会禁止所有本地中断线,而内核要响应大量服务和请求,中断上下文占用CPU时间太长会严重影响系统功能。中断处理程序的任务尽可能放在中断下半部执行。

  • 不能-访问用户空间的虚拟地址,因为中断运行在内核空间。

进程上下文与中断上下文的区别

触发方式 进程调度、阻塞、I/O 等系统调度引起的 外部中断源(硬件中断、定时器中断等)
发生时机 进程调度、时间片耗尽、进程终止、阻塞等 由硬件或软件触发(如 I/O 完成、时钟中断)
内容保存 保存当前进程的程序计数器、寄存器、堆栈指针、内存管理等信息 保存当前 CPU 寄存器、程序计数器、中断状态等
上下文切换的目标 切换到其他进程 切换到中断处理程序(ISR)
状态恢复 恢复另一个进程的执行状态 执行中断处理程序后,恢复中断发生前的状态
上下文切换的开销 较大,涉及进程的状态、资源等 较小,主要涉及保存和恢复寄存器和标志位等

08 父进程、子进程及其关系和区别

(1)父进程创建子进程

  • 在许多操作系统中,进程是通过系统调用如 fork() 来创建的。
  • 父进程调用 fork() 或类似系统调用时,操作系统会复制父进程的地址空间,并在此基础上创建一个新的进程,这个新进程就是子进程。
  • 父进程和子进程拥有独立的内存空间,它们的进程 ID (PID) 是不同的,但它们共享一些资源(如打开的文件描述符、信号等),除非特定地关闭或重新设置。

在操作系统中父进程和子进程是进程间的关系,其中父进程是创建子进程的进程,而子进程是由父进程创建并继承其部分资源的进程。

(2)PID 和 PPID概念

  • PID(Process ID):每个进程在操作系统中都有一个唯一的进程标识符(PID),用于标识进程。
  • PPID(Parent Process ID):每个进程也会有一个父进程 ID(PPID),表示该进程的父进程。
  • 父进程和子进程通过 PPID 和 PID 相互联系。父进程的 PID 会作为子进程的 PPID。

(3)父进程等待子进程结束

父进程可以通过系统调用(如 wait()waitpid())等待子进程的终止,并获取子进程的退出状态。这是为了保证父进程能够处理子进程的退出,防止僵尸进程的产生。

  • 僵尸进程 :当子进程终止时,它会保留在系统中,直到父进程通过 wait() 系统调用回收其退出状态。若父进程没有调用 wait(),子进程的退出状态会保留在系统中,变成僵尸进程。

(4)孤儿进程

如果父进程在子进程结束之前终止,那么该子进程会变成 孤儿进程。在 Unix/Linux 系统中,孤儿进程会被 init 进程(PID 1) 作为新的父进程收养,确保系统对这些进程进行管理,避免孤儿进程成为僵尸进程。

注意:父进程、子进程它们共享代码段,但并不共享数据段、堆、栈等,而是子进程拥有父进程数据段、堆、栈等副本,所以对于同一个局部变量,它们打印出来的值是不相同的。

相关推荐
魔镜前的帅比几秒前
Java 模块化(JPMS)解析
java·后端
Mr.W.T19 分钟前
Java实战经验分享
java·开发语言
烛.照10329 分钟前
rabbitMQ消息转换器
java·linux·rabbitmq
leo_hush31 分钟前
java解析复杂json
java·前端·json
P7进阶路41 分钟前
Spring Boot应用关闭分析
java·spring boot·后端
工程师老罗1 小时前
我用Ai学Android Jetpack Compose之CircularProgressIndicator
android·android jetpack
计算机-秋大田1 小时前
基于微信小程序的消防隐患在线举报系统设计与实现(LW+源码+讲解)
java·spring boot·后端·微信小程序·小程序·课程设计
极客先躯2 小时前
高级java每日一道面试题-2025年01月27日-框架篇[SpringBoot篇]-如何在Spring Boot启动的时候运行一些特定的代码?
java·spring boot·后端·初始化·启动执行
disgare2 小时前
设计模式——状态模式
java·设计模式·状态模式
m0_748235952 小时前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘(上)
java·spring boot·后端