计算机程序的三段人生(一)--计算机原理 -- 未完待续
系列导览
阶段一. 计算机原理:
计算机由哪些部件组成,程序是如何运行起来的呢?
链接:
阶段二. 编译原理:
当有了高级语言,有了各种各样的高级语言,比如C,Golang, Java这些,他又是怎么被计算机硬件所识别呢?
链接:
阶段三. 操作系统的介入
当程序不再直接运行在计算机的硬件设备上,而是由一个称为"操作系统"的超级大管家帮我们运行时,那又是一番怎样的情形呢?
链接:
前言
上大学的时候你一定学过"计算机原理"这门课程,说到"计算机原理", 虽然软硬不分家,但实际上这门课程更偏向于计算机硬件,
而对于狭义的软件,一般指的是高级语言编写的程序,可能大部分都是谭浩强他老人家了,哈哈哈哈哈....
本文是基于计算的核心硬件CPU, 寄存器,内存等这些概念,结合程序或叫软件来进行阐述,其中软件中关于高级语言的编译,则单独使用一篇博文进行讲解....
在学习计算机的底层运行原理时,要时刻将一个概念刻入脑子里:
计算机原理无非是这2点:
硬件方面:给数据找个安置的地方即"存储; 将数据进行"计算"并得到结果;
软件方面:而指导这一切的称为"指令"。
此处的"指令"其实就是我们所编写的软件程序在计算机底层时的身份。
计算机硬件
前面说了,计算机的核心影响只有两个:计算 和 存储,其余一切都是为了他们而服务
1. 中央处理器(CPU)
Central Processing Unit,是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心(Control Unit)。它的功能主要是解释计算机指令以及处理计算机软件中的数据。
中央处理器主要包括运算器(算术逻辑运算单元,ALU,Arithmetic Logic Unit),译码器,寄存器组成,和高速缓冲存储器(Cache),控制及状态的总线(Bus),以及实现它们之间联系的数据(Data)一起协同工作。
通过WIKI上的一段话,让我们将这些组件的工作串起来:
Principal components of a CPU include the 算术逻辑单元(ALU) that performs arithmetic and logic operations, 寄存器 (processor registers) that supply operands to the ALU and store the results of ALU operations, and a 控制单元(CU) that orchestrates the fetching (from memory), decoding and execution (of instructions) by directing the coordinated operations of the ALU, registers, and other components.
ALU:
通用寄存器: 包括累加器(ACC),数据寄存器(DR),基址寄存器(BR).
状态条件寄存器(PSW): Program Status Word,是一个特殊的寄存器,用于存储程序运行的状态和标志位。PSW通常包含一系列二进制位,每个位代表特定的标志或状态。它记录了当前程序的条件码、中断使能状态、当前运行模式(用户模式或特权模式)等信息。该寄存器影响着程序的执行流程和权限控制。通过对PSW的修改和读取,CPU可以实现条件判断、中断处理和模式切换等功能。
PSW通常是位于控制单元(CU)中,内部的标志位通常是由ALU执行算术和逻辑运算的结果设置的,但具体的标志位的含义和使用由控制单元根据运算结果来进行解释和处理。
CU:
指令寄存器(IR) :Instruction Register,用于暂时存储当前正在执行的指令, 存储的指令会被解码并执行;
程序计数器(PC):Program Counter,PC寄存器指示了当前指令执行完成后,下一条指令在内存中的地址位置。当CPU执行一条指令时,PC寄存器会自动递增,使其指向下一条要执行的指令的地址,以实现程序的顺序执行。
wxy:这个寄存器存放的是"正在执行的"还是"下一条要执行的"指令地址,其实都是说的通的。 大部分时间里,这个寄存器存放的是正在执行的指令地址,但是在当前执行完的一个刹那时间,他就后移了,即指向的是"即将要"执行的指令地址,然后紧接着这个指令也就变成了"正在"....
状态寄存器(SR):存储CPU的运行状态和标志位,如零标志、溢出标志和中断标志等;
堆栈指针寄存器(SP):用于存储堆栈的顶部地址,用于管理函数调用、局部变量和中断处理等;
地址寄存器(AR) : Address Register. 用于存储内存地址,主要用于内存访问时的地址计算。在执行内存读取和写入操作时,CPU会将AR中的地址与偏移量相加,以确定要访问的内存地址。AR寄存器在计算内存地址时起到重要的辅助作用。
指令译码器(ID):Instruction Decoder. 用于暂时存储从内存中读取的指令,并进行指令解码。在指令执行过程中,控制单元会将指令从内存读取到ID寄存器中,然后解析指令的操作码和操作数,确定指令的类型和执行方式。
编程语言
1. 编程语言的历史
开始, 计算机只能运行二进制,即她只认识0101,所以最开始的程序是用0101编码出来的,称为"机器语言"。用这些01串去控制CPU进行计算,什么样的01串对应什么样的操作,以及01串具体如何控制计算机硬件设备,则由计算机架构决定,见下一小节。除此之外,还要手动为这一坨0101....分配存储空间以及输入和输入,所以显然,这是反人类的.....。
后来,芯片厂商发明了一些助记符,也就是"汇编语言"。所谓的01串,无非就是让CPU去计算,去移动,去获取等等,于是把这些行为用固定的一个"单词"去代替。但是,汇编语言不过是机器码的另一种表现形式而已,还是会直接和硬件比如寄存器打交道,只要与硬件相关那么他的可移植性就....
最后,"高级语言"诞生了,管你什么硬件,我代表的就是一个逻辑(算法),至于硬件怎么去实现我的逻辑?不管!于是,可移植性大大提升!高级语言需要有编译工具将之转换成最终的机器码,这部分由阶段二进行详解。
wxy碎碎念:这三个语言并不是"层层递进"的关系,高级语言 -- 编译--> 汇编语言 --等同于--> 机器语言
2. 计算机架构(Computer architecture)
参考wiki:
https://en.wikipedia.org/wiki/Computer_architecture
https://en.wikipedia.org/wiki/Reduced_instruction_set_computer
及其他相关的连接
wiki定义:
是描述计算机系统功能,组织和实现的一组规则和方法。在有些定义中,计算机架构被描述成计算机的能力和编程模型,但并不是特定的实现. 在其他定义中,计算机体系结构包括指令集体系结构设计,微体系结构设计,逻辑设计和实现。
wxy: 首先,所谓架构并不是具体的实现,而是一种规范;其次,所谓的规则和方法,其实就是让软件指令和硬件设备,如何具体结合起来,即一条"指令"到底意味着什么, 他的背后对应着哪些硬件操作....
2.1 指令集架构(ISA)概念
Instruction set architecture (ISA) defines the machine code that a processor reads and acts upon as well as the word size, memory address modes, processor registers, and data type.
In general, an ISA defines the supported data types, the registers, the hardware support for managing main memory, fundamental features (such as the memory consistency, addressing modes, virtual memory), and the input/output model of a family of implementations of the ISA.
An ISA specifies the behavior of machine code running on implementations of that ISA in a fashion that does not depend on the characteristics of that implementation, providing binary compatibility between implementations.
An instruction set architecture is distinguished from a microarchitecture, which is the set of processor design techniques used, in a particular processor, to implement the instruction set. Processors with different microarchitectures can share a common instruction set.
解析:
定义了处理器在读取机器码后,综合其word size、内存地址、处理器寄存器、以及数据类型,所做的操作。
总体来说,ISA定义了支持的数据类型,有哪些寄存器,用于管理主存的硬件支持,基本功能(例如内存一致性吗, 寻址模式, 虚拟内存),以及对应的一系列IO模型。
一个ISA定义了基于这个架构运行的机器码所对应的具体行为,运行方式并不依赖于具体的实现,在不同的具体实现之间实现了二进制兼容(binary compatibility).
指令集架构不同于微体系架构(microarchitecture), 后者是针对特定的处理器,为了实现指令集,所使用的一种处理器设计技术。具有不同为微架构的处理器可以共享一个公共指令集。
wxy:
ISA即指令集架构,是将计算机各个硬件以及硬件如何被操作通通抽象出来,用一条条指令去定义这些,
然后,只要是对于基于某个特定架构实现的计算机,所有二进制程序都可以兼容的运行于其上。
另外,还提了一点微体系架构,正如其名称中的"微"字,是独立于架构之外,各个cpu厂商为了实现这个架构,自己处理器内部的实现技术。
相关概念区分:
machine code: 机器码,CPU只认识这样的指令,即一组组0101010110....
instruction: 指令
instruction set:指令集
instruction set architectures:指令集架构
opcode: 操作码,是operation code的缩写, 也称为instruction machine code, instruction code, instruction syllable(音节), instruction parcel or opstring),
是在机器语言指令中,用来指定所要执行的操作那部分. 除了操作码自身,大部分指令还会指定要处理的数据称为操作数(operands).
另外,不仅硬件CPU的指令集架构会使用操作码; 在abstract computing machines(虚拟机?)中,操作码也作为其字节码规范的一部分。
wxy: 1 + 2 = 3,"1"是操作数(operands),"+"是操作码(opcode),整个算式是一条指令(instruction)
3. 从高级语言到机器语言的转化
根据语言的历史我们基本上可以判断,转化基本上就是 高级 --->汇编 ---->机器
汇编编译器assembler编译目标代码二进制文件(nasm -f elf -g -F stabs *.asm),连接器linker(ld -o bin_file *.o)除了把目标代码组合成一个单个的块,还要确保模块以外的函数调用能够指向正确的内存引用(连接器必须建立一个索引,也就是符号表,里面存放的是它连接的每一个目标模块中的每一个已命名项,其中存放着一些关于哪个名字或叫符号指向模块内部哪个位置的信息)。
立即数,内置在机器指令内部,它不是存放在寄存器中,也不是存放在位于某个指令之外的内存中。
1, 寄存器打中括号代表寄存器的内存地址中的内存数据。例:
mov eax,[ebx+16]。
2, 在汇编中,变量名代表的地址,不是数据。例:
Msg: "Hello World"
mov ecx,Msg #复制Msg地址到ecx寄存器,而不是数据
mov edx,[Msg] #复制数据,而不是地址
MsgLen: equ $-Msg #$代表末尾,长度=末尾位置减开始位置
各家的汇编格式可能不一样,例如gcc的汇编器使用的是AT&T文法,常见的汇编语言可以使用nasm编译。
关于编译这块,详细见第二段人生:https://www.cnblogs.com/shuiguizi/p/11756224.html
4. 指令的执行
- 如果是无操作系统的,比如单片机,则需要提前将编译得到HEX格式文件烧到flash种,或者说是ROM中
- 如果是有操作系统的,则首先会执行一段叫做BIOS的程序,由BIOS加载操作系统到xxx,而这个BIOS是CPU出厂就写入到ROM中的,即BIOS ROM
- 机器语言(CPU可以直接识别的语言)不存在“变量”这个概念,它只能操作内存和寄存器(CPU内部的几个暂存电路,数量很有限)。变量这个概念在实现的时候通常是将之对应于某个寄存器或者某一片内存。
- CPU提供了一系列指令来方便程序员维护一个叫“栈”的数据结构。栈位于内存当中,栈顶和栈底都保存在特殊的寄存器当中,CPU可以随时将数据压栈或者出栈。这个“数据”的含义实际上比较宽泛,它可以是一个数字、字符,也可以是CPU的运行状态。CPU可以将某一刻的运行状态压栈,然后跳转到其他地方执行一段程序,然后出栈恢复之前的执行状态,这就实现了函数的调用。没错,CPU也不太认识什么叫做“函数”,不过将运行状态压栈以及恢复运行状态都有专门的指令,一般就把它们成为”子程序调用指令“和“返回指令”。
- 对于“局部变量”,CPU连“变量”都不认识,所以局部变量什么的也不存在了。不过这玩意可以依靠栈来实现,通过压栈就可以随时分配一个内存,出栈就可以认为这个内存区域不再被使用。配合刚才说过的“函数”调用的实现方法,这种依靠压栈分配出的内存一定只能被当前正在执行的函数使用。因为函数调用前,这片内存还没有被分配,返回后这片内存也不再使用了