操作系统笔记

该博客围绕操作系统展开,详细介绍了进程和线程的概念、区别、创建方式、通信与同步方法等,还探讨了堆和栈、上下文、并发互斥、内存管理等内容,如内存分配、虚拟内存、内存碎片等,同时提及死锁处理、系统调用等知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

操作系统

进程和线程

什么是进程?什么是线程?

进程是资源分配的基本单位,它是程序执行时的一个实例,在程序运行时创建。
线程是程序执行的最小单位,是进程的一个执行流,一个线程由多个线程组成的。

进程和线程有什么区别?

进程是资源分配的最小单位,线程是程序执行的最小单位。

进程拥有独立的地址空间,线程共享相同的地址空间。

进程间切换开销较大,线程间切换开销较小。

何时使用多进程,何时使用多线程?

对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。

进程有几种状态?画一下进程状态转换图?
创建进程有哪几种方式?

创建进程的几种方式包括:

  1. fork()系统调用:通过在父进程中调用fork(),创建一个与父进程相同的子进程,子进程从fork()函数返回处开始执行。
  2. exec()系列系统调用:通过在进程中调用exec()系列函数,将当前进程的地址空间替换为新的可执行程序,从而创建一个新的进程并开始执行。
进程间通信方式有哪些?有什么优缺点?
  1. 管道(Pipe):单向通信,适合具有亲缘关系的进程,但只能在父子进程或兄弟进程之间使用。
  2. 信号量(Semaphore):用于进程间同步和互斥操作,可以实现临界区保护和资源共享,但复杂度较高。
  3. 消息队列(Message Queue):可以实现进程间的异步通信,通过系统内核进行消息传递,但需要编程复杂。
  4. 共享内存(Shared Memory):多个进程共享一块内存区域,读写效率高,但需要自己进行同步和互斥操作。
  5. 套接字(Socket):适用于网络编程和分布式系统,可以实现不同主机之间的进程通信,但复杂度较高。

总结:管道简单但局限性高;信号量可用于同步和互斥,但复杂度较高;消息队列实现异步通信,但编程复杂;共享内存读写效率高,但需要自行处理同步;套接字适用于网络编程,但复杂度较高。

线程间同步方法有哪些?

临界区、互斥锁、信号量和事件是常用的线程间同步方法。临界区用于串行化访问共享资源,互斥量实现资源的独占访问,信号量控制并发线程数量,事件用于线程间的通知机制。选择适当的方法可以实现线程间的同步和互斥操作。

内核线程和用户线程?

内核线程由操作系统内核创建和管理,运行在内核态,拥有直接访问系统资源的权限。

用户线程由应用程序开发者创建和管理,运行在用户态,依赖于操作系统提供的线程库进行调度。内核线程消耗更多系统资源,而用户线程的创建和切换开销相对较小。

内核线程和用户线程有什么优缺点?

内核线程的优点是具有更高的性能和更广的功能,可以直接访问系统资源;缺点是创建和切换开销较大。

用户线程的优点是轻量级且创建和切换开销相对较小;缺点是受限于用户态的限制,无法直接访问系统资源。

什么是僵尸进程,孤儿进程,守护进程?

僵尸进程是指子进程在结束运行后,父进程未能及时回收其资源导致的进程状态,它已经停止运行但仍然占用系统资源。

孤儿进程是指父进程先于子进程退出,子进程成为没有父进程的进程,由 init 进程接管其管理。

守护进程是在后台运行的长期运行的进程,通常用于系统服务或后台任务,独立于终端会话,并且不受用户登录或注销的影响。

僵尸进程有什么危害?

僵尸进程占用系统资源,包括进程标识符(PID)和一些内核数据结构,会导致系统资源的浪费。如果僵尸进程过多积累,可能会耗尽系统资源,导致系统运行不稳定或崩溃。

如何清理僵尸进程?

通过父进程调用wait()或waitpid()系统调用来回收子进程的资源,确保子进程正常退出。

如何唤醒被阻塞的socket线程?

发送一个信号或者通过消息队列通知线程,从而使其从阻塞状态返回并继续执行。

如何确定当前线程是繁忙还是阻塞?

可以通过ps命令检查线程的状态或者当前所执行的任务/代码来确定当前线程是繁忙还是阻塞。如果线程正在主动执行任务或代码,则可以判断为繁忙;而如果线程在等待某个资源、锁或IO操作完成时,则可以判断为阻塞。

空闲的进程和阻塞的进程状态会不会在唤醒的时候误判?

在唤醒时,空闲的进程和阻塞的进程状态有可能被误判,因为它们在外部观察上可能非常相似。

唤醒操作需要根据具体的上下文和条件进行判断,以避免误判并确保准确地唤醒目标线程。

请问就绪状态的进程在等待什么?

就绪状态的进程在等待系统资源(如CPU时间片、内存等)以及满足执行条件(如等待某个事件完成或等待某个条件变为真)时,等待被调度器选择执行

如何实现线程池?

实现线程池需要创建一个固定数量的线程集合,并使用任务队列来管理待执行的任务

线程池接收任务后,将任务放入任务队列中,空闲的线程从队列中获取任务执行,执行完毕后继续获取下一个任务,以此循环重复

请你回答一下fork和vfork的区别?

fork 是创建一个子进程,子进程会复制父进程的所有资源,包括代码、数据和文件描述符;

vfork 是创建一个共享地址空间的子进程,子进程会共享父进程的地址空间,通常用于在子进程中立即执行 exec 系统调用,不会复制父进程的资源。

server端监听端口,但还没有客户端连接进来,此时进程处于什么状态?

当服务器端监听端口但没有客户端连接进来时,进程处于阻塞状态,等待客户端的连接请求。

具体来说,它可能在 accept() 系统调用处于阻塞状态,等待接受客户端的连接请求。

堆和栈

什么是代码段,数据段,bss段,堆,栈?

代码段是存储程序执行指令的区域;数据段是存储已初始化的全局变量和静态变量的区域;bss段(Block Started by Symbol)是存储未初始化的全局变量和静态变量的区域;堆用于动态内存分配,由程序员手动管理;栈用于存储函数调用时局部变量函数调用信息,由编译器自动管理。

为什么堆的空间不是连续的?

堆的空间不是连续的,主要是因为在动态内存分配过程中,频繁的申请和释放可能导致堆内存块的碎片化,留下了无法利用的间隙。这些空隙可能太小而无法容纳新的内存分配需求,因此堆空间不是完全连续的。

什么是用户栈和内核栈?

用户栈是用于存储用户程序的函数调用信息、局部变量和临时数据的栈空间,位于用户空间;内核栈是用于保存内核执行过程中的函数调用信息、异常处理等的栈空间,位于内核空间。两者分别用于不同的执行环境,用户栈用于用户程序执行,内核栈用于内核执行。

用户栈和内核栈,为什么不能共用一个栈?

用户栈和内核栈不能共用一个栈是因为它们分别用于存储用户空间和内核空间的运行时数据。

用户栈用于保存用户程序的函数调用信息及局部变量,而内核栈用于保存内核执行过程中的函数调用信息、异常处理等,为了保证安全和隔离,它们需要分开使用不同的栈空间

线程具有相同的堆栈?

线程在共享相同的代码段和堆,但每个线程都有独立的栈空间。每个线程有自己的栈空间用于存储局部变量、函数调用信息和返回地址等。

上下文

上下文有哪些?怎么理解?

上下文通常包括栈指针(SP)、程序计数器(PC)和通用寄存器(如EAX、EBX等)等。它是保存和恢复程序执行状态的关键信息集合。通过保存和切换上下文,操作系统能够实现多任务调度、异常处理和进程间的切换等功能。

为什么会有上下文这种概念?

上下文的存在是为了保存和恢复程序的执行状态,在多任务操作系统中,可以在不同的任务之间进行切换,实现并发执行。上下文记录了当前任务的执行位置和环境,使得任务能够在中断、异常或切换时正确地恢复执行,保证了系统的正常运行任务的无缝切换

什么情况下进行用户态到内核态的切换?

用户态到内核态的切换发生在进行系统调用、异常处理和任务切换等情况下,通过切换上下文来实现。这种切换允许用户程序执行特权操作和访问受保护资源,以及保证系统的稳定和多任务的正常切换。

中断上下文代码中有哪些注意事项?

在中断上下文代码中需要注意的事项包括:尽量避免使用阻塞操作和长时间的计算,因为中断处理程序需要尽快完成以确保系统的响应性;对共享资源进行合适的同步和互斥操作,以防止并发访问导致的数据一致性问题。

请问线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的?

线程需要保存的上下文包括栈指针(SP)、程序计数器(PC)和通用寄存器(如EAX、EBX等)。栈指针用于管理函数调用和局部变量的内存空间,程序计数器指示当前执行的指令位置,而通用寄存器则用于存储临时数据和运算结果。这些上下文的保存和恢复实现了线程的暂停和继续执行。

并发和互斥
驱动里面为什么要有并发、互斥的控制?如何实现?

驱动程序中需要有并发和互斥的控制,主要是为了保护共享资源,防止并发访问带来的数据竞争和不一致性问题,确保驱动的正确性和可靠性。

实现可以通过信号量、互斥锁、自旋锁等同步机制进行。

自旋锁是什么?信号量是什么?二者有何异同?

自旋锁是一种轻量级的线程同步机制,线程在尝试获取锁时会一直自旋等待,不会主动放弃CPU执行权。

信号量是一种用于控制对共享资源的访问的计数器,线程需要获取信号量才能继续执行,如果计数器为0则线程会被阻塞,直到有其他线程释放信号量。

二者的异同点在于实现方式适用场景:自旋锁适用于资源占用时间短竞争激烈的情况,但会占用CPU资源;信号量适用于资源占用时间长竞争不激烈的情况,线程在等待期间会被阻塞,不会占用CPU资源

自旋锁和信号量可以睡眠吗?为什么?

自旋锁不会主动睡眠,因为它在尝试获取锁时一直忙等待(自旋),不会主动放弃CPU执行权。

信号量可以使线程睡眠,因为当线程请求获取信号量时,如果计数器为0,线程会被阻塞并进入睡眠状态,直到有其他线程释放信号量唤醒它。

自旋锁和信号量可以用于中断中吗?

自旋锁不适合在中断中使用,因为中断上下文中的代码执行不能被阻塞,自旋锁会导致中断无法正常响应。

信号量可以用于中断中。中断处理程序在需要访问资源时,会先检查信号量的状态,如果信号量被其他程序占用,中断处理程序就会等待。只有当其他程序释放信号量时,中断处理程序才能继续执行并访问资源。这样可以确保在中断期间对共享资源的安全访问。

读写锁是什么?

读写锁是一种并发控制机制,它允许多个线程同时读共享资源,但只允许一个线程写共享资源,通过提供更高的并发性来优化读操作和写操作的性能。

产生死锁的原因是什么?

死锁的原因是多个进程或线程互相持有彼此所需的资源,并且彼此等待对方释放资源,导致所有进程或线程都无法继续执行。

死锁的4个必要条件是什么?

死锁的四个必要条件是:互斥条件(资源独占性)、请求与保持条件(已获得资源的进程可以继续请求其他资源)、不可剥夺条件(已分配的资源不能被强制性收回)、循环等待条件(存在进程之间的资源循环等待)。

如何避免死锁?

为了避免死锁,可以使用以下方法:破坏死锁的四个必要条件之一,如避免循环等待、统一资源分配顺序;使用资源分配图或银行家算法进行资源的合理分配和管理。

死锁的处理方式有哪些?

死锁的处理方式包括预防、避免、检测和解除。

预防死锁通过破坏死锁的四个必要条件来防止死锁的发生;

避免死锁通过动态地分配资源,避免进入可能导致死锁的状态;

检测死锁则是定期检查系统的资源分配状态,一旦发现死锁,采取相应的措施进行恢复;

解除则是通过剥夺进程资源、回滚或重启等手段解除已经发生的死锁。

请问单核机器上写多线程程序,是否需要考虑加锁,为什么?

在单核机器上写多线程程序,确实需要考虑加锁。尽管在单核机器上只能同时执行一个线程,但当多个线程竞争共享资源时,仍然存在并发访问的问题。使用锁可以确保在任意时刻只有一个线程能够访问共享资源,避免数据竞争和不一致性的问题。此外,还可以利用锁来实现线程的同步和互斥。

内存

在1G内存的计算机中能否malloc(1.2G)?为什么?

malloc()函数申请的内存空间大小受限于物理内存的大小,因为它需要在物理内存中分配对应大小的连续内存块

在1G内存的计算机中无,法直接使用malloc(1.2G)分配1.2G的内存空间,因为要分配的内存大于可用的内存大小,会导致内存分配失败。

malloc能申请多大的空间?

在理论上,malloc函数可以申请的空间大小取决于系统的限制和物理内存的大小。然而,在实际情况下,malloc能够成功申请的最大空间受到多个因素的限制,例如操作系统的限制编译器的限制可用内存的大小等。一般来说,32位系统上,malloc通常能够申请几十MB或者更大的空间,64位系统上,能够申请的空间更大,达到数TB甚至更多。但具体能够申请多大的空间还是要依赖于具体的环境和配置。

内存管理有哪几种方式?

静态分配,通过编译器在程序编译时分配固定大小的内存;

栈式分配,通过函数调用栈自动分配和释放内存;

堆式分配,由程序员手动申请和释放内存,使用malloc、free等函数;

内存池管理,提前申请一块较大的内存,并将其划分成多个大小相等的内存块。内存池会维护一个空闲内存块的列表,程序可以从列表中获取空闲内存块并进行使用。

当程序需要申请内存时,内存池会从空闲内存块列表中选择一个合适大小的内存块进行分配,并将其标记为已使用。当程序释放内存时,内存池会将被释放的内存块重新标记为空闲,以供后续的内存分配请求使用。

内存池的好处是避免了频繁的内存分配与释放操作,减少了动态内存管理的开销和碎片化问题。同时,内存池还可以提高内存分配的效率降低内存泄漏的风险,特别适用于具有高频率内存分配与释放操作的场景,如网络服务器、数据库等。

什么是虚拟内存?

虚拟内存是计算机操作系统中一种将硬盘空间作为辅助内存的技术。它允许将部分或全部程序和数据存储在硬盘上,并通过内存管理单元将其映射到物理内存中,提供了更大的可用内存空间,以及更灵活的内存管理方式,提高了系统的性能和稳定性。

解释下内存碎片,内碎片,外碎片?

内存碎片是指在内存中存在的不连续、无法被完全利用的空闲内存空间。它分为两种类型:

内碎片是指分配给进程的内存块中未被利用的部分。例如,当一个进程申请一块大于其实际需要的内存空间时,会导致内存中存在未被利用的空闲内存。

外碎片是指分散在已分配内存块之间的不连续空闲内存块。虽然总体上有足够的空闲内存,但由于这些空闲内存不连续,无法满足某些内存需要较大的进程。

内存碎片的存在会浪费内存资源,并降低系统性能。为了减少内存碎片,操作系统会采取一些内存管理技术,如内存紧缩、内存清理和内存整理等。

解释下虚拟地址、逻辑地址、线性地址、物理地址?
  • 虚拟地址:由操作系统提供给进程使用的地址空间,它在逻辑上是连续的,但不一定与物理内存中的实际位置对应。
  • 逻辑地址:进程生成的相对于自身地址空间的地址,它是在程序中使用的抽象地址,需要通过地址转换才能映射到物理地址。
  • 线性地址:经过地址转换后得到的地址,位于进程的地址空间,可作为虚拟地址和物理地址之间的中间层。
  • 物理地址:实际对应于计算机系统中内存模块的地址,用于读写操作的物理位置。

逻辑地址通过地址转换变成线性地址,再经过页表映射成物理地址,从而实现虚拟内存的管理。

虚拟内存置换方式是怎么样的?

当物理内存不足以容纳所有进程所需的页面时,就需要进行页面置换

常见的置换方式包括最佳(OPT)、先进先出(FIFO)、最近最久未使用(LRU)等。

这些算法根据页面的访问情况和优先级来选择合适的页面进行置换,以释放物理内存空间供其他页面使用,从而实现虚拟内存的管理。

给你一个类,里面有static,virtual之类的,来说一说这个类的内存分布?

这个类的内存分布包括以下几个部分:

  1. 静态成员变量:静态成员变量在类的整个生命周期内只存在一份,存储在静态数据区。
  2. 静态成员函数:静态成员函数没有隐含的this指针,不依赖于特定对象的状态,因此它们不属于任何具体对象,存储在代码段。
  3. 虚函数表指针(vptr):如果该类定义了虚函数,每个对象都会有一个指向虚函数表的指针(vptr),存储在对象的内存布局的开头位置。
  4. 成员变量:非静态成员变量存储在对象的内存布局中,每个对象都有自己的一份。
  5. 虚函数表(vtable):如果该类定义了虚函数,针对每个类维护一个虚函数表,其中存储这些虚函数的地址。每个对象的vptr指向对应的虚函数表。
  6. 其他数据:可能还包括填充字节、指向基类的指针(如果有继承关系)、虚基类表指针等。

总之,类的内存分布包括静态成员变量和函数、虚函数表指针、成员变量、虚函数表以及其他相关数据。具体的内存分布会根据编译器和操作系统的不同而有所差异。

假设临界区资源释放,如何保证只让一个线程获得临界区资源而不是都获得?

可以使用互斥锁(Mutex)来实现。

互斥锁是一种同步机制,它可以确保在任意时刻只有一个线程能够访问临界区资源。具体步骤如下:

1.在进入临界区之前,先尝试获取互斥锁。

2.若互斥锁已被其他线程获取,则当前线程会被阻塞,直到互斥锁被释放。

3.若互斥锁未被其他线程获取,当前线程成功获取互斥锁,并可以进入临界区执行操作。

4.当线程完成对临界区资源的操作后,释放互斥锁,以便其他线程可以获取。

通过互斥锁的机制,保证了同一时间只有一个线程能够进入临界区,从而避免了多个线程同时访问临界区资源的问题,确保数据的正确性和一致性

操作系统中的缺页中断是什么?

缺页中断是指当程序访问的页面在物理内存中不存在时,操作系统会产生一个中断,触发页面调度机制将所需页面从磁盘加载到内存中,以满足程序的继续执行。

系统调用是什么,你用过哪些系统调用,和库函数有什么区别?

系统调用是操作系统提供给应用程序的接口,用于访问操作系统的功能和资源,如文件操作、网络通信等。常见的系统调用包括open、read、write等。

与库函数相比,系统调用是与操作系统内核直接交互的接口,而库函数则是在用户空间提供的封装了常用功能的函数库,依赖于系统调用实现底层功能。

为什么要有page cache,操作系统怎么设计的page cache?

Page cache是操作系统中的一种缓存机制,用于缓存磁盘上的文件数据,提高文件系统的性能。操作系统会将读取的文件数据暂时存储在内存中的page cache中,以加速后续对同一文件的读取操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值