操作系统基础知识
线程和进程的区别是什么?
- 本质区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
- 在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小
- 稳定性方面:进程中某个线程如果崩溃了,可能会导致整个进程都崩溃。而进程中的子进程崩溃,并不会影响其他进程。
- 内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源
- 包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线
管道有几种方式?
管道在Linux中有两种方式:匿名管道和命名管道。
- 匿名管道:是一种在父子进程或者兄弟进程之间进行通信的机制,只能用于具有亲缘关系的进程间通信,通常通过pipe系统调用创建。
- 命名管道:是一种允许无关的进程间进行通信的机制,基于文件系统,可以在不相关的进程之间进行通信。
信号和信号量有什么区别?
- 信号:一种处理异步事件的方式。信号是比较复杂的通信方式,用于通知接收进程有某种事件发生,除了用于进程外,还可以发送信号给进程本身。
- 信号量:进程间通信处理同步互斥的机制。是在多线程环境下使用的一种设施,它负责协调各个线程,以保证它们能够正确,合理的使用公共资源。
共享内存怎么实现的?
共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。
为什么进程崩溃不会对其他进程产生很大影响
- 进程隔离性:每个进程都有自己独立的内存空间,当一个进程崩溃时,其内存空间会被操作系统回收,不会影响其他进程的内存空间。这种进程间的隔离性保证了一个进程崩溃不会直接影响其他进程的执行。
- 进程独立性:每个进程都是独立运行的,它们之间不会共享资源,如文件、网络连接等。因此,一个进程的崩溃通常不会对其他进程的资源产生影响。
进程是分配资源的基本单位,那么这个资源指的是什么?
虚拟内存、文件句柄、信号量等资源。
多线程比单线程的优势,劣势?
- 多线程比单线程的优势:提高程序的运行效率,可以充分利用多核处理器的资源,同时处理多个任务,加快程序的执行速度。
- 多线程比单线程的劣势:存在多线程数据竞争访问的问题,需要通过锁机制来保证线程安全,增加了加锁的开销,并且还会有死锁的风险。多线程会消耗更多系统资源,如CPU和内存,因为每个线程都需要占用一定的内存和处理时间。
多线程是不是越多越好,太多会有什么问题?
多线程不一定越多越好,过多的线程可能会导致一些问题。
- 切换开销:线程的创建和切换会消耗系统资源,包括内存和CPU。如果创建太多线程,会占用大量的系统资源,导致系统负载过高,某个线程崩溃后,可能会导致进程崩溃。
- 死锁的问题:过多的线程可能会导致竞争条件和死锁。竞争条件指的是多个线程同时访问和修改共享资源,如果没有合适的同步机制,可能会导致数据不一致或错误的结果。而死锁则是指多个线程相互等待对方释放资源,导致程序无法继续执行。
进程切换和线程切换的区别?
- 进程切换:进程切换涉及到更多的内容,包括整个进程的地址空间、全局变量、文件描述符等。因此,进程切换的开销通常比线程切换大。
- 线程切换:线程切换只涉及到线程的堆栈、寄存器和程序计数器等,不涉及进程级别的资源,因此线程切换的开销较小。
线程切换为什么比进程切换快,节省了什么资源?
线程共享同一进程的地址空间和资源,线程切换时只需切换堆栈和程序计数器等少量信息,而不需要切换地址空间,避免了进程切换时需要切换内存映射表等大量资源的开销,从而节省了时间和系统资源。
进程的状态(五种状态),如何切换?

进程间通讯有哪些方式?
- 管道
- 消息队列
- 共享内存
- 信号
- 信号量
- socket
Linux 进程间通信方式
-
管道(Pipe)
- 匿名管道:内存中特殊文件,单向数据流,仅父子进程使用,生命周期随进程结束。
- 命名管道(FIFO):文件系统中创建p类型设备文件,突破亲缘限制,无关系进程可通信,遵循FIFO,不支持定位。
-
消息队列
- 内核中消息链表,支持自定义数据类型消息,解决管道无格式问题;需用户态/内核态拷贝,实时性一般。
-
共享内存
- 直接分配共享空间,进程可直接访问,无内核态拷贝,速度最快;需配合信号量解决多进程竞争问题。
-
信号量(Semaphore)
- 计数器型同步机制,P/V原子操作控制资源访问,实现互斥与同步,保护共享内存等资源。
P 操作:申请资源 → 计数器 - 1 → 若结果 < 0,进程阻塞等待;若≥0,继续执行。
V 操作:释放资源 → 计数器 + 1 → 若结果≤0,唤醒一个等待的进程;若 > 0,无等待进程,直接结束。
-
信号(Signal)
- 异步通信机制,内核/硬件/软件可向进程发事件通知;进程可默认/捕捉/忽略,SIGKILL/SIGSTOP不可捕捉。
-
Socket
- 跨主机/本地进程通信,支持TCP(可靠)、UDP(不可靠)、本地域套接字,是网络通信基础。
线程间通信方式
-
互斥锁(Mutex)
- 作用:保护共享资源,保证临界区同一时间仅一个线程执行。
- 特点:加锁后其他线程阻塞,解锁后唤醒竞争;开销较大,避免死锁需按序获取锁。
-
读写锁(R/W Lock)
- 作用:区分读/写操作,读共享、写独占。
- 特点:读多写少场景效率高;写锁阻塞所有读写,读锁允许多线程并发读。
-
条件变量(Cond)
- 作用:实现「等待-通知」同步,配合互斥锁使用。
- 特点:线程等待条件成立时挂起,条件满足后被唤醒;解决生产者-消费者等同步问题。
-
自旋锁(Spinlock)
- 作用:用户态轻量锁,通过CAS实现原子操作。
- 特点:竞争锁时忙等待(不主动切换上下文),开销小;适合锁持有时间极短的场景。
-
信号量(Semaphore)
- 作用:控制资源访问次数,支持计数同步。
- 特点:P操作(减1,不足则阻塞)、V操作(加1,唤醒等待者);可用于进程/线程间同步。
除互斥锁外的常见锁/同步机制
-
读写锁
允许多线程同时读,仅单线程写;读多写少场景,提升并发效率。 -
自旋锁
加锁失败不阻塞,循环忙等;临界区极小、锁持有时间极短,避免线程切换开销。 -
条件变量
配合互斥锁实现等待-通知;用于线程需等待某条件成立(如生产者-消费者)。 -
信号量
计数器,P/V原子操作;可控制并发数、实现同步与互斥。
进程调度算法
-
先来先服务 FCFS
按到达顺序执行,非抢占;利于长作业,不利于短作业。 -
短作业优先 SJF
优先运行预计执行时间最短的进程;平均等待时间最短,但可能导致长作业饥饿。 -
优先级调度
按进程优先级运行,可抢占/非抢占;优先级高先运行,可能产生饥饿。 -
时间片轮转 RR
公平分配 CPU 时间片,抢占式;适合分时系统,响应快。 -
多级反馈队列
多个优先级队列,时间片逐级增大;新进程进高优先级,用完降级;综合性能好,Linux 常用。 -
高响应比优先 HRRN
响应比 = (等待时间+服务时间)/服务时间;兼顾长短作业,避免饥饿。
为什么并发执行线程要加锁?
因为多个线程并发操作共享资源时,会产生竞态条件,导致数据错乱、结果不可预期。
加锁是为了把并行操作变成串行临界区,保证同一时刻只有一个线程修改共享数据,从而保证数据一致性。
死锁四个必要条件
- 互斥:资源同一时间只能被一个线程占有
- 持有并等待:线程持有资源,又申请其他资源,且不释放已持有资源
- 不可剥夺:资源只能由持有线程主动释放,不能被强行抢占
- 环路等待:线程之间形成循环等待资源的环
如何避免死锁
破坏任意一个条件即可。
最常用:按固定顺序申请锁,破坏环路等待。
银行家算法
- 作用:避免死锁的经典算法,在分配资源前先判断系统是否安全。
- 思想:像银行放贷一样,只在能保证系统安全时才分配资源,防止“坏账(死锁)”。
- 核心步骤
- 计算每个进程还需要的资源
- 检查当前剩余资源能否满足某进程运行
- 若可以,让它运行完并释放所有资源
- 重复直到所有进程都能顺利执行,得到安全序列
- 关键结论
- 存在安全序列 → 系统安全,不会死锁
- 无安全序列 → 可能死锁,不分配资源
内存管理
网络I/O
-
阻塞 IO
发起 IO 后一直阻塞,直到数据就绪并拷贝完成。简单,并发差。 -
非阻塞 IO
立即返回,需轮询检查是否就绪;不阻塞但 CPU 消耗高。 -
IO 多路复用
用 select/poll/epoll 同时监听多个 fd,哪个就绪处理哪个。高并发服务端常用。 -
信号驱动 IO
内核数据就绪时发信号通知,再由程序去拷贝。异步通知,但拷贝仍阻塞。 -
异步 IO
内核完成所有 IO(就绪+拷贝)后再通知进程,全程不阻塞。真正异步,性能最好。
讲一下io多路复用
IO多路复用是一种IO的处理方式:复用一个线程,处理多个socket中的事件。能够资源复用,防止创建过多线程导致的上下文切换的开销。
select/poll/epoll 内核提供给用户态的多路复用系统调用,进程可以通过一个系统调用函数从内核中获取多个事件。
select/poll
select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。
所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。
select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。
poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。
但是 poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。
select/poll 实现及缺陷
1. select 核心逻辑
- 用固定长度 BitsMap 存待检测 fd,默认最多 1024 个;
- 全程 2 次拷贝(用户态→内核态→用户态)、2 次遍历(内核遍历查事件,用户态遍历找就绪 fd);
- 时间复杂度 O(n),fd 越多性能越差。
2. poll 改进与缺陷
- 用动态数组(链表) 替代 BitsMap,突破 1024 fd 限制;
- 但核心逻辑和 select 一致:仍需 2 次拷贝、2 次遍历,时间复杂度还是 O(n),高并发下性能损耗大。
总结
select/poll 本质都是「线性遍历+全量拷贝」,高并发场景下效率低;epoll 用红黑树+事件驱动优化了这两个核心问题。
epoll
先复习下 epoll 的用法。如下的代码中,先用epoll_create 创建一个 epol l对象 epfd,再通过 epoll_ctl 将需要监视的 socket 添加到epfd中,最后调用 epoll_wait 等待数据。
int s = socket(AF_INET, SOCK_STREAM, 0);
bind(s, ...);
listen(s, ...)
int epfd = epoll_create(...);
epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中
while(1) {
int n = epoll_wait(...);
for(接收到数据的socket){
//处理
}
}
epoll 通过两个方面,很好解决了 select/poll 的问题。
- 第一点,epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过 epoll_ctl()函数加入内核中的红黑树里,红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn)。而 select/poll 内核里没有类似epoll 红黑树这种保存所有待检测的 socket 的数据结构,所以 select/poll 每次操作时都传入整个 socket集合给内核,而 epoll 因为在内核维护了红黑树,可以保存所有待检测的 socket ,所以只需要传入一个待检测的
socket,减少了内核和用户空间大量的数据拷贝和内存分配。 - 第二点, epoll 使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait()函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket集合,大大提高了检测的效率。
- 数据结构层面
epoll 内核用红黑树保存所有待检测 fd,增删改 O(logn),仅需传递单个fd 给内核;select/poll无持久化结构,每次需传递全量 fd 集合,大量拷贝开销。
- 检测机制层面
epoll 是事件驱动,内核维护就绪链表,仅返回有事件的 fd;select/poll 需轮询全量 fd 检测状态,fd 越多效率越低。
epoll 边缘触发(ET) vs 水平触发(LT)
-
水平触发(LT)
只要 fd 满足就绪条件(如内核有数据),epoll_wait 就持续通知,直到数据读完;无需一次性读/写完毕,兼容阻塞/非阻塞 fd,是 epoll 默认模式。 -
边缘触发(ET)
仅当 fd 状态从未就绪变为就绪时通知一次;需一次性读完/写完内核缓冲区数据,必须搭配非阻塞 fd(避免读/写空时阻塞),减少 epoll_wait 调用次数,效率更高。
通俗类比
- LT:快递箱一直发短信提醒取件,直到取走为止;
- ET:快递箱只发一次取件短信,不管是否取走都不再提醒。
总结
- LT 更简单、不易漏数据,适合简单场景;
- ET 减少系统调用开销,高并发场景更高效,但编程复杂度高(需循环读写+非阻塞 fd)。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)