让我们回想到第一个c语言程序:
// hello.c
#include <stdio.h>
int main()
{
printf("hello,world\n");
return 0;
}
接下来会通过沿着hello
程序的生命周期(被程序员创建->仔系统上运行->输出消息->终止)来介绍概念、术语和组成部分。
1.信息就是位+上下文
hello.c
实际上是由0和1组成的位(又称比特)序列,8位被组织成一组,称为字节。大部分现代计算机都是用ASCII标准来表示文本字符。下图给出ASCII码表示的hello.c
程序:
hello.c
以字节序列存储在文件中,每个字节都有一个整数值用以对应某些字符。像hello.c
这种只由ASCII字符构成的文件称为文本文件,其他所有的文件都被称为二进制文件。
系统中的所有信息--包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文(可大致了解下面两段话)。
上下文:原作中为Content。对于content,我找到来自wikipedia的简短解释:In computer science, a task context is the minimal set of data used by a task (which may be a process or thread) that must be saved to allow a task interruption at a given date, and a continuation of this task at the point it has been interrupted and at an arbitrary future date:在计算机科学中,在计算机科学中,任务上下文是任务(可能是进程或线程)使用的最小数据集,必须保存以允许任务中断,并且稍后从同一点继续。在可中断任务的情况下,上下文概念假定是重要的,其中,在中断时,处理器保存上下文并继续服务于中断服务程序。因此,上下文越小,等待时间越短。
在计算机中抽象随处可见,一种资源往往被抽象为一种引用/句柄/描述符等概念以方便其他模块使用,这样其他模块只需要用一个抽象的对象去指称要操作的具体资源。这是从外部接口来看,但是从内部实现来说,这些抽象的对象必须还原成其原本的具体的资源才能真正的实现操作,这个还原中需要用到的信息就可以称为context。一个简单的例子来说,在POSIX系统中,文件在操作接口上被抽象成了文件描述符(file descriptor),是一个int,但是在内核中实现的时候需要把这个int还原/映射成内部的file数据结构,还原/映射到具体的文件系统或者设备,还原到……,一直到具体的硬件IO,这里面每一抽象层的还原/映射都需要额外的信息辅助完成,这些额外的信息就是每一步的上下文/context。
2.程序被其他程序翻译成不同的格式
- 预处理阶段:预处理器根据字符
#
开头的命令,修改原始的C程序。得到hello.i
- 编译阶段:编译器将文本文件
hello.i
翻译成hello.s
。它包含一个汇编语言程序。 - 汇编阶段:编译器(as)将
hello.s
翻译成机器语言指令,把这些指令打包成一种可重定位目标程序(relocatable object program)的格式。得到hello.o
- 链接阶段:
hello
调用了printf
函数,它存在于一个printf.o
的单独的预编译好了的目标文件。链接器(ld)将其合并得到hello
,它是一个可执行目标文件。
3.了解编译系统如何工作是有益的
- 优化程序性能:3+5+6章
- 理解链接时出现的错误:7章
- 避免安全漏洞:3章
4.处理器读并解释储存在内存中的指令
linux>./hello
hello,world
linux>
4.1系统的硬件组成
为理解运行hello程序时发生了什么,我们需要了解一个典型系统的硬件组织,如下
- 总线:贯穿整个系统的一组电子管道。它携带信息字节并负责在各个部件间传递。通常总线被设计成传送定长的字节块,也就是字(word)
- I/O设备:系统与外部世界的联系通道。eg:鼠标、键盘、显示器、磁盘。每个I/O设备都通过一个控制器或适配器与I/O总线相连。控制器(I/O设备本身或者系统的主印刷电路板(主板)上的芯片组)和适配器(插在主板插槽上的卡)的区别在于它们的封装方式。第6章说明磁盘之类的I/O设备是如何工作、第10章学习如何让在应用程序中利用Unix I/O接口访问设备。
- 主存:临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。
- 处理器:理解、执行存储在主存中指令的引擎。
4.2运行hello程序
我们键入hello
命令的时候,shell
讲字符读入寄存器,再把它存放到内存中。当我们回车时,shell
就知道命令的输入结束,然后执行一系列指令来加载可执行的hello
文件,这些指令讲hello
目标文件中的代码和数据从磁盘复制到主存。数据包括最终会被输出的字符串“hello,world\n”
。被加载到主存之后,处理器就开始执行hello
程序的main
程序中的机器语言指令。
5.高速缓存至关重要
hello
程序说明,系统花费了大量的时间把信息从一个地方挪到另一个地方(磁盘->主存->处理器->显示器)。因此系统设计者的一个主要目标就是使这些复制操作尽可能快地完成。
因为较大的存储设备要比较小的存储设备运行的慢,而快速设备的造价远高于同类低速设备。eg:一个典型系统上磁盘可能比内存大1000倍,但是处理器从磁盘上读一个字的时间开销要比主存中读取开销大1000万倍。寄存器和主存的情况也类似。
针对这种差异,系统设计者采用了高速缓存存储器(cachae memory)作为暂时的集结区域,存放处理器近期可能会需要的信息。下图为一个典型系统中的高速缓存存储器:
所以,利用高速缓存存储器可以将程序的性能提高一个数量级。第六章学习
6.储存设备形成层次结构
在处理器和一个较大较慢的设备(如主存)之间插入一个更小更快的存储设备已经成为一个普遍的观念。实际上每个计算机系统的存储设备都被组织成了一个存储器层次结构,如下图。在该层次结构中,从上至下,设备的访问速度越来越慢,容量越来越大,并且每字节的造价也越来越便宜。
7.操作系统管理硬件
当shell
加载和运行hello
程序时,它们都没有直接访问键盘、显示器、磁盘或者主存而是依靠操作系统提供的服务。我们可以把操作系统看成是应用程序和硬件之间插入的一层软件,所有应用程序对硬件的操作尝试都必须通过操作系统。
操作系统的基本功能(通过对进程、虚拟内存和文件的抽象来实现):
- 防止硬件被失控的应用程序滥用
- 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。
文件是对I/O设备的抽象表示,虚拟内存是对主存和磁盘I/O设备的抽象表示,进程是对处理器、主存和I/O设备的抽象表示。
7.1进程
hello
在操作系统上运行时,系统会提供一种好像只有这个程序运行的假象。程序看上去独占处理器、主存和I/O设备。这些假象是通过进程的概念来实现的。
进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,每个进程都好像独占硬件。并发运行是指一个程序的指令和另一个程序的指令是交错执行的。在大多数系统中,需要运行的进程数是多余可以运行它们的CPU个数的。传统系统在一个时刻只能执行一个程序,而多核处理器能同时执行多个程序。无论在单核还是多核系统中,一个CPU看上去都是并发地执行多个程序,这是通过处理器在进程间切换来实现的。操作系统的这种交错执行的机制称为上下文切换。
操作系统保持跟踪进程运行所需的所有状态信息(PC和寄存器文件的当前值,以及主存的内容),即上下文。在任何一个时刻,单处理器系统只能执行一个进程的代码。当操作系统要进行进程转移时就会进行上下文切换(保存当前进程的上下文,恢复新进程的上下文,然后将控制权传递到新进程)。
详见第八章
7.2线程
尽管我们通常认为一个进程只有单一的控制流,但现代系统中,一个进程实际上可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码全局数据。多线程之间比多进程之间更容易共享数据,线程一般来说逗比进程更高效。详见1.9.2以及第12章
7.3虚拟内存
在多任务操作系统中,每个进程都运行在属于自己的内存沙盘中,即虚拟地址空间。虚拟内存为每个进程提供一个独占主存的假象。每个进程看到的内存都是一致的,称为虚拟地址空间。在Linux中(如下图),地址空间最上面的区域是保留给操作系统中的代码和数据,地址空间的底部区域存放用户进程定义的代码和数据。途中地址从下往上增大。
7.4文件
文件就是字节序列。每个I/O设备,包括磁盘、键盘、显示器甚至网络都可以看成文件。Unix I/O 见第十章
8.系统之间利用网络通信
详见计算机网络
9.重要主题
系统不仅仅是硬件,系统是硬件和系统软件互相交织的集合体。
9.1Amdahl定律
主要观点:要想显著加速整个系统,必须提升全系统中相当大的部分的速度。
9.2并发和并行
并发(concurrency):通用概念,指一个同时具有多个活动的系统
并行(parallelism):指用并发来使一个系统运行得更快
下面,按照系统层次结构中由高到低的顺序强调三个层次
9.2.1线程级并发
在单处理器系统中,我们能够设计出同时有多个程序执行的系统,这就导致了并发。但这种并发只是模拟出来的,是通过一台计算机在它正在执行的进程间快速切换来实现的(好比杂耍艺人同时扔多个球)。此种系统也能实现同时浏览网页和听音乐等工作。
然后多核处理器和超线程(hyperthreading)出现并变得普及,典型的多核处理器组织结构--一个保存最近取到的指令,另一个存放数据。这些核共享更高层次的高速缓存以及主存的接口。
超线程有时称为多线程(simultaneous multi-threading)是一项允许一个CPU执行多个控制流的技术。采用超线程即是可在同一时间里,应用程序可以使用芯片的不同部分。
多处理器的使用可以从两方面提高系统性能。首先,它减少了在执行多个任务时模拟并发的需要。其次,它可以使以多线程方式编写的应用程序运行的更快。
9.2.2指令级并行
现代处理器可以同时执行多条指令的属性称为指令级并行。最近的处理器可以保持每个时钟周期2-4条指令。但是每条指令从开始到结束需要20个或者更多周期的时间。处理器使用了很多技巧来同时处理多达100条指令。在第4章中,我们会研究流水线(pipelining)。如果处理器可以达到比一个周期一条指令更快的执行速度,就称为超标量(super-scalar)第5章我们将描述超标量处理器
9.2.3单指令、多数据并行
现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据即SIMD并行。