#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
目标:通过跟踪上述程序的声明周期来开始对系统的学习——从它被创建开始到系统上运行,输出简单的消息,然后终止。沿着这个程序的生命周期,了解逐步出现的关键概念、专业术语和组成部分。
-
信息就是位 + 上下文
源程序实际上时一个值由0和1组成的比特序列,8个比特为一组称之为字节。每个字节表示程序中的某些文本字符(使用ASCII标准)。源程序的ASCII表示如下图
基本思想:系统中所有的信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。
上下文:由上可知,区分不同数据对象的唯一方法是通过这些数据对象的上下文。例如,在不同上下文中,同样的字节序列可能表示一个整数、浮点数、字符串。 -
编译成可执行文件
编译:通过编译器驱动程序可以项源程序转化成一系列低级机器语言指令,然后将这些指令按照可执行目标程序的格式打包,并以二进制磁盘文件的形式存放起来。如下图- 预处理阶段:预处理器根据以字符 # 开头的命令,修改原始的 C 程序。比如源程序中的 #include < stdio.h > 命令告诉预处理器读取系统头文件 stdio.h 的内容,并把它直接插入程序文本中。得到拎一个以 .i 作为文件扩展名的 C 程序。
- 编译阶段:编译器将文本文件 hello.i 翻译成文本文件 hello.s ,它包含一个汇编语言程序。该程序包含函数 main 的定义,如下所示
main: subq $8, %rsp movl $.LCO, %edi call puts movl $0, %eax addq $8, %rsp ret
- 汇编阶段:汇编器将 hello.s 翻译成机器语言指令,将这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件 hello.o 中
- 链接阶段:源程序中的 printf 函数是每个 C 编译器都提供的标准库函数。printf 函数存在于名为 printf.o 的单独预编译完成的目标文件中,因此需要以某种方式合并到 hello.o 程序中。链接器就负责处理这种合并。
-
系统的硬件组成
- 总线:贯穿整个系统的一组电子管道,携带定长的信息字节并附着在各个部件间传递。
- I/O设备:I/O设备是系统与外部世界的联系通道,如鼠标、键盘、显示器等。每个I/O设备都通过一个控制器或适配器与I/O总线相连。如下图
- 主存:临时存储设备,在处理器执行执行程序时,用来存放程序和程序处理的数据。
主存是由一组 动态随机存取存储器(DRAM) 构成。内部是一个线性的字节数组,每个字节都有其唯一的地址。 - 处理器:CPU是解释或执行存储在主存中指令的引擎
-
运行程序
- 键盘1输入命令 “hello” ,shell程序将字符逐一读入寄存器2,再把它放入内存3中。如下图
- 使用 直接存储器存取(DMA) 技术,数据可以不通过处理器而直接从磁盘4到达主存5。如下图。
- 数据被加载到主存后,处理器开始执行 main 程序中的及其语言指令。这些指令将 ”hello, world\n“ 字符串中的字节从主存6复制到寄存器7文件,在从寄存器8文件中复制到显示设备,最终显示在屏幕上。如下图。
- 键盘1输入命令 “hello” ,shell程序将字符逐一读入寄存器2,再把它放入内存3中。如下图
-
高速缓存至关重要
-
第4节的示例揭示了一个重要的问题,即系统花费大量时间把信息从一个地方移动到另一个地方。例如数据的流动1->2->3->4->5->6->7->8。为了减少复制操作的时间,使用高速缓存存储器(简称 cache 或高速缓存)作为暂时的集结区域。
-
利用高速缓存的局部性原理,即小恒徐具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据,大部分的内存操作都能在快速的高速缓存中完成。
-
-
存储设备的层次结构
根据机械原理,较大的存储设备要比较小的存储设备运行的慢,而快速设备的造价远高于同类的低速设备。为了平衡慢且大设备和快且小设备之间的存取速度,需要在两者之间插入较快且较大设备。如下图。
存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。因此,寄存器就是L1的高速缓存,L1是L2的高速缓存,L2是L3的高速缓存,L3是主存的高速缓存,而主存又是磁盘的高速缓存。 -
操作系统管理硬件
操作系统位于应用程序和硬件之间的一层软件,所有软件对硬件的操作都必须通过操作系统。
操作系统的功能:- 防止硬件被失控的应用程序滥用
- 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。
操作系统通过抽象的概念(进程、虚拟内存和文件)来实现上述功能。文件是对I/O设备的抽象表示,虚拟内存是对主存和磁盘I/O设备的抽象表示,进程则是对处理器、进程和I/O设备的抽象表示。
-
进程:操作系统对一个正在运行的程序的一种抽象。
上下文:操作系统保持跟踪进程运行所需的所有状态信息。
上下文切换:操作系统决定把控制权从当前进程转移到另一个新进程,就会进行上下文切换,即保存当前进程的上下文、恢复新进程的上下文,然后将控制权传递到新进程。示例场景中有两个并发的进程:shell进程和hello进程。最开始,只有shell进程在运行,即等待命令行上的输入。当我们让它运行hello程序时,shell通过系统调用,执行请求,系统调用会将控制权传递给操作系统。操作系统保存shell进程的上下文,创建一个新的hello进程及其上下文,然后将控制权传给新的hello进程。hello进程终止后,操作系统恢复shell进程的上下文,并将控制权传回给它,shell进程会继续等待下一个命令行输入。
-
线程
一个进程中包含多个线程,每个线程运行在进程的上下文中,并共享同样的代码和全局数据。 -
虚拟内存:为每个进程提供了一个假象,即每个进程都在独占的使用内存。如下图。
- 程序代码和数据。对于所有进程,代码是从同一固定地址开始,进接着的是C全局变量相对应的数据位置。代码和数据区是直接按照可执行目标文件的内容初始化。
- 堆。可以在运行时动态地扩展和收缩。
- 共享库。存放像C标准库和数学库这样地共享库地代码和数据地区域。
- 栈。实现函数调用。和堆一样在程序执行期间可以动态地扩展和收缩。
- 内核虚拟内存。不允许程序读写该区域地内容。必须通过内核执行操作。
- 文件
文件就是字节序列。通过向应用程序提供一个统一的视图,来看待系统中可能含有的所有的I/O设备。