Skip to content

Commit 03a805d

Browse files
add lec1/linux-kernel-services.md,lec2/ch1-kernel-analysis.md
1 parent 0c2833e commit 03a805d

2 files changed

Lines changed: 1192 additions & 0 deletions

File tree

lec1/linux-kernel-services.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
---
2+
marp: false
3+
theme: default
4+
paginate: true
5+
_paginate: false
6+
header: ''
7+
footer: ''
8+
backgroundColor: white
9+
---
10+
11+
<!-- theme: gaia -->
12+
<!-- page_number: true -->
13+
<!-- _class: lead -->
14+
15+
## 第一讲 操作系统概述
16+
17+
### 第六节 Linux 内核服务总体架构
18+
19+
<br>
20+
<br>
21+
22+
向勇 陈渝 李国良 任炬
23+
24+
<br>
25+
<br>
26+
27+
2026年春季
28+
29+
---
30+
31+
## 问题
32+
33+
- `helloworld` 为什么不能直接把字符写到硬件上?
34+
- `open``copy``fork``redirect``pipe` 为什么都要经过内核?
35+
- Linux 内核到底提供了哪些服务?这些服务如何组合起来?
36+
37+
---
38+
39+
## 总体主线
40+
41+
```mermaid
42+
flowchart TB
43+
subgraph userSpace["用户态"]
44+
shellProg["Shell / 用户程序"]
45+
appCode["helloworld / open / copy / forkexec"]
46+
libc["C 库封装\nprintf open read write fork exec"]
47+
end
48+
49+
subgraph kernelSpace["内核态"]
50+
syscallEntry["系统调用入口\ntrap / syscall"]
51+
procSvc["进程管理"]
52+
memSvc["内存管理"]
53+
fsSvc["文件系统与 VFS"]
54+
ioSvc["I/O 与设备驱动"]
55+
ipcSvc["IPC / pipe / socket"]
56+
end
57+
58+
hw["终端 / 磁盘 / 网卡 / 时钟 / CPU"]
59+
60+
shellProg --> appCode
61+
appCode --> libc
62+
libc --> syscallEntry
63+
syscallEntry --> procSvc
64+
syscallEntry --> memSvc
65+
syscallEntry --> fsSvc
66+
syscallEntry --> ioSvc
67+
syscallEntry --> ipcSvc
68+
ioSvc --> hw
69+
fsSvc --> hw
70+
procSvc --> hw
71+
```
72+
73+
- 用户程序看到的是函数和文件描述符
74+
- 内核看到的是进程、地址空间、文件对象、设备对象和缓冲区
75+
- 硬件访问被统一封装成受保护的内核服务
76+
77+
---
78+
79+
## `helloworld` 如何得到输出服务
80+
81+
```mermaid
82+
sequenceDiagram
83+
participant shell as Shell
84+
participant app as hello
85+
participant libc as libc
86+
participant kernel as LinuxKernel
87+
participant tty as TTYConsole
88+
participant device as UARTOrDisplay
89+
90+
shell->>kernel: fork + execve ./hello
91+
kernel-->>app: 开始执行 main
92+
app->>libc: printf hello_world newline
93+
libc->>kernel: write fd1 buf n
94+
kernel->>tty: 根据 fd=1 找到标准输出
95+
tty->>device: 输出字符流
96+
device-->>tty: 写入完成
97+
tty-->>kernel: 返回状态
98+
kernel-->>libc: write 返回
99+
libc-->>app: printf 返回
100+
app->>kernel: exit status 0
101+
kernel-->>shell: wait 返回退出状态
102+
```
103+
104+
- `helloworld` 并不知道屏幕或串口的寄存器地址
105+
- 它只知道调用 `printf``write`
106+
- 内核负责把“标准输出”翻译成真正的终端或串口设备
107+
108+
---
109+
110+
## 一次系统调用内部发生了什么
111+
112+
```mermaid
113+
flowchart TD
114+
userCall["用户态调用\nwrite fd buf n"] --> trap["执行 syscall 指令"]
115+
trap --> switchMode["硬件切换到内核态"]
116+
switchMode --> saveCtx["保存用户态上下文"]
117+
saveCtx --> dispatch["按 syscall 号分发"]
118+
dispatch --> sysWrite["执行 sys_write"]
119+
sysWrite --> kernelWork["检查参数\n查找 fd\n调用 VFS / 驱动"]
120+
kernelWork --> setRet["写回返回值"]
121+
setRet --> restoreCtx["恢复用户态上下文"]
122+
restoreCtx --> returnUser["返回用户态继续执行"]
123+
```
124+
125+
- 看起来像函数调用,本质上是受保护的内核入口
126+
- 应用不能直接跳进任意内核代码,只能通过规定好的系统调用接口
127+
- 这保证了安全性,也让不同程序共享通用服务
128+
129+
---
130+
131+
## Linux 内核服务的总体分工
132+
133+
```mermaid
134+
flowchart LR
135+
appReq["应用请求"] --> proc["进程管理\nfork exec wait exit"]
136+
appReq --> mem["内存管理\nmmap brk page fault"]
137+
appReq --> fs["文件系统\nopen read write close stat"]
138+
appReq --> io["设备与 I/O\nconsole disk net"]
139+
appReq --> ipc["进程间通信\npipe signal socket"]
140+
141+
proc --> cpu["CPU / 调度"]
142+
mem --> ram["内存 / 页表"]
143+
fs --> disk["磁盘 / 目录 / inode"]
144+
io --> terminal["终端 / 串口 / 网卡"]
145+
ipc --> buffer["内核缓冲区 / 事件"]
146+
```
147+
148+
- `fork/exec/wait/exit` 对应进程管理服务
149+
- `open/read/write/close/stat` 对应文件系统与 I/O 服务
150+
- `pipe``signal``socket` 对应通信服务
151+
- 各类服务共用同一个系统调用边界
152+
153+
---
154+
155+
## 文件与 I/O:`open / read / write / close`
156+
157+
```mermaid
158+
flowchart LR
159+
app["应用程序"] -->|"open out"| fdTable["进程文件描述符表"]
160+
fdTable --> fd3["fd 3"]
161+
fdTable --> fd1["fd 1 标准输出"]
162+
163+
fd3 --> fileObj["内核打开文件对象"]
164+
fd1 --> stdoutObj["终端文件对象"]
165+
166+
fileObj --> vfs["VFS / 文件系统"]
167+
stdoutObj --> vfs
168+
vfs --> inode["目录项 / inode / 页缓存"]
169+
vfs --> driver["终端驱动 / 块设备驱动"]
170+
```
171+
172+
- `open()` 返回的是文件描述符,不是直接返回“磁盘块”
173+
- `read()``write()` 总是先根据 `fd` 找到内核中的打开文件对象
174+
- 这样同一套接口既能访问普通文件,也能访问终端、管道、设备
175+
176+
---
177+
178+
## 进程服务:`fork / exec / wait / exit`
179+
180+
```mermaid
181+
flowchart TD
182+
shell["Shell"] --> forkStep["fork\n复制进程执行上下文"]
183+
forkStep --> parent["父进程\n得到子进程 pid"]
184+
forkStep --> child["子进程\nfork 返回 0"]
185+
child --> execStep["execve\n装入新程序映像"]
186+
execStep --> runProg["运行 hello 或其他程序"]
187+
runProg --> exitStep["exit\n返回状态"]
188+
parent --> waitStep["wait\n等待子进程"]
189+
exitStep --> waitStep
190+
waitStep --> prompt["Shell 打印下一个提示符"]
191+
```
192+
193+
- Shell 本身并不实现“运行别的程序”的全部细节
194+
- 它依赖内核的进程创建、程序装载和等待回收服务
195+
- `fork + exec + wait` 是 UNIX/Linux 非常经典的组合方式
196+
197+
---
198+
199+
## 重定向:为什么改 `fd=1` 就够了
200+
201+
```mermaid
202+
flowchart LR
203+
subgraph beforeRun["默认情况"]
204+
fd0a["fd 0"] --> stdinA["键盘 / 输入"]
205+
fd1a["fd 1"] --> stdoutA["终端 / 屏幕"]
206+
fd2a["fd 2"] --> stderrA["终端 / 屏幕"]
207+
end
208+
209+
subgraph redirected["执行 close 1 加 open output.txt 之后"]
210+
fd0b["fd 0"] --> stdinB["键盘 / 输入"]
211+
fd1b["fd 1"] --> fileOut["output.txt"]
212+
fd2b["fd 2"] --> stderrB["终端 / 屏幕"]
213+
end
214+
```
215+
216+
- 程序只会向 `fd=1` 写数据,不需要知道它后面连的是屏幕还是文件
217+
- 重定向的关键不在应用本身,而在 Shell 和内核共同维护的文件描述符绑定
218+
- 这说明内核抽象强调“统一接口、可替换后端”
219+
220+
---
221+
222+
## 管道:`pipe` 如何提供进程间通信
223+
224+
```mermaid
225+
flowchart LR
226+
procA["进程 A\nwrite 到 p1"] --> writeEnd["管道写端 p1"]
227+
writeEnd --> pipeBuf["内核管道缓冲区"]
228+
pipeBuf --> readEnd["管道读端 p0"]
229+
readEnd --> procB["进程 B\nread p0"]
230+
```
231+
232+
- `pipe()` 创建两个文件描述符:一个读端,一个写端
233+
- 数据并不是直接从一个用户进程内存拷到另一个用户进程内存
234+
- 内核提供缓冲、同步和阻塞唤醒机制,因此管道能安全工作
235+
236+
---
237+
238+
## `p5-tryunix` 的例子看内核服务
239+
240+
```mermaid
241+
flowchart TB
242+
examples["用户态小例子"] --> openEx["open.c"]
243+
examples --> copyEx["copy.c"]
244+
examples --> forkEx["fork.c"]
245+
examples --> execEx["exec.c"]
246+
examples --> forkexecEx["forkexec.c"]
247+
examples --> redirectEx["redirect.c"]
248+
examples --> pipeEx["pipe2.c"]
249+
250+
openEx --> fsSvc["文件系统 / fd 分配"]
251+
copyEx --> ioSvc["read write / 缓冲区 I/O"]
252+
forkEx --> procSvc["进程复制 / 调度"]
253+
execEx --> loadSvc["程序装载 / 地址空间重建"]
254+
forkexecEx --> procSvc
255+
forkexecEx --> loadSvc
256+
redirectEx --> fdSvc["fd 重绑定 / 文件对象继承"]
257+
pipeEx --> ipcSvc["管道缓冲区 / 同步"]
258+
```
259+
260+
- `open.c` 强调“名字 -> 文件对象 -> 文件描述符”
261+
- `copy.c` 强调“文件访问其实统一成 `read/write`
262+
- `fork/exec/forkexec` 强调程序执行依赖进程管理服务
263+
- `redirect``pipe2` 强调 UNIX 抽象具有很强的组合能力
264+
265+
---
266+
267+
## `helloworld` 到各个服务的联系
268+
269+
- `printf("hello world\\n")`
270+
- 常经过 libc,最终调用 `write`
271+
- `write(fd=1, buf, n)`
272+
- 用到文件描述符、VFS、终端驱动
273+
- Shell 启动 `./hello`
274+
- 用到 `fork + execve + wait`
275+
- 程序运行本身
276+
- 用到调度器、地址空间、栈和代码段
277+
- 最终看到字符输出
278+
- 依赖终端设备和驱动程序
279+
280+
---
281+
282+
## 为什么由内核统一提供这些服务
283+
284+
- 硬件访问需要保护,不能让任意应用直接操作设备和内存
285+
- 不同程序都需要进程、文件、通信等共性能力,适合由内核统一实现
286+
- 内核提供统一抽象后,应用只需面对少量简单接口
287+
- 这些接口还能组合出重定向、管道、Shell 执行等强大机制
288+
289+
---
290+
291+
## 小结
292+
293+
- 用户程序通过 `C 库 -> 系统调用 -> 内核服务` 获得能力
294+
- Linux 内核把硬件访问封装成进程、内存、文件、I/O、IPC 等抽象
295+
- `helloworld``open``copy``forkexec``redirect``pipe2` 都只是这些抽象的不同组合
296+
- UNIX/Linux 接口数量不多,但表达力很强,这正是其经典之处

0 commit comments

Comments
 (0)