6、其他拓展
大约 8 分钟操作系统面试题原创技术拓展程序员系统架构前沿技术
其它补充
Linux 的同步机制
🌟I/O 多路复用
- 边缘触发
- 水平触发
🌟大端序小端序
🌟零拷贝
BIO(同步阻塞)、NIO(同步非阻塞)、AIO(异步非阻塞)
- BIO 堵塞 IO:发现被使用,则一直等待
- NIO:发现被使用,先去干别的,然后每隔一段时间再回来,然后看看用完没。
- AIO:发现被使用,先去干别的,等结束了我通知你,你再回来
并发和并行的区别
- 并发(concurrency):指宏观上看起来两个程序在同时运行,比如说在单核 cpu 上的多任务。但是从微观上看两个程序的指令是交织着运行的,指令之间交错执行,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率(如降低某个进程的相应时间)。
- 并行(parallelism):指严格物理意义上的同时运行,比如多核 cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的 cpu 都是往多核方面发展。
什么是信号
一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。 Linux 系统上支持的 30 种不同类型的信号。 每种信号类型都对应于某种系统事件。低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。
发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。发送信号可以有如下两种原因:
- 内核检测到一个系统事件,比如除零错误或者子进程终止。
- —个进程调用了 kill 函数, 显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。
操作系统中用到了什么排序算法
进程调度:堆排序
文件系统排序:快速排序/归并排序
I/O 调度算法:插入排序/归并排序
内存管理:基数排序(变种)
网络协议栈:计数排序应用
了解的 I/O 模型有哪些
I/O 模型 | 阻塞? | 同步? | 核心机制 | 适用场景 |
阻塞 I/O | 是 | 同步 | 线程全程等待 | 简单低并发程序 |
非阻塞 I/O | 否 | 同步 | 线程轮询检查 | 需兼顾其他任务的程序 |
I/O 多路复用 | 是* | 同步 | `select`/`epoll` 监听 | 高并发网络服务 |
注:I/O 多路复用中,select
/epoll
调用本身是阻塞的,但可监听多个 I/O。
阻塞 I/O
- 调用 I/O 操作时,线程一直等待,直到数据就绪或操作完成。
- 期间线程无法执行其他任务(CPU 闲置)。
非阻塞 I/O
- 调用 I/O 操作时,若数据未就绪,立即返回错误(如
EWOULDBLOCK
)。 - 线程需轮询检查数据是否就绪(消耗 CPU)。
- 线程可执行其他任务(但需主动轮询)。
- 调用 I/O 操作时,若数据未就绪,立即返回错误(如
I/O 多路复用
- 使用
select
/poll
/epoll
等系统调用,单线程监听多个 I/O 事件。 - 当某个 I/O 就绪时,通知线程处理。
- 使用
常见的 I/O 多路复用机制
select
跨平台:支持所有主流操作系统(Linux/Windows/macOS)。
基于轮询:通过遍历文件描述符集合(
fd_set
)检查就绪状态。限制:
- 单个进程最多监听 1024 个文件描述符(FD)。
- 每次调用需全量拷贝
fd_set
到内核。
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(fd1, &read_fds); _// 添加监听 fd_
int ret = select(fd1 + 1, &read_fds, NULL, NULL, NULL); _// 阻塞等待_
if (FD_ISSET(fd1, &read_fds)) { _/* 处理就绪的 fd */_ }
- 缺点
- O(n) 时间复杂度:每次遍历所有 FD,性能随 FD 数量线性下降。
- 重复初始化:每次调用需重新设置
fd_set
。
poll
- 改进
select
的 FD 数量限制,使用链表存储 FD(理论无上限)。 - 仍需要遍历所有 FD 检查就绪状态(O(n) 时间复杂度)。
struct pollfd fds[2];
fds[0].fd = fd1; fds[0].events = POLLIN;
int ret = poll(fds, 2, timeout); _// 阻塞等待_
if (fds[0].revents & POLLIN) { _/* 处理就绪的 fd */_ }
**对比 **select
特性 | select | poll |
FD 数量 | 1024(硬编码) | 无限制(受内存约束) |
性能 | O(n) | O(n) |
编程复杂度 | 需手动管理 fd_set | 结构体更清晰 |
epoll(Linux 专属)
- 事件驱动:内核通过回调机制直接通知就绪的 FD,无需遍历(O(1) 时间复杂度)。
- 高效内存:使用红黑树管理 FD,支持水平触发(LT)和边缘触发(ET)模式。
核心函数
epoll_create()
:创建 epoll 实例。epoll_ctl()
:添加/修改/删除监听的 FD。epoll_wait()
:等待就绪事件。
int epfd = epoll_create1(0);
struct epoll_event ev, events[10];
ev.events = EPOLLIN; ev.data.fd = fd1;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &ev); _// 注册 fd_
int ret = epoll_wait(epfd, events, 10, timeout); _// 等待事件_
for (int i = 0; i < ret; i++) { _/* 处理 events[i] */_ }
触发模式
- 水平触发:只要 FD 可读/可写,epoll_wait() 会持续通知(默认模式,类似 poll)。
- 边沿触发:仅在 FD 状态变化时通知一次(需一次性处理完数据,否则可能丢失事件)。
优点
- 高性能:支持百万级并发(如 Nginx、Redis)。
- 低开销:无需每次调用传递所有 FD。
kqueue(FreeBSD/macOS 专属)
- 类似
epoll
,但使用事件队列机制,支持更多事件类型(如文件修改、信号)。 - 代码复杂度较高,但功能更强大。
struct kevent ev, events[10];
int kq = kqueue();
EV_SET(&ev, fd1, EVFILT_READ, EV_ADD, 0, 0, NULL);
kevent(kq, &ev, 1, NULL, 0, NULL); _// 注册 fd_
int ret = kevent(kq, NULL, 0, events, 10, &timeout); _// 等待事件_
对比 epoll
特性 | epoll | kqueue |
平台 | Linux | FreeBSD/macOS |
事件类型 | 网络 I/O 为主 | 支持文件、信号等 |
性能 | O(1) | O(1) |
IOCP(Windows 专属)
- 基于异步 I/O 的多路复用模型,与
epoll
/kqueue
设计哲学不同。 - 需配合完成端口(Completion Port)使用,适合高吞吐场景。
- 线程池从完成端口获取已完成的 I/O 操作,而非主动监听 FD。
总结对比
机制 | 平台支持 | 时间复杂度 | 最大并发量 | 编程复杂度 |
`select` | 跨平台 | O(n) | 1024 | 低 |
`poll` | 跨平台 | O(n) | 无硬限制 | 中 |
`epoll` | Linux | O(1) | 百万级 | 高 |
`kqueue` | FreeBSD/macOS | O(1) | 百万级 | 高 |
`IOCP` | Windows | O(1) | 百万级 | 极高 |
