本书的序言与前言
1、本书序言
计算机学科可概括为两大方面:计算系统的构建和基于计算机系统的计算技术应用。
从“系统观”来看待问题并解决问题。计算机系统类别的课程一直是计算机科学与技术专业的主要教学内容之一,我国计算机专业的课程体系曾广泛参考 ACM 和 IEEE 制订的计算机科学与技术专业教学计划设计。计算机系统类别课程分为二进制、汇编语言、操作系统、组成原理、体系结构、计算机网络、并发程序设计等。
本书系统地介绍了整个计算机系统的工作原理,帮助我们系统性地理解计算机如何执行程序、存储信息和通信;本书从程序员的角度看待计算机系统,重点讨论系统的不同结构对于上层应用软件编写、执行和数据存储的影响。
2、本书前言
本书的主要读者是计算机科学家、计算机工程师,以及那些想通过学习计算机系统的内在运作而能够写出更好程序的人。
本书从程序员的角度,解释了所有计算机系统的本质概念,并展示了这些概念是如何实实在在地影响应用程序的正确性、性能和实用性。讲述程序员如何能够利用系统知识来编写出更好的程序。
如果你研究并领会了本书中的概念,你将开始成为极少数的“牛人”,这些牛人知道事情是如何运作的,也知道当事情出现故障时如何修复。你写的程序将能够更好地利用操作系统和系统软件提供的功能,对各种操作条件和运行时参数都能正确操作,运行起来更快,并能避免出现使程序容易受到网络攻击的缺陷。这将是我们进一步学习编译器、计算机体系结构、操作系统、嵌入式系统、网络互联和网络安全的基础。
本书以 x86-64 微处理器为基础,本书讲解的内容几乎直接适用于这些“类Unix”操作系统(如 Linux、Solaris、FreeBSD、MacOS X等)。
本书学习的基本要求,希望你对 C 或 C++ 有一定的了解,并安装 VirtualBox / VMWare 虚拟机来运行本书中的程序示例。
本书前几章揭示了 C 语言程序和它们相应的机器语言程序之间的交互作用。本书中的机器语言示例,是由运行在 x86-64 处理器上的 GNU GCC 编译器编译而生成的。
3、如何阅读本书?
从程序员的角度学习计算机系统是如何工作的会非常有趣,主要是因为你可以主动地做这件事情。立即实验,立即就可以看到运行结果。事实上,学习计算机系统的唯一方法就是做(do)系统,即在真正的系统上解决具体的问题,或是编写和运行程序。
第01章:计算机系统漫游
计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。虽然系统的具体实现方式随着时间不断变化,但是系统内存的概念却没有改变。
所有的计算机系统都有着相似的硬件和软件组件,它们又执行着相似的功能。如果你希望深入了解这些组件是如何工作的以及这些组件是如何影响程序的正确性和性能的,那么本书便正是为你而写的。
如何避免由计算机表示数字的方式引起的数字错误?学习一些小窍门来优化自己的 C 代码,如何避免缓冲区溢出带来的安全漏洞?学会编写自己的 Unix Shell、动态存储分配包、Web服务器?
hello.c
#include
int main()
{
printf("hello, world\n");
return 0;
}
上述这段代码 hello.c 尽管程序非常简单,但是为了让它实现运行,计算机系统的每个主要组成部分都需要协调工作。从某种意义上来说,本书的目的就是要帮助你了解当运行上述这段代码时,计算机系统到底发生了什么以及为什么会这样。
我们通过跟踪 hello.c 程序的生命周期(编写程序、运行程序、输出消息、程序终止),开始学习计算机系统。在每个生命周期的阶段,将逐步介绍一些关键概念、专业术语和组成部分。
1、信息就是 位 + 上下文
1)源程序的本质
hello.c 是该程序的源程序(或者源文件),这是该程序生命周期的第一个阶段。源程序实际上就是“位 bit(0或1)”的序列,每8个位又被组织成一个“字节”。源程序中的文本字符,就是用“字节”来表示的。
2)hello.c 源程序的本质
大部分的现代计算机系统都使用 ASCII 标准来表示文本字符,即用一个唯一的单字节(8位)的整数值来表示一个文本字符。hello.c 源程序的 ASCII 文本表示如下图:
hello.c 源程序正是以字节(8位)序列的方式存储在文件中。每个字节都对应着一个整数值,进而对应着一个特定的字符。
例如,整数值 35 对应着字符 “#”;整数值 105 对应着字符 “i”;整数值 10 对应着源程序中的换行符 “\n”,依次类推。
3)什么是文本文件?什么是二进制文件?
像 hello.c 这样的仅由 ASCII 字节编码构成的文件被称作是“文本文件”。所有其它的文件,被称作是“二进制文件”。
4)什么是“上下文”?
事实上,计算机系统中所有的信息数据(包括磁盘文件、内存中的程序、内存中的用户数据、网络中传送的数据)都是由一串“位bit”来表示的。
区分不同数据对象的唯一方法就是我们读到这些数据对象时的上下文。在不同的上下文中,同一串字节序列可能代表着一个整数,也可能代表着一个浮点数,还可能代表着一个字符串或者机器指令。
作为程序员,需要搞懂数字的机器表示方式,因为它们和实际的整数、实数是不同的。本书第2章将进一步讲解其基本原理。
5)关于 C 语言的起源
C语言是贝尔实验室的 Dennis Ritchie 于1969年~1973年间创建的。
ANSI学会于 1989年颁布了 ANSI C 标准。
后来 ANSI C 标准成了 ISO 的责任,这些标准定义了 C 语言和一系列的函数库,即所谓的 C 标准库。
C语言是为了用于实现 Unix 系统而被发明的程序语言,大部分的 Unix内核以及所有支撑工具和函数库都是用 C语言编写的。正是由于 Unix系统几乎全部是用 C 语言编写的,所以它可以方便地移植到新的机器上。
C语言是系统级编程的首选。
2、源程序被其它程序翻译成不同的格式
1)可执行目标程序(文件)
高级语言的源程序,被其它程序转化为一系列的低级机器语言指令,这些指令再按照某种格式打包好,并以二进制磁盘文件的形式存放起来;即生成了可执行目标程序(文件)。可执行目标文件,可以被加载到内存中去,并由系统执行。
2)编译的四个阶段
3)关于 GNU 项目
3、了解编译系统如何工作是大有益处的
使用编译系统可以把源程序编译成最终可被系统执行的可执行目标程序,即正确有效的机器代码。作为程序员,我们非常有必要知识编译系统是如何工作的。
4、处理器读取并解释存储在内存中的指令
1)运行 hello 这个可执行目标文件。
源程序 hello.c 被编译完成后所生成的可执行目标文件 hello,存放在磁盘上。在Unix系统中可以执行该文件。
编译 hello.c 源程序:
gcc -o hello hello.c
执行 hello 可执行目标文件:
./hello
2)关于 shell 命令行解释器。
shell 是一个命令行解释器,它输出一个提示符,等待用户输入命令,然后执行这个命令。如果用户输入的命令的第一个单词不是内置的 shell 命令,那么 shell 就会假设这是一个可执行目标文件的名字,然后将该可执行目标文件加载到内存中并执行它。
上述,shell 解释器加载并运行 hello 文件,然后等待程序终止。当 hello 程序执行完成并终止后,shell 继续输出一个提示符,等待下一个输入的命令。
3)系统的硬件组成
4)运行 hello 可执行目标程序的过程,系统中到底发生了什么?
第1步,shell 等待我们输入命令。当我们输入 ./hello
,并敲下回车键时,shell 程序首先从键盘输入读取我们输入的命令。图示如下:
第2步,然后 shell 程序将 hello 文件中的代码和数据从磁盘中读取到寄存器中,再把它存放至主存中去。图示如下:
第3步,一旦 hello 可执行目标文件中的代码和数据被加载至主存中,处理器就开始执行 hello 程序,程序指令会把 “hello, world\n” 字符串中的字节从主存复制至寄存器文件中,再从寄存器文件中复制到显示设备,最终显示在屏幕上。图示如下:
5、高速缓存至关重要
1)为什么需要高速缓存存储器?
从上面分析可以看出,当在 shell 中输入./hello
命令后,hello 文件中的数据首先被复制到主存中,部分数据如“hello, world\n”又从主存中复制到寄存器文件,再到显示设备。
从程序员的角度来看,这些复制工作就是开销,必然会降低程序的执行效率。
2)高速缓存存储器解决了什么问题?
鉴于这种处理器和主存之间差异,系统设计者采用了更小更快的存储设备,即高速缓存存储器(cache memory),简称 cache 或 高速缓存。它作为暂时的集结区域,存放着处理器近期可能会需要的信息数据。图示如下:
在处理器和一个较大较慢的设备(如主存)之间插入一个更小更快的存储设备(如cache高速缓存),已经成为了一个普遍的系统设计观念。
6、存储设备所形成的层次结构
实际上,每个计算机系统中的存储设备都被组织成了一个存储器层次结构。图示如下:
在这个层次结构中,从上至下,设备的访问速度越来越慢,容量越来越大,并且每字节的造价也越来越便宜。
程序员可以利用对整个存储器层次结构的理解,来提高程序性能。后续章节中将更深入探讨。
7、操作系统管理硬件
1)操作系统
上述 hello 程序的加载和运行,并向显示器输出"hello, world\n"字样。事实上在这个过程中,shell程序和 hello程序都没有直接访问键盘、显示器、磁盘或者主存。取而代之,它们是依靠操作系统所提供的服务来完成上述工作的。
我们可以把操作系统看成是应用程序和硬件之间的一层软件。所有应用程序对硬件的操作,都必须通过操作系统来完成。
2)关于 Unix 及其规范
3)操作系统有哪两个基本功能?
4)进程
进程是计算机科学中最重要、最成功的概念之一。
什么是上下文?为什么需要上下文切换?
5)线程
6)虚拟内存(虚拟地址空间)
虚拟内存是一个抽象的概念,它为每个进程提供了一个假象,即每个进程都在独立占有并使用主存。对每个进程来讲,它们看到的主存是一致的,这被称为“虚拟地址空间”。
虚拟地址空间最上面的区域,存放着操作系统中的代码和数据,这对所有进程来讲都是一样的。虚拟地址空间的底部区域,存放着用户进程的代码数据。
进程所看到的虚拟地址空间,由大量准确定义的区域构成,每个区域都有专门的功能。如下图示:
7)文件
文件,这个简单而精致的概念,是非常强大的,因为它向应用程序提供了一个统一的视图来看待系统中可能含有的所有各式各样的 I/O 设备。
8)关于 Linux
8、系统之间使用网络通信
系统之间可以通过网络进行通信。对一个单独的系统来讲,网络可被视为一个 I/O 设备。
系统可以从主存中复制一串数据到网络适配器,数据流经网络到达另一台系统。系统还可读取其它系统发送来的数据,并将其复制到自己的主存中。这便是系统之间的网络通信。
这种客户端和服务端之间的交互,在所有网络应用中是非常典型的。后续章节中,我们进一步学习如何构造网络应用程序,并利用这些知识来创建一个简单的 Web 服务器。
9、三个重要的主题概念
计算系统不仅仅只是硬件。计算系统是硬件和系统软件互相交织的集合体,它们必须共同协作以达到运行应用程序的目的。本书余下部分,将讲述硬件和软件的详细内容,以帮助大家写出更快捷、更可靠、更安全的程序。
本小节将强调三个贯穿计算机系统所有方面的重要概念,并讨论这三个概念的重要性。
1)Amdahl 定律,什么是“相对性能”?
2)并发和并行
- 第一层次的并行:线程级并行
使用线程,能够在一个进程中执行多个控制流。对单处理器的系统来讲,在系统上同时运行多个程序进程,并发是通过快速切换进程来模拟实现的。这种并发允许多个用户同时与系统进行交互,如Web服务器;还允许一个用户同时从事多个任务,如用户同时运行音乐程序和网页浏览器。
当系统内核控制着多个处理器时,就得到了一台多处理器系统。这种多处理系统,伴随着多处理器和超线程的出现,变得越来越普及。
多核处理器是把多个 CPU(“核”)集成到一个集成电路芯片上。每个核都有自己的 L1/L2 高速缓存。
多处理器的使用,可以从两个方面来提升系统性能。首先,它减少了在执行多个任务时模拟并发的需要;其次,它可以使应用程序运行得更快(前提是这些应用程序是以多线程的方式来书写的)。
虽然,并发原理的形成和研究,已经超过了50年,但是多核和超线程系统的出现才极大地激发了一种愿望,即利用硬件能力开发出线程级并行性的应用程序。
- 第二层次的并行:指令级并行
在较低的抽象层次上,现在处理器可以同时执行多条指令的属性,被称为指令级并行。本书后续章节中,将探讨“流水线”,在流水线中,把执行一条指令所需要的活动划分成多个不同的步骤,把处理器硬件组织成一系列的阶段,每个阶段执行一个步骤,让这些阶段并行地操作,以用来处理不同指令的不同部分。
如果处理器能够实现比“一个周期一条指令”更快的执行速率,那么我们就称之为“超标量处理器”。事实上,大多数的现代处理器,都支持超标量操作。
- 第三层次的并行:单指令、多数据并行(SIMD并行)
在最低层次上,许多现代处理器拥有特殊的硬件,它允许一条指令产生多个可以并行执行的操作,这种方式被称为“单指令、多数据”,即 SIMD并行。
提供 SIMD指令支持的,多是为了提高处理器影像、声音和视频数据应用的执行速度。
3)计算机系统中“抽象”的重要性
“抽象”的使用是计算机科学中最为重要的概念之一。比如,为一组函数规定一个简单的应用程序接口(API)就是一个很好的编程习惯,程序员无须了解它的内部原理也可以使用这些API。不同的编程语言,提供了不同形式和等级的抽象支持,比如Java中的类,C中的函数原型等。
在操作系统中:文件是对I/O设备的抽象;虚拟内存(虚拟地址空间)是对程序存储器的抽象;进程是对一个正在运行的程序的抽象;虚拟机提供了对整个计算机系统(包括操作系统、处理器和程序)的抽象。
虚拟机的思想,是IBM在20世纪60年代提出来的,但是在近几年才显示出其在管理计算机方式上的优势。因为,一些计算机希望能够运行支持不同操作系统或支持不同版本的同一操作系统的应用程序。后续章节中,将进一步探讨这些“抽象”。
10、本章小结
END 2019-01-28