第 2 章 操作系统介绍
2.1 虚拟化 CPU
程序会重复打印传入的字符串,Spin()
函数用于暂停 1 秒。
$ ./cpu "A"
A
A
A
A
^C
如果后台运行多个实例:
./cpu A & ./cpu B & ./cpu C & ./cpu D &
[1] 7353
[2] 7354
[3] 7355
[4] 7356
A
B
D
C
A
B
D
C
A
...
只有一个处理器,但 4 个程序似乎在同时运行。
将单个 CPU 转化为看似无限数量的 CPU,从而让多个程序看起来同时运行,这就是虚拟化 CPU。
2.2 虚拟化内存
$ ./mem &; ./mem &
[1] 24113
[2] 24114
(24113) address pointed to by p: 0x200000
(24114) address pointed to by p: 0x200000
(24113) p: 1
(24114) p: 1
(24114) p: 2
(24113) p: 2
(24113) p: 3
(24114) p: 3
(24113) p: 4
(24114) p: 4
可以看到两个程序在相同地址分配了内存,并独立更新该处的值。
这就是虚拟化内存,每个进程访问自己的私有虚拟地址空间,操作系统将其映射到机器的物理内存上。
注意,直接运行是无法得到相同地址的,需要禁止地址随机化,例如使用 setarch $(uname -m) -R ./mem & setarch $(uname -m) -R ./mem &
来运行。
2.3 并发
两个线程都更新共享计数器 counter
的值。
编译运行:
$ gcc -o thread thread.c -Wall -pthread
$ ./thread 1000
Initial value : 0
Final value : 2000
$ ./thread 100000
Initial value : 0
Final value : 143012
$ ./thread 100000
Initial value : 0
Final value : 137298
一些结果与预期不同,这是因为 counter
自增需要 3 条指令:
- 将
counter
的值从内存读取到寄存器。 - 将寄存器中的值自增、将寄存器中的值写回内存。
- 这 3 条指令不是原子方式执行。
无法在本地复现此实例,原因不明,可能是并发数太少。
2.4 持久性
DRAM 是易失性的,需要硬件和软件来持久地保存数据。
硬件是一些 I/O 设备,如 HDD、SSD。
软件指文件系统。
与 CPU 和内存不同,操作系统不会对每个程序虚拟化磁盘。
该程序会创建文件 /tmp/file
,在其中写入 hello world
。
程序向操作系统发出 3 个系统调用:
- 对
open()
的调用,打开文件并创建它。 write()
将一些数据写入文件。close()
关闭文件。
这些系统调用会转到文件系统进行处理。
2.5 设计目标
- 提供高性能,在提供虚拟化和其它 OS 功能的情况下,减少时间或空间上的开销。
- 提供保护,即在程序之间以及在 OS 和应用程序之间提供保护,让进程彼此隔离。
- 提供高度的可靠性。
- 其它目标:能源效率(降低功耗)、安全性、移动性等。
2.6 简单历史
早期 OS:只是一些库
OS 基本只是一组常用函数库,程序以过程调用来访问。
OS 一次运行一个程序,计算模式通常是批处理。
超越库:保护
系统调用诞生。与过程调用不同,系统调用把控制转移到 OS 中同时提高硬件特权级别,用户程序以用户模式运行,这意味着硬件限制了应用程序的功能,例如不能直接进行磁盘 I/O、访问物理内存页、在网络上发送数据包等。
通常通过陷阱(trap)硬件指令发起系统调用,硬件将控制转移到陷阱处理程序,同时将特权级别提升到内核模式,在内核模式下,OS 可以完全访问系统硬件。OS 完成请求时,通过陷阱返回指令将控制权交还给用户,该指令返回到用户模式,同时将控制权交还给应用程序。
多道程序时代
将大量作业加载到内存中并在它们之间快速切换,避免 I/O 时占用 CPU,提高 CPU 利用率。