本章通过跟踪hello程序的生命周期来开始对计算机系统进行学习。一个源程序从它被程序员创建开始,到在系统上运行,输出简单的消息,然后终止。我们将沿着这个程序的生命周期,简要地介绍一些逐步出现的关键概念、专业术语和组成部分。@[TOC]
好久没有更新博客了,从国庆节到现在一直在整理秋招的一些资料,简历模版,嵌入式软件面试知识点总结,秋招笔试题目整理,面经总结复盘等。一共整理了将近400页,16W字。顺便把百度网盘的资料也整理了下,到10.16才整理完(需要资料的在主页有我联系方式)。不得不说,整理资料是真的磨人性。
接下来的计划是补充下操作系统和计算机组成原理相关的知识。从《深入理解计算机系统》这本书开始吧,系统学习下《深入理解计算机系统》这本书,还有9个Lab可以做下,以便加深理解。初步计划一周一章(不知道行不行),争取在放寒假前做完这些。
我会把看书过程中一些重要的知识点,概念的理解以及做实验的详细过程都放在博客深入理解计算机系统专栏中。欢迎关注我的博客以便第一时间获取文章更新的内容。
下面就是本书第一章的一个简单总结。
源程序是如何存储的
#include
int main()
{
printf("hello,worldn");
return 0;
}
以上程序是我们通过文本编辑器创建的文本文件,保存为hello.c。源程序实际上就是一个由值0和1组成的位(又称为比特)序列,8个位被组织成一组,称为字节。每个字节表示程序中的某些文本字符。现代计算机都使用ASCII标准来表示文本字符。hello.c程序的ASCII文本字符如下所示。
hello.c程序是以字节序列的方式储存在文件中的。
hello.c的表示方法说明了一个基本思想:系统中所有的信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。区分不同数据对象的唯一方法是我们读到这些数据对象的上下文。
源程序到可执行文件的过程
GCC编译器驱动程序读取源程序文件hello.c,并把它翻译成一个可执行目标文件hello。这个翻译过程可分为四个阶段:预编译,编译,汇编,链接。
预编译
在预编译的过程中,主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令(#ifdef,#ifndef,#else,#elif,#endif),宏的替换,添加行号,保留所有的编译器指令。
编译
在预处理结束后,进行的是编译。编译过程所进行的是对预处理后的文件进行语法分析,词法分析,语义分析,符号汇总,然后生成汇编代码。
汇编
汇编过程将汇编代码转成二进制文件,二进制文件就可以让机器来读取。每一条汇编语句都会产生一句机器语言。
链接
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数等等。所有这些问题,都需要经链接程序的处理方能得以解决。链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
shell是什么
shell是一个命令行解释器,它输出一个提示符,等待输入一个命令行,然后执行这个命令。如果该命令行的第一个单词不是一个内置的shell命令,那么 shell就会假设这是个可执行文件的名字,它将加载并运行这个文件。
典型系统的硬件组成
总线
贯穿整个系统的是一组电子通道,称作总线。通常总线中传输的是固定长度的字节块,也就是字(word)。字中的字节数(字长)是一个基本的系统参数。不同系统字长不同。比如32位系统的字长为4个字节,64位系统的字长为8个字节。
IO设备
I/O(输入/输出)设备是系统与外部世界的联系通道。我们的示例系统包括四个I/O设备:作为用户输入的键盘和鼠标,作为用户输出的显示器,以及用于长期存储数据和程序的磁盘驱动器(简单地说就是磁盘)。
每个IO设备都通过一个控制器或适配器与I/O总线相连。控制器和适配器之间的区别主要在于它们的封装方式。控制器是I/O设备本身或者系统的主印制电路板(通常称作主板)上的芯片组。而适配器则是一块插在主板插槽上的卡。无论如何,它们的功能都是在I/O总线和I/O设备之间传递信息。
主存
主存是一个临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。从物理上来说,主存是由一组动态随机存取存储器(DRAM)芯片组成的。从逻辑上来说,存储器是一个线性的字节数组,每个字节都有其唯一的地址(数组索引),这些地址是从零开始的。
处理器
中央处理单元(CPU),简称处理器,是执行存储在主存中指令的引擎。处理器的核心是一个大小为一个字的存储设备(或寄存器),称为程序计数器(PC)。在任何时刻,PC都指向主存中的某条机器语言指令(即含有该条指令的地址)。
运行hello程序
shell读取到我们从键盘输入的“./hello”后,计算机中的信息流向如下图红线所示:
键盘->USB控制器->I/O总线->I/O桥->系统总线->寄存器
寄存器->系统总线->I/O桥->内存总线->主存
shell程序需要把用户输入的内容作为一个变量使用,而这个变量一定在内存中有个地址,所以它最终会到达内存。
当我们在键盘上敲回车键时, shell程序就知道我们已经结東了命令的输入。然后shell执行一系列指令来加载可执行的hello文件,这些指令将hello目标文件中的代码和数据从磁盘复制到主存。数据包括最终会被输出的字符串“ hello,wor1dn”。信息流向如下所示。
磁盘->磁盘控制器->I/O总线->I/O桥->内存总线->主存
这种访问数据的方式数据不会经过CPU,而是直接从磁盘到主存,这种方式称为DMA。DMA(直接存储器访问)有利于减轻CPU的负荷,使CPU可以在数据转移的同时做其它任务。
加载完hello文件后,CPU将会开始从hello程序的主函数处执行指令。这些指令将“hello,worldn”字符串中的字节从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上。信息流向如下图所示。
主存->寄存器->系统总线->I/O桥->I/O总线->图形适配器->显示器
高速缓存
通过运行hello程序,我们可以知道,指令和数据需要多次在寄存器、主存、磁盘之间来回复制,这些复制其实就是开销,减慢了程序工作的速度。这个时候我们就需要高速缓存存储器(cache memory)来解决这个问题。
L1高速缓存的容量可以达到数万字节,访问速度几乎和访问寄存器文件一样快。
L2高速缓存容量为数十万到数百万字节,通过一条特殊的总线连接到处理器。进程访问L2高速缓存的时间要比访问L1高速缓存的时间长5倍,但是这仍然比访问主存的时间快5~10倍。
L1和L2高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。
高速缓存局部性原理:程序具有访问局部区域中的数据和代码的趋势。因此,高速缓存存储器作为暂时的集结区域,存放处理器近期可能会需要的信息。
存储设备的层次结构
从上至下,设备的访问速度越来越慢、容量越来越大,并且每字节的造价也越来越便宜。寄存器文件在层次结构中位于最顶部,也就是第0级或记为L0。
存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。因此,寄存器文件就是L1的高速缓存,L1是L2的高速缓存,L2是L3的高速缓存,L3是主存的高速缓存,而主存又是磁盘的高速缓存。
操作系统管理硬件
操作系统是应用程序和硬件之间插入的一层软件。所有应用程序对硬件的操作尝试都必须通过操作系统。
操作系统有两个基本功能:(1)防止硬件被失控的应用程序滥用;(2)向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。
操作系统通过几个基本的抽象概念(进程、虛拟内存和文件)来实现这两个功能:文件是对1/O设备的抽象表示,虚拟内存是对主存和磁盘I/O设备的抽象表示,进程则是对处理器、主存和I/O设备的抽象表示。
进程&线程
进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像在独占地使用硬件。而并发运行,则是说一个进程的指令和另个进程的指令是交错执行的。
上下文:操作系统保持和跟踪进程运行所需的所有状态信息(PC值,主存的内容等)。
上下文切换:操作系统通过控制处理器在进程间切换以达到交错执行的目的。
从一个进程到另一个进程的转换是由操作系统内核( kernel)管理的。内核是操作系统代码常驻主存的部分。内核不是一个独立的进程。相反,它是系统管理全部进程所用代码和数据结构的集合。
一个进程由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。多线程比多进程更容易共享数据,而且线程间切换所有的开销要远小于进程切换。
虚拟内存
虚拟内存是一个抽象概念,它为每个进程提供了一个假象,即每个进程都在独占地使用主存。每个进程看到的内存都是一致的,称为虚拟地址空间。
上图将虚拟地址空间分为了若干个部分,并用箭头表示该部分的扩展方向。最下端地址为0,向上地址逐渐增长。每个部分作用如下:
程序代码和数据:存放可执行程序代码和代码中的全局变量。
堆:用于动态申请的内存变量,比如malloc
函数申请的动态内存空间,可以向上扩展。
共享库:用于存放C语言库函数的代码和数据。本例中即printf
的代码和数据。
栈:位于虚拟地址空间的顶部,用于函数调用、存放局部变量等。当我们调用一个函数时,栈会向下扩展,返回时,向上收缩。
内核虚拟内存:地址空间顶部的区域是为内核保留的。不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。相反,它们必须调用内核来执行这些操作。对于一个64为的操作系统来说,用户空间为0-3G,内核空间为3G-4G。(用户空间和内核空间有何区别,见秋招资料整理中的嵌入式软件工程师笔试面试知识点总结)
并发&并行
并发: 指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
并行:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
多核处理器&多线程
多核处理器:多核处理器是将多个CPU(称为“核”)集成到一个集成电路芯片上。如下图所示,微处理器芯片有4个CPU核,每个核都有自己的L1和L2高速缓存,其中的L1高速缓存分为两个部分——一个保存最近取到的指令,另一个存放数据。这些核共享更高层次的高速缓存,以及到主存的接口。
超线程:超线程,有时称为同时多线程( simultaneous multi-threading),是一项允许一个CPU执行多个控制流的技术。举个例子,Intel Core i7处理器可以让每个核执行两个线程,所以一个4核的系统实际上可以并行地执行8个线程。
养成习惯,先赞后看!如果觉得写的不错,欢迎一键三连,谢谢!
CSDN:CSDN搜索“嵌入式与Linux那些事”
我的GZH:嵌入式与Linux那些事,领取秋招笔试面试大礼包(华为小米等大厂面经,嵌入式知识点总结,笔试题目,简历模版等)和2000G学习资料。**