操作系统问答
概述¶
中断有哪些种类¶
进程与线程¶
进程与线程¶
进程和线程有什么区别¶
- 定义方面:资源分配 / 任务调度执行
- 共享资源:堆、.data、.text
- 上下文切换:都需要用户内核切换,进程间需要切换页表,刷新 TLB
- 安全性:线程一个出问题导致进程退出
为什么需要线程¶
- 并发(并行):同一时间执行多个任务
- 切换开销:线程不影响页表
- 通信方便:同一进程的不同线程共享部分资源
为什么线程更高效¶
线程越多越好吗¶
- 切换开销
- 保持同步(竞态条件,死锁)开销更低
线程共享了什么¶
- 虚拟内存空间:.text , .data, 堆
- 文件描述符(进程打开的文件)
- 信号处理器(进程收到的信号)
为什么创建进程比创建线程更慢¶
虚拟内存映射创建需要消耗时间:
- 多个线程可以共用内存的虚拟内存空间,只需要新分配栈
僵尸进程,孤儿进程,守护进程¶
- 僵尸进程和孤儿进程的描述对象都是子进程:
- 僵尸进程是子进程已经终止,但是父进程没有调用 wait() 或者 waitpid() 函数来获取子进程的终止状态,导致子进程的 pid 仍然存在,且其 PCB 没有被回收
- 孤儿进程是说子进程的父进程终止,孤儿进程会被 pid = 1 的 init 进程接管,父进程变为 init
- 守护进程是在后台一直运行的一种进程,伴随着系统的启动和关闭而启动 / 关闭
僵尸进程会占用 CPU 吗¶
如何杀死僵尸进程¶
- 可以使用 top 来查看当前是否存在僵尸进程
- 首先使用 ps aux | grep Z 来找到僵尸进程
- 必须 kill 其父进程才能杀死僵尸,让 init 来接管僵尸的回收
ps 指令是 process status 的缩写,用于列出进程
- a:显示所有用户的进程(包括其他用户的进程)。
- u:以用户友好的格式显示进程信息(例如,显示进程的启动用户、CPU 和内存使用率等)。
- x:显示没有控制终端的进程(通常是后台进程或守护进程)。
-9 :强制终止
一个进程 fork 出一个子进程,它们总共占用的内存是之前的两倍吗¶
不是
- 由于 copy on write 机制,创建出的子进程首先复制一份和父进程完全相同的页表,同时将父、子进程页表对应的页表项的属性被标记为只读,这样在写操作发生之前子进程和父进程共用一块物理内存
- 当父或子向这个内存发起写操作时,CPU会触发写保护中断,然后会在中断处理函数中进行物理内存的复制(谁写复制给谁),重新设置写进程的页表与权限位,最后再写内存
- 这样可以节省物理内存
进程间通信¶
1. 进程间的通信方式有哪些¶
- 管道
- 匿名管道
- 有名管道
- 消息队列
- 共享内存
- 信号量(Semaphore,保护共享资源)
- 信号(异步通知,kill() 向目标发送特定信号,signal() 捕获目标信号,并绑定处理函数)
- socket (不同主机的进程)
2. 有名管道和匿名管道有什么区别¶
匿名没有文件实体,只能在父子兄弟间;有名有文件实体,可以在任意进程间通信,数据可以持久化
cat a.c | grep x
使用匿名管道通信,具体执行了以下操作:调用
fork()
创建两个子进程:
- 一个子进程执行
cat a.c
,将其输出重定向到管道的写端。- 另一个子进程执行
grep x
,将其输入重定向到管道的读端。
3. 哪个进程间的通信方式效率最高¶
共享内存
将一块虚拟地址空间映射到同一个物理地址空间。
- 好处:不涉及内核态缓冲区和用户态缓冲区之间的数据拷贝(例如管道、消息队列、Socket 等,数据需要从发送进程的用户态缓冲区拷贝到内核缓冲区,再从内核缓冲区拷贝到接收进程的用户态缓冲区)
- 坏处:安全、复杂性
内存¶
虚拟内存¶
1. 操作系统为什么要有虚拟内存¶
- 安全性:没有虚拟内存,进程可以直接进行物理寻址,虚拟内存可以进行权限控制
- 碎片化:有了虚拟内存后,可以虚拟连续不要求物理连续,从而可以利用物理内存中的碎片空间
- 内存大小:虚拟内存可以使进程的运行内存超出物理内存的大小,通过内存页的替换可以实现内存容量的扩展
2. 内存分段 segment 是什么¶
对于一个进程,通过 segment table 来将逻辑地址映射到物理地址(每个进程都有自己的段表)
- 每个段内部的物理地址连续分配,段之间的物理地址可以离散分配
- 一个进程被分为多个段
3. 内存分页是什么¶
将内存划分为一块块固定大小的块(通常为 4KB),每个进程分得的页在物理内存中可能是不连续的,需要在页表中根据虚拟地址来找到物理页号以及偏移量
4. 数组的物理空间连续吗¶
虚拟空间连续,物理空间不一定连续
5. 进程的虚拟内存布局是怎样的¶
注意,是 虚拟 地址!!
- 内核空间
- 用户栈
- 文件映射段(动态库、共享内存、内存映射文件)
- 堆
- BSS(未初始化的静态变量和全局变量)
- 数据段(已初始化的静态变量和全局变量)
- 代码段(二进制的可执行代码)
动态库也称为共享库,在程序运行时加载的库文件。与静态库不同,编译时不会直接嵌入到可执行文件中,而是在程序运行时动态地加载到内存中(多个程序可以共享)
- linux 中为 .so 文件(shared object)
- windows 中为 .dll 文件(dynamic link library)
6. 栈的增长趋势是什么¶
从高地址向低地址增长
7. 栈和堆的区别是什么¶
- 对程序员来说,堆区可以由程序员进行分配与清理
- 大小:堆区向高地址增长,空间较大;栈区向下延申,通常为 8 MB
8. 为什么在栈上的操作比在堆上快¶
- 寄存器中有专门指向栈的栈指针,可以直接访问栈地址(访问堆内存时需要使用堆的起始地址 + 偏移量的方式访问)
- 即使是虚拟连续,还是有一定的机会可以利用到缓存(CPU cache)的空间局部性
9. 32 位操作系统,4G物理内存,程序可以申请 8G 内存吗?64 位操作系统呢?¶
- 32 位操作系统的用户虚拟地址空间也只有 3G,无法申请
- 64 位操作系统可以申请,但是可能需要用到 swap space 的大小是否足够
10. malloc 会陷入内核态吗?¶
不一定
- malloc 被调用时首先会尝试从它所管理的内存池中分配内存,当有足够的内存时直接分配给用户
- 当内存池中没有足够的内存时,需要通过 brk() / mmap() 等系统调用来申请内存
文件系统¶
1. 读取一个文件时,操作系统会发生什么¶
- 用户进程调用 read() 系统调用,然后阻塞
- CPU 将 IO 请求分配给 DMA,随后 DMA 负责内存与磁盘的交互:
- DMA 向磁盘发起 IO 请求,磁盘将数据读取到磁盘控制器的缓冲区中,完成后向 DMA 发起中断
- DMA 将磁盘控制器缓冲区的数据拷贝到内核缓冲区中,完成后向 CPU 发起中断
- CPU 将数据从内核缓冲区拷贝到用户空间,系统调用返回
2. 硬链接和软连接有什么区别¶
- 软连接本身并不存放实际的链接内容,而是相当于一个指向目标文件的快捷方式,软链接文件具有独立的 inode
- 硬链接和原始文件共享同一个 inode,只有当删除所有硬链接时,才会使文件无法访问
网络 I/O¶
IO 模型¶
1. Linux 有哪五种 IO 模型¶
- 阻塞 IO:应用发起 IO ,若数据未准备好,则应用被挂起(进入 waiting queue)
- 非阻塞 IO:应用发起 IO ,数据未准备好,操作系统立即返回一个错误,但是此后应用可以执行其他操作
- IO 多路复用:应用程序可以同时监控多个 fd,当没有任何 fd 准备好时,应用程序会被挂起,直到至少有一个 fd 准备好或者达到指定的超时时间
- 信号驱动:当数据准备好时,操作系统发送一个信号给应用,这样可以避免阻塞和轮询
- 异步:操作系统会在数据准备好时将数据复制到用户缓冲区,并通知用户
前四个都是同步 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 提供的数组中