第 2 章 操作系统介绍
2.1 虚拟化 CPU
cpu.c view raw 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> #include <stdlib.h> #include "common.h" int main (int argc, char *argv[]) { if (argc != 2 ) { fprintf (stderr , "usage: cpu <string>\n" ); exit (1 ); } char *str = argv[1 ]; while (1 ) { printf ("%s\n" , str); Spin(1 ); } return 0 ; }
程序会重复打印传入的字符串,Spin()
函数用于暂停 1 秒。
如果后台运行多个实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ./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.c view raw 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include "common.h" int main (int argc, char *argv[]) { int *p = malloc (sizeof (int )); assert(p != NULL ); printf ("(%d) address pointed to by p: %p\n" , getpid(), p); *p = 0 ; while (1 ) { Spin(1 ); *p = *p + 1 ; printf ("(%d) p: %d\n" , getpid(), *p); } return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 $ ./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 并发
threads.c view raw 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <stdio.h> #include <stdlib.h> #include "common.h" #include "common_threads.h" volatile int counter = 0 ; int loops;void *worker (void *arg) { int i; for (i = 0 ; i < loops; i++) { counter++; } return NULL ; } int main (int argc, char *argv[]) { if (argc != 2 ) { fprintf (stderr , "usage: threads <loops>\n" ); exit (1 ); } loops = atoi(argv[1 ]); pthread_t p1, p2; printf ("Initial value : %d\n" , counter); Pthread_create(&p1, NULL , worker, NULL ); Pthread_create(&p2, NULL , worker, NULL ); Pthread_join(p1, NULL ); Pthread_join(p2, NULL ); printf ("Final value : %d\n" , counter); return 0 ; }
两个线程都更新共享计数器 counter
的值。
编译运行:
1 2 3 4 5 6 7 8 9 10 $ 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 和内存不同,操作系统不会对每个程序虚拟化磁盘。
io.c view raw 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include <unistd.h> #include <assert.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <string.h> int main (int argc, char *argv[]) { int fd = open("/tmp/file" , O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); assert(fd >= 0 ); char buffer[20 ]; sprintf (buffer, "hello world\n" ); int rc = write(fd, buffer, strlen (buffer)); assert(rc == (strlen (buffer))); fsync(fd); close(fd); return 0 ; }
该程序会创建文件 /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 利用率。
现代