本文借鉴自深入理解计算机系统和南京大学的计算机系统基础,只作为学习总结用。
一 计算机基本原理
1.信息就是位+上下文
系统中的所有信息—包括磁盘文件 , 存储器中的程序 ,存储器中存放的用户数据, 网络上传送的数据 都只是一串bit表示的而已。区分 不同的数据对象的唯一方法是我们读到这些数据对象的上下文。在不同的上下文中,同样的字节序列可能表示为一个整数、浮点数、字符串或者机器指令。
只有ASCII字符构成的文件称为文本文件,所有其他文件都称为二进制文件。
2.程序被其他程序翻译称不同格式
预处理器,编译器,汇编器,链接器一起构成了 编译系统(compliation system)
3.冯诺依曼结构的主要思想:
- 计算机应由运算器、控制器、存储器、输入设备和输出设备五个基本部件组成。
- 各基本部件的功能是:
• 存储器不仅能存放数据,而且也能存放指令,形式上两者没有区别,但计算机应能区分数据还是指令;
• 控制器应能自动取出指令来执行;
• 运算器应能进行加/减/乘/除四种基本算术运算,并且也能进行一些逻辑运算和附加运算;
• 操作人员可以通过输入设备、输出设备和主机进行通信。 - 内部以二进制表示指令和数据。每条指令由操作码和地址码两部分组成。操作码指出操作类型,地址码指出操作数的地址。由一串指令组成程序。
- 采用“存储程序”工作方式。
4.计算机如何工作:
用做菜描述执行过程:
第一步:从pc上取菜谱,比如5(根据PC从主存中取指令)
第二步:看菜谱(指令译码,分为操作码和操作数)
第三步:从架上或盘中取原材料(取操作数,)
第四步:洗、切、炒等具体操作(指令执行)
第五步:装盘或直接送桌(回写结果)
第六步:算出下一菜谱所在架子号6=5+1(修改PC的值)
继续做下一道菜(执行下一条指令)
5.重要概念汇总
CPU:由控制器,ALU和一些寄存器组成
ALU:算数逻辑部件,在ALU操作控制信号ALUop控制下,可以进行各种算数和逻辑运算
控制器:CU,自动逐条取出指令并进行译码的部件
通用寄存器:GPR,通用寄存器可用于传送和暂存数据,也可参与算术逻辑运算,并保存运算结果,组成通用寄存器组GPRS
程序计数器:pc,自动计算出下一条指令的地址的地方(存放着计算机启动后的第一条指令的地址,之后不断自增1)
指令寄存器:IR,从MDR中取指令后临时存到IR
标志寄存器:ALU运算的结果会产生标志信息,例如结果为0(零标志ZF),为负数(符号标志位),这些信息存放在标志寄存器中
主存储器(主存):用于存放指令和数据,可以简单理解为内存加高速缓存 (本文为简单起见,后续会补充高速缓存相关知识)
以下是存储器层次结构示意图:
主要思想就是上一层存储器作为低一层存储器的高速缓存,而L1级别的高速缓存就已经跟寄存器速度差距不大了
总线:连接不同部件进行信息传输的介质称为总线。而cpu与主存之间存取指令需要介质,功能上分为三种:地址,数据,控制信息。逻辑上可以分为三条,但实际上现在总线只有一条就可以完成三条的功能。
主存地址寄存器MAR与主存数据寄存器MDR:CPU访问主存时,需要将主存地址,读/写命令分别送到总线的地址线和控制线,然后通过数据线发送或接收数据。CPU送到地址线的主存地址先放到MAR中,发送或从数据线取来的信息存放在MDR中。
指令:用01表示的一串序列,用来指示CPU完成一个特定的原子操作。例如常用的指令:取数指令(load)从主存单元中取出数据放到GPR中,存数指令(store)将GPR的内容写入主存单元,加法指令(add)将两个GPR中的内容相加后送入结果寄存器(这两个中的一个,根据指令先后顺序决定,汇编部分会详细解释),传送指令(mov)将一个GPR的内容送到另一个GPR。
指令操作码:指定指令的操作类型,如存取数,加减,传送,跳转
地址码字段:指出指令所处理的操作数的地址,如寄存器编号,主存单元编号
显然指令的长度是不一定是定长的,这也是精简指令集和复杂指令集最直观的区别。它们之间具体的区别如下:
但在具体实现上,x86为代表的复杂指令集为了对指令集的前向兼容是不可以采用精简指令集的,考虑到效率的优化,会将自己的指令由CISC翻译为RISC后执行,从软件开发人员角度可以不必过于在意两者的区别
(二)程序的开发和运行
在此之前补充一下缓冲区的知识:
1.缓冲区
缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
(1)为什么要引入缓冲区?例如,我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。
所以缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。
(2)缓冲区的类型缓冲区分为三种类型:全缓冲、行缓冲和不带缓冲。
- 全缓冲在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
- 行缓冲在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入(stdin)和标准输出(stdout)。
- 不带缓冲也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
(3)缓冲区的大小如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是512个字节的大小。缓冲区大小由 stdio.h 头文件中的宏 BUFSIZ 定义,如果希望查看它的大小,包含头文件,直接输出它的值即可:printf("%d", BUFSIZ);缓冲区的大小是可以改变的,也可以将文件关联到自定义的缓冲区,详情可以查看 setvbuf()和 setbuf() 函数。
(4)缓冲区的刷新(清空)下列情况会引发缓冲区的刷新:缓冲区满时;行缓冲区遇到回车时;关闭文件;使用特定函数刷新缓冲区。
2.hello,world程序的执行过程
在shell命令行输入"./hello"后,屏幕打印hello,world,现在逐步分解各个步骤,看看到底其中发生了什么事。
用户输入命令:./指当前目录,hello指定文件名,./hello 字符串是存放在输入缓冲区内,当按下enter后才会真正执行io操作。
shell程序会把这个字符串的每个字符逐个经由总线读入到寄存器中,然后保存到主存储器中(图中红线标记)。在./hello 字符串后是一个换行符enter时,shell程序在io操作中获取到命令后才真正开始执行。首先shell将调出操作系统内核中对应的服务,操作系统内核加载完可执行文件中的代码及其所处理的数据(这里的数据是字符串"hello,world\n",对应图示蓝线)后,将hello第一条指令的地址送到程序计数器PC中。处理器随后开始执行hello程序,将会把主存中的字符串"hello,world\n"加载到寄存器中,然后在屏幕上显示出来(对应绿线)。
由上面可以看出操作系统的重要性,任何用户的程序都要依靠操作系统的支持才能顺利执行。
3.高级编程语言的开发环境和运行环境:
(三)其他重要概念的补充
1.计算机抽象层次
2.计算机用户层次
3.计算机性能评估
1.衡量计算机性能的两个重要指标
吞吐率:表示单位时间内完成的工作量
响应时间:表示从作业提交到作业完成所花费的时间,
2.CPU的几个性能指标
时钟周期,时钟频率(也称主频,是前者倒数,频率越高时钟周期越短),CPI(表示执行一条指令所需时钟周期数,对特定指令是具体数值,对程序或机器是所有指令CPI的平均值)
上述三者是相互制约的,但并不能仅仅通过比较两台计算机三项指标的数值来完全断定孰优孰劣。
MIPS指平均每秒执行多少百万条指令。虽然可以绝对的衡量指令的条数执行快慢,但相同程序在不同机器上指令条数千差万别,所以这也不能作为绝对的衡量标准。
基准程序性能评估是现在最为广泛采用的一种方式。绝大多数情况下,一个优秀的基准程序是能对机器的性能做出比较客观的判断的。
4.对于操作系统的一些认识
操作系统两个基本功能:
(1)防止硬件被失控的应用程序滥用
(2)向应用程序提供简单一致的机制来控制复杂而又大相径庭的低级硬件设备。
操作系统通过几个基本的抽象概念(进程, 虚拟存储器, 文件 )来实现这两个功能:
文件:是对I/O设备的抽象表示(这部分会在之后具体讲解)
虚拟存储器:是对主存和磁盘I/O设备的抽象表示
进程:是对处理器,主存和I/O设备的抽象表示。(这部分内容较多,单独列为一节)
虚拟内存
虚拟内存是一个抽象的概念,它为每个进程提供了一个假象,即每个进程独占地使用主存。
每个进程看到的内存都是一致的,称为虚拟地址空间。
地址空间最上面的区域为操作系统的代码和数据保留的,这对所有进程都是一样的。
地址空间的底部区域存放用户进程定义的代码和数据。
程序代码和数据:对于所有进程来说,代码从一固定地址开始,紧接和C全局变量相对应的数据位置。
堆 :代码和数据区紧随着运行时堆。代码和数据区是在进程一开始就被规定了大小,而堆不一样,通过调用malloc和free,堆可以动态扩展和收缩。
共享库:地址空间的中间部分存放C标准库和数学库这样共享库代码和数据的区域。
栈: 用户虚拟地址顶部的是用户栈,编译器用它来实现函数调用。和堆一样,在执行时动态的扩展和收缩。每当调用一个函数,栈就会增长,而函数返回时,栈就会收缩。
内核虚拟内存: 地址空间顶部区域是为内核保留的。
5.线程与进程相关知识
进程与线程
进程是操作系统对一个正在运行的程序的一种抽象。
进程数都是多于可以运行他们的CPU个数的。传统系统在一个时刻只能执行一个程序,而先进的多核处理器能够执行多个程序,无论实在单核还是多核系统,一个CPU看上去都像是在并发地执行多个进程。
并发运行,是说一个进程的指令和另外一个进程的指令是交错执行的。这是通过处理器在进程间切换来实现的。 操作系统实现这种交错执行的机制称为上下文切换
操作系统保持跟踪进程运行的所有状态信息。这种状态,也就是上下文。包含PC和寄存器文件的当前值。
在任何一个时刻,单处理器系统都只能执行一个进程的代码。当操作系统决定把控制权从当前进程转移到新进程时,就会进行上下文切换,即保存当前上下文,恢复新进程上下文。控制权传递到新进程。
进程的进阶概念就是线程
一个进程实际可以由多个称为线程的执行单元完成。线程间共享同样的代码和全局变量。
由于网络服务器对并行处理的需求,线程成为越来越重要的编程模型。
多线程比多进程更容易共享数据。
有多处理器的时候,多线程也是一种使程序更快运行的方法。
进程和线程的关系:
一个进程不仅仅可能包含多个线程,进程还包含着:被操作系统分配的资源空间,比如操作系统在内存中分配给它的栈,数据区域等。这些资源空间被多个线程所共享。比如你和朋友们一次聚餐,这就很像一个进程。桌子上那些饭菜就是资源,你们每个人都是一个线程,每个人每次伸手去夹菜就是在使用这个共享的资源。
并发(Concurreny)与并行(Parallelism):
并发 = 小明可以一边玩手机一边看电视。
但是在事实上,他的眼睛在看电视的时候不能看手机,他在看手机没法盯着电视屏幕。
他的眼睛飞快在两个屏幕上切换。所以实际上他玩一下手机,暂停,去看电视,看一下电视,暂停,继续玩手机,循环下去,给人以他同时玩手机和看电视的感觉。
这不是真正意义上的同时进行,但又是客观存在同时进行两件事,这叫并发。所以我们说:
并发 = 小明在玩一下手机再看一下电视 (不是真正意义上同时进行)
并行 = 小明可以一边坐公交一边听音乐。
这两件事同时进行互不干扰,做到真正意义的同步同时进行,这叫并行。
并行 = 小明在 “一心两用”(真正的同时进行)
几个重要概念
1.线程级并发:
在多核处理器诞生之前,并发只是通过计算机在当前执行的多个进程间快速切换实现的,像是一个杂耍演员在抛接空中的小球一样。在1967年IBM将这样的系统开发出来后称为单处理系统。工作原理示意:
但在多核处理器诞生并普遍进入市场后,有了以下改变:
2.线程级并行:
虽然IBM实现了并发,但这对CPU而言并不是真正意义上的同时进行,只是让它的效率更高而已。因此人们期望能同时处理多个线程,实现线程的并行。自从1980年开始,系统开发人员也就已经发出了多处理系统(Multiprpcessor system),由单一的操作系统核心控制的多进程的系统。
与此同时,CPU的晶体管也遵循摩尔定律高速发展。但问题是:依旧不能同时处理多个线程,仅仅提高单核芯片的速度会产生过多热量且无法带来相应的性能改善,也永远不能实现真正意义上的同时处理多任务。还只不过像是是玩杂耍的小球的那个例子。
芯片大厂英特尔和AMD也都意识到,当主频接近4GHz时,速度也会遇到自己的极限:那就是单靠主频提升,已经无法明显提升系统整体性能。因此迫切需要一个能支持同时处理2个线程以上的处理器,来提升CPU的瓶颈。需求推动了技术,线程级并行应运而生。主要由下面两种技术的支撑:
多核技术:
超线程:
在1960s 通过操作系统已经实现了线程级并发,虽然那个时候还没建立线程的概念。这种方式依赖操作系统,频繁的上下文切换意味损失了CPU的处理效率。但直到2000年随着CPU的技术突破以后,从1960s到2000s年前后花了50多年的时间,才实现和更高级的线程级并行。
使用了以上技术,可以在一个核上运行多个线程,多个线程共享执行单元,从而提高部件的利用率和吞吐量。使用了这样的技术的系统称为多处理系统。
总结一下线程级并行的好处:
(1)当运行多任务时,它减少了之前的模拟出来的并发,那么用户进行多任务处理时可以运行更多的程序进行并发了。
(2)它可以使单个程序运行更快。(仅当该程序有大量线程可以并行处理时)
3.指令集并行(具体原理之后章节讲解)
指令集的是更低层次的概念,是一种隐式并行。计算机处理问题是通过指令实现的,每个指令都是交给CPU执行。在1978年的 Intel 8086 处理器都只能一次执行单指令。直到 Intel首次在486芯片中开始使用一种指令流水线技术,原理是:当指令之间不存在相关时,它们在流水线中是可以重叠起来并行执行。此外还有超标量乱序执行技术,分支预测技术。通过以上技术,使得一个程序的指令序列多条同时乱序运行,顺序提交,可有效提高程序的运行速度。
如果一个处理器在一个时钟周期内能执行完一条以上指令,就被称为超标量处理器。
4.数据级并行
数据级并行是一种显式并行,主要指单指令多数据流技术(SIMD),比如a,b和c都是相同大小的数组
进行的计算是a的每一个元素与b的相应元素进行运算,结果放入c的对应元素中。如果没有
SIMD,就需要循环执行一条指令多次来完成,而SIMD中一条指令就可以并行地执行运算。
SIMD指令集可以提供更快的图像,声音,视频数据等运行速度。
在看一个更简单的例子:
我们考虑下面这个计算式子:(a+b)*(c+d)该计算过程被分解为三步:
- e = a +b
- f = c +d
- m = e * f
要知道:早期的计算机一次只能处理一条指令,它要先算步骤1(加法操作),再算步骤2(加法操作),最后算3(乘法操作)。需要三步(花费三个指令)得到答案。但是我们观察到:3的结果依赖于1和2,而1和2都单纯的加法操作,所以开始想办法让1和2同时计算,那么CPU只要两步得到答案,步骤1和2一次算出来的结果,直接进行乘法运算。
此时步骤1和2作为一步,运用了SIMD技术。一个指令执行了(a,b,c,d) 4个操作数。