本章经过跟踪hello程序的生命周期来开始对计算机系统进行学习。一个源程序从它被程序员建立开始,到在系统上运行,输出简单的消息,而后终止。咱们将沿着这个程序的生命周期,简要地介绍一些逐步出现的关键概念、专业术语和组成部分。git
@[TOC]程序员
很久没有更新博客了,从国庆节到如今一直在整理秋招的一些资料,简历模版,嵌入式软件面试知识点总结,秋招笔试题目整理,面经总结复盘等。一共整理了将近400页,16W字。顺便把百度网盘的资料也整理了下,到10.16才整理完(须要资料的在主页有我联系方式)。不得不说,整理资料是真的磨人性。面试
接下来的计划是补充下操做系统和计算机组成原理相关的知识。从《深刻理解计算机系统》这本书开始吧,系统学习下《深刻理解计算机系统》这本书,还有9个Lab能够作下,以便加深理解。初步计划一周一章(不知道行不行),争取在放寒假前作完这些。shell
我会把看书过程当中一些重要的知识点,概念的理解以及作实验的详细过程都放在博客深刻理解计算机系统专栏中。欢迎关注个人博客以便第一时间获取文章更新的内容。编程
下面就是本书第一章的一个简单总结。数组
源程序是如何存储的
#include <stdio.h> int main() { printf("hello,world\n"); 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,wor1d\n”。信息流向以下所示。
磁盘->磁盘控制器->I/O总线->I/O桥->内存总线->主存
这种访问数据的方式数据不会通过CPU,而是直接从磁盘到主存,这种方式称为DMA。DMA(直接存储器访问)有利于减轻CPU的负荷,使CPU能够在数据转移的同时作其它任务。
加载完hello文件后,CPU将会开始从hello程序的主函数处执行指令。这些指令将“hello,world\n”字符串中的字节从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上。信息流向以下图所示。
主存->寄存器->系统总线->I/O桥->I/O总线->图形适配器->显示器
高速缓存
经过运行hello程序,咱们能够知道,指令和数据须要屡次在寄存器、主存、磁盘之间来回复制,这些复制其实就是开销,减慢了程序工做的速度。这个时候咱们就须要高速缓存存储器(cache memory)来解决这个问题。
L1高速缓存的容量能够达到数万字节,访问速度几乎和访问寄存器文件同样快。
L2高速缓存容量为数十万到数百万字节,经过一条特殊的总线链接处处理器。进程访问L2高速缓存的时间要比访问L1高速缓存的时间长5倍,可是这仍然比访问主存的时间快5~10倍。
L1和L2高速缓存是用一种叫作**静态随机访问存储器(SRAM)**的硬件技术实现的。
高速缓存局部性原理:程序具备访问局部区域中的数据和代码的趋势。所以,高速缓存存储器做为暂时的集结区域,存放处理器近期可能会须要的信息。
存储设备的层次结构
从上至下,设备的访问速度愈来愈慢、容量愈来愈大,而且每字节的造价也愈来愈便宜。寄存器文件在层次结构中位于最顶部,也就是第0级或记为L0。
<img src="https://gitee.com/dongxingbo/Picture/raw/master//%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F/%E5%9B%BE1-9_%E5%AD%98%E5%82%A8%E5%99%A8%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E5%9B%BE.png" alt="image-20201019200335061" />
存储器层次结构的主要思想是上一层的存储器做为低一层存储器的高速缓存。所以,寄存器文件就是L1的高速缓存,L1是L2的高速缓存,L2是L3的高速缓存,L3是主存的高速缓存,而主存又是磁盘的高速缓存。
操做系统管理硬件
操做系统是应用程序和硬件之间插入的一层软件。全部应用程序对硬件的操做尝试都必须经过操做系统。
操做系统有两个基本功能:(1)防止硬件被失控的应用程序滥用;(2)向应用程序提供简单一致的机制来控制复杂而又一般大不相同的低级硬件设备。
操做系统经过几个基本的抽象概念(进程、虛拟内存和文件)来实现这两个功能:文件是对1/O设备的抽象表示,虚拟内存是对主存和磁盘I/O设备的抽象表示,进程则是对处理器、主存和I/O设备的抽象表示。
进程&线程
进程是操做系统对一个正在运行的程序的一种抽象。在一个系统上能够同时运行多个进程,而每一个进程都好像在独占地使用硬件。而并发运行,则是说一个进程的指令和另个进程的指令是交错执行的。
上下文:操做系统保持和跟踪进程运行所需的全部状态信息(PC值,主存的内容等)。
上下文切换:操做系统经过控制处理器在进程间切换以达到交错执行的目的。
从一个进程到另外一个进程的转换是由操做系统内核( kernel)管理的。内核是操做系统代码常驻主存的部分。内核不是一个独立的进程。相反,它是系统管理所有进程所用代码和数据结构的集合。
<img src="https://gitee.com/dongxingbo/Picture/raw/master//%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F/%E5%9B%BE1-12_%E8%BF%9B%E7%A8%8B%E7%9A%84%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2.png" alt="image-20201019203213287" />
一个进程由多个称为线程的执行单元组成,每一个线程都运行在进程的上下文中,并共享一样的代码和全局数据。多线程比多进程更容易共享数据,并且线程间切换全部的开销要远小于进程切换。
虚拟内存
虚拟内存是一个抽象概念,它为每一个进程提供了一个假象,即每一个进程都在独占地使用主存。每一个进程看到的内存都是一致的,称为虚拟地址空间。
<img src="https://gitee.com/dongxingbo/Picture/raw/master//%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F/%E5%9B%BE1-13_%E8%BF%9B%E7%A8%8B%E7%9A%84%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80%E7%A9%BA%E9%97%B4.png" alt="image-20201019203450101" />
上图将虚拟地址空间分为了若干个部分,并用箭头表示该部分的扩展方向。最下端地址为0,向上地址逐渐增加。每一个部分做用以下:
**程序代码和数据:**存放可执行程序代码和代码中的全局变量。
**堆:**用于动态申请的内存变量,好比malloc
函数申请的动态内存空间,能够向上扩展。
**共享库:**用于存放C语言库函数的代码和数据。本例中即printf
的代码和数据。
**栈:**位于虚拟地址空间的顶部,用于函数调用、存放局部变量等。当咱们调用一个函数时,栈会向下扩展,返回时,向上收缩。
**内核虚拟内存:**地址空间顶部的区域是为内核保留的。不容许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。相反,它们必须调用内核来执行这些操做。对于一个64为的操做系统来讲,用户空间为0-3G,内核空间为3G-4G。(用户空间和内核空间有何区别,见秋招资料整理中的嵌入式软件工程师笔试面试知识点总结)
并发&并行
并发: 指在同一时刻,有多条指令在多个处理器上同时执行。因此不管从微观仍是从宏观来看,两者都是一块儿执行的。
并行:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具备多个进程同时执行的效果,但在微观上并非同时执行的,只是把时间分红若干段,使多个进程快速交替的执行。
多核处理器&多线程
多核处理器:多核处理器是将多个CPU(称为“核”)集成到一个集成电路芯片上。以下图所示,微处理器芯片有4个CPU核,每一个核都有本身的L1和L2高速缓存,其中的L1高速缓存分为两个部分——一个保存最近取到的指令,另外一个存放数据。这些核共享更高层次的高速缓存,以及到主存的接口。
<img src="https://gitee.com/dongxingbo/Picture/raw/master//%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F/%E5%9B%BE1-17_%E5%A4%9A%E6%A0%B8%E5%A4%84%E7%90%86%E5%99%A8%E4%BA%8C%E7%AD%89%E7%BB%84%E7%BB%87%E7%BB%93%E6%9E%84.png" alt="image-20201019212750093" />
超线程:超线程,有时称为同时多线程( simultaneous multi-threading),是一项容许一个CPU执行多个控制流的技术。举个例子,Intel Core i7处理器可让每一个核执行两个线程,因此一个4核的系统实际上能够并行地执行8个线程。
养成习惯,先赞后看!若是以为写的不错,欢迎一键三连,谢谢!
CSDN:点击访问个人CSDN博客 欢迎欢迎关注个人公众号:嵌入式与Linux那些事,领取秋招笔试面试大礼包(华为小米等大厂面经,嵌入式知识点总结,笔试题目,简历模版等)和2000G学习资料。