|
首先,你在键盘上依次按下了1+1。
键盘上的电路触点被接通。键盘主控芯片此时在不停地、依次地检测各个触点两端是否导通,于是它发现了按键。按照预先烧录的程序,它在向USB线上发送的电信号中写入一个数字,告诉线另一头的庞然大物:“有键被按下了!”
信号内容——左边的ctrl没按 左边的shift没按 左边的alt没按 左边的win没按 右边的ctrl没按 右边的shift没按 右边的alt没按 右边的win没按,啦啦啦啦啦啦啦啦,按了“1”键,别的没按,别的没按,别的没按,别的没按,别的没按。
USB线的另一头连着电脑。电脑上的USB控制器读到了信号,把它转交给CPU(对,就是灯等灯等灯生产的那个)。CPU暂时停下了手上的工作,运行了操作系统中的一小段程序,把按键记录了下来。
CPU的动作很快,它总是马不停蹄地忙活各种不同的事。终于,它开始处理这个按键了。CPU上运行着操作系统,操作系统看到你按了键,于是找到了你正在操作的计算器程序。按照事先的约定,操作系统告诉CPU,“你去关心下计算器吧,它处理按键的程序在这里”。
于是,计算器中的一段程序开始运行。它读出按键“1”,记了下来。它告诉图形库,“给我在屏幕上显示1”。
图形库照着做了,它通知操作系统“在计算器的窗口上用这个字体、这个字号画上1”。操作系统找到了负责绘图的GUI(不是“鬼”)模块,一个点一个点地把“1”画了出来:
白黑白
黑黑白
白黑白
白黑白
白黑白
黑黑黑
就这样,屏幕上依次显示出了“1+1”。
当计算器读到回车的时候,它知道自己摊上大事了。
计算器想起自己读过1,加号,还有另一个1。它想,加号是个低优先级的二元运算符(就是两块钱做一次的运算符(误)),那么它两边的1就应该是用来加的两个数了。它分析道,“这是要做一个加法的节奏啊”。
于是,它把之前拿到的左边的1和右边的1取了出来,然后告诉CPU——
你快给我算出来:加法,这个数(左边的1),那个数(右边的1)。
在程序猴子们的视角下,这是一条长这样的指令:add %rcx, %rdx。在电脑的视角下,这是一条长这样的指令:010010000000000111001010。
CPU看到这条指令,很快明白了要做的事,把之前计算器获得的两个数000...01和000...01放到了用于计算的电路上。数字在电路上走着走着,来到了一段叫ALU的电路里。首先,末尾的两个小1经过了几道门,它们变成了小1(进位)和小0(当前位),然后进位的小1又和倒数第二位的两个小0擦出了激情的火花,变成了小0(进位)和小1(当前位)……
啊,这样写下去节bian4奏cheng2不xiao3太huang2对wen2了呀。
这里描述的是一个朴素的加法器——用逻辑门(二进制位运算)逐个算出进位,依次计算每一位的结果。
但这样的效率是很低的,因为高位的计算要等低位的进位算出来之后才能继续。事实上,现代的CPU里普遍会使用进位预测器。
一个常规的进位预测器通常是依次将进位信息从低位向高位推1,2,4,8……位。下面是用软件实现的Kogge-Stone加法器:
# input a, b
g = a & b
p = a ^ b
g = p & (g << 1) | g
p = p & (p << 1 | 0b1)
g = p & (g << 2) | g
p = p & (p << 2 | 0b11)
g = p & (g << 4) | g
p = p & (p << 4 | 0b1111)
sum = a ^ b ^ (g << 1)
在硬件实现中,位运算对应各种逻辑萌…哦不对,逻辑门;而位移,直接把电路接上就可以了。为了避免电路的规模过大,有时会将预测器和朴素方法混合使用。
总之,它们最终变成了000...10。当两个1的基情结晶从电路的另一头出来的时候,计算器收到了结果——1+1=2。
然后计算器说,“好,再把2显示出来吧”。于是它再次找到了图形库,把结果画在了屏幕上。
白黑白白白白白白白黑白
黑黑白白白黑白白黑黑白
白黑白白黑黑黑白白黑白
白黑白白白黑白白白黑白
白黑白白白白白白白黑白
黑黑黑白白白白白黑黑黑
白白白白白白白白白白白
白白白白白白白白白黑白
白白白白黑黑黑白黑白黑
白白白白白白白白白白黑
白白白白黑黑黑白白黑白
白白白白白白白白黑白白
白白白白白白白白黑黑黑
我们得到了2。
|
|