操作系统八股
本文最后更新于 2024年8月12日 上午
操作系统八股
谈一谈你对操作系统的理解
操作系统是计算机系统中的大管家,它负责管理和协调计算机的各种资源,为应用程序提供一个安全、高效的运行环境。
主要作用包括:
- 资源管理:操作系统负责管理计算机的硬件资源,如处理器、内存、硬盘和外部设备等,以便合理地分配和利用这些资源。它通过调度算法和资源分配机制,确保每个任务或进程都能得到适当的资源。
- 进程管理:操作系统能同时运行多个程序,通过进程管理,它可以控制程序的执行、调度和协作,以便提高计算机的整体效率。它负责创建、终止、挂起和恢复进程,以及管理进程之间的通信与同步。
- 文件管理:操作系统负责管理计算机的文件系统,方便用户存储和获取数据,确保数据的安全性和完整性。它提供了文件的创建、读写、删除和重命名等操作,以及文件的权限管理和保护。
- 用户界面:操作系统提供了与计算机交互的用户界面,可以是命令行界面或图形用户界面(GUI),使得用户可以方便地使用计算机。用户可以通过输入指令或点击图标进行操作和访问系统功能。
- 错误检测和恢复:操作系统能够监测和处理软件和硬件错误,提供错误检测和恢复的机制,以保证计算机的稳定性和可靠性。它可以监测和捕获程序的异常、处理硬件故障、提供备份和恢复机制等。
谈一谈你对中断的理解
中断是计算机系统中的一种基本操作,用于处理外部事件或内部异常。在计算机运行过程中,可能会发生各种外部或内部的事件,比如硬件故障、IO请求、定时器事件等,这些事件需要在合适的时机中断正常的程序执行。
中断的作用是打断当前的程序执行流程,转而执行与中断事件相关的处理程序。处理程序执行完毕后,再返回到原来的程序继续执行。中断可以实现多任务的并发执行,提高系统的响应能力和效率。
中断可以分为硬件中断和软件中断。硬件中断是由硬件设备触发的,比如外部设备的请求、时钟中断等。软件中断是由软件程序主动触发的,比如系统调用、异常处理等。
中断和异常有什么区别
- 触发条件:中断通常是由外部事务触发的,如用户输入、外设请求等。这些事件不一定与当前执行的程序有关。而异常通常是由程序本身在运行过程中产生的,比如除零错误、非法指令等。
- 处理方式:中断的处理通常是将当前的程序执行指针保存起来,然后切换到中断处理程序去执行,处理完后返回原来指针处继续执行。而异常处理则需要首先确定是不是可以恢复的错误,如果可以恢复,那么在处理完成后可以从出错的地方继续执行;如果不可恢复,那么可能需要终止程序。
- 终止与持续:一般来说,处理完中断后,CPU会恢复执行被中断的程序,而异常可能会导致程序的终止。
- 预期性:中断是可以被预期且常规的行为。例如,系统可以预期硬件设备的中断,并根据这些中断进行响应。然而异常则是非预期的,它们是因为程序错误、硬件问题或其他不可预料的条件产生的。
- 指向性:中断指向的是特定的中断服务程序,而异常指向的是错误处理程序或者是系统。
一个程序从开始运行到结束的完整过程,简要陈述一下
- 编写程序:程序员根据需求和规范,使用编程语言编写程序代码。
- 编译/解释:根据程序的编写语言,将程序源代码转化为机器可执行的形式。对于编译型语言,程序需要通过编译器将源代码编译成机器代码;对于解释型语言,程序会逐行解释执行。
- 加载:操作系统将编译/解释后的程序加载到内存中,为运行做准备。加载过程中会分配所需的内存空间,并进行一些初始设置。
- 运行:程序开始执行,通常从程序的入口点开始,在操作系统的调度下,逐行执行程序代码。
- 数据处理:程序根据算法和逻辑对数据进行操作,进行计算、判断、循环等各种处理过程。
- IO操作:程序可能需要与外设交互,进行输入输出操作。如文件读写、网络通信、用户交互等。
- 异常处理:在程序运行过程中,可能会出现各种异常情况,包括错误、异常输入、资源不足等。程序需要进行适当的异常处理,避免程序崩溃或数据丢失。
- 结束运行:程序执行到结束点,或者通过某种条件判断需要提前结束,程序会执行相应的结束操作。资源会被释放,可能会输出一些结果或保存数据。
- 卸载:程序运行结束后,操作系统会将程序卸载,释放相关资源。
什么是用户态和核心态
用户态和内核态是操作系统的两种运行状态。
内核态
:处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。用户态
:处于用户态的 CPU 只能受限的访问内存,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。
为什么区分用户态和内核态
用户态(User Mode)和内核态(Kernel Mode)是两种不同的执行状态。这种区分主要是为了保障操作系统的稳定性,安全性以及提高效率。
- 保护操作系统及关键内核数据:在内核态,操作系统代码有权访问系统的所有资源,而在用户态,应用程序的活动受到限制,不能直接访问硬件或内核数据结构,所有涉及硬件操作的请求都必须通过内核代码(例如设备驱动程序)来完成。这种限制可以避免用户态的应用程序意外或恶意地破坏系统资源。
- 安全性:用户态和内核态的划分是操作系统进行权限管理和访问控制的主要方式,它可以防止用户级应用程序越权访问和操作系统资源,提高系统的安全性。
- 系统稳定性:如果所有程序都在同一权限级别运行,一个程序的错误可能导致整个系统崩溃。通过将权限划分为用户态和内核态,可以有效地防止用户程序的错误影响到整个系统,提高系统的稳定性。
- 效率:内核态与用户态的切换会带来一定的开销,但这背后的好处是系统运行的效率和稳定性。例如,IO操作通常需要在内核态进行,以防止用户程序直接控制硬件设备,这样可以防止多个程序同时对一个设备进行操作,引起混乱。
并发和并行有什么区别
并发是指程序在同一时刻处理多个任务的能力,这些任务在单核处理器上通过切换执行,看起来是同时进行的。而并行是指程序在同一时刻真正地执行多个任务,这需要程序运行在多核处理器系统上。并行可以提高程序的执行效率,但也会增加程序的复杂性。
- 并发(Concurrency)是指系统能够处理多个任务的能力,这并不意味着这些任务一定会同时进行。并发的任务可能会交错进行,因此并发可以在单核CPU上实现。这是因为CPU可以通过时间片轮转或其他任务切换策略,在各个任务之间快速切换,给人以它们在同时进行的错觉。
- 并行(Parallelism)则是指系统同时执行多个任务的能力。并行显然需要硬件的支持,如多核心或多处理器。在这种情况下,多个任务确实可以在同一时间内进行。例如,现代的多核CPU可以让我们在看电影的同时进行视频编码,每一个任务在不同的处理器核心上执行,这就是并行。
同步和异步有什么区别
- 同步(Synchronous)操作是在一个操作完成之前,不进行下一个操作。这是一种阻塞调用,也就是说,进行某项操作的过程中,不得不停下来等待,直到这个操作完成。
- 异步(Asynchronous)操作是不需要立刻得到结果,即使未完成也可进行其它操作。这是一种非阻塞调用,也就是说,还没得到结果,就继续做别的事情,不会因为单一操作的等待而阻塞。
阻塞和非阻塞的区别
阻塞和非阻塞是描述任务或操作在等待结果时的行为方式的概念。
- 阻塞是指任务在等待某个操作完成时,暂停自己的执行,并等待操作完成后再继续执行。在阻塞状态下,任务会一直等待,直到所需的资源或结果就绪。在此期间,任务不能执行其他操作。
- 非阻塞是指任务在等待某个操作完成时,不会暂停自己的执行,而是立即返回,继续执行其他任务。非阻塞的任务会周期性地查询所需资源或结果的状态,判断是否就绪,从而决定是否继续执行。
阻塞适用于需要确保结果完整性和依赖顺序的情况,而非阻塞适用于需要提高并发性和响应性的情况。选择适合的阻塞和非阻塞方式可以提高程序的效率和性能。
什么是进程
在操作系统中,进程是指正在执行的程序实例。它是计算机系统中的基本执行单位,拥有独立的内存空间和系统资源。每个进程都有自己的指令序列、数据和执行环境。
进程的创建是通过操作系统调度和管理的,当一个程序被执行时,操作系统会为其创建一个独立的进程。每个进程都有一个唯一的进程标识符(PID),用于在系统中标识和管理进程。
进程的主要特征包括:
- 独立性:每个进程都有独立的内存空间和系统资源,不会受其他进程的影响。
- 执行状态:进程可以处于运行、就绪、阻塞等不同的执行状态,根据进程调度算法决定执行顺序。
- 上下文切换:由于操作系统需要在不同进程之间进行切换,进程可以通过上下文切换保存和恢复自己的执行环境。
- 通信与同步:进程可以通过进程间通信机制实现信息的交换和资源共享,也可以通过同步机制实现协调和合作。
什么是线程
在操作系统中,线程是进程的一部分,是进程内的一个执行单元。与进程相比,线程更轻量级,多个线程可以在同一个进程中并发执行。线程共享进程的内存空间和系统资源,每个线程有独立的程序计数器(PC)和栈空间,但它们可以访问共享的数据和全局变量。
线程的主要特征包括:
- 并发执行:多个线程可以在不同的处理器或核心上同时执行,从而实现并发性。
- 共享内存:线程之间共享同一个进程的地址空间,可以互相访问和修改共享数据。
- 轻量级:相对于进程来说,线程的创建、销毁和切换开销较小,执行效率更高。
- 协作与通信:线程之间可以通过共享内存进行通信和协作,也可以使用同步机制控制线程的执行顺序。
进程与线程有什么区别
进程是独立的执行实体,拥有独立的内存空间和系统资源;而线程是进程内的执行单元,共享进程的内存空间和系统资源。线程的切换和通信开销较小,并发性更高。选择使用进程还是线程,取决于具体的应用需求。
- 资源占用
- 进程:每个进程拥有独立的内存空间和系统资源。
- 线程:多个线程共享同一个进程的内存空间和系统资源。
- 调度与切换
- 进程:进程是独立的执行实体,操作系统以进程为单位进行调度,进程的切换开销相对较大。
- 线程:线程是进程的一部分,线程的调度和切换开销较小,因为它们共享进程的上下文。
- 并发性和并行性
- 进程:多个进程可以并发执行,每个进程都有自己的地址空间,可以在多个处理器或核心上并行执行。
- 线程:多个线程可以在同一个进程内并发执行,共享进程的地址空间,可以在同一个处理器或核心上并行执行。
- 用户态和核心态
- 进程:进程切换涉及到用户态到内核态的切换,需要较高的权限和开销。
- 线程:线程切换只涉及用户态的切换,开销较小。
- 创建和销毁
- 进程:创建和销毁进程的开销较大,包括分配独立的内存空间、初始化数据结构等。
- 线程:创建和销毁线程的开销相对较小,线程依赖于进程的内存和资源完成创建过程。
为什么有了进程,还要有线程
- 提高系统的并发性能:在单个进程内运行多个线程可以提高系统并行处理能力,使得CPU的利用率更高。
- 简化程序设计:当程序需要处理一些异步或并行的任务时,使用线程可以使程序设计变得更简单。例如,在一个文本编辑器中,一个线程用于键盘输入,另一个线程用于屏幕刷新,这样保证了用户的输入和界面的显示可以同时进行。
- 资源共享:线程之间可以共享进程资源,在很多场景下这是非常有用的。例如,在Web服务器中,每个用户的请求可能会生成一个线程,所有线程共享服务器的资源,如内存、硬盘等,以处理多个并发的用户请求。
进程有哪些常见的状态,如何切换
- NULL -> 创建状态:一个新进程被创建时的第一个状态;
- 创建状态 -> 就绪状态:当进程被创建完成并初始化后,一切就绪准备运行时,变为就绪状态,这个过程是很快的;
- 就绪状态 -> 运行状态:处于就绪状态的进程被操作系统的进程调度器选中后,就分配给 CPU 正式运行该进程;
- 运行状态 -> 结束状态:当进程已经运行完成或出错时,会被操作系统作结束状态处理;
- 运行状态 -> 就绪状态:处于运行状态的进程在运行过程中,由于分配给它的运行时间片用完,操作系统会把该进程变为就绪态,接着从就绪态选中另外一个进程运行;
- 运行状态 -> 阻塞状态:当进程请求某个事件且必须等待时,例如请求 I/O 事件;
- 阻塞状态 -> 就绪状态:当进程要等待的事件完成时,它从阻塞状态变到就绪状态;
进程间的通信方式有哪些,各有什么优缺点
进程间通信(Inter Process Communication,IPC)是一个进程与另一个进程传输和分享数据的机制。
- 管道(Pipe):管道是最早的进程间通信机制,数据可以在父子或兄弟进程间单向流动。
- 优点:简单易用
- 缺点:数据只能在有亲缘关系的进程之间传输,并且是无格式的字节流,需要进程自行解析
- 消息队列(Message Queue):消息队列是一种先进先出的队列结构,允许进程将消息发送到队列,并允许其他进程根据消息的优先级从队列中读取。
- 优点:可以在无关进程间传输数据,支持数据的优先级设定
- 缺点:数据读写需要系统调用,消耗相对较高,复杂消息可能需要额外处理逻辑
- 共享内存(Shared Memory):共享内存允许多个进程访问同一块内存空间,是最快的IPC方式。
- 优点:无需系统调用,直接读写内存,效率较高
- 缺点:需要手动解决进程间的同步问题,开发难度相对较高
- 信号(Signal):信号是一种简单的进程间通信方式,用来通知接收进程有某事件发生。
- 优点:简单,可以异步地通知事件
- 缺点:信息量有限,只能传递一个数量,不能携带更复杂的信息
- 套接字(Socket):套接字可以在不同机器上的进程间通信。
- 优点:可以进行跨机器的通信,通用性强
- 缺点:开发相对复杂,数据读写需要系统调用,效率较低
- 信号量(Semaphore):信号量常用于多个进程间的同步和互斥问题。
线程间的通信方式有哪些,各有什么优缺点
线程间的通信方式通常利用同一个进程下线程所共享的资源来实现。
- 锁机制(Locks):当多个线程需要访问共享资源时,可以使用锁机制来避免并发问题。一个线程在访问资源时可以”锁定”该资源,阻止其他线程的访问,直到该线程释放锁。
- 优点:简单而直接
- 缺点:必须小心处理,否则可能导致死锁
- 信号量(Semaphores):信号量是一个更为高级的同步机制,可以控制多个线程对共享资源的访问。信号量有一个计数器和一个等待队列组成,计数器表示可用的资源数目。
- 优点:可以控制资源的同时访问数
- 缺点:使用不当也可能导致死锁
- 条件变量(Condition Variables):条件变量是另一种同步机制,允许一个线程等待某个条件满足。当条件满足时,可以通知一个或多个正在等待的线程。条件变量通常与互斥锁一起使用。
- 优点:能够实现更复杂的同步,如按顺序访问等
- 缺点:使用不当可能导致死锁或饥饿现象
- 共享变量:由于线程共享内存,因此一个线程可以访问另一个线程的变量。但是,当两个线程需要访问同一个变量时,可能会产生冲突。因此,需要使用某种同步机制(如锁或信号量)来保护共享变量。
- 事件驱动(Event-driven):在事件驱动的模型中,线程之间通过等待和触发事件来进行通信。这种方式不仅适用于线程间的通信,也可以用于进程或异步输入/输出等的通信。
- 优点:适应性强,可以应对多种不同的通信需求
- 缺点:需要编程模型支持,且在设计和实现上可能较为复杂
- 线程本地存储(Thread-Local Storage,TLS):有些变量是线程不安全的,例如静态变量,全局变量等,这些变量如果在多线程环境下共享,可能会造成不可预料的结果。为了解决这个问题,我们可以为每个线程提供一份该变量的副本,这就是线程本地存储。
- 优点:能避免资源竞争
- 缺点:会增加内存的使用
进程的地址空间里有什么
线程切换要保存哪些上下文
什么是协程?和线程有什么区别
协程(Coroutine)是一种用户级别的轻量级线程。它们的调度完全由用户控制,而不是由操作系统内核控制。与线程不同,协程的上下文切换极其快速且成本低,主要因为它所需保存和恢复的状态较少。
对于协程和线程的比较,参考以下四个方面
- 切换开销:线程由系统内核控制,切换开销大;协程由程序员在用户空间控制,切换开销小。
- 调度:线程是抢占式调度,需要操作系统来进行线程的调度切换;协程是非抢占式的,由协程自身决定何时进行切换,这也是其使用复杂性的来源之一。
- 数据共享和同步:线程并发编程需要考虑锁等同步机制的问题;而协程在同一时间只有一个运行,它对共享资源的访问不需要加锁,只需要确保在协程切换的时候保存好共享资源的状态即可。
- 应用场景:线程适合cpu密集型任务; 协程适合IO密集型任务。
什么是僵尸进程
僵尸进程(Zombie Process)指的是一个已经结束执行的子进程,但其父进程尚未调用wait()或waitpid()函数来获取子进程的终止状态信息。在这种情况下,子进程的进程控制块(PCB)仍然存在系统中,但没有正常退出,因此处于僵尸状态。
主要原因
进程在结束执行后,父进程并没有及时处理子进程的终止状态信息。通常,父进程会通过调用wait()或waitpid()函数来等待子进程的退出,并获取其终止状态。若父进程没有这样做,子进程就会成为僵尸进程。
解决方法
虽然僵尸进程本身无害,但过多的僵尸进程可能是不可取的。为了避免僵尸进程的积累,父进程应适时调用wait()或waitpid()来获取终止状态信息,并通过kill()或终止自身来回收僵尸进程。在某些情况下,可以使用信号处理程序,如SIGCHLD信号来自动处理子进程的退出状态。
如果僵尸进程太多,会出现什么问题
- 资源浪费:僵尸进程会占用系统的一些资源(如PID、内存等),当僵尸进程过多时,会导致系统的资源浪费,降低系统的运行效率。
- 进程管理困难:僵尸进程会使得进程管理变得复杂。在进程终止后,其父进程需要调用wait()函数或者相关的系统调用来回收其资源,如果父进程没有正确处理僵尸进程,会导致僵尸进程无法完全终止并释放资源。
- 进程满载:在某些情况下,如果系统产生大量的僵尸进程,可能会导致系统的进程表被填满,无法再创建新的进程来运行其他任务。
- 内存泄漏:僵尸进程的资源无法被回收释放,可能会导致内存泄漏问题,最终导致系统的性能降低或崩溃。
如何处理过多的僵尸进程呢
- 修复父进程:确保父进程正确处理子进程的终止状态,使用wait()或waitpid()等系统调用来回收僵尸进程的资源。父进程在fork子进程后,应该在适当的时候使用这些系统调用来检查子进程的终止状态,并进行处理。
- 手动杀死僵尸进程:可以使用kill命令结合僵尸进程的PID来手动杀死僵尸进程。使用命令”ps aux | grep Z”来查找僵尸进程的PID,然后使用”kill PID”命令来杀死该进程。但这只是临时性的处理方式,仍然需要修复父进程来避免僵尸进程的产生。
- 编写垃圾回收程序:可以编写一个定期运行的垃圾回收程序来扫描系统中的僵尸进程,并回收它们的资源。这个程序可以使用系统调用waitpid()来检查并回收僵尸进程的资源。
- 修改子进程创建方式:通过修改子进程的创建方式,可以避免产生僵尸进程。例如,使用fork()后紧接着调用exec()或_exit()来替换原始的进程映像,并在父进程中忽略SIGCHLD信号,这样子进程终止时不会成为僵尸进程。
- 调整系统资源限制:适当调整系统的资源限制,如进程表大小、最大进程数等限制,以防止系统中过多的僵尸进程积累。
一个进程可以创建多少线程
- 在理论上,一个进程可以创建的线程数目由其系统资源限制,比如内存、CPU等。实际上,当一个进程创建大量线程时,由于每个线程都都需要一定的系统资源(例如,存储线程的上下文消息、栈空间等),所以当系统资源耗尽时,无法再创建新的线程。
- 从操作系统的角度讲,没有硬性的规定一个进程最多可以创建多少个线程,具体的数量取决于操作系统的实现和配置。
进程的调度算法有哪些
调度算法是指根据系统的资源分配策略所规定的资源分配算法。
- 先来先服务(FCFS,First-Come, First-Served):按照进程到达的先后顺序进行调度,先到达的进程先执行,适用于短作业时间的场景。但长作业时间的进程可能会导致等待时间较长,又称为非抢占调度算法。
- 最短作业优先(SJF,Shortest Job First):根据进程的执行时间,选择剩余时间最短的进程优先执行。这种算法可以减少平均等待时间,但需要准确估计每个进程的执行时间,且不适用于长作业时间的进程。
- 优先级调度(Priority Scheduling):为每个进程分配优先级,优先级高的进程先执行。可以根据进程的优先级动态调整调度顺序,但可能会导致低优先级的进程饥饿。
- 轮转调度(Round Robin):将CPU时间切片分配给每个进程,按照轮转的方式进行调度。每个进程在一个时间片内执行,如果时间片用完,则将进程放到队列尾部继续等待,适用于多任务并发执行。
- 多级反馈队列调度(Multilevel Feedback Queue):将进程根据优先级划分为多个队列,每个队列具有不同的时间片大小。进程根据到达时间和优先级进入对应的队列,并按照轮转调度算法执行。可根据进程的行为和执行情况调整优先级和时间片大小。
- 最短剩余时间优先(SRTF,Shortest Remaining Time First):类似于SJF算法,但考虑到新进程的到达时间,如果有更短剩余执行时间的进程到达,则抢占当前进程。
进程终止的方式
- 正常终止:进程完成任务后,调用exit()系统调用或者main函数执行完毕,进程会自动终止。
- 异常终止:进程遇到致命错误,例如除零错误、内存访问错误等,操作系统会强制终止进程。
- 人工终止:用户通过操作系统提供的终止命令(如kill命令)来终止进程。
- 父进程终止:父进程终止时,所有子进程会收到一个SIGCHLD信号,子进程会被操作系统终止。
- 系统关机:当系统关闭时,操作系统会终止所有运行的进程。
谈一谈你对锁的理解
在操作系统中,锁是一种用于控制多线程和多进程访问共享资源的同步机制。当多个线程或进程需要访问某个共享资源时,如果没有适当的保护措施,会发生各种问题,比如数据不一致性。
锁的基本工作原理是:当一个线程或进程想要访问一个共享资源,它首先会尝试获得锁–如果锁是可用状态(也就是说锁“打开”或者”未锁”),那么该线程或进程会锁住它,然后访问该资源。如果锁不可用(也就是说已经“上锁”或者“被锁定”),则该线程或进程必须等待,直到拥有该锁的线程或进程释放它。
- 互斥锁(Mutex):保证同时只有一个线程或进程能够执行某一段临界区的代码。这是最简单,也是最基本的一种锁类型。
- 读写锁(Read-Write Lock):允许多个线程同时读取某一资源,但在写入该资源时只能由一个线程进行。这种锁能提高系统的并发性。
乐观锁和悲观锁有什么区别
乐观锁和悲观锁是并发控制中两种不同的策略,用于处理多个线程对共享资源的并发访问问题。悲观锁假设会有冲突发生,因此在访问共享资源前进行加锁;而乐观锁假设不会有冲突发生,在更新操作时进行冲突检测。选择哪种锁策略应根据具体场景和需求来决定。
- 悲观锁(Pessimistic Locking):悲观锁的策略是在访问共享资源之前,假设会发生冲突并进行保护。在悲观锁机制下,如果一个线程要访问共享资源,它会假设其他线程可能会对该资源进行修改,因此会将资源加锁,直到完成操作后才会释放锁。
- 乐观锁(Optimistic Locking):乐观锁的策略是在访问共享资源时不加锁,而是在更新操作时进行冲突检测。线程在读取共享资源时,不会对其加锁,而是记录下读取时的版本号或其他标识信息。在提交更新操作时,会再次检查共享资源是否被其他线程修改过。如果没有冲突,就执行更新操作;如果有冲突,就放弃当前更新并重新尝试。
- 性能比较:悲观锁会在访问共享资源之前就加锁,即使没有实际的冲突,也会造成性能的损失。而乐观锁避免了大部分的锁竞争,提高了并发性能。但是,如果冲突频繁发生,乐观锁需要不断地进行重试,可能会导致性能下降。
操作系统是如何实现原子操作的
原子操作(Atomic Operations)是不可中断的一个或一系列操作。一旦原子操作开始,就会独占直到完成,无法在中间阶段被其他操作打断。
什么是死锁
死锁(Deadlock)是指在多任务环境下,当两个或更多的任务各自拥有一个资源并且等待获取另一个任务持有的资源时,就会发生的一种状态。涉及的任务无法继续执行,因为每个任务都在等待其他任务释放资源,但是没有任务会释放它的资源,因为它们都在等待。这就形成了一个循环的等待状态,从而导致了死锁。
死锁的四个必要条件
- 互斥条件:一个资源只能由一个任务拥有,在资源释放之前任何其他任务都无法请求到。
- 占有并等待:一个任务持有至少一个资源,但又申请新的资源,而新资源正被别的任务持有,所以申请任务阻塞,但又对自己已获得的资源保持不放。
- 不可抢占:别的任务不能把已获得的资源从任务中强行回收,资源只能由获得它的任务自行释放。
- 循环等待:存在一种可能,即任务之间形成一种任务-资源的环形链,链中每个任务都占有下一个任务所需的资源。
一个经典的死锁例子是两个人在餐桌上吃饭,他们之间有两只筷子,每个人吃饭都需要用到两只筷子,但是每个人都先拿起了一只筷子,然后都在等待另一只筷子。这就是一个典型的死锁,两个人都在等待对方释放筷子,但是都不愿意先放下自己的筷子。
解决死锁的基本方法
解决死锁的主要方法可以归结为:死锁预防、死锁避免、死锁检测和恢复。
- 死锁预防:预防策略的主要思想是破坏造成死锁的四个必要条件中的至少一个。
- 死锁避免:死锁避免采取了一种更加精细的策略。它需要保持关于系统当前的哪些资源被哪些任务占用、哪些资源是空闲的、哪些任务正在等待资源等的信息。然后,操作系统在每次有资源请求时,都会先检查是否授予该资源可能导致系统进入不安全状态(即可能死锁),如果是,就不给任务分配资源。
- 死锁检测和恢复:有时,我们可能认为死锁可能发生得比较少,或者避免死锁的成本比较高,所以我们愿意冒险,但是当检测到死锁时需要有一种恢复办法。这就需要一种检测死锁的算法。当检测到死锁后,通常的做法是中止一些任务或抢占一些资源,以解除死锁状态。
怎么避免死锁
银行家算法
如何解除死锁
- 资源抢占:选择一些进程,并强制终止它们或抢占它们占有的资源,然后将这些资源分配给其他进程。这种方法需要谨慎选择终止或抢占的进程,以及决策哪些资源应该被抢占、如何选择抢占的顺序等。
- 回滚(Rollback):回滚是将一部分进程的状态和操作撤销到先前的状态,通过释放资源来解除死锁。回滚涉及到保存和回复进程状态的机制,需要合理地决定回滚的程度和方式,以及如何避免进一步的死锁发生。
- 进程终止:选择一些死锁的进程,将它们终止并释放它们占用的资源,以解除死锁。终止进程会导致数据丢失或系统服务中断,因此需要权衡决策。
什么是物理地址
物理地址,又称实际地址或绝对地址,是数据在计算机系统中物理内存(RAM)的实际位置或者地址。这个地址是总线或者内存控制器用来读取或者写入特定的物理内存的。逻辑地址在经过CPU的内存管理单元(MMU)的地址转换后,变成物理地址。这个物理地址才是数据真正存储的地方。
什么是逻辑地址
在操作系统中,逻辑地址(有时也被称为虚拟地址),是在运行过程中的程序或进程所看到的地址。这个地址是由 CPU 生成的,并且不同于物理地址,后者是数据在主存储器RAM中的实际地址。
对于程序来说,它只需要对内存进行抽象的、逻辑的操作,不用关心具体数据在物理内存中的位置。这就是为什么操作系统需要引入逻辑地址的原因。
什么是虚拟内存
虚拟内存是操作系统提供的一种内存管理技术,它通过将实际内存和磁盘空间组合使用,给每个进程提供一个抽象的、看似连续的地址空间。
在虚拟内存中,每个进程能够访问的内存空间大于实际物理内存的容量。操作系统会根据进程的需要,将部分内存数据存储在物理内存中,并将未使用的数据存储在磁盘上,以便于管理和利用。
通过使用虚拟内存,操作系统能够为每个进程提供独立的地址空间,使得进程之间相互隔离,更安全稳定。同时,虚拟内存也提供了一种内存扩展机制,使得系统能够运行更多的程序,而不会因为物理内存不足而导致程序崩溃。
什么是内存泄漏
一个程序在运行过程中,通常需要不断地申请内存来存储数据和对象。当这些数据或对象不再需要时,应该由程序负责释放这些内存,以便操作系统可以将其分配给其他的程序或者用于其他的用途。但是,湍急的程序可能会忘记(或者没有正确地)释放它申请的内存,尽管它已经不再使用这些内存。随着时间的推移,这些未被释放的内存资源会逐渐积累,导致系统可用内存减少,这就是内存泄漏。
内存泄漏可能会导致程序运行缓慢,严重时可能会导致系统资源不足,最终可能导致程序或系统崩溃。
内存泄漏和内存溢出有什么区别
内存泄漏是指未正确释放不再使用的内存,导致系统内存的浪费;而内存溢出是指申请的内存超过了系统可用内存大小,可能导致程序崩溃或无法响应。
- 引发原因:内存泄漏通常是由程序中未正确释放内存的错误操作引起的,例如忘记调用释放内存的函数、指针引用不正确等。而内存溢出通常是由程序在执行过程中,动态申请的内存超过了系统的限制,尤其在递归函数调用、无限循环等情况下更容易发生。
- 影响范围:内存泄漏只会影响程序本身,逐渐占用系统内存,导致程序性能下降甚至崩溃。而内存溢出是系统级的问题,可能会影响其他正在运行的程序,导致整个系统崩溃。
- 调试和解决:内存泄漏通过内存分析工具和代码分析来发现和解决,需要找到未释放内存的位置并进行修复。内存溢出的处理较为困难,通常需要优化算法和数据结构,减少内存的占用,并确保程序在申请内存前通过判断来避免申请超出系统限制的内存。