大厂系列:操作系统八股文,速速收藏(七)

51.进程有哪些状态?

新建(New)

  • 进程刚被创建,操作系统为其分配资源并初始化必要的数据结构(如进程控制块PCB)。此时,进程还没有开始执行。

就绪(Ready)

  • 进程已经获得了所有资源,准备开始执行。它被放入就绪队列中,等待CPU分配时间片。在此状态下,进程并没有执行,等待操作系统的调度器决定何时运行。

运行(Running)

  • 进程正在CPU上执行,处于运行状态。此时,操作系统正在为进程分配CPU时间片,执行进程中的指令。只有一个进程能在同一时刻占用CPU,但在多核处理器中,每个核心可以同时运行不同的进程或线程。

等待(Blocked / Waiting)

  • 进程正在等待某个事件的发生或某些资源的释放(例如I/O操作完成、信号量释放、锁被释放等)。当进程无法继续执行时,它会进入等待状态,直到等待的事件发生或资源变为可用。此时,进程无法继续执行,但它占用系统资源,直到其可以再次进入就绪队列。

挂起(Suspended / Sleeping)

  • 进程被暂停或暂时中止,它并不完全进入等待状态,而是由于某些原因被挂起(例如,系统资源紧张,操作系统决定将其挂起等)。此时,进程的上下文和状态被保存,它可以在稍后的时间恢复。这个状态通常用于进程被暂停时,例如在操作系统需要进行内存管理或调度优化时。

终止(Terminated / Exit)

  • 进程执行完毕或被操作系统终止,进入终止状态。进程的执行结束后,系统回收其占用的资源(如内存、文件描述符等),并清理相应的控制块和数据结构。一个进程的终止状态包括正常终止和异常终止两种情况。

僵尸(Zombie)

  • 进程已经终止,但其父进程尚未调用 wait 系统调用来获取其退出状态或清理资源。此时,进程的资源虽然已被释放,但进程的控制块仍然保留在系统中。僵尸进程的存在是为了让父进程可以读取终止进程的状态信息。操作系统会在父进程调用 wait 后,清除僵尸进程。

孤儿(Orphan)

  • 孤儿进程是指其父进程已终止,但它仍在运行。这些进程会被操作系统的初始化进程(通常是init进程)收养,init进程会成为它们的父进程。

52.僵尸进程,孤儿进程,守护进程的区别?

僵尸进程 (Zombie Process):僵尸进程指的是已经结束执行的进程,但其父进程尚未调用 wait()waitpid() 来获取该进程的退出状态,因此操作系统仍然保留该进程的进程控制块(PCB)和资源,虽然进程已终止,但它仍然存在于进程表中,等待父进程读取其退出状态。僵尸进程不再占用CPU资源,但仍占用少量内存资源。要清除僵尸进程,父进程需要调用 wait() 系统调用来回收资源。如果父进程已经终止,操作系统会将僵尸进程的父进程设置为 init 进程(通常是PID 1),并由 init 进程负责清理工作。

孤儿进程 (Orphan Process):孤儿进程是指父进程已经终止,但子进程仍在运行的进程。孤儿进程将会被操作系统的 init 进程(PID 1)收养,init 进程成为它们的新的父进程。孤儿进程并不会变成僵尸进程,因为它们依然会被 init 进程管理和回收资源。孤儿进程的存在一般不会造成资源泄漏,且可以继续执行。

守护进程 (Daemon Process):守护进程是一个常驻后台的进程,通常在系统启动时启动,它的主要作用是执行系统级任务或为其他进程提供服务。守护进程通常没有控制终端,且在系统启动时由 init 或其他系统管理工具启动,独立于用户的登录会话。守护进程在系统中会持续运行,直到系统关闭或进程结束。典型的守护进程包括系统日志记录进程、网络服务进程等。

53.并行和并发的区别

并行 (Parallelism)并发 (Concurrency) 是计算机科学中的两个重要概念,它们经常被混淆,但实际上它们有本质的区别。并发指的是在同一时间段内处理多个任务,但不一定是在同一时刻同时执行。并发强调的是任务之间的“交替执行”,即多个任务在同一时间段内有机会运行,但它们并不需要同时运行,而是通过时间分片或者调度机制在一个处理单元(如一个CPU核心)上轮流执行。并发是一个关于“管理多个任务”的概念,涉及如何设计和安排多个任务在时间上的交替执行。在并发系统中,多个任务可能在同一时刻有多个任务在执行,也可能只是系统安排这些任务在不同的时刻切换执行,并发的目标是确保程序能有效处理多个任务,即使它们之间可能没有相互依赖。并发并不要求任务同时执行,可以通过多任务调度或者时间片轮转来实现。实例上,在单核CPU上,操作系统通过快速切换任务,使得多个任务(例如处理用户输入、下载文件、运行后台进程等)看起来是同时执行的,这种方式就体现了并发。

并行指的是在同一时刻通过多个处理单元(如多个CPU核心或多个计算机)同时执行多个任务。并行计算要求系统能够同时处理多个任务,它的目标是加速程序执行过程。并行是一个关于“同时执行多个任务”的概念,强调的是任务在时间上的“同时性”。在并行系统中,多个任务或任务的子部分在多个处理器或多个核心上同时执行。并行性通常用在需要进行大规模计算的场景,例如科学计算、数据处理和视频渲染等。并行任务之间可能有依赖关系,因此并行执行的任务往往是通过分割任务和合并结果来实现的。在一个多核CPU上,程序可以将多个计算任务分配给不同的CPU核心,同时执行,这样可以加快整个计算过程的速度。例如,在处理大数据集时,可以将数据分成若干部分,每个核心处理一部分数据,最终将结果合并。

54.怎么杀死僵尸进程?

僵尸进程是指那些已经完成执行但其父进程未能正确读取其退出状态的进程,因此它们仍然占用系统的进程表。要杀死僵尸进程,可以通过几种方法。首先,终止父进程是最常见的做法,因为僵尸进程通常是由于父进程没有回收子进程的退出状态导致的。可以通过 pstop 命令查看僵尸进程及其父进程的PID,然后使用 kill -9 <父进程PID> 来终止父进程,父进程结束后,僵尸进程会被操作系统自动清理。如果父进程是系统进程并且无法终止,可以依赖操作系统的 init 进程(PID为1)来回收这些进程。init会自动回收所有孤立的僵尸进程,确保系统正常运行。另一种方法是通过重启系统,这会清除所有僵尸进程,但这可能不适用于高可用性要求的生产环境。在长期运行的系统中,也可以编写脚本定期检查和清理僵尸进程。总结来说,常见的方法包括手动终止父进程、让 init 进程回收僵尸进程,或者通过重启系统来清理。

55.一个进程fork出一个子进程,那么他们占用的内存是之前的2倍吗?

当一个进程通过 fork() 系统调用创建子进程时,父子进程的内存空间并不会立刻占用两倍的内存。实际上,它们在初始时是共享内存的,并且并不会立即复制所有的内存。具体来说,操作系统采用了 写时复制(Copy-on-Write, COW) 技术,这意味着父进程和子进程在 fork() 调用后会共享同一块内存区域,直到其中一个进程对内存进行修改时,操作系统才会复制相关的内存页面。因此,直到有修改发生,父进程和子进程是共享内存的,不会导致内存占用翻倍。

只有在父进程或子进程修改内存时,操作系统才会为修改的部分内存分配新的物理内存空间,这时才会产生额外的内存开销。这个过程是渐进的,只有当写操作发生时才会复制相应的内存页面,所以初期父子进程的内存占用是相同的,直到进行修改才会有所增加。因此,fork() 后的内存占用并不会立刻翻倍,只有在实际写入时,内存使用才会增加。

总结来说,fork() 创建子进程时,父子进程的内存是共享的,只有在进行写操作时,内存才会发生复制,进而增加内存的实际占用。所以,父子进程初始占用的内存并不是两倍。

56.多进程和多线程的区别?

多进程和多线程的区别主要体现在资源隔离性、内存管理、创建与销毁开销、切换开销、并发与并行、稳定性与可靠性以及适用场景等方面。进程是操作系统资源分配的基本单位,每个进程拥有独立的内存空间、文件描述符等资源,进程间的通信通常通过IPC机制进行,像管道、消息队列、共享内存等。而线程是进程内部的执行单元,多个线程共享进程的内存空间、文件描述符等资源,因此线程间的通信非常高效,不需要像进程间通信那样的额外开销。由于进程间是完全隔离的,操作系统会为每个进程分配独立的内存空间,进程间的资源不共享,除了显式地共享内存或通过IPC。线程共享进程的地址空间,这使得线程之间的内存访问和数据共享变得容易,但也容易出现竞争条件或数据一致性问题。创建进程比创建线程的开销大,因为创建进程时需要为进程分配独立的资源,如内存空间、文件描述符等。而线程共享同一个进程的资源,创建和销毁线程的开销相对较小。线程切换比进程切换的开销小。进程切换涉及到上下文切换,包括保存和加载进程的所有上下文信息(如寄存器、内存映射等),而线程切换仅涉及到进程内线程的上下文切换,这通常只需要保存和加载少量的寄存器信息。多进程和多线程都可以实现并发,但并发和并行的区别在于多进程更容易实现真正的并行,尤其是在多核处理器上,因为每个进程运行在独立的内存空间,它们可以在多个处理器核心上并行运行。而多线程在多核系统上也能实现并行,但由于线程之间共享内存空间,线程间的调度和资源竞争可能导致并行效率受限。由于进程之间互相隔离,一个进程的崩溃通常不会影响其他进程。即使一个进程崩溃,操作系统通常会确保其他进程继续运行。而线程是共享内存的,如果一个线程崩溃,可能会导致整个进程崩溃,影响所有其他线程。多进程适用于需要完全隔离的任务,特别是当任务彼此之间没有太多共享数据时,或者任务可能会崩溃时。比如在处理器密集型任务、服务器进程等场景下,多进程模式能够提供更高的稳定性和隔离性。多线程适用于需要共享数据、轻量级任务的场景,特别是当线程间需要频繁通信时。比如在 I/O 密集型应用中,使用多线程可以有效提升性能,因为线程之间的数据共享和通信非常高效。

57.什么是协程?

协程(Coroutine)是一种轻量级的线程,能够在单一或多个线程中并发执行任务。它允许在执行过程中挂起当前任务并在稍后恢复执行,这种切换是由程序员显式控制的,而非由操作系统调度器自动管理。协程通常用于处理需要高并发但对性能和资源消耗有严格要求的场景,如网络请求、I/O 操作等。

与线程不同,协程并不需要操作系统进行上下文切换,因为它们通常由程序本身的调度器管理,不会占用独立的操作系统资源。多个协程可以共享同一线程的栈空间,因此协程的创建和切换开销比线程要小得多,能够在极低的资源消耗下实现高效的并发。

58.乐观锁怎么实现?

乐观锁是一种基于“假设不会发生冲突”的锁机制,它与悲观锁不同,悲观锁假设会有冲突,因此在操作之前就锁定资源,而乐观锁假设冲突发生的概率较低,只有在提交操作时才检查是否有冲突。乐观锁常用于并发访问量较大的场景,可以减少锁的竞争,提高性能。实现乐观锁的方法主要有以下几种:基于版本号的乐观锁、基于时间戳的乐观锁和 CAS(比较并交换)算法。

基于版本号的乐观锁是最常见的实现方式之一。每次更新数据时,都会增加一个版本号,当操作完成后,检查当前版本号和数据库中保存的版本号是否相同。如果相同,则执行更新操作;如果不相同,说明数据已经被其他事务修改过,此时会放弃更新或者重新尝试。具体步骤包括在数据库表中增加一个版本号字段,在读取数据时读取版本号,在更新数据时,先检查数据库中的版本号,确保它与读取时的版本号一致

基于时间戳的乐观锁与版本号类似,区别在于它是使用时间戳来标识数据的修改时间。当读取数据时,获取数据的时间戳;在更新时,检查数据的时间戳是否与之前读取时一致。如果一致,执行更新操作;如果不一致,说明数据已被修改过,操作失败。具体步骤是:在数据库表中增加一个时间戳字段,在读取数据时获取时间戳,在更新时比较当前时间戳和记录的时间戳,如果一致则更新,否则返回操作失败。

CAS(Compare-And-Swap)算法是一种原子操作,通常在并发控制中广泛使用。CAS 通过比较内存中的值与预期值是否相等,如果相等则进行交换操作。CAS 是基于版本号或数据的预期值进行操作的,是乐观锁的一种实现方式。它通过原子比较和交换操作来保证数据更新的正确性,避免了多线程竞争中的不一致问题。在实现中,CAS 操作通常由硬件或 JVM 层提供,能够在并发环境下保证更新的原子性,常见于无锁编程中。

59.悲观锁和乐观锁有什么区别?

悲观锁:悲观锁假设在并发环境中资源争用较为频繁,因此对每个资源的访问都会进行加锁。它假定数据在操作期间可能被其他线程或进程修改,因此在执行操作前就对数据进行加锁,确保在执行时数据不会被并发访问。这种方式确保了数据的一致性,但也会导致较高的锁竞争和性能损失。

乐观锁:乐观锁假设在并发环境中资源争用较少,因此它不在操作前进行加锁,而是允许多个线程并发访问资源。它通过某种方式在提交操作时检查是否发生了冲突(如数据是否被其他线程修改),如果没有冲突,则执行操作;如果有冲突,则放弃或重新尝试。这种方式降低了锁竞争,提高了性能,但需要额外的检查机制来保证数据一致性。

60.自旋锁和互斥锁有什么区别?分别适合哪些应用场景?

自旋锁通过自旋的方式等待锁,适用于锁持有时间短的场景,具有较小的开销,但可能浪费CPU资源。

互斥锁通过线程阻塞的方式等待锁,适用于锁持有时间较长或竞争激烈的场景,避免了CPU浪费,但代价是需要进行线程上下文切换。

61.信号量和互斥锁应用场景有什么区别?

互斥锁:适用于保护临界区,只允许一个线程访问资源的场景,如全局变量、共享内存、文件等需要严格互斥访问的场景。

信号量:适用于资源数量有限且需要控制并发数的场景,如数据库连接池、线程池、对多个资源的并发访问控制等。#牛客AI配图神器#

全部评论

相关推荐

不愿透露姓名的神秘牛友
11-21 11:29
已编辑
斯卡蒂味的鱼汤:知道你不会来数马,就不捞你😂最近数马疯狂扩招,招聘要求挺低的,你能力肯定够,应该就是因为太强了,知道你不会来才不捞你
投递腾讯云智研发等公司10个岗位
点赞 评论 收藏
分享
评论
1
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务