操作系统

0 阅读8分钟

产生死锁需要同时满足四个必要条件:

  • 互斥条件(Mutual Exclusion):资源不能被多个进程共享,即资源一次只能被一个进程使用。如果一个资源已经被分配给了一个进程,其他进程必须等待,直到该资源被释放。
  • 持有并等待条件(Hold and Wait):一个进程已经持有了至少一个资源,同时还在等待获取其他被占用的资源。在此期间,该进程不会释放已经持有的资源。
  • 不可剥夺条件(No Preemption):已分配给进程的资源不能被强制剥夺,只有持有该资源的进程可以主动释放资源。
  • 循环等待条件(Circular Wait):存在一个进程集合 ,其中  等待  持有的资源, 等待  持有的资源,依此类推,直到  等待  持有的资源,形成一个进程等待环。

假设有两个进程  和 ,以及两个资源  和 ,一个简单的死锁场景是这样的:

  1.  持有资源 ,并请求资源 。
  2.  持有资源 ,并请求资源 。

在这种情况下,发生死锁的步骤如下:

  1. 互斥条件: 和  都只能被一个进程占用。
  2. 持有并等待条件: 持有  并等待 ,同时  持有  并等待 。
  3. 不可剥夺条件: 和  都不能被强制从  和  中剥夺。
  4. 循环等待条件: 等待  持有的 ,而  等待  持有的 ,形成一个循环。

19、如何避免死锁呢

产⽣死锁的有四个必要条件:互斥条件、持有并等待条件、不可剥夺条件、环路等待条件。

避免死锁,破坏其中的一个就可以。

消除互斥条件

这个是没法实现,因为很多资源就是只能被一个线程占用,例如锁。

消除请求并持有条件

消除这个条件的办法很简单,就是一个线程一次请求其所需要的所有资源。

消除不可剥夺条件

占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可剥夺这个条件就破坏掉了。

消除环路等待条件

可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后就不存在环路了。

1. 进程和线程的区别

这是最基础也最重要的问题。你可以从定义、开销、稳定性、通信这四个维度来回答。

维度进程 (Process)线程 (Thread)
基本定义资源分配的基本单位。拥有独立的内存空间(代码段、数据段、堆等)。CPU 调度和执行的基本单位。不拥有系统资源,只拥有少量运行时资源(栈、寄存器)。
开销成本。创建/销毁需分配独立内存,切换涉及页表刷新,开销大。。共享进程资源,创建/切换仅需维护少量上下文,开销小。
稳定性。一个进程崩溃(如段错误),通常不会影响其他进程(隔离性好)。。一个线程崩溃(如非法指针访问),会导致整个进程(包括该进程下所有线程)挂掉。
通信方式复杂。需通过 IPC 机制(管道、消息队列、共享内存等)。简单。直接读写同一进程内的全局变量或堆内存(但需考虑同步)。

2. 线程为什么比进程更轻量

线程之所以被称为“轻量级进程”,主要源于资源的共享上下文切换的成本

  • 内存共享:同一进程内的线程共享代码段、数据段和堆内存。创建线程时,不需要像创建进程那样复制大量的内存页或建立独立的页表。

  • 切换成本低

    • 进程切换:涉及用户态到内核态的切换,需要保存当前进程的 CPU 环境(寄存器、程序计数器),刷新内存管理单元(MMU)的快表(TLB),加载新进程的页表等,开销很大。
    • 线程切换:同一进程内的线程切换,只需保存和恢复少量的寄存器内容和栈指针,不需要刷新 TLB,也不需要切换内存空间,因此速度极快。

3. 进程通信方式有哪些

由于进程间内存是隔离的,它们必须通过操作系统提供的内核机制来交换数据。常见方式包括:

  1. 管道 (Pipe) :半双工,数据只能单向流动。通常用于父子进程或兄弟进程间。
  2. 命名管道 (FIFO) :去除了亲缘关系限制,任何进程可以通过路径名访问。
  3. 消息队列 (Message Queue) :存放在内核中的消息链表,可以随机读写,克服了管道字节流限制。
  4. 共享内存 (Shared Memory)最快的 IPC 方式。映射一段能被多个进程访问的内存区域,进程直接读写该内存(需配合信号量同步)。
  5. 信号量 (Semaphore) :主要用于同步,控制多个进程对共享资源的访问(计数器)。
  6. 套接字 (Socket) :不仅可用于不同机器间的通信,也可用于本机进程通信。

4. 线程同步方式有哪些

当多个线程访问共享资源时,为了防止数据不一致(竞态条件),需要同步机制:

  1. 互斥锁 (Mutex) :保证同一时刻只有一个线程能访问共享资源。
  2. 自旋锁 (Spinlock) :线程不进入睡眠,而是“自旋”(循环检查)等待锁释放。适用于锁持有时间极短的场景。
  3. 读写锁 (Read-Write Lock) :允许多个读线程同时访问,但写线程独占。适合“读多写少”场景。
  4. 条件变量 (Condition Variable) :允许线程在某个条件不满足时挂起(睡眠),直到其他线程改变条件并唤醒它。
  5. 信号量 (Semaphore) :允许多个线程同时访问共享资源(限制最大并发数)。

5. 什么是死锁

死锁是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象。若无外力干涉,这些进程都将无法推进。

简单比喻:你拿着钥匙 A 等我的钥匙 B 开门,我拿着钥匙 B 等你的钥匙 A 开门,咱俩就这么僵持着。

6. 死锁的四个必要条件

产生死锁必须同时满足以下四个条件(Coffman 条件):

  1. 互斥条件:资源是独占的,一次只能被一个进程使用。
  2. 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能被其他进程强行剥夺,只能由自己释放。
  4. 环路等待条件:存在一种进程资源的循环等待链(P1 等 P2,P2 等 P1)。

7. 怎么解决死锁问题

解决死锁通常有三种策略:

  1. 死锁预防(破坏四个必要条件之一):

    • 破坏请求与保持:规定进程在运行前一次性申请完所有资源。
    • 破坏不剥夺条件:允许抢占资源(当一个进程请求的资源得不到满足时,必须释放已占有的资源)。
    • 破坏环路等待:对资源进行编号,规定进程必须按编号递增的顺序请求资源。
  2. 死锁避免(银行家算法):

    • 在分配资源前,先计算此次分配是否会导致系统进入“不安全状态”。如果会,就不分配。
  3. 死锁检测与恢复

    • 允许死锁发生,系统定期运行检测算法(如资源分配图化简)。一旦发现死锁,通过剥夺资源回滚杀死进程来恢复。

8. 什么是内存泄漏和内存溢出

这两个概念常被混淆,但本质不同:

  • 内存泄漏

    • 定义:指程序在申请内存后,无法释放已申请的内存空间。
    • 本质:是逻辑错误。内存虽然不再被程序使用,但因为还持有引用,垃圾回收器(GC)无法回收它。
    • 后果:长期运行后,泄漏累积,最终导致内存溢出。
    • 例子:静态集合类(如 static List)不断添加对象却不清理。
  • 内存溢出

    • 定义:指程序在申请内存时,系统无法提供足够的内存空间。
    • 本质:是资源不足
    • 原因:可能是内存泄漏导致的,也可能是申请的数据量确实超过了物理内存限制(如加载一个 10GB 的文件到 4GB 内存中)。
    • 例子OutOfMemoryError

9. 什么是用户态和内核态

现代操作系统为了保护系统安全,将 CPU 的执行权限分为两个级别:

  • 用户态

    • 权限低:应用程序运行在此模式。只能执行非特权指令(如算术运算),不能直接访问硬件设备或核心内存区域。
    • 受限:如果需要读写文件、分配内存或访问网络,必须通过系统调用陷入内核。
  • 内核态

    • 权限高:操作系统内核运行在此模式。可以执行特权指令(如开关中断、操作 MMU),访问所有内存和硬件。
  • 切换代价

    • 从用户态切换到内核态(如发生系统调用、中断、异常)需要消耗资源。这涉及到保存当前上下文(寄存器、程序计数器)、刷新流水线等操作,这就是上下文切换的开销。