跳转至

操作系统问答

概述

中断有哪些种类

进程与线程

进程与线程

进程和线程有什么区别

  • 定义方面:资源分配 / 任务调度执行
  • 共享资源:堆、.data、.text
  • 上下文切换:都需要用户内核切换,进程间需要切换页表,刷新 TLB
  • 安全性:线程一个出问题导致进程退出

为什么需要线程

  • 并发(并行):同一时间执行多个任务
  • 切换开销:线程不影响页表
  • 通信方便:同一进程的不同线程共享部分资源

为什么线程更高效

线程越多越好吗

  • 切换开销
  • 保持同步(竞态条件,死锁)开销更低

线程共享了什么

  • 虚拟内存空间:.text , .data, 堆
  • 文件描述符(进程打开的文件)
  • 信号处理器(进程收到的信号)

为什么创建进程比创建线程更慢

虚拟内存映射创建需要消耗时间:

  • 多个线程可以共用内存的虚拟内存空间,只需要新分配栈

僵尸进程,孤儿进程,守护进程

  • 僵尸进程和孤儿进程的描述对象都是子进程:
    • 僵尸进程是子进程已经终止,但是父进程没有调用 wait() 或者 waitpid() 函数来获取子进程的终止状态,导致子进程的 pid 仍然存在,且其 PCB 没有被回收
    • 孤儿进程是说子进程的父进程终止,孤儿进程会被 pid = 1 的 init 进程接管,父进程变为 init
    • 守护进程是在后台一直运行的一种进程,伴随着系统的启动和关闭而启动 / 关闭

僵尸进程会占用 CPU 吗

如何杀死僵尸进程

  • 可以使用 top 来查看当前是否存在僵尸进程
  • 首先使用 ps aux | grep Z 来找到僵尸进程
  • 必须 kill 其父进程才能杀死僵尸,让 init 来接管僵尸的回收
ps aux | grep Z

pstree -p | grep ZPID

kill -9 PPID

ps 指令是 process status 的缩写,用于列出进程

  • a:显示所有用户的进程(包括其他用户的进程)。
  • u:以用户友好的格式显示进程信息(例如,显示进程的启动用户、CPU 和内存使用率等)。
  • x:显示没有控制终端的进程(通常是后台进程或守护进程)。

-9 :强制终止

一个进程 fork 出一个子进程,它们总共占用的内存是之前的两倍吗

不是

  • 由于 copy on write 机制,创建出的子进程首先复制一份和父进程完全相同的页表,同时将父、子进程页表对应的页表项的属性被标记为只读,这样在写操作发生之前子进程和父进程共用一块物理内存
  • 当父或子向这个内存发起写操作时,CPU会触发写保护中断,然后会在中断处理函数中进行物理内存的复制(谁写复制给谁),重新设置写进程的页表与权限位,最后再写内存
  • 这样可以节省物理内存

进程间通信

1. 进程间的通信方式有哪些

  1. 管道
    • 匿名管道
    • 有名管道
  2. 消息队列
  3. 共享内存
  4. 信号量(Semaphore,保护共享资源)
  5. 信号(异步通知,kill() 向目标发送特定信号,signal() 捕获目标信号,并绑定处理函数)
  6. socket (不同主机的进程)

2. 有名管道和匿名管道有什么区别

匿名没有文件实体,只能在父子兄弟间;有名有文件实体,可以在任意进程间通信,数据可以持久化

cat a.c | grep x 使用匿名管道通信,具体执行了以下操作:

调用 fork() 创建两个子进程:

  • 一个子进程执行 cat a.c,将其输出重定向到管道的写端。
  • 另一个子进程执行 grep x,将其输入重定向到管道的读端。

3. 哪个进程间的通信方式效率最高

共享内存

将一块虚拟地址空间映射到同一个物理地址空间。

  • 好处:不涉及内核态缓冲区和用户态缓冲区之间的数据拷贝(例如管道、消息队列、Socket 等,数据需要从发送进程的用户态缓冲区拷贝到内核缓冲区,再从内核缓冲区拷贝到接收进程的用户态缓冲区)
  • 坏处:安全、复杂性

内存

虚拟内存

1. 操作系统为什么要有虚拟内存

  • 安全性:没有虚拟内存,进程可以直接进行物理寻址,虚拟内存可以进行权限控制
  • 碎片化:有了虚拟内存后,可以虚拟连续不要求物理连续,从而可以利用物理内存中的碎片空间
  • 内存大小:虚拟内存可以使进程的运行内存超出物理内存的大小,通过内存页的替换可以实现内存容量的扩展

2. 内存分段 segment 是什么

对于一个进程,通过 segment table 来将逻辑地址映射到物理地址(每个进程都有自己的段表)

  • 每个段内部的物理地址连续分配,段之间的物理地址可以离散分配
  • 一个进程被分为多个段

3. 内存分页是什么

将内存划分为一块块固定大小的块(通常为 4KB),每个进程分得的页在物理内存中可能是不连续的,需要在页表中根据虚拟地址来找到物理页号以及偏移量

4. 数组的物理空间连续吗

虚拟空间连续,物理空间不一定连续

5. 进程的虚拟内存布局是怎样的

注意,是 虚拟 地址!!

image-20241230205351388

  • 内核空间
  • 用户栈
  • 文件映射段(动态库、共享内存、内存映射文件)
  • BSS(未初始化的静态变量和全局变量)
  • 数据段(已初始化的静态变量和全局变量)
  • 代码段(二进制的可执行代码)

动态库也称为共享库,在程序运行时加载的库文件。与静态库不同,编译时不会直接嵌入到可执行文件中,而是在程序运行时动态地加载到内存中(多个程序可以共享)

  • linux 中为 .so 文件(shared object)
  • windows 中为 .dll 文件(dynamic link library)

6. 栈的增长趋势是什么

从高地址向低地址增长

7. 栈和堆的区别是什么

  • 对程序员来说,堆区可以由程序员进行分配与清理
  • 大小:堆区向高地址增长,空间较大;栈区向下延申,通常为 8 MB

8. 为什么在栈上的操作比在堆上快

  • 寄存器中有专门指向栈的栈指针,可以直接访问栈地址(访问堆内存时需要使用堆的起始地址 + 偏移量的方式访问)
  • 即使是虚拟连续,还是有一定的机会可以利用到缓存(CPU cache)的空间局部性

9. 32 位操作系统,4G物理内存,程序可以申请 8G 内存吗?64 位操作系统呢?

image-20241230212513434

  • 32 位操作系统的用户虚拟地址空间也只有 3G,无法申请
  • 64 位操作系统可以申请,但是可能需要用到 swap space 的大小是否足够

10. malloc 会陷入内核态吗?

不一定

  • malloc 被调用时首先会尝试从它所管理的内存池中分配内存,当有足够的内存时直接分配给用户
  • 当内存池中没有足够的内存时,需要通过 brk() / mmap() 等系统调用来申请内存

文件系统

1. 读取一个文件时,操作系统会发生什么

  • 用户进程调用 read() 系统调用,然后阻塞
  • CPU 将 IO 请求分配给 DMA,随后 DMA 负责内存与磁盘的交互:
    • DMA 向磁盘发起 IO 请求,磁盘将数据读取到磁盘控制器的缓冲区中,完成后向 DMA 发起中断
    • DMA 将磁盘控制器缓冲区的数据拷贝到内核缓冲区中,完成后向 CPU 发起中断
  • CPU 将数据从内核缓冲区拷贝到用户空间,系统调用返回

image-20250104204001785

2. 硬链接和软连接有什么区别

  • 软连接本身并不存放实际的链接内容,而是相当于一个指向目标文件的快捷方式,软链接文件具有独立的 inode
  • 硬链接和原始文件共享同一个 inode,只有当删除所有硬链接时,才会使文件无法访问

image-20250104210403721

网络 I/O

IO 模型

1. Linux 有哪五种 IO 模型

  1. 阻塞 IO:应用发起 IO ,若数据未准备好,则应用被挂起(进入 waiting queue)
  2. 非阻塞 IO:应用发起 IO ,数据未准备好,操作系统立即返回一个错误,但是此后应用可以执行其他操作
  3. IO 多路复用:应用程序可以同时监控多个 fd,当没有任何 fd 准备好时,应用程序会被挂起,直到至少有一个 fd 准备好或者达到指定的超时时间
  4. 信号驱动:当数据准备好时,操作系统发送一个信号给应用,这样可以避免阻塞和轮询
  5. 异步:操作系统会在数据准备好时将数据复制到用户缓冲区,并通知用户

前四个都是同步 IO ,因为数据从内核复制到用户空间时,用户进程会被阻塞。

2. 计算密集型场景和传视频,分别用阻塞还是非阻塞

  • 计算密集型场景用 阻塞,因为这样等待 IO 结果的进程可以让出 CPU 时间片
  • 传视频用非阻塞,因为瓶颈不在 CPU,非阻塞可以避免进程阻塞在传输函数上

3. IO 多路复用有什么用

  • 当不使用 IO 多路复用时,如果服务端需要并发处理多个客户端的 IO 时间,通常需要通过创建子线程来一对一进行处理。当连接的客户端较多时,可能造成较大的开销。
  • IO 多路复用只需要一个进程去处理多个客户端的 IO 事件,进程可以通过 select, poll, epoll 系统调用接口从内核获取有 IO 事件发生的 socket 集合,然后遍历这个集合,对每个 socket 进行处理。

4. select, poll, epoll 有什么区别

  • select 和 poll 都需要想内核态中传入线性表结构,有网络事件发生时,内核需要遍历这个数组,然后找到事件对应的 socket ,设置其状态为可读 / 可写(数据到达/可发送数据),然后把整个集合返回给用户态,用户态再来遍历集合找到可读 / 可写的 socket
  • epoll 可以解决集合的拷贝和遍历带来的开销。epoll 在内核的内部使用红黑树来关注所有待检测的 socket 提高了基于发生事件的搜索效率,同时使用链表来记录哪些文件描述符 (socket)处于就绪状态(有事件发生),只会返回有事件发生的 socket 集合返回给用户,使用户不需要进行遍历

当用户程序调用 epoll_wait (阻塞)时,内核会检查就绪链表,并将就绪事件拷贝到 epoll_wait 提供的数组中