操作系统面试题 | 小林coding
mmap()函数是内存映射函数。在文件映射区域偷一块内存。
用户态和内核态
用户态和内核态的区别?
-
内核态(Kernel Mode):在内核态下,CPU可以执行所有的指令和访问所有的硬件资源。执行特权指令。
-
用户态(User Mode):在用户态下,CPU只能执行部分指令集,无法直接访问硬件资源。执行用户指令。
指令集:MOV EAX, [0x1000]。汇编后的或者编译后的代码
进程管理
线程和进程的区别是什么?
本质区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
源码中的全局变量属于进程,这个全局变量就可以是 进程的硬件呀
线程是执行的基本单位。因为底下这句代码
pthread_create(&thread1, NULL, task, (void*)"数据加载");
//task就是这个线程执行的任务,cpu就分给这个任务
进程,线程,协程的区别是什么?
协程不会,不学了
你说到进程是分配资源的基本单位,那么这个资源指的是什么?
虚拟内存:每个进程都会有一个虚拟内存,虚拟地址再 = 》 物理地址。父子进程都会即使有相同 的虚拟内存,但虚拟地址=》物理地址不同
文件描述符: 父子进程都会有相同的fd,但是是两个完全一样的,并不是一个
进程切换和线程切换的区别?
进程切换 换的是内存
线程切换 换的是 线程带着的寄存器(寄存器是小内存嘛)
进程上下文有哪些?
首先明白 cpu上下文 CPU 寄存器(cache)和程序计数器(PC)
根据cpu寄存器中存储的【任务】,把 CPU 上下文切换分成:进程上下文切换、线程上下文切换和中断上下文切换。
所以 进程的上下文切换就是把 进程所管的用户内存空间的内存换了一下,内核空间换了一下。而切换的东西都记在PCB中
进程间通讯有哪些方式?
- 在我们的用户内存空间之外还有 内核空间
- 内核空间中有 进程间通信的 帮手
匿名管道
- 父子进程间拿 一个fd 交流
- 内核空间提供一段内核空间内存当【缓冲区】
【缓冲区】是为了 缓冲父子进程间时机不匹配 一个想do一个不想do时 就放在【缓冲区】
int pfd[2]; // pfd[0] 读端, pfd[1] 写端
char buf[1024];
read(pfd[0], buf, sizeof(buf));
命名管道
- 两进程 约定一个有名字的管道(管道也是文件类型的一种)交流一下
- 内核空间提供一段内核空间内存当【管道】
const char *fifo_name = "/tmp/my_fifo";
// 创建命名管道(如果已存在,mkfifo会失败,所以这里忽略错误)
mkfifo(fifo_name, 0666); // 权限 0666
消息队列
- 有一个meg的结构体,有了原子性,不再和管道一样是二进制的流
- 会先将消息写在用户内存空间中,再 复制到 内核空间中的消息队列上
strcpy(msg_send.mtext, "Hello Message Queue!");
msgsnd(msqid, &msg_send, sizeof(msg_send.mtext), 0); // 注意长度计算
共享内存
- 开辟的共享内存就在 用户内存空间中(不在内核空间中)
- 进程A在虚拟内存中开辟一块空间,进程A在虚拟内存中开辟一块空间,通过mmap,映射到同一块物理内存中。
- 所以就有了 进程间的 同步问题

信号
在命令行使用 kill -9 1234命令,就是 shell 进程通过 kill系统调用,向 PID 为 1234 的进程发送了一个 SIGKILL信号,要求它强制终止。
线程间通讯有什么方式?
原子操作
原子操作:只能保护一个变量
比如 std::atomic count; count++;。它只能保证这一个数字的加减是绝对安全的,不能被拆分的。
锁机制:保护的是一段复杂的代码块(临界区)
原子操作(纯硬件级):依赖 CPU 的特定指令
机制(OS 软件级):也有一部分原子操作,但是也依赖操作系统的调度。
原子操作:遇到另一个原子操作时。是“忙等”的,线程一直是运行态。
锁机制:会在os的调度下,从运行态=》阻塞态。
互斥锁:
如果进线遇到互斥,主动放弃CPU,从运行态=》阻塞态。(与自旋锁的区别)
当我上锁时,其他人都不能来读。(与读写锁的区别)

自旋锁
通过TAS或者CAS指令实现的。
如果进线遇到互斥,线程会一直循环尝试获取锁,一直处于运行态。

条件变量
互斥锁是线程间互斥的机制,条件变量则是同步机制。
写出条件变量得知道这几个点。
- unique_lock lock(mtx);只有这个智能锁有mtx的所有权
在这个【有参构造函数】中,会自动给对象lock上锁。
cv.wait(lock)时,又会自动给对象lock解锁。 - 阻塞时,必须是这个模板。
while (条件不满足) {
cv.wait(lock);
}
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
mutex mtx;
// 💡 听你的!搞两个条件变量,泾渭分明!
condition_variable cv_odd;
condition_variable cv_even;
int count_num = 1;
const int MAX_NUM = 100;
// ==========================================
// 奇数线程
// ==========================================
void printOdd() {
while (count_num <= MAX_NUM) {
unique_lock<mutex> lock(mtx);
// 1. 我在奇数专属的床上睡觉
while (count_num % 2 == 0 && count_num <= MAX_NUM) {
cv_odd.wait(lock);
}
if (count_num > MAX_NUM) break;
cout << "奇数线程: " << count_num << endl;
count_num++;
// 2. 💡 核心绝杀:我不随便叫人了!我精准地按响偶数线程床头的闹钟!
cv_even.notify_one();
}
}
// ==========================================
// 偶数线程
// ==========================================
void printEven() {
while (count_num <= MAX_NUM) {
unique_lock<mutex> lock(mtx);
// 1. 我在偶数专属的床上睡觉
while (count_num % 2 != 0 && count_num <= MAX_NUM) {
cv_even.wait(lock);
}
cout << "偶数线程: " << count_num << endl;
count_num++;
// 2. 💡 核心绝杀:精准按响奇数线程床头的闹钟!
cv_odd.notify_one();
}
}
int main() {
thread t1(printOdd);
thread t2(printEven);
t1.join();
t2.join();
cout << "打印完毕!" << endl;
return 0;
}
利用信号量实现的生产者-消费者
使用2个信号量,一个生产,一个消费
利用信号量实现的读写锁
使用2个信号量,一个读写
一个互斥

读写锁
1个锁。用unique_lock /和std::shared_lock来实现
多个读者可以同时进行读
写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
有一个独享所有权的写锁,和一把共享所有权的读锁
写锁
读锁
无锁队列
一个队列,如果最后这个结点的下一个是null,我就插入新的结点。如果有人插队,我就后移一下尾结点,再CAS。
锁
自旋锁是什么?应用在哪些场景?
自旋锁就是 CPU 提供的 CAS 函数(Compare And Swap)
当锁住的代码执行时间很短,就不应该用互斥锁,而应该选用自旋锁
互斥锁VS自旋锁
-
互斥锁 当cpu上有上锁的进程a时,进程b调用了PCB恢复了现场,试了一下,发现不行,就又回到阻塞态了。
-

-
自旋锁 当cpu上有上锁的进程a时,进程b调用了PCB恢复了现场,试了一下,发现不行,还会一直在Run态一直试。好处是:不用频繁调用PCB恢复现场。
乐观锁和悲观锁有什么区别?
- 悲观锁 以防有人害我
像MySQL中的,不管有没有人用,我先用【记录锁】锁住。
SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- 🔒 锁定数据
- 乐观锁
就像自旋锁一样。即使忙等,我也只用等一会会。
介绍一下brk,mmap
mmap()函数是内存映射函数。在文件映射区域偷一块内存。
网络 i/o
你了解过哪些io模型?
-
你了解io吗?
io过程是指 数据在内存(进程的用户空间 buffer)与外部设备之间(如socket buffer内核空间,硬盘)的传输过程
read(),write()函数是io函数 -
阻塞
客户端进程因为某些事故 卡在了代码的一处 -
阻塞IO模型:
int main() {
// 1. 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation error");
exit(EXIT_FAILURE);
}
// 2. 连接服务器
printf("Connecting to server...\n");
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Connection Failed");
exit(EXIT_FAILURE);
}
// 3. 发送数据
printf("Sending message: %s\n", hello);
send(sock, hello, strlen(hello), 0);
//阻塞IO
// 这个 read() 调用会阻塞客户端进程
int valread = read(sock, buffer, BUFFER_SIZE);
// 4. 关闭套接字
close(sock);
printf("Client closed\n");
return 0;
}
- 非阻塞IO模型
-
非阻塞:将一个fd(一块内核空间) 设置为非阻塞状态。进程在io时 如果read不到数据到buffer中,就会(1)稍后轮询 (2)IO复用
-
IO复用:使用了epoll等机制,将该fd注册到epoll上。epoll此时像一个小秘书,一旦fd发生变化(fd满了可读/fd空了可写),epoll就会通知进程快来IO。这个小秘书还管上百个fd,这就是复用。
-
select、poll、epoll 的区别是什么?
select:将需要监视的fd放到文件描述符集合中,数据结构是位图。一次次遍历这个位图,标记为是否可读。需要处理读写时需要再遍历第二遍。
poll:--------------------------------------------------------------------用户空间中是用数组,内核中是用链表
事件驱动机制:事件驱动(一有事,就触发某事) vs select的轮询机制(一直问我能不能do)。事件驱动,通过回调函数实现。比如epoll中,epoll监视epfd,一旦sockfd的内存中有变化,就会给到events数组中。一有事件,就放到events数组中。
epoll
- epoll_create( )创建树
- epoll_ctl( ):将sockfd添加到树上,在树上的都会被监视
- 触发:(1)LT模式,sockfd的内存中有数据,就会一直往evets数组中给,epoll_wait( )几秒钟就会读一次,就读到他。(2)ET模式。sockfd的内存中只有状态变化时,才会往events数组中给
- epoll_wait( ):一段时间就会统计events数组中的个数。
代码加强理解触发模式
LT。假设客户端发送了 1000 字节数据,服务器每次读取 500 字节。
// 第一次发现一个sockfd中有数据。立马触发。epoll_wait返回1
n = epoll_wait(epoll_fd, events, 10, 1000); // 返回1个事件
read(sockfd, buf, 500); // 服务端读取500字节
// 第二次发现sockfd中还有数据。立马触发。epoll_wait返回1
n = epoll_wait(epoll_fd, events, 10, 1000); // 再次返回1个事件(还有500字节可读)
read(sockfd, buf, 500); // 服务端读取剩余500字节
// 第三次sockfd中没有数据。epoll_wait返回0
n = epoll_wait(epoll_fd, events, 10, 1000); // 无事件(阻塞或超时)
redis,nginx,netty 是依赖什么做的这么高性能?
Reactor模式:1.对于高性能服务器的一种开发模型。
2.[反应堆]=====》【对事件的反应堆】= ===》【一有事件,Reactor就会有反应】
惊群现象
当多个进程/线程在等待同一个资源(如网络连接、锁、信号量)时,当资源可用(事件发生),系统会唤醒所有等待者,但最终只有一个进程/线程能获得该资源,其他进程/线程白忙一场后重新进入等待状态。.
.
惊群现象发生的典型场景
- 网络服务器中的 accept() 惊群(经典案例)
当一个新连接到达时,所有4个子进程都会从 accept() 阻塞中唤醒【阻塞态】,但只有一个进程能成功接受这个连接,其他3个进程"扑了个空"【一瞬间的就绪态】,需要重新进入【阻塞状】.(都上了一遍cpu,运转了一会发现不合适又下来了------cpu上下文切换) - 解决方案: 使用互斥锁保护 accept()(传统方案)
- 用上了锁不还是会有惊群现象吗?
是的还是会有,从大惊群变成了小惊群。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)