shoufeng

瘦风的南墙

CSAPP 1 - 计算机系统漫游

0 序言及摘要

(1) 序言:

CS:APP —— Computer Systems: A Programmer's Perspective, 中译本名为: 《深入理解计算机系统》.

从书名可以得知:

大部分系统书籍都是从 构建者的角度 来写, 讲述如何实现硬件或系统软件, 包括操作系统、编译器和网络接口;
而 CS:APP 是从 程序员的角度 出发, 讲述应用程序员如何能够利用系统知识来编写出更好的程序, 涉及到系统的硬件架构、操作系统、编译器、网络等基础方面.

(2) 摘要:

计算机系统是由一个硬件和系统软件组成的, 它们共同协作以运行应用程序. 计算机内部的信息被表示为一组组的位, 他们依据上下文有不同的解释方式. 程序被其他程序翻译成不同的形式, 开始时是ASCII文本, 然后被编译器和链接器翻译成二进制可执行文件.

处理器读取并解释存放在主存里的二进制指令. 因为计算机花费了大量的时间在内存、I/O设备和CPU寄存器之间复制数据, 所以将系统中的存储设备划分成层次结构 —— CPU寄存器在顶部, 接着是多层的硬件高速缓存存储器、DRAM主存和磁盘存储器.

更高层的存储设备比低层的存储设备读写更快、单位比特造价也更高.
较高层次的存储设备可以作为较低层次设备的高速缓存.

操作系统内核是应用程序和硬件之间的媒介, 它提供三个基本的抽象:

a. 文件是对I/O设备的抽象;
b. 虚拟内存是对贮存和磁盘的抽象;
c. 进程是处理器、主存和I/O设备的抽象.

网络提供了计算机系统之间通信的手段 —— 可以把网络当作是一种I/O设备.

1 信息就是位+上下文

以下面的C程序hello.c为例:

#include <stdio.h>
int main() {
    printf("hello, world\n");
    return 0;
}

源程序实际上就是一个由0、1组成的位(bit, 比特)序列, 8个位被组成一组, 就是一个字节(Byte). 每个字节表示程序中的某些文本字符.

大部分现代计算机系统都适用ASCII标准来表示文本字符 —— 实际上就是用一个唯一的单字节大小的整数值来表示每个字符, 比如#由35表示, i由105表示.

hello.c程序是以 字节序列 的方式储存在文件中的, 每个字节都有一个整数值, 对应某些特定的字符.

注意: 每一行都是以一个看不见的换行符\n结束的, 对应的ASCII码是10.

hello.c的表示方法说明一个基本思想:

系统中的所有信息——包括磁盘文件、内存中的程序、内存中存放的用户数据, 以及网络上传输的数据, 都是由一串比特表示的.
区分不同数据对象的唯一方法就是我们读到这些数据对象时的上下文.

读书笔记: 上下文: 可以类比文章的前后文, 一个单独的int, 我们并不能明确地知道它的实际作用, 只有给出相邻的比特, 我们才能确定它的具体含义. 比如hello.c中, 由int的上下文可以知道, 这里的int是指明当前函数的返回值类型为int(整型).

2 程序被其他程序翻译成不同的格式

像上面的hello.c是我们开发人员可以看懂的高级语言编写的源代码文件, 要在系统上运行, 就必须通过其他程序将其转化为一系列的低级 机器语言 指令, 然后再将这些指令大包围 可执行目标程序(目标文件), 并以二进制磁盘文件的形式存放起来.

在Unix系统上, 从源文件到目标文件的转化是由 编译器驱动程序 完成的:

gcc hello.c -o hello

这里通过GCC编译器驱动程序读取源程序文件hello.c, 并把它变异成一个可执行目标文件hello, 具体步骤为:

编译系统

(1) 预处理阶段: 预处理器(cpp)根据以字符#开头的命令, 修改原始的C程序: 这里将读取引入的系统头文件stdio.h, 将它插入到源程序中文本, 得到另一个C程序, 一般为hello.i;

(2) 编译阶段: 编译器(ccl)将文本文件hello.i翻译成文本文件hello.s, 就是一个汇编语言程序;

(3) 汇编阶段: 汇编器(as)将hello.s翻译成机器语言指令, 把这些指令打包成一种叫做 可重定位目标程序 的格式, 并将结果保存在目标文件hello.o中 —— 是一个二进制文件;

(4) 链接阶段: hello.c中调用了printf打印函数, 这个函数是每个C编译器都提供的 标准C库 中的, 存在于printf.o的预编译好了的目标文件中, 在此阶段由链接器(ld)将printf.o合并到hello.o中, 最后得到hello文件 —— 可执行目标文件, 可以被加载到内存中由系统执行.

3 了解编译系统如何工作是大有益处的

(1) 优化程序性能

比如:

一个switch语句是否总是比if-else语句高效?
一个函数调用的开销有多大? while循环比for循环高效吗?
指针引用比数组索引更有效吗?
为什么将循环求和的结果放到一个本地变量中, 会比将其放到一个通过引用传递过来的参数中, 运行要快很多呢?

(2) 理解链接时出现的错误

一些最令人困扰的、难以定位的程序错误往往都和链接器操作有关. 比如:

链接器报告说无法解析一个引用是什么意思?
静态变量和全局变量的区别是什么?
静态库和动态库的区别是什么?
命令行上排列库的顺序有什么影响?

(3) 避免安全漏洞

缓冲区溢出错误是造成大多数网络和Internet服务器上安全漏洞的主要原因 —— 因为很少有程序开发人员能够理解 需要限制从不受信任的源接收数据的数量和格式.

安全编程的第一步就是理解 数据和控制信息存储在程序栈上的方式会引起的后果.

未完待续...

posted @ 2019-09-01 16:33  瘦风  阅读(386)  评论(0编辑  收藏  举报