# jyy_os **Repository Path**: HduersIT/jyy_os ## Basic Information - **Project Name**: jyy_os - **Description**: 南京大学蒋炎岩 2024年春学期《操作系统》课程代码 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-07-29 - **Last Updated**: 2025-07-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #
return 的时候发生了段错误。
ret 之后 pc 变成了 1 从而触发段错误。 说明栈顶存的1,本来应该存子函数结束后的下一条语句的地址。
说明:直接用 gcc hello.c 编译出的 a.out 不会触发段错误,因为默认链接了许多库。gcc hello.c -Wl,-v 查看链接的详细信息。
```bash
kwq@kwq-machine:~$ gcc -c hello.c # 只编译不链接 生成 hello.o
kwq@kwq-machine:~$ ld hello.o # 链接 hello.o 有警告,但成功生成 a.out
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
kwq@kwq-machine:~$
kwq@kwq-machine:~$ ld hello.o --entry main # 链接警告消除
kwq@kwq-machine:~$ ./a.out # 执行报段错误。Segmentation fault (core dumped)
kwq@kwq-machine:~$
kwq@kwq-machine:~$ gdb a.out # 调试。starti 程序执行到 main()。layout asm 显示汇编代码。si 单步执行
```
程序不返回
```C++
int main() {
while(1);
}
```
编译、链接、执行都成功。但是程序不会结束了。
引出问题,程序怎么结束? 电脑怎么关机? 有关机指令吗?(没有)。
引出 系统调用。
```sh
gcc -c hello.c && ld hello.o # 编译 && 链接 得到 hello.o && a.out
./a.out # 执行成功
```
### 2.2 系统调用实现 hello world
系统调用实现最小 hello world 程序。具体代码见 [minimal.S](./course02/minimal.S)
strace 是一个Linux系统下的强大调试工具,用于跟踪用户空间进程对系统调用的执行情况。
```sh
make # 编译 minimal.S 生成 minimal.out
strace ./minimal.out # 跟踪执行 minimal.out
```
### 2.3 汉诺塔
[hanoi](./course02/hanoi/README.md)
#### 2.3.1 递归实现汉诺塔
[hanoi-r.c](./course02/hanoi/hanoi-r.c)
#### 2.3.2 非递归实现汉诺塔
[hanoi-nr.c](./course02/hanoi/hanoi-nr.c)
### 2.4 总结
C语言是状态机、gdb是状态机、程序是状态机、操作系统也是状态机
## 3.硬件视角的操作系统
### 3.1 固件
固件(Firmware)是位于BIOS芯片中的代码。其功能是运行操作系统前扫描计算机硬件,加载操作系统。
如今的固件(UEFI, Unified Extensible Firmware Interface)有图形化界面,算得上一个小的“操作系统”。
- CPU reset 后初始化硬件, 对接操作系统 Boot Loader
对于40年前的 IBM PC/PC-DOS 2.0(1983),BIOS启动后会访问硬盘的**主引导记录(MBR, Master Boot Record)**,如果MBR最后2个字节是0x55, 0xaa,则认为该MBR可以启动。
- 主引导记录(MBR)是硬盘的第一个扇区。
- Firmware(BIOS)会加载 MBR 到 0x7c00
MBR(Master Boot Record) 有三个部分。
- 主引导例程:446 字节的主引导例程包括一个可变负载编码器,这是 MBR 的必要数据。硬盘驱动器启动后,MBR 会立即将控制权转移到分区表中列出的操作系统。
- 磁盘分区表 (DPT):DPT 位于硬盘的第一个扇区中,包含有关分区位置的信息。它有 64 个字节。可以根据需要创建扩展分区,最多四个分区(每个分区 16 字节)。
- 识别码:MBR 可以通过其识别码来识别。它的值为 55AAH 或 AA55H,大小为 2 字节。
### 3.2 观察和调试 legacy BIOS
使用 __qemu-system-x86_64__ 虚拟机加载镜像文件。通过调试发现 BIOS使用 rep insl 指令每次从磁盘加载两个字节到内存。
[Makefile](./course03/debug-firmware/Makefile)
```sh
cd debug-firmware # 进入工作目录
make # 生成镜像文件 minimal.img
make run # 启动 qemu-system-x86_64 加载镜像文件
make debug # 开启gdb调试、启动 qemu-system-x86_64 加载镜像文件
```
### 3.3 实验框架的正确打开方式
请完成[0.开发环境搭建](#0.开发环境搭建)中的内容[Makefile](./course03/debug-bootloader/Makefile)才能跑起来
> make debug 报错 gdb.error: \$(AM_HOME)/am/src/x86/qemu/boot/boot.o: No such file or directory.
报错原因: 下载的[abstract-machine](https://github.com/NJU-ProjectN/abstract-machine)工具有问题。
解决方法: 修改\$(AM_HOME)/am/src/x86/qemu/boot/Makefile 第4行(编译行之后)添加 cp bootblock.o boot.o
## 4.数学视角的操作系统
- 程序是一种“数学严格”的对象。
- 程序 = 初始状态 + 迁移函数
### 4.1 为操作系统建模
操作系统模型 [os-model.py](./course04/os-model/os-model.py)
- 应用视角(自顶向下)
- 操作系统 = 对象 + API
- 应用通过 syscall 访问操作系统
- 机器视角(自底向上)
- 操作系统 = C程序
- 运行在计算机硬件上的一个普通程序
> 操作系统 = 状态机的管理者
当然,操作系统自己也是状态机,有自己的状态。
- 初始状态
- 仅有一个“main”状态机
- 迁移
- 选择一个状态执行一步
- 调度:状态机的选择不确定
- current = random.choice(self.procs)
- 操作系统每次次可以随即选择一个状态机执行一步
- I/O:系统外的输入不确定
- read返回的结果也有两种可能性
- 执行t = sys_read()后,t=0 或 t=1
### 4.2 枚举法理解操作系统
构建状态图检查程序正确性。 [mosaic.py](./course04/mosaic/mosaic.py)
## pthread_create()这个API的使用。
- [memory.c](./course05/thread-qa/memory.c)证明了多线程共享全局变量。[Makefile](./course05/thread-qa/Makefile)中的TLIB_PATH是[thread.h](./course05/thread-lib/thread.h)的路径。
- [stack.c](./course05/thread-qa/stack.c)测试了堆栈的大小。8MB
### 5.2 原子性
编译命令: make TLIB_PATH=../thread-lib/
- [山寨支付宝](./course05/alipay/README.md): 余额100,但[alipay.c](./course05/alipay/alipay.c)两次扣款100后,余额变成了264 - 100展示了并发编程的隐患。
- [加法测试](./course05/sum/README.md): [sum.c](./course05/sum/sum.c)展示了并发编程比想象中更困难。
- [加法模型](./course05/sum-model/README.md): 可以使用[mosaic.py](./course05/mosaic/mosaic.py)和[collect.py](./course05/mosaic/collect.py)这两个工具得到[sum.py](./course05/sum-model/sum.py)多线程执行的所有情况。./mosaic.py -c ../sum-model/sum.py | ./collect.py
### 5.3 顺序执行
还是[加法测试](./course05/sum/README.md): 用不同的优化等级编译[sum.c](./course05/sum/sum.c)
```sh
# 不做优化,每次执行的结果都不同。 很容易触发并发bug
make TLIB_PATH="../thread-lib/ -O0"
gcc -I../thread-lib/ -O0 -o sum sum.c
./sum
sum = 102893221
2*n = 200000000
./sum
sum = 99391903
2*n = 200000000
# O1,几乎每次都是正确结果的一半。查看汇编发现 T_sum()等价
# rax=1; rdx+=0x5f5e101;
# flag: rcx=rax; rax++; cmp(rax, rdx); jne flag;
# sum = rcx
make TLIB_PATH="../thread-lib/ -O1"
gcc -I../thread-lib/ -O1 -o sum sum.c
./sum
sum = 100000000
2*n = 200000000
# O2,几乎每次都得到正确答案。查看汇编发现 T_sum() 只有一条等价语句 sum = 0x5f5e100;
# 其中 0x5f5e100 == 100000000 。一条语句很难触发并发bug!隐患很难查
make TLIB_PATH="../thread-lib/ -O2"
gcc -I../thread-lib/ -O2 -o sum sum.c
./sum
sum = 200000000
2*n = 200000000
```
### 5.4 全局指令顺序
单处理器多线程符合如下假设:
- 处理器会保证指令“看起来”顺序完成
- 处理器也是编译器。(编译器可以调换指令的顺序,处理器也可以)
- 预取状态机执行的若干步,然后像编译器一样优化。
每个处理器都有自己的高速缓存。(内存在高速缓存之下。硬盘在内存之下。)导致多处理器多线程的实际执行的情况更加杂乱。
[mem-model.c](./course05/mem-model/mem-model.c) 实现了 [mem-model.py](./course05/mem-model/mem-model.py) 中的功能。
在C代码中,主线程T_flag是一个死循环,睡眠1us同时举起2个旗子,然后等待2面旗子被T1和T2放下。
T1和T2都在等自己的旗子举起来,然后写自己的变量为1,再读别人的变量并打印。最后放下自己的旗子。
> F1和F2是atomic_int flag;这个变量的第0位和第一位。所以flag=3;就是同时举起两面旗子。
```mermaid
flowchart LR
B1((T_1)) --->B2(while !F1)
B2 --一.1--> B3(x=1)
B3 --一.2--> B4(read y; print y)
B4 --一.3--> B5(F1=0)
B5 --> B2
A1((T_flag)) --> A2(x=y=0; usleep 1)
A2 --> A3("F1=F2=1
同时举旗")
A3 ----> A4("while F1 || F2
等待2个旗子都放下")
A4 ----> A2
C1((T_2)) --->C2(while !F2)
C2 -- 二.1 --> C3(y=1)
C3 -- 二.2 --> C4(read x; print x)
C4 -- 二.3 --> C5(F2=0)
C5 --> C2
```
可以肯定不会出现0 0,但实验出现了很多次0 0; 可以肯定1 1一定会出现, 但实验结果1 1几乎不出现。出现 1 1 必须 满足如下条件:
一.1 和 二.1 必须在 一.2和二.2 之前执行。
``` sh
../mosaic/mosaic.py -c mem-model.py | ../mosaic/collect.py
01
10
11
|V| = 60, |E| = 69.
There are 3 distinct outputs.
make TLIB_PATH=../thread-lib/
gcc -O2 -I../thread-lib/ -o mem-model mem-model.c
# 一千万次很耗时, 跑了一个多小时
./mem-model | head -n 10000000 | sort | uniq -c
8982738 0 0
920526 0 1
96735 1 0
1 1 1
```
## 6.并发控制:互斥(1)
### 6.1 实现互斥:关中断
[Stop-the-world 实现互斥](./course06/stop-the-world/README.md), 在[cli.c](./course06/stop-the-world/cli.c)代码中只有关中断和开中断两条语句。程序在应用层不能正常执行,OS会拒绝APP直接使用cli汇编。如果编译到系内核,然后运行在qemu-system-x86_64,发现模拟器一直重启。因为开中断了,然而并没有写中断处理函数,所以导致电脑一直重启。
``` sh
make
make debug
(gdb) layout asm
(gdb) d 1
(gdb) c
```
### 6.2 实现互斥:peterson算法
[Peterson 算法](./course06/peterson/README.md)的python实现见[peterson.py](./course06/peterson/peterson.py),本质是访问公共变量时,将自己的旗子举起,再谦让让别人访问(将turn设置为别人)。然后观察别人的旗子是否举起,以及 turn是不是自己。
``` sh
../../course05/mosaic/mosaic.py -c peterson.py | grep \"cs\" | sort | uniq
"cs": ""
"cs": "❶"
"cs": "❷"
make TLIB_PATH=../../course05/thread-lib/
./peterson | wc -c
peterson: peterson.c:35: critical_section: Assertion `atomic_fetch_add(&inside, -1) == 1' failed.
peterson: peterson.c:25: critical_section: Assertion `atomic_fetch_add(&inside, +1) == 0' failed.
1699840
```
对于C语言实现的[peterson.c](./course06/peterson/peterson.c), 开启 __禁止CPU乱序执行__ 能正常互斥。
- 如果取消了 __禁止CPU乱序执行__ ,很容易触发 __互斥失效__。
- 但是调用了 putchar() ;即使允许乱序执行,在有的CPU上很难触发 __互斥失效__。
- peterson算法只能满足两个线程的互斥。
### 6.3 实现互斥:原子指令与自旋
[使用原子指令实现求和](./course06/sum-atomic/README.md),具体代码见[sum.c](./course06/sum-atomic/sum.c)。原子指令的执行速度比不加原子关键字慢。
有了原子指令关键字。就可以实现自选锁了。
[cmpxchg 实现自旋锁](./course06/sum-locked/README.md),具体代码见[sum.c](./course06/sum-locked/sum.c)。
make TLIB_PATH=../../course05/thread-lib
## 7.并发控制:互斥(2)
##