Compare commits
19 Commits
312dc04755
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 64e47032d4 | |||
| a623e36b6f | |||
| 8557145281 | |||
| 0336f7f059 | |||
| fc1dd68191 | |||
| 70711a3644 | |||
| 05b39090b1 | |||
| 5feb29cc95 | |||
| aed90fbad1 | |||
| 64ba1237d8 | |||
| 718fbe03d8 | |||
| 0f999de708 | |||
| 6b2fc2c7f0 | |||
| e81f36b247 | |||
| 39cbe09cfe | |||
| 373bf17b17 | |||
| f53eb2275e | |||
| 166d3a9de9 | |||
| 75720adacd |
3
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.vscode/
|
||||
.vscode/
|
||||
git-push-all.bat
|
||||
@@ -1,4 +1,4 @@
|
||||
# Lecture 07 Procedures 函数的调用
|
||||
# Lecture 07 Procedures 过程 \(函数的调用\)
|
||||
|
||||
## Mechanisms 机制 / 目录
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
- [Passing Data 传递数据](#passing-data-传递数据)
|
||||
- [传入参数](#传入参数)
|
||||
- [返回值](#返回值)
|
||||
- [Managing Local Data 管理其他变量](#managing-local-data-管理其他变量)
|
||||
- [Managing Local Data 管理其他变量与内存](#managing-local-data-管理其他变量与内存)
|
||||
- [Stack Frame 栈帧](#stack-frame-栈帧)
|
||||
- [Recursion 递归](#recursion-递归)
|
||||
- [`ret` 指令](#ret-指令)
|
||||
@@ -23,7 +23,7 @@
|
||||
在画图时,栈底通常画在上方。
|
||||
在内存地址视角中,栈底对应较高地址,因此按地址从低到高展示时,栈底会出现在下方。
|
||||
|
||||

|
||||

|
||||
|
||||
#### 表 1:栈底在上(高地址在上)
|
||||
|
||||
@@ -97,20 +97,29 @@
|
||||
|
||||
返回值一般使用```%rax```进行传递。
|
||||
|
||||
## Managing Local Data 管理其他变量
|
||||
## Managing Local Data 管理其他变量与内存
|
||||
|
||||
### Stack Frame 栈帧
|
||||
|
||||
一般情况下,由于诸多与函数相关的特点,我们会在栈上分配一些内存给局部变量。加上其他要分配的内存[^extra-mmr-allocate],这部分被称作“栈帧”。
|
||||
有时,我们会在栈上分配一些内存给局部变量。常见情况比如:
|
||||
|
||||
- 寄存器不够存放所有的局部变量
|
||||
- 某些局部变量是个Array或者Struct
|
||||
- 对一个局部变量取地址(&)。这个操作需要它在内存中有一个固定地址才能进行,因此会给它分配一个栈空间。
|
||||
|
||||
加上其他要分配的内存[^extra-mmr-allocate],这部分被称作“栈帧”。
|
||||
|
||||
| 函数特点 | 栈特点 |
|
||||
| ---------- | ---------- |
|
||||
| 返回后,函数内分配的局部变量被丢弃 | 更改```rsp```指针即可丢弃数据 |
|
||||
| 函数内可能会调用其他函数 | 直接将返回地址压栈 |
|
||||
| 单线程调用其他函数时,原函数停止继续运行 | 保存原函数 的帧指针,在其之上可直接建立新栈帧 |
|
||||
| 原函数的数据保持不变 | 新分配一个栈即可避免数据被覆盖 |
|
||||
|
||||
[^extra-mmr-allocate]: 上一个函数的寄存器内容、Windows下的影子空间、返回地址等等内容。
|
||||
|
||||
需要注意的是,在C语言中,struct、union这种“庞大”的数据类型也会被留在栈上。如果处理不当,很可能Stack Overflow。因此,在处理大型数据结构时,通常建议使用动态内存分配(如malloc)来避免栈空间的过度使用,并使用free来手动释放。
|
||||
|
||||
### Recursion 递归
|
||||
|
||||
由于“栈帧”的特性,它很适合用来做递归。系统会为每一层递归单独分配内存空间,彼此之间不会因为相同的代码导致变量的地址也相同(使用```%rbp + 数```管理)。
|
||||
@@ -152,17 +161,15 @@ A: 在编译时,编译器会根据函数预计内存的使用情况来生成
|
||||
| ```%rax``` | Caller-Save | 返回值 |
|
||||
| ```%rdi``` | Caller-Save | 传递参数 |
|
||||
| ```%rsi``` | Caller-Save | 传递参数 |
|
||||
| ```%rdx``` | Caller-Save | 传递参数 |
|
||||
| ```%rcx``` | Caller-Save | 传递参数 |
|
||||
| ```%rdx``` | Caller-Save | 传递参数 |
|
||||
| ```%r8``` | Caller-Save | 传递参数 |
|
||||
| ```%r9``` | Caller-Save | 传递参数 |
|
||||
| ```%r10``` | Caller-Save | 临时寄存器 |
|
||||
| ```%r11``` | Caller-Save | 临时寄存器 |
|
||||
| | | |
|
||||
| **```%rbx```** | Callee-Save | 被调用者保存寄存器 |
|
||||
| ```%r12``` | Callee-Save | 被调用者保存寄存器 |
|
||||
| ```%r13``` | Callee-Save | 被调用者保存寄存器 |
|
||||
| ```%r14``` | Callee-Save | 被调用者保存寄存器 |
|
||||
| ```%r12``` to ```%r15``` | Callee-Save | 被调用者保存寄存器 |
|
||||
| ```%rbp``` | Callee-Save[^rbp-linux-reg-saving] | 特殊[^rbp-linux-reg-saving] |
|
||||
| ```%rsp``` | Callee-Save[^rsp-linux-reg-saving] | 特殊[^rsp-linux-reg-saving] |
|
||||
|
||||
@@ -70,8 +70,8 @@ get_num: # 这是Debug配置下的编译结果,因此略显冗长
|
||||
|
||||
概念如图所示:
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### 内存中的二维数组
|
||||
|
||||
@@ -88,7 +88,7 @@ get_num: # 这是Debug配置下的编译结果,因此略显冗长
|
||||
```
|
||||
|
||||
这个数组在内存中存储的顺序如图:
|
||||

|
||||

|
||||
|
||||
也可以以这样的方式理解那个二维数组:
|
||||
|
||||
@@ -148,7 +148,7 @@ get_num_from_2d_arr: # assume: arr -> %rdi, row -> %esi, col -> %edx
|
||||
```
|
||||
|
||||
它也可以用get_num_from_2d_arr C语言函数来获取对应位置的数据,但**在内存中的情况完全不同**。它的内存布局长这样:
|
||||

|
||||

|
||||
|
||||
## Structures 结构体
|
||||
|
||||
@@ -166,7 +166,7 @@ struct structExample
|
||||
};
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
因此,对结构体的访问也很简单。编译器直接“按图索骥”,根据定义找到对应的偏移量就行了。
|
||||
|
||||
@@ -188,7 +188,7 @@ get_short_int:
|
||||
|
||||
出于硬件原因[^Memory-Alignment-Hardware-Reason],结构体中的成员在内存中的位置需要按照特定的边界进行对齐。这意味着编译器可能会在成员之间插入填充字节,以确保每个成员都位于正确的内存地址上。
|
||||
|
||||
[^Memory-Alignment-Hardware-Reason]: 内存对齐是为了提高访问效率。现在的机器一次从内存中读取大约64字节。如果数据没有对齐,可能需操作系统或者硬件的额外处理,导致效率降低。
|
||||
[^Memory-Alignment-Hardware-Reason]: 无论数据是否对齐,x86-64硬件都基本可以正常工作。但Intel依旧建议对其数据,以获得更好的性能。如果数据未对齐,CPU可能需要两次内存访问来获取数据,导致性能下降。
|
||||
|
||||
- 编译器会找到最大的成员的大小,并将结构体的总大小调整为该大小的倍数。内存对齐也会按照该大小进行。
|
||||
|
||||
@@ -208,7 +208,7 @@ struct badStruct
|
||||
};
|
||||
```
|
||||
|
||||

|
||||

|
||||
它足足空出来了8个字节的空间没有使用!这使得它的总大小达到了24字节。
|
||||
|
||||
如果我们稍稍改动一下排布顺序......
|
||||
@@ -223,7 +223,7 @@ struct goodStruct
|
||||
};
|
||||
```
|
||||
|
||||

|
||||

|
||||
这样子,我们只需要1个字节的填充。结构体的总大小也因此减少到了16字节。
|
||||
|
||||
通常来说,将结构体成员按照**从大到小**的顺序排列,可以最大程度地减少填充字节的数量。
|
||||
@@ -255,13 +255,13 @@ add_doubles:
|
||||
- ```addps```:对两个寄存器内的所有单精度浮点数进行加法运算
|
||||
|
||||
它们的图解如下图所示:
|
||||

|
||||

|
||||
*这里使用了SSE的128位寄存器来演示```addss```和```addps```指令的功能。对于AVX的256位寄存器,```addss```和```addps```指令的功能是一样的,只不过它们可以同时处理更多的浮点数。*
|
||||
|
||||
- ```addsd```:对两个寄存器内的第一个双精度浮点数进行加法运算
|
||||
- ```addpd```:对两个寄存器内的所有双精度浮点数进行加法运算
|
||||
|
||||

|
||||

|
||||
有了前面```addss```和```addps```的图解,```addsd```就不难理解了。它们的区别在于处理的数据类型不同,```addss```和```addps```处理单精度浮点数,而```addsd```和```addpd```处理双精度浮点数。
|
||||
|
||||
与此类似,```addpd```指令可以同时处理两个寄存器内的所有双精度浮点数进行加法运算。对于AVX的256位寄存器,```addpd```可以同时处理四个双精度浮点数进行加法运算。
|
||||
52
CMU-CSAPP/LSP1 Quick Reference Table 速查表.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 目录
|
||||
|
||||
- [CPU寄存器](#cpu寄存器)
|
||||
- [x86-64 Linux System V ABI的寄存器用途](#x86-64-linux-system-v-abi)
|
||||
- [Flags 标志位](#flags-标志位)
|
||||
|
||||
## CPU寄存器
|
||||
|
||||
### x86-64 **Linux** System V ABI
|
||||
|
||||
| 寄存器 | 用途 | 调用约定 | 32位 | 16位 | 8位 |
|
||||
| ------ | ---- | -------- | ----- | ----- | --- |
|
||||
| ```%rax``` | 返回值 | Caller-saved | ```%eax``` | ```%ax``` | ```%al``` |
|
||||
| ```%rbx``` | 基址寄存器 | Callee-saved | ```%ebx``` | ```%bx``` | ```%bl``` |
|
||||
| ```%rcx``` | 传递参数 | Caller-saved | ```%ecx``` | ```%cx``` | ```%cl``` |
|
||||
| ```%rdx``` | 传递参数 | Caller-saved | ```%edx``` | ```%dx``` | ```%dl``` |
|
||||
| ```%rsi``` | 传递参数 | Caller-saved | ```%esi``` | ```%si``` | ```%sil``` |
|
||||
| ```%rdi``` | 传递参数 | Caller-saved | ```%edi``` | ```%di``` | ```%dil``` |
|
||||
| ```%rsp``` | 栈指针 | Callee-saved | ```%esp``` | ```%sp``` | ```%spl``` |
|
||||
| ```%rbp``` | 帧指针 | Callee-saved | ```%ebp``` | ```%bp``` | ```%bpl``` |
|
||||
| ```%r8``` | 传递参数 | Caller-saved | ```%r8d``` | ```%r8w``` | ```%r8b``` |
|
||||
| ```%r9``` | 传递参数 | Caller-saved | ```%r9d``` | ```%r9w``` | ```%r9b``` |
|
||||
| ```%r10``` | 临时寄存器 | Caller-saved | ```%r10d``` | ```%r10w``` | ```%r10b``` |
|
||||
| ```%r11``` | 临时寄存器 | Caller-saved | ```%r11d``` | ```%r11w``` | ```%r11b``` |
|
||||
| ```%r12``` - ```%r15``` | 被调用者保存寄存器 | Callee-saved | ```%r12d``` - ```%r15d``` | ```%r12w``` - ```%r15w``` | ```%r12b``` - ```%r15b``` |
|
||||
| | | |
|
||||
| **```%rip```** | 指令指针 | | ```%eip``` | ```%ip``` | |
|
||||
| **```%rflags```** | 标志寄存器 | | ```%eflags``` | ```%flags``` | |
|
||||
|
||||
### Flags 标志位
|
||||
|
||||
| VS显示名称 | 标志位 | 全称 | 为1时表明... | 用户态可用 |
|
||||
| ---------- | ------ | ---- | ---- | ---------- |
|
||||
| OV | OF | **O**ver**f**low Flag | 有符号数运算结果溢出 | √ |
|
||||
| UP | DF | **D**irection **F**lag | 字符串指令的处理方向递减 | √ |
|
||||
| EI | IF | **I**nterrupt **F**lag | 允许外部中断 | |
|
||||
| PL | SF | **S**ign **F**lag | 结果为负数 | √ |
|
||||
| ZR | ZF | **Z**ero **F**lag | 结果为零 | √ |
|
||||
| AC | AF | **A**uxiliary carry **F**lag | (辅助进位) | √ |
|
||||
| PE | PF | **P**arity **F**lag | 结果的二进制表示中1的个数为偶数 | √ |
|
||||
| CY | CF | **C**arry **F**lag | 无符号数运算结果溢出 | √ |
|
||||
|
||||
## GDB调试常用命令
|
||||
|
||||
### 程序运行控制
|
||||
|
||||
| 命令 | 别名 |作用 | 备注 |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| ```run``` | ```r``` | 运行程序 | 可以带参数,如```run arg1 arg2``` |
|
||||
| ```continue``` | ```c``` | 继续执行程序 | 在断点处暂停后使用 |
|
||||
| ```stepi``` | ```s``` | 单步执行 | 进入函数调用 |
|
||||
|
||||
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
1837
CMU-CSAPP/labs/bomb_lab/dbomb.asm
Normal file
6
CMU-CSAPP/labs/bomb_lab/pswd.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Border relations with Canada have never been better.
|
||||
1 2 4 8 16 32 64
|
||||
0 207
|
||||
7 0
|
||||
)/.%&'
|
||||
4 3 2 1 6 5
|
||||
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Overview
|
||||
|
||||
这个仓库是学习CS的笔记。各个文件夹分别对应不同的课程,里面是我在学习过程中总结的笔记.
|
||||
|
||||
This repository contains notes on various CS courses. Each folder corresponds to a different course, and inside are the notes I have compiled during my learning process.
|
||||
52
UCB-CS162/L01-Overview-概述.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Overview 概述
|
||||
|
||||
- [What is an OS? 什么是操作系统?](#what-is-an-os)
|
||||
- [Process Abstraction 进程抽象](#process-abstraction-进程抽象)
|
||||
- [Separate 分割](#separate-分割)
|
||||
- [Service Provider 提供服务](#service-provider-提供服务)
|
||||
- [Conclusion 结论](#conclusion-结论)
|
||||
|
||||
## What is an OS?
|
||||
|
||||
操作系统是一个软件。它像是一个协调人,或者幻术师,简化硬件细节,给程序提供更友好、安全的接口。
|
||||

|
||||
|
||||
- 程序认为的“机器”实际上是操作系统提供的抽象接口,而不是物理硬件。
|
||||
- 每个正在运行的程序只需关心它自己,而无需协调其他程序的运行。
|
||||
- 操作系统提供更易于使用、安全的接口,隐藏了底层硬件。
|
||||
|
||||
操作系统不一定要实现全部功能。有时候,在特定环境下(比如在一个月球探测车上),会构建简化的操作系统。这种操作系统可能会缺乏很多功能,比如不支持网络或者缺乏安全措施。
|
||||
|
||||
## Process Abstraction 进程抽象
|
||||
|
||||
一个进程有:
|
||||
|
||||
- 地址空间 Address Space
|
||||
- 一个或多个线程,用以执行任务
|
||||
- 其他的系统状态(打开的文件、网络连接,etc.)
|
||||
|
||||
## separate 分割
|
||||
|
||||
在进程运行时,每个程序都会假设自己独占处理器。它无需关心是否有别的进程会和它产生冲突,或者抢占它的CPU时间。操作系统通过提供一个抽象的“机器”来实现这一点,使得每个进程都感觉自己独占了整个系统。
|
||||
|
||||
同样地,为了保护,操作系统一般也不会允许程序访问其他程序的内存或资源。这么做会让系统立刻终止该程序,并生成一份core dump。
|
||||

|
||||
|
||||
## Service Provider 提供服务
|
||||
|
||||
操作系统还会提供一些服务:
|
||||
|
||||
- 文件系统、网络、UI, etc.
|
||||
- 传输文件、权限管理、用户管理, etc.
|
||||
- Look and feel
|
||||
- 电源状态
|
||||
|
||||
## Conclusion 结论
|
||||
|
||||
操作系统:
|
||||
|
||||
- 提供便于使用的抽象层,应付各种各样的硬件
|
||||
- 便利性、保护、可靠性...
|
||||
- 统合可用的硬件资源,保护用户进程
|
||||
- 通过提供标准化的服务,简化应用开发
|
||||
- 提供fault containment(错误隔离), fault tolerance(容错), 以及fault recovery(错误恢复)机制。
|
||||
187
UCB-CS162/L02-Fundatmental-OS-Concepts.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Four Fundamental OS Concepts
|
||||
|
||||
## Complexity 复杂性
|
||||
|
||||
操作系统的硬件非常复杂。如果没有操作系统,用户必须直接与硬件交互,这将非常困难。操作系统通过提供抽象来隐藏硬件的复杂性,使用户能够更容易地使用计算机。
|
||||
|
||||
这也就意味着,如果处理不当:
|
||||
|
||||
- 复杂性会“泄露”出来。例如一些第三方驱动程序导致系统崩溃,或者某些功能无法正常工作。
|
||||
- 安全漏洞。例如2017年,Intel的CPU存在一个名为Meltdown的漏洞。攻击者甚至可以访问kernel模式的内存!
|
||||
- 不同版本的库会导致应用程序可能出问题。(现在我们有了Docker)
|
||||
|
||||
一般来说,操作系统进行的"抽象"包括:
|
||||
|
||||
- Processor -> Thread
|
||||
- Memory -> Address Space
|
||||
- Disks, SSDs, ... -> Files
|
||||
- Networks -> Sockets
|
||||
- Machines -> Processes
|
||||
|
||||
## Thread of Control
|
||||
|
||||
Thread 是一个独一无二的context, 其包括程序计数器、寄存器、堆栈、CPU标志位、内存等等。
|
||||
|
||||
### Resident / Running 驻留(运行态)
|
||||
|
||||
当一个线程处于Resident状态时,表示该线程正在被CPU执行。
|
||||
|
||||
Resident指: 寄存器当前保存了该线程的state (content)。
|
||||
|
||||
- 包括程序计数器(PC, program counter)、当前执行的指令的地址
|
||||
- PC指向内存中下一条指令的地址
|
||||
- 包括程序当前正在计算的数据
|
||||
- 栈指针
|
||||
- 剩下的、内存里的数据
|
||||
|
||||
### Suspended / Ready 挂起(就绪态)
|
||||
|
||||
当一个线程处于Suspended状态时,表示该线程已经准备好运行,但由于某些原因(如等待资源、等待I/O操作完成等)而暂时未执行。
|
||||
|
||||
与Resident状态具有很多相反的状态:
|
||||
|
||||
- CPU的state不再是该线程的content,而是另一个线程的content
|
||||
- PC指向另一个线程的指令地址
|
||||
- 大多数情况下,线程的content被保存在内存中,而不是寄存器中
|
||||
|
||||
### Content Switch 上下文切换
|
||||
|
||||
#### 使用场景
|
||||
|
||||
现代的操作系统通常支持多线程。电脑上可以同时运行很多线程,但单个CPU核心一次只能执行一个线程。当操作系统需要切换正在运行的线程时,就会发生上下文切换。
|
||||
|
||||
以图中为例:
|
||||

|
||||
|
||||
- T1时刻,vCPU1在CPU核心上,vCPU2在内存中
|
||||
- T2时刻,vCPU1在内存中,vCPU2在CPU核心上
|
||||
|
||||
在T1和T2之间,操作系统进行了一次**context switch**,也就是上下文切换:
|
||||
|
||||
- 将vCPU1的状态保存到**Thread Control Block** (TCB, 位于内存) 中。
|
||||
- 将vCPU2的状态从内存加载到CPU核心上
|
||||
- 其他操作.....
|
||||
|
||||
上下文切换不是毫无开销的。一般来讲,它会耗费几微秒的时间。因此,操作系统会尽量减少上下文切换的次数,并使用各种手段减少上下文切换的开销,以提高系统性能。
|
||||
|
||||
有多种条件可以出发一次Content Switch: 计时器、I/O事件、系统调用、线程优先级变化等等。
|
||||
|
||||
#### Thread Control Block (TCB)
|
||||
|
||||
TCB是一些数据,保存了线程的状态信息,包括寄存器值、堆栈指针、程序计数器、PC等。
|
||||
|
||||
通常来讲,TCB被存储在内存的kernel空间中。
|
||||
|
||||
#### Address Space 地址空间
|
||||
|
||||

|
||||
|
||||
## Reliability 可靠性
|
||||
|
||||
简单的上下文切换不提供操作系统保护,而我们显然不会希望一个user program崩掉整个系统!因此,操作系统需要提供一些保护机制。
|
||||
|
||||
- 可靠性:破坏操作系统通常会导致其崩溃
|
||||
- 安全性:限制线程的操作范围
|
||||
- 隐私性:限制每个线程仅能访问其被允许访问的数据
|
||||
- 公平性:每个线程应限制在其应得的系统资源份额内(CPU时间、内存、I/O等)
|
||||
|
||||
仅靠软件是不足以更好的保护系统的,因此我们有了一些硬件层面的保护机制:
|
||||
|
||||
### Base and Bound (B&B) 基址寄存器、边界寄存器
|
||||
|
||||

|
||||
如图所示,程序地址“看起来”位于 0 ~ 100 之间;但,当它被加载到内存内时,它被重新定位到 1000 ~ 1100 之间。
|
||||
|
||||
这是一种基于编译器、加载器的保护机制。它把操作系统和用户程序隔离,保护操作系统。
|
||||
|
||||
我们也可以通过添加一个加法器来实现更高效的B&B机制:
|
||||

|
||||
这是一种硬件辅助的内存重定向。
|
||||
|
||||
B&B机制有很多缺点。最显著的缺点之一是,它无法简单地处理用户程序allocate或者free内存的情况。由此,我们有了一个更复杂的机制:
|
||||
|
||||
### Address Space Translation 地址空间转换
|
||||
|
||||
我们可以通过增添一个特定的“小盒子”,来实现更高效的地址空间转换:
|
||||

|
||||
这个“小盒子”被称为**Memory Management Unit** (MMU),它可以将虚拟地址转换为物理地址。
|
||||
|
||||
这个解决方案很好地解决了B&B机制的缺点。用户程序可以allocate或者free内存,而不需要担心地址空间的重叠问题。其中,有一个至今仍然在使用的机制: **Paging** (分页机制)。
|
||||
|
||||
### Paging 分页机制
|
||||
|
||||
分页机制将内存划分为固定大小的页(通常是4KB)。每个页都有一个对应的页表项,记录了该页在物理内存中的位置。当程序访问一个虚拟地址时,MMU会查找对应的页表项,将虚拟地址转换为物理地址。
|
||||

|
||||
|
||||
我们还可以实现一点更“酷”的东西。为了节约内存,我们甚至可以让一些Page实际上不在内存中!对这些page的访问会触发一个**page fault**,程序中断后,操作系统将所需的page从磁盘加载到内存中,然后继续执行程序。
|
||||
|
||||
## Processes 进程
|
||||
|
||||
进程是具有受限权限的执行环境。
|
||||
|
||||
- (受保护的)具有一个或多个线程的地址空间
|
||||
- 拥有内存(地址空间)
|
||||
- 拥有文件描述符、文件系统上下文...
|
||||
- 封装一个或多个共享进程资源的线程
|
||||
|
||||
复杂的应用程序可以 fork/exec(创建/执行)子进程。
|
||||
|
||||
某种意义上来说,进程是一些“城市”,而操作系统是管理他们的“政府”。
|
||||
|
||||
- 每个城市都有自己的居民(线程),有自己的资源(内存、文件描述符等)。程序假设它们是独立的,不会相互干扰。
|
||||
- 政府负责管理这些城市,确保城市A出问题不会波及到城市B、审查城市AB之间的通信、分配硬件资源给城市, etc.
|
||||
|
||||
### 为什么需要进程?
|
||||
|
||||
- 它让不同程序、组件之间相互隔离,不会一个崩溃连带其他崩溃。
|
||||
- 操作系统免受它们的影响
|
||||
- 提供内存保护
|
||||
|
||||
### 保护与效率之间的基本权衡
|
||||
|
||||
- 进程**内**通信更容易。
|
||||
- 线程之间可以直接访问共享内存
|
||||
- 进程**间**通信更难
|
||||
- 需要使用IPC机制(如管道、消息队列、共享内存等)来进行通信
|
||||
|
||||
### 单线程与多线程
|
||||
|
||||

|
||||
可以从途中看到,各个线程**共享资源**(同一个地址空间、文件描述符等),但每个线程都有**自己的程序计数器、寄存器、堆栈**等。
|
||||
|
||||
## privilege levels 权限级别
|
||||
|
||||
上边我们提到了页表。那如果,应用A想要把页的映射地址改到一个它不应该访问的地址上怎么办?我们有了权限级别来解决这个问题。
|
||||
|
||||
### Dual Mode Operation 双模式操作
|
||||
|
||||
CPU至少会提供以下两个模式:
|
||||
|
||||
- User mode (Ring 3) 用户模式:应用程序运行在这个模式下,权限受限,无法直接访问硬件资源。
|
||||
- Kernel mode (Ring 0) 内核模式:操作系统运行在这个模式下,拥有完全的权限。
|
||||
|
||||
由此,我们解决了问题:当操作系统让其他应用程序运行时,它会将CPU切换到User mode。这样子,应用A就不能直接修改页表了。
|
||||
|
||||
如果应用程序需要从硬盘读取数据,它会发出一个系统调用(system call),请求操作系统执行这个操作。操作系统会将CPU切换到Kernel mode,执行相应的操作,然后再切换回User mode。
|
||||
|
||||
由此,我们得出了经典的UNIX系统架构:
|
||||

|
||||
|
||||
### How to switch? 如何切换当前模式?
|
||||
|
||||
从用户态到内核态:
|
||||
|
||||
- ```System call``` 系统调用
|
||||
- 定义: 进程请求系统服务,例如exit
|
||||
- 系统调用类似于函数调用
|
||||
- 没有要调用的系统函数的地址
|
||||
- 类似于远程过程调用 (RPC)
|
||||
- 将系统调用 ID 和参数整理到寄存器中,并执行系统调用
|
||||
- ```Interrupt``` 中断
|
||||
- 某些原因触发上下文切换
|
||||
- 例如:Timer 、I/O 设备
|
||||
- ```Trap``` 陷阱或```Exception``` 异常
|
||||
- 进程内部同步事件触发上下文切换
|
||||
- 例如:```Protection Violation - Segmentation Fault```(段错误)、除以零, etc.
|
||||
|
||||
在一次,例如```Interrupt```发生后,CPU会把当前正在运行的程序保存在```Thread Control Block (TCB)```中。TCB中保存了程序计数器、寄存器值、堆栈指针等,并存储在Kernal memory中。
|
||||
127
UCB-CS162/L03-Processes-and-Threads.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Processes and Threads
|
||||
|
||||
## Motivation for threads 为什么我们需要线程
|
||||
|
||||
- 操作系统必须支持多个任务同时运行。例如,进程、终端或系统维护。
|
||||
- 操作系统必须支持多个连接同时运行。例如,多用户、Web连接等。
|
||||
- 进程必须能够同时执行多个任务。例如,一个游戏必须同时有图形、声音与运算逻辑等。
|
||||
- 操作系统必须同时访问多个硬件,或者至少看起来像是同时访问。你不会希望动鼠标、按键盘时,系统不能访问网络或磁盘。
|
||||
|
||||
一些定义:
|
||||
|
||||
- Multi-Processing:多个 CPU(核心)
|
||||
- Multi-Programming:多个作业/进程
|
||||
- Multi-Threading:多个线程/进程
|
||||
|
||||
### Concurrency 并发
|
||||
|
||||
**并发**运行两个线程意味着什么?
|
||||
|
||||
- 调度器可以自由地,以任意顺序和交错方式,运行线程
|
||||
- 线程可能会运行至完成,或者以大块或小块的时间片运行
|
||||
|
||||

|
||||
|
||||
### Computes that never finish 永远无法完成的计算
|
||||
|
||||
想象如下代码,其中ComputePi()会尝试计算π的最后一位,并将计算过程写入文件pi.txt。
|
||||
|
||||
```C
|
||||
main() {
|
||||
ComputePI("pi.txt");
|
||||
PrintClassList("classlist.txt");
|
||||
}
|
||||
```
|
||||
|
||||
这个例子中,显然ComputePi是个不可能完成的函数。因此我们完全没有办法print ClassList!程序也无法停止,除非你Ctrl-C或者关闭电脑。
|
||||
|
||||
但是使用线程,我们可以这么做:
|
||||
|
||||
```C
|
||||
main() {
|
||||
create_thread(ComputePI, "pi.txt");
|
||||
create_thread(PrintClassList, "classlist.txt");
|
||||
}
|
||||
```
|
||||
|
||||
这样,即便ComputePI永远无法完成,我们也可以得到ClassList。
|
||||
|
||||
### IO / Compute Overlap
|
||||
|
||||
这是一个各种操作的时间表:
|
||||
|
||||
| Operation | Time |
|
||||
| :--- | :--- |
|
||||
| L1 cache reference | 0.5 ns |
|
||||
| Branch mis-predict | 5 ns |
|
||||
| L2 cache reference | 7 ns |
|
||||
| Mutex lock/unlock | 25 ns |
|
||||
| Main memory reference | 100 ns |
|
||||
| Compress 1K bytes with Zippy | 3,000 ns |
|
||||
| Send 2K bytes over 1 Gbps network | 20,000 ns |
|
||||
| Read 1 MB sequentially from DDR2 memory | 250,000 ns |
|
||||
| Round trip within same datacenter | 500,000 ns |
|
||||
| Disk seek on HDD | 10,000,000 ns |
|
||||
| Read 1 MB sequentially from HDD disk | 20,000,000 ns |
|
||||
| Send packet CA->Netherlands->CA | 150,000,000 ns |
|
||||
|
||||
Disk Seek(硬盘寻址)等操作显然需要很久,比如10ms。我们显然不希望CPU的一个核心等待这么久,不做任何计算。
|
||||
|
||||
## Introduction to threads 线程简介
|
||||
|
||||
线程有三个状态。如下:
|
||||
|
||||
- Running 运行态: 线程正在使用CPU的时间片
|
||||
- Ready 就绪态: 线程准备好运行了,但还没有上CPU
|
||||
- Blocked 阻塞态: 线程不能继续运行
|
||||
|
||||
我们可以基于此设计调度器。例如,如果两个线程都不需要IO,它们很可能会轮流使用同一个CPU核心:
|
||||

|
||||
|
||||
但是如果其中一个线程需要执行IO操作,那么它就会被阻塞,另一个线程就可以继续使用CPU核心:
|
||||

|
||||
|
||||
注: Blocked状态不会直接转为Running状态,必须先转为Ready状态。
|
||||
|
||||
### How to manage threads 如何管理线程
|
||||
|
||||
一个程序默认就含有一个线程。如果想要创建其他线程,我们必须使用一次System Call。
|
||||
|
||||
在C语言中,使用```pthread_create()```函数来创建线程。
|
||||
|
||||
```C
|
||||
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
void *(*start_routine)(void*), void *arg);
|
||||
```
|
||||
|
||||
- 创建线程,并以 `arg` 为唯一参数执行 `start_routine`。
|
||||
- `return` 相当于隐式调用 `pthread_exit`。
|
||||
|
||||
```C
|
||||
void pthread_exit(void *value_ptr);
|
||||
```
|
||||
|
||||
- 终止线程,并使 `value_ptr` 对任何成功的 `join` 操作可用
|
||||
|
||||
```C
|
||||
int pthread_join(pthread_t thread, void **value_ptr);
|
||||
```
|
||||
|
||||
- 挂起调用线程的执行,直到目标线程终止。
|
||||
- 返回时,若 `value_ptr` 非 `NULL`,则终止线程传递给 `pthread_exit()` 的值将存储在 `value_ptr` 指向的位置。
|
||||
|
||||
#### `void *(*start_routine)(void*)`是什么玩意?
|
||||
|
||||
我们从内向外看这个东西。
|
||||
`*start_routine` 是一个函数指针,指向一个函数,这个函数接受一个 `void*` 类型的参数,并返回一个 `void*` 类型的值。
|
||||
|
||||
## System Calls
|
||||
|
||||
System Call 接口隔开了用户程序与操作系统内核。
|
||||
这使得系统设计看起来像一个沙漏,而System Call正式沙漏中间最窄的地方,
|
||||
如图所示:
|
||||

|
||||
你会注意到System Call之上,用户程序之下还有一层Portable OS Library。这个库允许程序员使用同一套API来编写程序。
|
||||
|
||||
- C程序猿们会使用`libc`,提供很多以某种方式包装的系统调用。
|
||||
|
||||
BIN
UCB-CS162/images/L01-OS-Overview.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
UCB-CS162/images/L01-OS-stop-illegal-memory-access.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
UCB-CS162/images/L02-Address-Space-Translation-Overview.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
UCB-CS162/images/L02-B&B-Hardware-Assistant-1.png
Normal file
|
After Width: | Height: | Size: 377 KiB |
BIN
UCB-CS162/images/L02-Base-and-Bound.png
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
UCB-CS162/images/L02-Paged-Memory.png
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
UCB-CS162/images/L02-Simplified-Address-Space.png
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
UCB-CS162/images/L02-Simplified-Unix-System-Architecture.png
Normal file
|
After Width: | Height: | Size: 496 KiB |
BIN
UCB-CS162/images/L02-Single-Thread-vs-Multi-Thread.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
UCB-CS162/images/L02-content-switch.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
UCB-CS162/images/L03-MultiProcessing-vs-MultiProgramming.png
Normal file
|
After Width: | Height: | Size: 233 KiB |
BIN
UCB-CS162/images/L03-System-Call-In-OS.png
Normal file
|
After Width: | Height: | Size: 827 KiB |
BIN
UCB-CS162/images/L03-Thread-Scheduling-With-IO.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
UCB-CS162/images/L03-Thread-Scheduling-Without-IO.png
Normal file
|
After Width: | Height: | Size: 65 KiB |