nonsense
ok啊兄弟们晚上好
今天也是实在无聊简单了解了一下计算机组成原理,不过也有一些好奇心的因素在就是了
但是其中具体电路的实现我实在是看不懂,电学本来就没学好,遂放弃理解电路实际组成,只关注抽象的过程,也因此不得不赞叹抽象的力量,使得我们不用过多关注于这些复杂的实现!
取名为nonsense,也算是一种自嘲吧!
开始吧!
你有没有想过,你写的c语言程序计算机是怎么运行的?
根据panorama这篇(其实我还没写完qwq),可以得知c代码经过编译器变为汇编代码,再经过汇编器转化为机器语言,也就是CPU能直接理解的二进制指令,接着,链接器把目标文件和共享库整合成一个完整的可执行文件
当你决定要运行它,这个可执行文件会通过操作系统从硬盘加载到主存(RAM)中,CPU的程序计数器(PC/IP)会指向机器代码的起始地址,然后开始一条条执行:取指、译码、执行,再取下一条,循环往复…
但是等等,二进制指令计算机这个福瑞又是怎么看懂的?
再深入一点吧
从晶体管开始
晶体管简单来说可以理解为一个微型开关,只有两种状态:通电即为1,断电即为0
单个晶体管虽然简单,但可以通过电路组合成逻辑门:与门(串联)、或门(并联)、非门和异或门(复杂的电路组合,我反正懵了)
与门是两个输入都为1输出才为1,或门是任意一个输入为1输出就为1,非门则是输入取反,异或门是两个输入不同时输出1,相同则输出0,也就是c语言学过的位运算,很好理解
这些逻辑门就是数字电路的语言,所有后续的计算都建立在它们之上
直觉来看这些逻辑门似乎能做的事情很有限,但并非如此,我们继续抽象
先来看看加法是如何实现的,先考虑两位二进制的加法,只需要考虑和和进位
和可以抽象为
1 | |
是不是就是一个异或门便能处理
再考虑进位
1 | |
这个也简单,就是与门吧
恭喜你,发明了半加器!
然而两位能表示的信息还是太有限了,在真实多位计算中,单个半加器无法处理来自低位的进位,于是我们引入全加器!
全加器在半加器的基础上,额外考虑了前一位的进位,我们进一步抽象,当多个全加器首尾相连时,就可以构成8位,32位甚至64位的多位加法器
具体电路的实现请原谅我直接忽略了,若好奇请自行查阅资料~
当然也要考虑最高位的进位,这便是后面标志位寄存器的工作了~
众所周知,实现了加法,便也实现了减法,减法本质上就是补码加法,即
A − B <=> A + (~B + 1)
左右移位操作在电路中是很简单基础的逻辑,但是在数学上,左移一位,就相当于乘以2;右移一位,就相当于除以2
于是通过移位和加减操作的结合,又能分别实现乘法和除法
加、减、乘、除、与、或、异或、移位,这些在高级语言里看起来完全不同的操作,在硬件里竟然都可以归结为对少数几种基础电路的组合和控制,太神秘了
最后,这些运算电路被统一封装在一个模块中,这个模块就是ALU,即算数逻辑单元~
不得不再次提到抽象的力量,你可以不用关注复杂的晶体管与电路实现,只需要封装+抽象即可!
现在计算机拥有了运算的能力,但如何获得记住的能力?
很容易想到,电容可以实现记住的功能,但电容会随时间逐渐损失(漏电),因此需要不断刷新来保持数据,用在CPU中似乎并不合适,CPU的运算节奏极快,它需要的是一种几乎可以随取随用的存储单元,而不是隔一段时间就要维护一次状态的电容,但其结构简单,容量大且成本低,因此主存便通常采取这样的方式,也就是DRAM,动态随机存取存储器
区别于此,CPU中通过晶体管电路的精妙实现,组成一个带反馈的逻辑结构,把输出重新送回输入,构成一个稳定的逻辑状态,一旦被写入0或1,只要电源存在,它就能一直保持这个值不变,这便是SRAM,静态随机存取存储器,常用于CPU寄存器与L1,L2缓存,需要更多的晶体管,成本更高,更复杂,容量也做不大,但换来的,是极高的速度和稳定性,称为锁存器(latch)!
同理,多个锁存器组合起来便是喜闻乐见的寄存器了~
寄存器用于在CPU内部保存当前指令执行过程中最关键,最频繁访问的数据
相比主存,寄存器容量极小,但访问速度极快!
为了让寄存器在复杂的电路中正常工作,还需要配合读使能和写使能信号,写使能决定当前时钟边沿是否把数据写入寄存器,读使能则决定寄存器的内容是否被送上数据总线,这些信号由控制单元统一调度!
出现了许多概念
先解释一下时钟
时钟是一个周期性跳变的信号,CPU内部几乎所有状态的更新,都严格发生在时钟的某一个边沿上,这里的状态,指的就是寄存器,程序计数器,标志位等由锁存器构成的存储单元,组合逻辑可以在一个周期内自由变化,但只有在时钟边沿到来时,这些变化才会被采样并真正写入寄存器
数据总线则可以理解为将ALU,寄存器,主存串联起来的传输数据的共享线路,除此之外还有地址总线,用来告诉主存我要访问的是哪个位置,以及控制总线,用来传递读写使能,时钟相关信号等控制信息…
等等,CPU是怎么在主存中找到地址的?
从CPU的视角来看,主存被抽象为一个巨大的,线性编号的存储空间,每一个存储单元都有一个唯一的地址,CPU只需要给出一个地址,就能通过地址总线访问对应的位置,但是主存是将地址解码成行,列等更底层的电路结构,用来选中具体的存储单元,太复杂的实现,我们抽象掉…
CPU又是怎么识别如add,mov等指令的呢,CPU并不理解add,mov这样的助记符,这些只是汇编语言中为了方便人类阅读而引入的名字,尽管似乎对于人类来说还是很难理解(x_x),在机器层面,指令就是一串固定格式的二进制比特
这似乎取决于具体架构,但离不开译码器,当一条指令被从主存取到CPU后,它会被送入指令寄存器(即取指),随后,控制单元开始对这串比特进行解析,即译码
译码的核心是译码器,译码器本质上也是一个组合逻辑电路,它根据指令中不同字段的比特模式,产生一组控制信号,这些控制信号会告诉 CPU这是哪一类指令,需要使用哪些寄存器,ALU要执行什么运算以及结果是否需要写回寄存器或内存等等
在执行阶段,这些控制信号会被真正送达各个硬件模块,相关寄存器的读使能被打开,操作数被送上数据总线,ALU根据译码结果选择对应的运算路径,若需要访问主存,相应的地址和读写信号也会被同时发出
当运算完成后,在下一个时钟边沿,如果写使能信号有效,结果就会被写回到目标寄存器或内存中,与此同时,程序计数器也会根据指令类型被更新,要么顺序指向下一条指令,要么被改写为跳转目标地址
至此,一条指令的生命周期才算结束!
随后,CPU再次进入取指阶段,重复取指、译码、执行这一循环,程序也就以这种方式一条一条地向前推进…
让我们重点看看程序计数器(IP/PC),其始终指向当前即将执行的那一条指令在主存中的地址
在取指阶段,CPU会根据IP给出的地址,通过地址总线从主存中取出对应的指令,并送入指令寄存器(IR)
当一条指令执行完成后,IP会被更新,对于顺序执行的指令,IP 通常会自动增加,指向下一条指令的位置,而对于跳转(jmp),调用(call)等控制流指令,IP则会被直接改写为新的目标地址
也正是通过不断地读取和更新IP,CPU才能沿着程序设定的执行路径不断向前推进,看似只是一个简单的寄存器,却决定了程序接下来要做什么,是整个指令执行流程中最关键的状态之一!
所以在pwn中,hacker们的目标通常就是控制IP~
原来如此啊~
当然,这似乎只是最简单的实现,尽管其已经让我彻底懵了
在更复杂的CPU中,这些阶段甚至会被流水线化(x_x),不同指令的不同阶段可以在同一个时钟周期内并行进行,后面再来探索吧!
想不到吧,这里也有资本家
同时,如你所见,由于频繁访问主存会带来较大的延迟和性能开销,现代CPU内部会集成高速缓存(Cache)系统,从而减少每次访问主存的等待时间,提高CPU整体运行效率
寄存器,操作系统,汇编指令似乎也不止于此…
太神秘了…
最后总结一下!
在CPU内部,最核心的组成包括几部分
首先是运算逻辑单元,即ALU,负责执行加减、逻辑运算和移位等基本计算
其次是寄存器系统,由大量基于锁存器构成的寄存器组成,用来保存指令执行过程中最关键、最频繁使用的数据,其中也包括程序计数器IP,用来指向下一条即将执行的指令
为了让这些部件在高速下仍然能够有序协作,CPU还依赖统一的时钟信号
时钟把连续变化的电路切分成一个个离散的周期,使寄存器只在特定的时刻更新状态,保证整个系统的确定性
而控制单元则通过译码器,把指令中的二进制比特翻译成一组控制信号,统一调度寄存器读写,ALU运算以及访存行为
在存储层面,CPU内部使用的是基于SRAM的寄存器和高速缓存,追求极致的访问速度
而主存则采用DRAM,负责提供大容量的数据存储
两者之间通过总线相连,形成清晰的分工
当CPU需要与主存交互时,会由IP给出指令地址,或由指令本身给出数据地址
CPU将目标地址放到地址总线上,同时通过控制总线发出读或写信号
主存在接收到地址和控制信号后,把对应的数据放到数据总线上,CPU 再将这些数据读入寄存器,供后续运算使用,或将计算结果写回主存
最终,CPU 正是通过不断重复取指、译码、执行这一循环,在时钟的约束下,对寄存器和主存中的状态进行有序更新
看似抽象复杂的程序执行过程,本质上就是这些基础部件在硬件层面一次次精确协作的结果!
最后 借用YS-os中的一句话 — 善用 LLM 进行学习! (´・ω・)つ旦
so free~
最后的最后依旧借用一下2022年上海中考的作文题目,这不过是个开始…
move forward!
感谢阅读!
生活愉快…
补充:
我觉得有必要补充一下寄存器相关的知识
掌握了寄存器
汇编便也不再晦涩难懂(你确定??? (´⊙ω⊙`) )
我以我熟悉的linux x86-64 为例
首先,有哪些寄存器?
1.通用寄存器
比如
rdi rsi rdx rcx rbx rax r8-r15
用于整数运算,函数参数传递,临时存储等
2.栈相关寄存器
1 | |
栈帧
3.程序计数器
rip
保存下一条即将执行的指令地址
4.标志寄存器(RFLAGS/EFLAGS)
保存运算结果状态,例如零标志ZF,进位标志CF,溢出标志OF,符号标志SF等等,用于条件跳转和ALU决策
5.浮点与向量寄存器
涉及二进制浮点数表示,暂时不管
6.段寄存器
CS DS ES FS GS SS
用于内存段选择,现代用户态程序基本不操作,不管
7.控制寄存器(CR0–CR4等)
用于控制CPU工作模式,分页,保护等机制,主要在内核态使用
8.调试寄存器(DR0–DR7)
用于硬件断点设置(硬件中断),gdb的底层原理!
最后,是一套寄存器调用约定(ABI)
函数前6个整型参数依次放在寄存器 RDI RSI RDX RCX R8 R9
caller-saved
RAX RCX RDX RSI RDI R8–R11callee-saved
RBX RBP R12–R15返回值放在寄存器 RAX
栈16字节对齐(RSP)
熟悉吗孩子们
哈哈哈哈哈哈哈
pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn pwn
启航2026!