操作系统基础¶
学习目标¶
学完本章后,学习者应该能够:
- 理解操作系统在应用程序和硬件之间承担的职责。
- 区分用户态、内核态和系统调用。
- 解释为什么一次普通文件读写或网络请求需要操作系统参与。
- 能把 Go 后端服务和操作系统资源联系起来。
操作系统解决什么问题¶
应用程序不能直接随意操作 CPU、内存、磁盘和网卡。否则一个程序写错,就可能破坏其他程序的数据,甚至让整台机器不可用。
操作系统负责在应用程序和硬件之间建立秩序:
| 职责 | 后端工程中的体现 |
|---|---|
| 进程管理 | 启动服务、停止服务、信号处理 |
| 内存管理 | 虚拟内存、堆栈、OOM |
| 文件管理 | 日志、配置、上传文件、文件描述符 |
| 网络管理 | socket、端口、TCP 连接 |
| 权限管理 | 用户权限、文件权限、容器能力 |
| 资源隔离 | cgroups、namespace、容器资源限制 |
从后端工程师的角度看,操作系统就是服务运行时的资源管理者。
用户态与内核态¶
CPU 执行程序时通常分为两类权限状态:
- 用户态:普通应用程序运行的位置,权限受限。
- 内核态:操作系统内核运行的位置,可以访问硬件和核心资源。
Go 代码大多数时候运行在用户态。但当程序需要读文件、发网络包、创建线程、申请内存映射时,就需要通过系统调用进入内核态。
系统调用¶
系统调用是应用程序请求内核帮忙做事的接口。
常见系统调用包括:
open:打开文件。read:读取文件或 socket。write:写入文件或 socket。close:关闭文件描述符。socket:创建网络 socket。accept:接受新连接。mmap:建立内存映射。clone/fork:创建执行单元。
Go 标准库把很多系统调用包装成了更易用的 API:
这段 Go 代码看起来很简单,但底层会涉及打开文件、分配文件描述符和关闭文件描述符。
Go 后端实际应用例子¶
例子一:忘记关闭文件导致描述符泄漏¶
文件和网络连接都依赖文件描述符。如果请求中反复打开文件但不关闭,最终可能出现 too many open files。
func ReadConfig(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return io.ReadAll(f)
}
defer f.Close() 不是格式问题,而是资源释放问题。后续文件系统与 IO 章节会继续展开。
例子二:HTTP 请求最终也是系统资源¶
一个 HTTP Server 监听端口,本质上是进程持有一个监听 socket:
srv := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return err
}
监听端口、接受连接、读写 socket 都需要操作系统参与。理解这一点后,连接泄漏、端口占用、文件描述符耗尽就不再是神秘问题。
常见误区¶
- 误区一:操作系统只是运维需要懂。
后端服务运行在操作系统上。端口、文件描述符、信号、内存限制都会影响代码行为。
- 误区二:系统调用只是底层细节。
系统调用多了会有成本。高并发场景下,频繁读写小文件、频繁创建连接、频繁跨用户态和内核态切换都可能影响性能。
- 误区三:Go 标准库屏蔽了所有系统差异。
标准库提供了统一接口,但不同操作系统的文件路径、信号、网络行为、权限模型仍然存在差异。
实战任务¶
观察当前机器上一个 Go 服务或任意长期运行进程:
- 找到它的进程 ID。
- 判断它是否监听端口。
- 找出它打开了多少文件或连接。
- 思考这些资源如果不释放,会出现什么问题。
参考答案
Windows 上可以先用 Get-Process 找到目标进程,再用 netstat -ano 根据 PID 查看监听端口或连接。Linux / macOS 上可以用 ps aux 找 PID,ss -lntp 看监听端口,lsof -p <pid> 看进程打开的文件、socket 和动态库。
如果文件或连接持续增长且没有释放,可能导致文件描述符耗尽。表现包括无法接受新连接、无法打开日志文件、数据库连接创建失败,常见报错是 too many open files。
面试题¶
1. 操作系统对后端服务最重要的职责是什么?¶
参考答案
操作系统负责管理 CPU、内存、文件、网络、进程和权限。对后端服务来说,它决定进程如何被调度、内存如何分配、端口如何监听、文件如何读写、连接如何建立,以及资源达到限制时会发生什么。
后端工程师不需要掌握所有内核实现,但要理解这些资源如何影响服务稳定性和性能。
2. 什么是用户态和内核态?¶
参考答案
用户态是普通应用程序运行的状态,权限受限,不能直接操作硬件和关键内核数据。内核态是操作系统内核运行的状态,拥有更高权限,可以管理硬件和系统资源。
应用程序需要读写文件、创建 socket、申请内存映射等操作时,会通过系统调用从用户态进入内核态,请求内核完成工作。
3. 系统调用为什么有成本?¶
参考答案
系统调用涉及从用户态切换到内核态,再从内核态返回用户态,这个过程需要保存和恢复上下文,还可能触发调度、等待 IO 或锁竞争。
单次系统调用成本通常可接受,但高频、小粒度、无批处理的系统调用会放大开销。例如频繁写小日志、频繁创建短连接,都可能让系统调用成本变成性能问题。