深入理解计算机系统笔记 1,计算机系统漫游

让我们以一个hello程序为例:
#include <stdio.h>

int main()
{
  printf("hello, world\n");
}


1.1 信息就是位+上下文
hello程序的生命周期是从源程序hello.c开始的
该源程序实际上就是一个由0和1组成的位(比特)序列,每8个一组,称为字节
大部分现在系统使用ASCII标准来表示文本字符
hello.c程序是以字节序列的方式存储在文件中的,每个字节都有一个整数值来对应于某个字符
如#是35,i是105,换行符\n是10
hello.c这种由ASCII字符构成的文件称为文本文件,其他则称为二进制文件

hello.c的表示方法说明了一个基本的思想:系统中所有的信息--包括磁盘文件、存储器中的程序、存储器中存储的用户数据以及网络上传输的数据,都是由一串比特表示的
区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文,比如在不同的上下文中,同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令

1.2 程序被其他程序翻译成不同的格式
为了在系统上运行hello.c程序,每条C语句都必须被其他程序转化为一系列的低级机器语言指令
这些指令按照一种称为可执行目标程序的格式打好包,并以二进制磁盘文件的形式存放起来,目标程序也称为可执行目标文件
unix> gcc -o hello hello.c

这个过程分四个阶段,执行这四个阶段的程序(预处理器、编译器、汇编器和链接器)构成编译系统

1,预处理阶段根据#开头的命令修改原始的C程序,#include <stdio.h>告诉预处理器读取系统头文件stdio.h的内容并插入到程序文本中,从而得到另一个C程序,通常以.i作为文件扩展名
2,编译阶段将文本文件hello.i翻译成文本文件hello.s,它为一个汇编语言程序。汇编语言为不同高级语言的不同编译器提供了通用的输出语言,如C编译器和Fortran编译器产生的输出文件用的是一样的汇编语言
3,汇编阶段将hello.s翻译成机器语言指令,把这些指令打包成一种叫可重定位目标程序的格式,结果保存在目标文件hello.o中,hello.o是二进制文件,它的字节编码是机器语言指令而不是字符
4,链接阶段由于hello程序调用了printf这个标准C库中的一个函数,而printf函数存在于一个名为printf.o的单独预编译目标文件中,链接器就负责将它并入到我们的hello.o程序中,结果就得到hello文件,它是一个可执行目标文件,可执行文件加载到存储器后由系统负责执行

1.3 了解编译系统如何工作室大有益处的
一些重要原因促使程序员必须知道编译系统是如何工作的:
1,优化程序性能
switch和if-then-else、while和do、指针引用和数组索引
2,理解链接时出现的错误
链接器报告说无法解析一个引用、静态变量和全局变量、静态库和动态库
3,避免安全漏洞
缓冲区溢出错误

1.4 处理器读并解释储存在存储器中的指令
unix> ./hello
hello, world
unix>

shell等待命令输入并执行,如果命令的第一个单词不是一个内置的shell命令,那么shell就会假设这是一个可执行文件的名字,要加载和执行该文件
这里shell将加载和执行hello程序,然后等待程序终止,hello程序在屏幕上输出信息,然后终止

系统的硬件组成:
总线 -- 贯穿整个系统的是一组电子管道,称做总线,它携带信息字节并负责在各个部件间传递,通常总线被设计成传送定长的字节块,也就是字,字中字节数(字长)是基本的系统参数
I/O设备 -- 系统与外界的联系通道,例如用户输入的键盘和鼠标、用户输出的显示器、长期存储数据和程序的磁盘,每个I/O设备都是通过一个控制器或适配器与I/O总线连接起来,它们在I/O总线和I/O设备之间传递信息
主存 -- 主存是一个临时存储设备,在处理器执行程序时,它被用来存放程序和程序处理的数据,物理上来说就是DRAM芯片,逻辑上来说存储器是由一个线性的字节数组组成的,每个字节都有自己唯一的地址(数组索引),这些地址从零开始
处理器 -- 中央处理单元(CPU)简称处理器,是解释(或执行)存储在主存中指令的引擎,处理器的核心是一个被称为程序计数器(PC)的字长大小的存储设备(寄存器),任何时间点上,PC都指向主存中的某条机器语言指令(内含其地址)。从系统通电开始直到系统断电,处理器一直重复执行相同的基本任务:从程序计数器指向的存储器处读取指令,解释指令中的位,执行指令指示的简单操作,然后更新程序计数器指向下一条指令,而这条指令并不一定在存储器中和刚刚执行的指令相邻
操作在主存、寄存器文件和算术逻辑单元(ALU)之间循环
CPU在指令要求下可能会执行这些操作:
加载:从主存拷贝一个字节或者一个字到寄存器,覆盖寄存器原来的内容
存储:从寄存器拷贝一个字节或者一个字到主存的某个位置,覆盖这个位置上原来的内容
更新:拷贝两个寄存器的内容到ALU,ALU将两个字相加,并将结果存放到一个寄存器中,覆盖该寄存器中原来的内容
I/O读:从一个I/O设备中拷贝一个字节或者一个字到一个寄存器
I/O写:从一个寄存器中拷贝一个字节或者一个字到一个I/O设备
转移:从指令本身中抽取一个字,并将这个字拷贝到程序计数器中,覆盖PC中原来的值

程序执行过程:
shell等待我们输入字符串"./hello"后,shell逐一读取字符到寄存器,然后放到存储器中
当我们敲回车键时,shell知道我们已经结束了命令的输入,然后shell执行一系列指令
这些指令将hello目标文件的代码和数据从磁盘拷贝到主存,从而加载hello文件,数据包括最终被输出的字符串"hello, world\n"
利用DMA(直接存储器存取)技术,数据可以不通过处理器而直接从磁盘到达主存
一旦hello目标文件中的代码和数据被加载到存储器,处理器就开始执行hello程序的主程序中的机器语言指令
这些指令将"hello, world\n"串中的字节从存储器中拷贝到寄存器文件,再从寄存器文件拷贝到显示设备,最终显示在屏幕上

1.5 高速缓存
上面的例子可以看出,系统花费了大量的时间把信息从一个地方挪到另一个地方
hello程序最初位于磁盘上,程序加载时拷贝到主存,处理器运行程序时,指令拷贝到处理器
类似的,数据串"hello, world/n"开始在磁盘上,再被拷贝到主存,然后拷贝到显示设备
磁盘驱动器可能比主存大100倍,对处理器而言,从磁盘读取一个字的时间开销要比从主存读取的开销大1000万倍,处理器从寄存器文件中读数据比主存中读取则要快几乎100倍
针对这种处理器与主存之间的差异,系统设计者采用了更小更快的存储设备,称为高速缓存存储器(cache memories,简称高速缓存)
位于处理器芯片上的L1高速缓存的容量可达数万字节,访问速度几乎和寄存器文件一样快
容量为数十万到数百万的更大的L2高速缓存是通过一条特殊的总线连接到处理器的
访问L2的时间开销比L1大5倍,但仍然比访问主存快5~10倍
L1和L2高速缓存是用一种叫静态随机访问存储器(SRAM)的硬件技术实现的

1.6 形成层次结构的存储设备
每个计算机的存储设备组织成一个存储器层次模型,从上至下设备变得更慢、更大,并且每字节的造价也更便宜
寄存器文件位于层次模型的最顶部,第0级记为L0,L1高速缓存为第一层,L2高速缓存为第二层,主存为L3,本地磁盘等本地二级存储为L4,分布式文件系统、Web服务器等远程二级存储为L5
层次结构的主要思想是一个层次上的存储器作为下一层次上存储器的高速缓存,寄存器文件是L1的高速缓存,L1是L2的高速缓存,L2是主存的高速缓存,主存是磁盘的高速缓存,本地磁盘是分布式文件系统的高速缓存

1.7 操作系统管理硬件
当shell加载和运行hello程序时,当hello程序输出自己的消息时程序并没有直接访问键盘、显示器、磁盘或者主存储器,而是依靠操作系统提供的服务
操作系统可以看作是应用程序和硬件之间插入的一层软件,所有应用程序对硬件的操作尝试都必须通过操作系统
操作系统的两个基本功能:访问硬件被失控的应用程序滥用;在控制复杂而又通常广泛不同的低级硬件设备访问为应用程序提供简单一致的方法
操作系统通过几个基本的抽象概念(进程、虚拟存储器和文件)来实现这两个功能
文件是对I/O设备的抽象表示,虚拟存储器是对主存和磁盘I/O设备的抽象表示,进程则是对处理器、主存和I/O设备的抽象表示

进程是操作系统对运行程序的一种抽象,在一个系统上可以同时运行多个进程,而每个进程都好像在独占地使用硬件,我们称之为并发运行,实际上是说一个进程的指令和另一个进程的指令是交错执行的
操作系统实现这种交错执行的机制称为 上下文切换(context switching)
操作系统保存进程运行所需的所有状态信息,这种状态就是上下文(context),包括许多信息,比如PC和寄存器文件的当前值,以及主存的内容
在任何一个时刻,系统都只有一个进程正在运行
当操作系统决定从当前进程转移控制权到某个新进程时,它就会进行上下文切换,即保存当前进程的上下文、恢复新进程的上下文,然后将控制权转移到新进程,新进程就会从它上次停止的地方开始
进程这个抽象概念还暗示着由于不同的进程交错执行,打乱了时间的概念,使得程序员很难获得运行时间的准确和可重复测量

尽管通常我们认为一个进程只有单一的控制流,但是在现代系统中,一个进程实际上可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据
由于网络服务器中对并行处理的要求,线程成为越来越重要的编程模型,因为多线程之间比多进程之间更容易共享数据,也因为线程一般都比进程更高效

虚拟存储器是一个抽象概念,它为每个进程提供了一个假象,好像每个进程都在独占地使用主存
每个进程看到的存储器都是一致的,称之为虚拟地址空间,地址从小到大为:
1,程序代码和数据
2,运行时堆(运行时动态地扩展和收缩)
3,共享库(C标准库和数学库等)
4,栈(编译器用它来实现函数调用,调用一个函数时栈就会增长,从函数返回时栈就会收缩)
5,内核虚拟存储器(操作系统驻留在存储器中的部分,地址空间顶部四分之一的部分为内核预留,应用程序不允许读写这个区域的内容或者直接调用内核代码定义的函数)

文件就是字节序列,每个I/O设备,包括磁盘、键盘、显示器甚至网络都可以看成文件
系统中所有输入输出都是通过使用称为Unix I/O的以小组系统函数调用读写文件来实现的
文件这个简单而精致的概念非常强大,因为它使得应用程序能够统一地看待系统中可能含有的所有各式各样的I/O设备,如处理磁盘文件内容的程序员可以 非常幸福地无需了解具体的磁盘技术

1.8 利用网络系统和其他系统通信
从一个单独的系统来看,网络可以视为又一个I/O设备
当系统从主存拷贝一串字符到网络适配器时,数据流经过网络到达另一台机器,而不是到达本地磁盘驱动器
相似地,系统可以读取从其他机器发送来的数据,并把数据拷贝到自己的主存
例如telnet连接远程服务器并运行hello程序的例子,命令和结果在网络I/O中传输

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

处理器读取并解释存放在主存里的二进制指令
计算机花费了大量的时间在存储器、I/O设备和CPU寄存器之间拷贝数据,所以系统中的存储设备就被按层次排列,CPU寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM主存储器和磁盘存储器
在层次模型中位于更高层的存储设备比低层的存储设备要快,单位比特造价也更高
程序员通过理解和运用这种存储层次结构的知识,可以优化他们C程序的性能

操作系统内核是应用程序和硬件之间的媒介,它提供三个基本的抽象概念:文件是对I/O设备的抽象概念;虚拟存储器是对主存和磁盘的抽象概念;进程是对处理器、主存和I/O设备的抽象概念

网络提供了计算机系统之间通信的手段,从某个系统的角度来看,网络就是一种I/O设备

你可能感兴趣的:(C++,应用服务器,虚拟机,网络应用,C#)