一. 操作系统
说白了,操作系统,就是个 (控制型)软件程序,比如: linux, windows, android, ios, Harmony OS等 操作系统有两个方面的功能:
- 对上,给应用程序提供服务(控制应用程序的执行,同时限制不同应用程序占用的资源,给应用程序提供各种各样的服务{如:I/O服务、声卡的访问、网卡的访问})
- 对下,对硬件资源进行管控,管理
作用:
程序员比较关心的操作系统内部组件:
二.操作系统的大体启动过程
每种操作系统启动流程,以及启动细节都不一样,只简单描述大体的启动过程,如图:
预备知识:
操作系统程序事先存放在磁盘中(如:windows操作系统是放在C盘中的)
Bootloader程序:将磁盘中的 " OS " 加载到内存
BIOS(Basic I/O System): 提供开机支持,固定在主板的ROM(Read Only Memory主板中的一块只读内存)区域
ROM(Read Only Memory): 只读内存
RAM(Random Access Memory):随机访问内存
三.用户态与内核态
为了计算机的安全使用着想,有了用户态和内核态 内核态:可以完全访问所有的硬件,也可以执行机器能够运行的任何指令
操作系统在运行时 CPU 所处的状态, 这个时候的CPU可以执行任何一条指令(包括特权指令,访问I/O指令)
用户态:只能执行一部分机器指令,对于哪些会影响机器的控制或者可进行I/O操作的指令,在用户态中的程序中是禁止的
应用程序在运行时 CPU 所处的状态,这个时候的CPU所处的状态级别特别低,不能直接访问某些机器指令,或者不能直接访问I/O(读写磁盘)
四.操作系统如何与外设硬件交互
操作系统与硬件交互主要使用: 汇编指令、中断机制(硬中断)
4.1 操作系统使用汇编指令与硬件交互
预备知识:总线,设备控制器,寄存器。 可以AI了解一下。
CPU芯片不会直接与这些设备交互,通常计算机中有个抽象的概念叫 “设备控制器”。 当然有很多种控制器,可以AI问一下,截图只是举几个小例子。
设备控制器(Device Controller)确实是计算机系统中一个至关重要却又容易被忽视的硬件组件。它是CPU(或更广义地说,计算机系统)与各种物理硬件设备(如磁盘、键盘、打印机、显示器、网卡等)之间进行交互的核心桥梁。
详细的 “设备控制器”, 拿打印机做举例,
操作系统会给这个三个寄存器做唯一标识,方便CPU知道控制那个寄存器,操作系统给这个三个寄存器分配个端口,学名叫“端口映射I/O”,举例如下:
端口映射I/O : 将每个设备控制器的寄存器标志为不同的端口
与之相仿的还有个 “内存映射I/O”
内存映射I/O : 把I/O设备的各个寄存器都编址,看成“内存地址”
Window操作系统里就可以看到,某个设备管理器里面的某个I/O方式,它们各有优缺点,AI了解一下吧。
继续以端口I/O方式叙述
操作系统,做如下汇编指令时
OUT 0X03B0 EAX
就是要把 数据A, 从CPU中的EXA寄存器中给到 设备控制器中的 数据寄存器,最后通过控制电路给到打印机,更新详细点的可以看下这个张图片,我我觉得大家了解一下就行,更深原理不用深究
4.2 操作系统使用中断机制与硬件交互 (也叫 硬中断,是由硬件触发的)
简单了解一些知识:
(1)所有的操作系统内核部分都有一个服务程序叫——》“中断服务程序(Interrupt Service Routine, ISR)”
(2)中断服务程序在操作系统启动时会初始化出来一个表——》“中断描述符表(Interrupt Descriptor Table)” 里面维护了很多很多中断号,就是个数组表
(3)硬件设备会发送中断信号——》中断号(Interrupt Request, IRQ), 下图展示了window操作系统中某个硬件对应的中断号
工作流程简单描述:
-
硬件事件发生:
- 一个硬件设备完成了某个操作(如磁盘读取完成)、有数据到达(如网络数据包到达网卡)、需要服务(如键盘按键被按下)或 发生错误。
- 该硬件设备通过主板上的中断控制器(如Intel的APIC或传统的PIC)向CPU发送一个特定的中断请求信号(IRQ)。每个设备通常关联一个或多个唯一的IRQ号。
-
CPU响应中断:
- CPU在每条指令执行结束时,都会检查是否有待处理的中断请求(除非中断被明确屏蔽)。
- 如果检测到未屏蔽的有效中断请求(IRQ),且当前程序的执行优先级低于该中断的优先级,CPU会立即中断当前程序的执行流。
- CPU自动保存当前程序的关键上下文信息(至少包括程序计数器PC/指令指针IP、程序状态字PSW/标志寄存器Flags)。这些信息被压入当前栈(通常是内核栈)或特定的中断栈,以便后续能正确恢复被中断的程序。
-
查找中断服务程序:
- CPU根据收到的IRQ号,查询一个由操作系统在启动时设置好的数据结构——中断描述符表(Interrupt Descriptor Table, IDT)。
- IDT 本质上是一个数组,每个条目对应一个可能的中断号(包括硬件IRQ和软件异常/陷阱)。每个条目包含了对应中断服务程序(ISR)的入口地址(段选择子和偏移量)以及必要的特权级等信息。
-
执行中断服务程序:
- CPU跳转到查找到的ISR入口地址,开始执行。
这种方式是现代操作系统管理硬件、实现并发和多任务处理的核心机制,它解决了轮询(Polling)效率低下的问题,让CPU能够在硬件需要服务时被“主动”通知。
核心思想:硬件设备(如键盘、鼠标、网卡、磁盘控制器、定时器)在需要操作系统的关注时(例如,数据准备好、操作完成、发生错误),会主动向CPU发送一个中断信号(Interrupt Request, IRQ)。CPU收到这个信号后,会立即暂停当前正在执行的程序(无论它是用户程序还是内核代码),保存其当前状态(称为上下文切换的一部分),然后转而执行一段专门为处理该硬件事件而编写的函数,即中断服务程序(ISR)。ISR执行完毕后,CPU通常会恢复之前被中断的程序继续执行。
扩展知识点:
DMA (Direct Memory Access) 直接内存访问 DMA 是一种硬件机制(在主板上的一个硬件),允许某些计算机内部的子系统(主要是外部设备,如磁盘驱动器、网卡、声卡、图形卡等)直接在系统内存(RAM)中读取或写入数据,而无需中央处理器(CPU)的持续参与。 它的核心目标就是显著减少 CPU 在处理大量数据传输任务上的开销。 为什么需要 DMA? (没有 DMA 的世界) 想象一下没有 DMA 的情况(也称为 PIO - Programmed I/O):
- CPU 全程参与: 当设备(比如硬盘、磁盘 )需要将数据读取到内存时:
- CPU 必须从硬盘控制器的一个 I/O 端口逐个字节(或字)地读取数据。
- 然后 CPU 再把这个数据逐个字节(或字)地写入到内存的目标地址。
- 高 CPU 占用率: 对于一个大文件(比如 1GB),CPU 就需要执行数十亿次这样的“读端口 -> 写内存”操作。这期间 CPU 几乎完全被占用,无法执行其他更有用的任务(如运行应用程序)。
- 效率低下: CPU 的速度远高于 I/O 设备。让高速的 CPU 等待慢速的 I/O 设备完成每一次字节传输是巨大的资源浪费。
- 增加延迟: 因为 CPU 被 I/O 传输阻塞,整个系统的响应速度会变慢。
五.应用程序如何与操作系统交互
5.1 系统调用
作用:系统调用主要解决的是用户态和内核态的切换
过程:Linux操作系统如何从用户态陷入(trap)内核态
- linux 32位操作系统:80中断
- linux 64位操作系统:syscall汇编指令
5.1.1 32位操作系统:INT 80 软中断(应用程序触发的
5.1.2 64位操作系统:汇编指令syscall
操作系统的 “系统调用服务程序” 是怎么知道程序代码调用那个系统调用方法?
在 Linux 操作系统中,当用户空间的程序(比如你的 C 程序)执行一个系统调用(如 read, write, open, fork 等)时,内核的系统调用服务程序(通常指中断/异常处理程序,特别是系统调用入口点)通过 sys_call_table 来找到并执行对应的内核函数的。核心机制是利用系统调用号作为索引。这里对应的方法参数传递涉及到寄存器的数据存储使用,过程有些复杂,大家AI了解一下。
-
系统调用号:
- 每个系统调用在内核中都被分配一个唯一的整数编号,称为系统调用号(syscall number)。
- 这个编号是系统调用在 sys_call_table 中的索引。
- 当用户程序想要发起一个系统调用时,它必须将想要调用的系统调用号放入特定的寄存器。在 x86-64 架构上,这个寄存器是 %rax (RAX)。
-
触发系统调用:
- 用户程序通过执行一条特殊的指令来请求内核服务,从而从用户态(User Mode)切换到内核态(Kernel Mode)。在 x86-64 架构上,常见的指令是:
- syscall (现代 x86-64 CPU 上最常用的、更快的指令)
- int 0x80 (传统的、较慢的软件中断方式,现在主要用于 32 位兼容模式或旧系统)
- 执行这条指令会触发一个硬件异常(或称为陷阱/Trap),导致 CPU 切换到内核模式,并跳转到内核预定义的系统调用入口点代码(例如 entry_SYSCALL_64 或 entry_INT80_34)。
- 用户程序通过执行一条特殊的指令来请求内核服务,从而从用户态(User Mode)切换到内核态(Kernel Mode)。在 x86-64 架构上,常见的指令是:
六.异常(Exception)
明明操作系统也没崩溃或死机啊!为什么管他们叫异常呢? 那是因为从应用程序角度来看的,下面截图介绍
中断、系统调用(陷阱)、故障、终止。它们在操作系统中都属于异常 ,因为它们打断了 应用程序 的按顺序执行。
6.1 中断(interrupt)
6.2 陷阱(trap 系统调用)
Linux (x86_64) 常见系统调用表
| 系统调用号 | 用户态函数名 | 内核态函数名 | 简要说明 |
|---|---|---|---|
| 0 | read | sys_read | 从文件描述符读取数据 |
| 1 | write | sys_write | 向文件描述符写入数据 |
| 2 | open | sys_open | 打开或创建一个文件 |
| 3 | close | sys_close | 关闭一个文件描述符 |
| 4 | stat | sys_stat | 获取文件状态 |
| 5 | fstat | sys_fstat | 根据文件描述符获取文件状态 |
| 6 | lstat | sys_lstat | 获取符号链接文件状态 |
| 8 | lseek | sys_lseek | 重新定位读/写文件偏移量 |
| 9 | mmap | sys_mmap | 将文件或设备映射到内存 |
| 10 | mprotect | sys_mprotect | 设置内存区域的保护属性 |
| 11 | munmap | sys_munmap | 取消内存映射 |
| 12 | brk | sys_brk | 改变数据段空间大小 |
| 13 | rt_sigaction | sys_rt_sigaction | 设置信号处理函数 |
| 14 | rt_sigprocmask | sys_rt_sigprocmask | 设置或获取阻塞信号集 |
| 22 | pipe | sys_pipe | 创建管道 |
| 32 | dup | sys_dup | 复制一个文件描述符 |
| 33 | dup2 | sys_dup2 | 复制一个文件描述符到指定编号 |
| 39 | getpid | sys_getpid | 获取进程ID |
| 56 | clone | sys_clone | 创建子进程(线程) |
| 57 | fork | sys_fork | 创建子进程 |
| 58 | vfork | sys_vfork | 创建一个子进程,并阻塞父进程 |
| 59 | execve | sys_execve | 执行程序 |
| 60 | exit | sys_exit | 终止当前进程 |
| 61 | wait4 | sys_wait4 | 等待子进程状态改变 |
| 62 | kill | sys_kill | 向进程发送信号 |
| 63 | uname | sys_uname | 获取当前内核名称和信息 |
| 80 | chdir | sys_chdir | 改变当前工作目录 |
| 81 | fchdir | sys_fchdir | 根据文件描述符改变工作目录 |
| 82 | rename | sys_rename | 重命名文件 |
| 83 | mkdir | sys_mkdir | 创建目录 |
| 84 | rmdir | sys_rmdir | 删除空目录 |
| 85 | creat | sys_creat | 创建并打开一个新文件 |
| 86 | link | sys_link | 创建硬链接 |
| 87 | unlink | sys_unlink | 删除一个文件名(可能是软/硬链接) |
| 88 | symlink | sys_symlink | 创建符号链接 |
| 89 | readlink | sys_readlink | 读取符号链接指向的路径 |
| 90 | chmod | sys_chmod | 改变文件权限 |
| 158 | arch_prctl | sys_arch_prctl | 架构特定的进程控制(x86_64 设置 FS/GS 段等) |
| 202 | futex | sys_futex | 快速用户空间互斥锁(线程同步底层原语) |
| 217 | getdents64 | sys_getdents64 | 获取目录项列表(现代版本) |
| 257 | openat | sys_openat | 相对于目录描述符打开文件(open 的现代版本) |
6.3 故障(fault)·
6.4 终止(abort)
发生这些异常时,操作系统是怎么知道它到底是那种异常的呢?
(1) 异常号: 操作系统为每一种类型的异常,都分配了一个 “唯一” 的非负整数的异常号(exception number)
举例:X86-64位操作系统中定义的 “异常号”
(2) 异常跳转表:操作系统在启动后会初始化一个向量表——异常跳转表,说白了就是数组