深入理解计算机系统 ---计算机系统漫游

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_34916272/article/details/102589462

hello.c源程序
本书的目的: 了解你在系统上执行hello程序时,系统发生了什么 以及 为什么会这样。
(这句话的背后,隐藏了不为人知,令人抓狂的故事)

1.1 信息就是上下文 + 位

计算机系统是由硬件和系统软件 组成的,它们共同工作来运行应用程序。

源程序(hello.c)就是由0和1组成的位(bit,又称为比特)序列,8个位被组织成一组,成为字节。每个字节表示程序中的某些文本字符。

8bit = 1byte(B)
1kb = 1024byte(b)
1mb = 1024kb
类推。

大部分计算机都使用ASCLL标准来表示文本字符,用一个唯一的字节大小的整数值来表每个字符。

ASCLL表
Hello.c程序是以字节序列的方式存储在文件中的。
Hello.c的表示方法说明了一个基本思想:系统中所有的信息,都是一串比特表示的。

1.2程序被其它程序翻译成不同的格式

在这里插入图片描述
为了在系统上运行hello.c程序,每条C语句都必须被其它程序转换为一系列低级机器语言指令,这些指令按照一种称为**可执行目标程序的格式(windows PE, Linux ELF)**打好包并以二进制文件的形式存储起来。

预处理阶段,预处理器根据以字符#开头的命令,修改原始的C程序
比如: #include <stdio.h> 命令告诉预处理器读取系统头文件stdio.h的内容
并把它直接插入程序文本中,然后得到了另一个C程序, .i 作为文件扩展名

编译阶段,编译器将文本文件hello.i翻译成文本文件hello.s,它包含一个会变语言程序
该程序包含一个main的定义
在这里插入图片描述
2-7行, 每条语句都以一种文本格式(汇编语言)描述了一条低级机器语言指令。

汇编阶段,汇编器将hello.s翻译成机器指令,把这些指令打包成一种叫做可重定位目标程序的格式,保存在目标文件hello.o中
Hello.o是一个二进制文件,包含指令编码

链接阶段,hello程序调用了printf函数,标准c库中的一个函数,位于printf.o的单独编译好了的目标文件中,这个文件已某种方式合并到我们的hello.o程序中,链接器负责合并,结果得到hello文件,可执行目标文件,可以被加载到内存中,由系统执行

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

优化程序性能
switch 为什么 比 if…else… 高效,什么情况下高效
while 比 for 更有效吗?
等等诸多问题

理解链接时出现的错
静态变量 全局变量 区别,等等诸多问题

避免安全漏洞
如 缓冲区溢出 等等诸多问题

1.4处理器读并解释存储在内存中的指令
Hello.c被翻译成了可执行目标文件hello,Linux中执行这个目标文件:
在这里插入图片描述
Shell命令行解释器,输入并执行这个指令,如果不是一个指令,shell会假设这是可执行目标文件的名字,加载并运行这个文件。

1.4.1系统的硬件组成

在这里插入图片描述

  1. 总线
    贯穿整个系统的是一组电子管道,称为总线,携带信息字节负责在各个部件间传递
  2. I/O设备
    输入/输出设备是系统与外部世界联系的通道
  3. 主存
    主存是一个临时设备,在处理器执行程序时,用来存放程序和程序处理的数据
    从物理上来说,主存是由一组动态随机存取存储器(DRAM)芯片组成
    从逻辑上来说,存储器是一个线性的字节数组,每个字节都有其唯一的地址(数组索引)
    这些地址是从0开始,一般来说,组成程序的每条机器指令都由不同数量的字节构成
  4. 处理器
    中央处理单元,是解释(执行)存储在主存中的指令引擎
    处理器核心是存储设备(寄存器),成为程序计数器(PC)
    在任何时候,PC都指向主存中的某条机器语言指令(即含有该条指令的地址)

从通电开始,指点系统断电,处理器一直在不断地执行程序计数器指向的指令
再更新程序计数器,使其只想下一条指令

处理器看上去是按照一个非常简单的指令模型来操作的,这个模型是由 指令集架构 决定的

在这个模型中,指令按照严格的顺序执行,而执行一条指令包含执行一系列的步骤
处理器从程序计数器指向的内存处读取指令,解释指令中的位,执行该指令的简单操作
然后更新PC,使其指向下一条指令,而这条指令并不一定和在内存中刚刚执行的指令相邻

它们围绕着主存,寄存器文件,算术/逻辑单元(ALU)进行。
在这里插入图片描述

1.4.2运行hello程序

在这里插入图片描述
初始时,shell程序执行它的指令,等待我们输入一个命令
Shell程序将字符逐一读入寄存器再把它放到内存中
当我们在键盘上敲击回车键时,shell程序知道我们结束了命令的输入

然后shell执行一系列指令来加载可执行的hello文件
这些指令将hello文件中的数据和代码从磁盘复制到主存
利用直接存储器存取(DMA)技术,数据可以不通过处理器直接从磁盘到达主存

在这里插入图片描述
一旦目标文件hello中的代码和数据被加载到主存,处理器开始执行hello程序的main程序中的机器语言指令,这些指令将”hello world\n”,字符串中的字节从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上
在这里插入图片描述

1.5高速缓冲至关重要

上边简单的示例揭示了一个重要的问题,系统花费了大量的时间把信息从一个地方挪到另一个地方,对程序运行的效率有很大的阻碍
在这里插入图片描述
针对这种处理器与只存之间的差异,设计了采用更小更快的存储设备
称为高速缓存存储器(cache memory),作为暂时的集结区域,存放处理器近期可能会需要的信息
在这里插入图片描述
访问速度几乎和访问寄存器文件一样快
L1和L2高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的

告诉缓存存在的应用程序员能够利用高速缓存将程序的性能提高一个数量级

1.6存储设备形成层次结构

在这里插入图片描述

1.7操作系统管理硬件

当shell加载和运行hello程序时,以及hello程序输出自己的消息时,shell和hello程序都没有直接访问键盘,显示器,磁盘或者主存
取而代之的是,他们依靠操作系统提供的服务
我们可以把操作系统看作是应用程序和硬件之间插入的一层软件
在这里插入图片描述
所有应用程序对硬件的操作尝试都必须通过操作系统

操作系统两大基本功能:
1.防止硬件被失控的应用程序滥用
2.向应用程序提供简单一致的机制控制复杂而又通常大不相同的低级硬件设备
操作系统通过几个基本的抽象概念(进程,虚拟内存和文件),来实现这两个功能
在这里插入图片描述
文件是对I/O设备的抽象表示
虚拟内存是对主存和磁盘I/O设备的抽象表示
进程则是对处理器,主存和I/O设备的抽象表示

1.7.1进程

像hello这样的程序在现代系统上运行时,操作系统会提供一种假象,就好像系统上只有这个程序在运行
程序看上去是独占地使用处理器,主存和I/O设备
处理器看上去就像在不间断地一条接一条地执行程序中的指令
即程序的代码和数据是系统内存中唯一的对象
这些假象是通过进程的概念来实现的

进程是操作系统对一个正在运行的程序的一种抽象,一个系统可以同时运行多个进程
而每个进程都好像独占地使用硬件
而并发运行,一个进程的指令和另一个进程的指令是交错执行的

大多数操作系统中,需要运行的进程数是多于可以运行它们的CPU个数的
传统系统在一个时刻只能运行一个程序,多核处理器同时能够执行多个程序
无论是在单核还是多核系统中,一个CPU看上去都像是在并发地执行多个进程
这是通过处理器在进程间切换来实现的,操作系统实现这种交错执行的机制称为上下文切换

操作系统保持跟踪进程运行所需的所以状态信息,这种状态也就是上下文
包括许多信息,比如PC和寄存器文件的当前值,主存的内容
在任何一个时刻,单处理器系统都只能执行一个进程的代码
当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换
即保存当前进程的上下文,恢复新进程的上下文,然后将控制权转移到新进程
新进程就会从它上次停止的地方开始

在这里插入图片描述
A为shell程序,B为hello程序。
最开始只有shell程序在运行,等待命令输入,让它运行hello程序时,shell通过调用一个专门的函数,即系统调用,来执行我们的请求,系统调用会将控制权传递给操作系统
操作系统保存shell进程的上下文,创建一个新的hello程序及其上下文,然后将控制权给新的hello进程,hello进程终止后,操作系统恢复shell进程的上下文,并将控制权传回给它,shell进程会继续等待下一个命令行输入

从一个进程到另一个进程的转换是由操作系统内核(kernel)管理的
内核是操作系统代码常驻主存的部分,当应用程序需要操作系统的某些操作时,比如读写文件,就会执行一条特殊的**系统调用(System Call)**指令,将控制权传递给内核,然后内核执行被请求的操作并返回应用程序
内核不是一个独立的进程,它是系统管理全部进程所用代码和数据结构的集合

实现进程这个抽象的概念需要低级硬件和操作系统之间的紧密合作

1.7.2线程

在现代操作中,一个进程实际上可以由多个称为线程的执行单元组成
每个线程都运行在进程的上下文中,并共享同样的代码和全局数据

通常来说,进程是容器-空间,存放资源的
线程是真正执行东西的

1.7.3虚拟内存

虚拟内存是一个抽象概念,为每个进程提供了一个假象,即每个进程都在独占使用主存
每个进程看到的内存都是一致的,称为虚拟地址空间
在这里插入图片描述
图为Linux进程的虚拟地址空间,地址从下往上增长
地址空间最上面的区域是保留给操作系统中的代码和数据的,这对所有进程来说都是一样的

每个进程看到的虚拟地址空间由大量准确定义的区构成,每个区都有专门的功能

程序代码和数据。 对所有的进程来说,代码是从同一固定地址开始
紧接着的是和C全局变量相对应的数据位置,代码和数据区是直接按照可执行文件内容初始化的

堆。 代码和数据区后紧随着的是运行时 堆。代码和数据区在进程一开始时就被指定了大小
与此不同,当调用像malloc和free这样的函数时,堆可以在运行时动态地扩展和收缩

共享库。 大约在地址空间的中间部分是一块用来存放像C标准库和数学库的共享库的代码和数据的区域

栈。 位于用户虚拟空间顶部的就是用户栈,编译器用它来实现函数调用
和堆一样可动态扩展和收缩,每次调用一个函数时,就会增长,返回时,就会收缩

内核虚拟内存。 地址空间顶部区域是为内核保留的,不允许应用程序读写这个区域的内容
或者直接调用内核代码定义的函数,它们必须调用内核来执行这些操作

虚拟内存的运作需要硬件和操作系统软件之间精密复杂的交互,包括对处理器生成的每个地址的硬件翻译
基本思想是把一个进程虚拟内存的内容存储在磁盘上,然后用主存作为磁盘的高速缓存

1.7.4文件

文件就是字节序列。

1.8系统之间利用网路通信

系统漫游就到这里了
系统是一个孤立的硬件和软件的集合体

现在系统经常通过网络和其他系统连接到一起,从一个单独的系统看,网络可视为一个I/O设备,当从主存复制一串字节到网络适配器时,数据流经过网络到达另一台机器,相似地,系统可以读取从其他机器发送来的数据,并把数据复制到自己的主存
在这里插入图片描述

1.9.1Amdahl定律

……………

1.9.2并发和并行

…………….

1.9.3计算机系统中抽象的重要性

例如,为一组函数规定一个简单的应用程序接口(API)就是一个良好的变成习惯
程序员无需了解它内部的工作便可以使用这些代码,不同变成语言提供不同形式和等级的抽象支持,例如JAVA类声明和C语言的函数原型

在处理器中指令集架构提供了对实际处理器硬件的抽象,使用这个抽象,机器代码程序表现得就好像运行在一个一次只执行一条指令的处理器上
底层硬件永远比抽象描述的要复杂精细,它并行地执行多条指令,但又总是与那个简单有序的模型保持一致,只要执行模型一样,不用的处理器也能执行相同的机器代码,提供不同的开销和性能
在这里插入图片描述
文件是对I/O设备的抽象
虚拟内存是对程序存储器的抽象
进程是对一个正在运行的程序的抽象
虚拟机是对整个计算机的抽象,包括操作系统,处理器和程序

本章小结:

在这里插入图片描述
所有你看起来非常厉害的技术,都是建立在基础上的

永远不要对知识局限于书本上的内容

规则,是可以被打破的!

猜你喜欢

转载自blog.csdn.net/qq_34916272/article/details/102589462