操作系统
本文最后更新于 2024年12月14日 晚上
操作系统
内部结构
1. 用户态和内核态的区别
用户态
- 权限限制:在用户态下运行的程序拥有有限的系统资源访问权限,不能直接执行特权指令或访问硬件设备。
- 运行环境:用户态是应用程序的运行状态,程序在此状态下只能在操作系统划定的特定空间内运行。
- 系统调用:当用户程序需要操作系统帮助完成某些它没有权力和能力完成的工作时(如文件操作、网络通信等),会通过系统调用陷入到内核态。
- 安全性:由于权限受限,用户态可以防止用户程序对系统造成不可修复的破坏,从而提高了系统的安全性
内核态
- 高权限:内核态是操作系统内核的运行状态,具有最高权限,可以执行所有指令,访问所有内存地址。
- 资源访问:在内核态下运行的程序可以访问系统的所有资源,包括CPU、内存、I/O等。
- 中断处理:内核态下,操作系统可以响应所有的中断请求,处理硬件事件和系统调用。
- 稳定性:如果一个用户程序崩溃或出现错误,它不会影响整个系统的稳定性,因为内核态下的操作系统可以继续正常工作
用户态和内核态的区别
- 权限
- 用户态:运行在用户态的程序拥有有限的系统资源访问权限。
- 内核态:内核态具有最高权限,可以执行所有指令,访问所有内存地址。
- 资源访问
- 用户态:只能访问受限的资源,不能直接操作硬件设备或执行特权指令。
- 内核态:可以访问系统的所有资源,包括CPU、内存、I/O等。
- 系统调用
- 用户态:需要通过系统调用来请求操作系统提供的服务。
- 内核态:可以直接访问系统资源,不需要通过系统调用。
- 中断处理
- 用户态:只能响应部分中断请求。
- 内核态:可以响应所有的中断请求,处理硬件事件和系统调用。
- 内存访问
- 用户态:只能访问受限的内存地址。
- 内核态:可以访问所有的内存地址。
- 运行环境
- 用户态:应用程序在用户空间运行,受到操作系统的限制。
- 内核态:操作系统在内核空间运行,拥有对整个系统的控制权。
2. 用户态线程和内核态线程的区别
参考文章:[14 用户态和内核态:用户态线程和内核态线程有什么区别?](https://learn.lianglianglee.com/专栏/重学操作系统-完/14 用户态和内核态:用户态线程和内核态线程有什么区别?.md)
用户态线程
优点
- 管理开销小:创建、销毁不需要系统调用。
- 切换成本低:用户空间程序可以自己维护,不需要走操作系统调度。
缺点
- 与内核协作成本高:比如这种线程完全是用户空间程序在管理,当它进行 I/O 的时候,无法利用到内核的优势,需要频繁进行用户态到内核态的切换。
- 线程间协作成本高:设想两个线程需要通信,通信需要 I/O,I/O 需要系统调用,因此用户态线程需要支付额外的系统调用成本。
- 无法利用多核优势:比如操作系统调度的仍然是这个线程所属的进程,所以无论每次一个进程有多少用户态的线程,都只能并发执行一个线程,因此一个进程的多个线程无法利用多核的优势。
- 操作系统无法针对线程调度进行优化:当一个进程的一个用户态线程阻塞(Block)了,操作系统无法及时发现和处理阻塞问题,它不会更换执行其他线程,从而造成资源浪费。
内核态线程
优点
- 可以利用多核 CPU 优势:内核拥有较高权限,因此可以在多个 CPU 核心上执行内核线程。
- 操作系统级优化:内核中的线程操作 I/O 不需要进行系统调用;一个内核线程阻塞了,可以立即让另一个执行。
缺点
- 创建成本高:创建的时候需要系统调用,也就是切换到内核态。
- 扩展性差:由一个内核程序管理,不可能数量太多。
- 切换成本较高:切换的时候,也同样存在需要内核操作,需要切换内核态。
用户态线程和内核态线程的区别
用户态线程工作在用户空间,内核态线程工作在内核空间。用户态线程调度完全由进程负责,通常就是由进程的主线程负责(包括分配资源,时间片等)。相当于进程主线程的延展,使用的是操作系统分配给进程主线程的时间片段。内核线程由内核维护,由操作系统调度。
用户态线程无法跨核心,一个进程的多个用户态线程不能并发,阻塞一个用户态线程会导致进程的主线程阻塞,直接交出执行权限。这些都是用户态线程的劣势。内核线程可以独立执行,操作系统会分配时间片段。因此内核态线程更完整,也称作轻量级进程。内核态线程创建成本高,切换成本高,创建太多还会给调度算法增加压力,因此不会太多。
3. 哪几种情况会从用户态陷入内核态
- 系统调用:这是最常见的一种情况,是用户态主动要求切换到内核态的一种方式,当用户程序需要操作系统帮助完成某些它没有权力和能力完成的工作时(如文件操作、网络通信等),会通过系统调用申请使用操作系统提供的服务程序完成工作。例如,fork()函数实际上就是执行了一个创建新进程的系统调用。
- 异常处理:当CPU在执行运行在用户态的程序时,如果发生了某些事先不可知的异常(如缺页异常、非法指令等),这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态。
- 外围设备的中断:当外围设备(如硬盘、网卡等)完成用户请求的操作后,会向CPU发出相应的中断信号。CPU暂停执行下一条即将要执行的指令,转而去执行与中断信号对应的处理程序。如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换
4. 中断是什么
中断是计算机系统中的一种重要机制,它允许CPU在运行过程中响应外部或内部事件,暂停当前正在执行的程序,转而去处理紧急或优先级较高的任务。
- 中断的定义
- 基本定义:中断是指CPU在正常运行程序时,由于系统出现了某些需要立即处理的情况(如外部设备请求、定时器溢出等),CPU暂时中止现行程序的运行,自动转入相应的处理程序(即中断服务程序),待处理完毕后再返回原程序继续执行的过程。
- 中断源:任何能够引发中断的事件都称为中断源,包括外部设备(如键盘、鼠标、网络接口)和内部事件(如定时器溢出、程序错误等)。
- 中断的类型
- 硬件中断:由外部设备或内部硬件模块触发,如I/O设备中断、时钟中断等。硬件中断通常用于处理紧急或实时性要求高的任务。
- 软件中断:由CPU执行特定指令(如INT指令)引发,常用于系统调用和服务请求。软件中断通常用于实现操作系统与用户程序之间的交互。
- 中断的处理流程
- 中断请求:当系统出现需要处理的紧急情况时,会产生一个中断请求信号,该信号会被发送给中断控制器。
- 中断响应:中断控制器接收到中断请求信号后,会根据优先级选择一个中断源,并向CPU发出中断响应信号。CPU接收到中断响应信号后,会立即停止当前正在执行的程序,并把当前程序的现场信息保存到栈中。
- 中断处理:CPU根据中断向量表中的中断向量号,找到对应的中断服务程序的入口地址,并跳转到该地址开始执行中断服务程序。中断服务程序会根据中断源的不同,进行相应的处理。
- 中断返回:处理完中断后,CPU执行中断返回指令,恢复被中断程序的现场信息,并继续执行被中断的程序。
进程管理
1. 进程和线程有什么区别
进程 | 线程 | |
---|---|---|
基本定义 | 进程是操作系统资源分配的基本单位,它包含了程序运行所需的所有资源,如代码、数据、堆栈、打开的文件和网络连接等。每个进程都有独立的内存地址空间,因此一个进程的崩溃通常不会影响到其他进程 | 线程是线程是CPU调度的基本单位,是进程中的一个执行单元,负责执行程序中的特定任务。线程共享其所在进程的资源(如内存空间、文件描述符),但每个线程有自己的寄存器、栈和程序计数器。线程被称为轻量级进程,因为它们比进程占用更少的系统资源 |
资源开销 | 每个进程拥有独立的代码和数据空间,进程切换时需要保存和恢复大量的上下文信息(如CPU寄存器、程序计数器等),导致较大的开销。创建和销毁进程也需要更多的时间和资源 | 线程之间的切换开销较小,因为它们共享同一个进程的地址空间和资源,只需保存和设置少量的寄存器内容。创建和销毁线程的速度更快,所需资源也较少 |
独立性 | 进程是独立的执行单位,拥有自己的地址空间和资源,不同进程之间的通信需要通过特定的机制,如管道、消息队列、共享内存等 | 线程共享其所属进程的地址空间和资源,这使得线程间的通信和数据共享变得相对简单,可以通过共享内存、全局变量等方式进行通信 |
健壮性 | 一个进程崩溃后,通常不会影响其他进程的稳定性,因为每个进程都有独立的虚拟地址空间 | 由于线程共享进程的地址空间和资源,一个线程的崩溃可能会导致整个进程的崩溃 |
通信方式 | 多进程间的通信需要通过管道、消息队列、信号量、共享内存和套接字等方式实现,需要操作系统介入,通常具有较高的性能开销 | 多线程由于同一进程内的线程共享相同的内存空间,因此可以直接访问彼此的变量,使用互斥锁、条件变量、读写锁和信号量等方式。因为在同一个地址空间内,通信速度较快,切换开销较小 |
2. 需要线程的原因有哪些
- 并发能力强:由于进程间独立性强,并行执行的能力受到限制,特别是在需要频繁通信和数据共享的场景下;而线程由于共享其所属进程的地址空间和资源,因此线程间的通信和数据共享相对简单。在并行能力上线程要强于进程。
- 资源开销小:进程的创建和销毁涉及分配和回收大量系统资源(如内存、文件句柄等),导致较高的系统开销;而同一进程中的线程共享相同的地址空间和资源,因此线程间的通信和数据共享更加高效。线程的创建和销毁所需的系统资源较少。
- 切换开销小:操作系统在调度进程时,需要保存和恢复大量的上下文信息(如寄存器、程序计数器等),导致较大的调度开销;而线程切换时只需保存和恢复少量的上下文信息,调度开销相对较小,切换速度也更快。
- 通信方便:由于进程间独立性强,每个进程拥有独立的地址空间,因此多进程间的通信具有较高的性能开销;而同一进程内的线程共享相同的内存空间,因此通信速度较快,切换开销较小
3. 多线程是线程越多越好吗
回答:多线程不是线程越多越好
- 资源开销:每个线程都需要占用一定的系统资源,包括内存和CPU时间。如果线程数量过多,可能会导致系统资源的过度消耗,从而影响整体性能。如果在极端情况下导致线程崩溃,那么也会导致相对应的进程崩溃。
- 上下文切换:当线程数量增加时,操作系统需要频繁地进行上下文切换,以管理和调度这些线程。这种频繁的上下文切换会消耗大量的CPU时间,从而降低程序的运行效率。
- 数据共享:在多线程环境下,数据共享是一个复杂的问题。虽然同一进程内的线程可以共享内存空间,但这也带来了数据一致性和同步的问题。过多的线程可能会导致数据竞争和死锁等问题,从而影响程序的稳定性和性能。
- 适用场景:多线程并发适用于计算密集型任务较少、I/O密集型任务较多的情况,如Web服务器处理多个客户端请求等。然而,对于CPU密集型任务,过多的线程反而会导致性能下降,因为每个线程都需要占用CPU资源来执行任务。
4. 同一进程中的资源,哪些是线程间共享的,哪些是线程独占的
共享的资源
- 虚拟内存空间:进程的代码区、堆区、数据区的资源
- 文件描述符:共享进程打开的文件、套接字等资源
- 信号处理器:共享进程的信号处理器,用于接收和处理进程收到的信号
独占的资源
- 程序计数器:程序计数器记录了当前线程正在执行的指令地址,由于每个线程都可能在不同的代码路径上执行,因此它们各自拥有独立的程序计数器
- 寄存器组:每个线程在执行时,会使用一组寄存器来保存临时变量和计算结果,这些寄存器是线程私有的
- 栈帧:函数调用时会在线程的栈上创建栈帧,用于存储函数的参数、局部变量以及返回地址等信息。这些栈帧是线程私有的
- 线程优先级:线程优先级用于指导操作系统的调度器如何分配CPU时间给不同的线程。每个线程都有自己独立的优先级
5. 为什么创建进程比创建线程慢
回答:创建进程需要为进程划分完整的内存空间,包括代码区、堆栈、数据区等内存资源;而创建线程只需要PC指针和寄存器,并且分配一个栈桢即可。同一个进程中的线程可以复用进程的虚拟空间,减少创建虚拟内存的开销。
6. 为什么说进程的切换开销大
回答:进程在切换时,除了需要切换 CPU 上下文外,还需要切换页表,而切换页表会影响 TLB 的命中率,那么虚拟地址转换为物理地址就会变慢,就导致了程序执行变慢。而线程切换只需要切换 CPU 上下文,不需要改变虚拟空间地址,因此不会影响TLB命中率。
PS:
页表:将虚拟地址转换为物理地址需要查找页表
TLB:缓存常用的地址映射,加速页表查询
7. 进程有哪些状态
- 新建态:进程正在被创建,系统为其分配资源和初始化数据结构。此时,进程尚未开始执行,但已准备好进入就绪状态。
- 就绪态:进程已经具备运行条件,等待CPU分配以便开始执行。在就绪状态下,进程随时可以被调度器选中并投入运行。
- 运行态:进程正在CPU上执行指令,处于活动状态。这是进程实际进行工作的状态,也是进程生命周期中最关键的部分。
- 阻塞态:进程因等待某些事件(如I/O操作完成、资源释放等)而无法继续执行。在阻塞状态下,进程会释放CPU资源,并等待相关事件的完成。一旦事件完成,进程将被唤醒并转入就绪状态或继续执行。
- 终止态:进程已完成执行或因错误而被终止。在终止状态下,进程将释放所有资源并从系统中移除。
8. 解释一下什么是协程
回答:协程是用户态的线程,是一种轻量级的线程,上下文切换都是在用户态,相比于现场没有从用户态切换到内核态的成本。
9. 进程间有哪些通信方式
回答:进程间的通信方式主要有管道(Pipe)、消息队列(Message Queue)、共享内存(Shared Memory)、信号量(Semaphore)、套接字(Socket)和信号(Signal)等。
- 管道(Pipe)
- 匿名管道:用于具有亲缘关系的进程(如父子进程)之间的通信,数据只能单向流动。
- 命名管道(FIFO):允许无亲缘关系进程间的通信,数据也是单向流动,但通过文件系统中的命名管道实现。
- 消息队列(Message Queue)
- 消息队列是一种链表结构,存放在内核中,允许多个进程以消息为单位进行通信。它克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享内存(Shared Memory)
- 共享内存是最快的IPC(进程间通信)方式,允许多个进程直接访问同一段内存区域,适用于需要高效数据传输的场景。然而,它需要配合其他同步机制(如信号量)来避免数据竞争和不一致问题。
- 信号量(Semaphore)
- 信号量是一个计数器,用于控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。
- 套接字(Socket)
- 套接字不仅可以用于本地进程间通信,还可以用于不同机器间的进程通信。它支持基于TCP或UDP协议的通信方式。
- 信号(Signal)
- 信号是一种异步通信方式,用于通知接收进程某个事件已经发生。进程可以响应信号执行默认操作、捕捉信号或忽略信号。
10. 进程的调度算法有哪些
- 先来先服务(FCFS):按照作业提交或进程变为就绪状态的先后次序分派CPU。当前作业或进程占用CPU,直到执行完或阻塞,才出让CPU(非抢占方式)。
- 时间片轮转法(RR):基于适中的抢占策略,以一个周期性间隔产生时钟中断,当中断发生后,当前正在运行的进程被置于就绪队列中,然后基于先来先服务策略选择下一个就绪作业的运行。每个进程被分配一个时间片,在时间片用完后,如果进程还未完成,则它被放回就绪队列的末尾等待下一次调度。
- 最短进程优先(SPF):选择预计处理时间最短的进程进行调度。该算法对长作业不利,不能保证紧迫性作业(进程)被及时处理。
- 最短剩余时间优先(SRTF):针对最短进程优先增加了抢占机制的版本。进程调度总是选择预期剩余时间最短的进程。
- 最高响应比优先(HRRN):根据比率R=(w+s)/s计算响应比,其中w为等待处理的时间,s为预计的服务时间。选择R值最大的就绪进程进行调度。
- 多级反馈队列调度算法:设置多个就绪队列,并为各个队列赋予不同的优先级。新进程进入内存后,首先放入第一队列的末尾,按先来先去原则排队等候调度。如果未完成,就转入第二队列的末尾,同样等待调度……如此下去,当一个长作业从第一队列依次降到第n队列后,便按第n队列时间片轮转运行。仅当第一队列空闲的时候,调度程序才调度第二队列中的进程运行;仅当第1到(i-1)队列空时,才会调度第i队列中的进程运行,并执行相应的时间片轮转。
- Linux进程调度算法:包括SCHED_OTHER(分时调度策略)、SCHED_FIFO(实时调度策略,先到先服务)和SCHED_RR(实时调度策略,时间片轮转)。实时进程将得到优先调用,实时进程根据实时优先级决定调度权值。
网络IO
1. 介绍一下 Linux 五种 IO 模型
- 阻塞式I/O(Blocking I/O)
- 定义:在阻塞式I/O模型中,当用户进程请求数据时,如果数据不可用,则用户进程会被挂起,直到内核将数据准备好并复制到用户进程中。
- 特点:简单易用,但效率不高,因为用户进程在等待数据时无法执行其他操作。
- 适用场景:适用于单线程、同步、串行的应用程序,如文件传输、打印机等。
- 非阻塞式I/O(Non-blocking I/O)
- 定义:非阻塞式I/O允许用户进程在等待数据准备完成的过程中执行其他操作。
- 特点:提高了程序的效率和吞吐量,但需要应用程序不断轮询内核缓冲区,以判断数据是否准备好。
- 适用场景:适用于需要高并发处理的场景,如网络服务器等。
- IO多路复用(I/O Multiplexing)
- 定义:IO多路复用可以同时监控多个文件描述符,提高了应用程序对输入输出操作的管理能力。
- 常见实现:select、poll、epoll等函数。
- 特点:允许单个进程同时处理多个网络连接的I/O,系统开销小,但处理大量连接时性能可能受限。
- 适用场景:适用于需要同时处理多个网络连接的场景,如聊天室服务器等。
- 信号驱动式I/O(Signal-driven I/O)
- 定义:信号驱动式I/O允许应用程序在数据准备好时接收到通知,从而避免了轮询。
- 特点:通过信号通知用户进程数据传输完成,减少了CPU的无效占用。
- 适用场景:适用于需要异步处理I/O操作的场景,如实时性要求较高的应用。
- 异步I/O(Asynchronous I/O)
- 定义:异步I/O可以让应用程序在发起异步读写操作后立即返回,并不等待操作完成。
- 特点:不阻塞请求进程,通过回调函数或状态通知来告知操作完成,提高了程序的响应速度和可伸缩性。
- 适用场景:适用于需要高并发和高性能的场景,如大规模分布式系统、实时数据处理等。
2. 什么是 Socket?介绍一下 Socket
基本概念
- 定义:Socket(套接字)是一种网络编程接口,用于描述IP地址和端口,是不同主机间进程进行双向通信的端点。可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。它是网络环境中进程间通信的API(应用程序编程接口),也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 Socket中,该 Socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的 Socket中,使对方能够接收到这段信息。 Socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制。
- 作用:Socket提供了应用层进程利用网络协议交换数据的机制,构成了单个主机内及整个网络间的编程界面。
工作原理
- 数据传输过程:应用程序产生的数据通过传输层协议(如TCP或UDP)进行传输,而应用程序不会直接与传输层建立联系,而是通过Socket这一中间层来实现。
- Socket连接过程:包括服务器监听、客户端请求和连接确认三个步骤。在TCP/IP协议中,还涉及TCP三次握手的连接建立过程。
主要类型
- 流式套接字(SOCK_STREAM):基于TCP协议,提供面向连接、可靠的数据传输服务。
- 数据报套接字(SOCK_DGRAM):基于UDP协议,提供无连接、不可靠的数据传输服务。
- 原始套接字(SOCK_RAW):可以读写内核没有处理的IP数据包,主要用于一些协议的开发和底层操作。
应用场景
- 服务器与客户机通信:Socket通常用于实现服务器与客户机之间的通信模型,服务器监听特定端口等待客户机连接请求。
- 网络编程:Socket是网络编程中不可或缺的部分,广泛应用于各种网络应用程序的开发中。
特点
- 跨平台性:Socket作为一种通用的网络编程接口,具有跨平台的特性,可以在不同操作系统上实现网络通信。
- 灵活性:支持多种传输层协议(如TCP和UDP),可以根据应用需求选择不同的传输方式。
Socket 编程 API
函数名 作用 socket 根据指定的地址族、数据类型和协议来分配一个socket的描述字及其所用的资源。 bind 把一个地址族中的特定地址赋给socket listen 监听socket connect 连接某个socket accept TCP服务器监听到客户端请求之后,调用accept()函数取接收请求 read 读取socket内容 write 向socket写入内容,其实就是发送内容