深入理解计算机系统(1)

一、五个现实
1.int不是整数,float不是实数
例1: int如果是4字节,每字节4位,则共16位,除去符号位,还有十五位,那么问:x^2>=0?
float x;Yes!
int x;40000*40000 = 1600000000
50000*50000 = -1794967296(2^15 = 32768)
例 2: (x + y) + z = x + (y + z)?
无符号/有符号 Int: Yes!
浮点数Float:
(1e20 + -1e20) + 3.14 –> 3.14
1e20 + (-1e20 + 3.14) –> ??
2.汇编很重要
虽然可能永远不会写汇编语言,但是要理解汇编是机器级执行模型的关键:
(1)出现Bug时程序的行为,高级语言模型可能会失败!
(2)调整程序性能:理解编译器做或不做的优化、理解程序低效的根源
(3)实现软件系统:编译器把机器代码作为目标、操作系统要管理进程状态
(4)创造/战斗恶意软件:x86汇编是很好的语言选择
3.存储事宜 RAM随机存储器是一个非物理抽象
(1)存储器不是无限的:存储器需要分配与管理、很多应用是存储支配/控制的
(2)存储引用错特别致命
(3)存储器性能是不一致的:Cache与虚拟存储器的效应性能大大影响程序性能、针对存储系统的特点调整程序,能带来大幅度提升
例:存储引用Bug

typedef struct {
  int a[2];
  double d;
} struct_t;

double fun(int i) {
  volatile struct_t s;
  s.d = 3.14;
  s.a[i] = 1073741824; /* Possibly out of bounds */
  return s.d;
}

结果:
fun(0) -> 3.14
fun(1) -> 3.14
fun(2) -> 3.1399998664856
fun(3) -> 2.00000061035156
fun(4) -> 3.14
fun(6) -> Segmentation fault
在i = 2时,数组越界,s.a[i]的数值将s.d的数值3.14覆盖一小部分,随着越界的增加,覆盖越来越多,返回的s.d的值变化越大,当i = 4时,s.a[i]的值完全越过s.d的值。
4.性能比渐进复杂性/时间复杂度更重要!
(1)一定要多层优化:算法、数据表示、过程、循环
(2)优化性能一定要理解系统:程序是怎么编译和执行的、怎样测量系统性能和定位瓶颈、在不破坏代码模块化与整体性下,怎么改进性能
*例:内存系统
void copyij(int src[2048][2048],
int dst[2048][2048])
{
int i,j;
for (i = 0; i < 2048; i++)
for (j = 0; j < 2048; j++)
dst[i][j] = src[i][j];
} ms

void copyji(int src[2048][2048],
int dst[2048][2048])
{
int i,j;
for (j = 0; j < 2048; j++)
for (i = 0; i < 2048; i++)
dst[i][j] = src[i][j];
} 81.8ms
5.计算机比执行程序做的多的多
(1)计算机要进行数据的输入输出
(2)计算机需要通过网络互相通讯
注:以C语言hello程序在Linux上的生命周期为例:

#include 

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

二、可执行程序的生成
1.信息=位+上下文

  • hello程序的生命周期是从一个源程序(也叫源文件)开始,即程序员通过编辑器创建并保存的文本文件,文件名是hello.c;
  • 源程序实际上是一个由值0和1组成的位序列 (8位为一字节,每个字节表示这个程序中的某个文本字符)
  • 大部分现代计算机系统是使用ASCII码来表示文本字符,即用一个唯一的单字节大小的整数值来表示单个字节
    例:hello.c程序的ASCII文本表示
    # i n c l u d e < s t d i o .
    35 105 110 99 108 117 100 101 32 60 115 116 100 105 111 46
    h > \n \n i n t m a i n ( ) \n {
    104 62 10 10 105 110 116 32 109 97 105 110 40 41 10 123
    \n p r i n t f ( ” h e l
    10 32 32 32 32 112 114 105 110 116 102 40 34 104 101 108
    l o , w o r l d \ n ” ) ; \n }
    108 111 44 32 119 111 114 108 100 92 110 34 41 59 10 125
    (教材第2页)
    注意:每个文本行都是以一个看不见的换行符’\n’来结束的,像hello.c这样由ASCII构成的文件称为文本文件,其他的文件都成为二进制文件
    基本思想
    系统中的所有信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文
    注:我们必须了解数字的机器表示方式,因为它们与实际的整数与实数是不同的。它们是对真值的有限近似值,有时候会有意想不到的行为表现,这方面请参考第2章!
    2.程序被其他程序翻译成不同的格式
    hello.c程序的生命周期是从一个高级C语言程序开始的,这种形式能被人读懂,但计算机并不能够直接执行hello.c,所以为了在系统上运行hello.c程序,每条C语句都必须被其他程序转化成一系列的低级机器语言指令,然后这些指令按照一种被称为可执行目标程序的格式打包并以二进制磁盘文件的形式存放起来
    以下是GCC+Linux平台中的处理过程
    hello.c(源文件、文本)——>预处理(cpp)——>编译(ccl)——>汇编(as)——>链接(ld)——>可执行目标文件(hello、二进制)
    共4步
    (1)预处理阶段:预处理器(cpp)根据以字符#开头的命令,修改原始C程序。以hello.c为例,第一行的命令#include 告诉cpp(预处理器)读取系统头文件stdio.h的内容并把它直接插入到程序文本中,结果得到hello.i文件。
    (2)编译阶段:编译器(ccl)将文本文件hello.i翻译成文本文件hello.s。hello.s包含一个汇编语言程序,该程序包含main函数的定义,如下所示:
main:
     subq     $a,%rsp
     movl     $.LCO,%edi
     call       puts
     movl     $0,%eax
     addq     $8,%rsp
     ret

定义中2—7行的每条语句都是以一种文本格式描述了一条机器语言指令
(3)汇编阶段:汇编器(as)将hello.s翻译成机器语言指令,并把这些指令打包成一个可重定位目标程序并将结果保存到hello.o中(这是一个二进制文件)
(4)链接阶段:hello程序调用了一个printf()函数,它是C编译器提供的一个标准C库中的一个函数,printf函数存在于一个名为printf.o的单独的预处理好了的目标文件中,而这个文件必须以某种方式合并到hello.o程序中,这个步骤又链接器(ld)进行,结果得到hello文件,它是一个可执行目标文件,可以被加载到内存中有系统执行。
3.了解编译系统是如何工作的
- 优化程序性能:为了在C程序中做出更好的编码选择,我们需要了解一些机器代码以及C语句转化成机器代码的方式;比如一个switch语句是否比一系列的if-else语句高效的多?一个函数调用的开销有多大?while循环比for循环更有效吗?指针引用比数组索引更有效吗?为什么循环求和的结果放到一个本地变量中,会比其放到一个通过引用传递过来的参数中,运行起来快得多?为什么我们只是简单排列一下算数表达式中的括号就能让函数运行的更快呢?
- 理解链接时出现的错误
- 避免安全漏洞
4.系统的硬件构成
(1)总线:贯穿整个系统的是一组电子管道,称作总线,它携带信息字节并负责在各个部件间传递。通常总线被设计成传送定长的字节快,称为,字长是系统的基本参数,大多数是4个字节(32位)或8个字节(64位)。
(2)I/O设备:包括鼠标、键盘、显示器、磁盘驱动器(或简称磁盘);每个I/O设备都通过一个控制或适配器与I/O总线项链。.
(3)主存:从物理上说,主存是由一组动态随机存取存储器(DRAM)芯片组成。从逻辑上将,存储器是一个线性的字节数组,每个字节都有其唯一的地址(数组索引)。
(4)处理器
5.运行hello程序
深入理解计算机系统(1)_第1张图片
6.计算机系统的层次模型
深入理解计算机系统(1)_第2张图片
站在不同的角度,我们看到的计算机可能是不一样的。比如说只搞人工智能的人,那么他可能注重应用算法部分,编程语言也用但可能用的都是比较高级的编程语言,Python的一个调用,一个模型,可能就包含好几百万条代码,但更多的还是站在应用角度考虑问题。不过,无论什么样的语言,什么样的算法,最终程序的执行都要在操作系统的环境下运行。当然有些计算机没有操作系统,比如单片机,控制系统,内存很小,可能只有16k,不可能安装操作系统,那么一开电源就是执行里面的程序了,当然程序的功能要相对简单而且一些复杂的功能它完成不了,比如调试、调度都不行,这些工作要在电脑中先做好,然后写到芯片中去。我们一般说的都是相对复杂的计算机系统比如手机、电脑等,这些需要操作系统支持,而程序也需要由操作系统进行调度,程序中的函数的执行也可能需要调用操作系统的函数来实现。操作系统及其以上是搞软件的人所关注的。
那么软件和硬件之间怎么沟通呢?软件在运行的时候与运行在什么样的硬件平台上?硬件究竟可以完成那些工作呢?————通过中间这个ISA(Instruction Set Architecture)它是指一台电脑能够执行的机器指令的类型。有的机器支持64位操作,有的支持32位操作,有的机器寄存器有32个,有的有64个,还有的有128个,这样不同的机器,其指令集是不一样的。比如有的指令集能够直接运行数组运算(拷贝、比较、搜索),但有的机器就不可以。指令集不一样,这台电脑的功能以及其所负责的工作任务可能不同。我们所设计的电脑所拥有的指令集可能只是为了完后某些特定的功能。所以指令集体系结构直接区别了电脑的类型。指令集体系结构指的是这台电脑执行的机器指令,所有的上层软件的执行都要转化成机器指令。指令集体系结构给出这台电脑设计的能够执行的数据类型,数据寻址方式等等模式,那么硬件人员就可以按照这套模式来设计硬件(CPU、I/O接口、存储总线等等)了。设计师不可能一次性就把电路图画出,要先从微体系结构开始,把所有这些指令进行梳理成十几条基本的指令,把原本有的功能比较强大的、各不一样的指令规划成统一的、功能简单的微指令。把一条条指令分解成这样的微指令,形成微体系结构。实际上我们的电脑要执行一条指令的时候是要调用微程序的。在这个基础上,再来设计相应的功能部件——寄存器、运算器、控制器、I/O接口部件。而功能部件需要电路来进行实现了。搞计算机的人一般后两部分不考虑。
那么如此看来,程序执行结果不仅取决于算法、程序编写,还取决于语言处理系统(使用Java、C或汇编语言是不一样的)、操作系统、ISA-机器语言、微体系结构。程序出问题时要从这些方面综合考虑。将各层次关联起来解决问题在考虑Bug、系统优化时非常有用。比如作除法时,有的人右移一位而有的人则乘0.5,高下立判!
7.计算机系统的抽象表示
深入理解计算机系统(1)_第3张图片
随着计算机的发展,计算机工作者发现一个问题:很多程序只能在一种操作系统上运行。在其他操作系统上不能运行。那怎么能够支持多重硬件平台、多种软件平台(包括操作系统)呢?这就要用到一种新的技术——中间层语言,微软有自己的中间层语言,Java也有(叫Java字节码),凡是微软的语言比如C、C++,在运行时都要先编译成MSIL(中间层语言),然后通过微软的通用语言运行时(Common Language Runtime)来运行,而Java语言在编译成Java字节码之后要在Java虚拟机上运行,Java虚拟机通过通用语言运行时对生成的中间层代码进行解释。这样程序的可移植性就有了提高。因此我们一般编程只需面对中间层预言、虚拟机即可,这是计算机系统抽象表示的发展趋势。
小结:
1.简述C、ASM、ML的关系,各自优缺点?
2.Hello.c 经过那些工具、步骤、生成什么类型的文件?
3.什么是程序可移植性?汇编语言可移植吗?为什么?
4.编译与解释有什么区别?各举出2个语言的例子
5.程序优化的目的?
6.计算机软件与硬件的界面/接口是什么?
7.简述中间层语言及其运行机制。
8.设计开发一个Java处理器是否可行?
9.可执行程序的组织与其在内存的一致吗?变量地址呢?
10.程序执行结果与那些相关呢?

你可能感兴趣的:(计算机系统,计算机系统学习笔记)