计算机组成 -- 加法器
基本门电路 基本门电路:输入都是两个单独的bit,输出是一个单独的bit 如果要对2个8bit的数字,计算与或非的简单逻辑(无进位),只需要连续摆放8个开关,来代表一个8bit数字 这样的两组开关,从左到右,上下单个的位开关之间,都统一用『与门』或者『或门』连起来 就能实现两个8bit数的AND运算或者OR运算 异或门 + 半加器一bit加法 个位 输入的两位为00和11,对应的输出为0 输入的两位为10和01,对应的输出为1 上面两种关系都是异或门(XOR)的功能 异或门是一个最简单的整数加法,所需要使用的基本门电路 进位 输入的两位为11时,需要向更左侧的一位进行进位,对应一个与门 通过一个异或门计算出个位,通过一个与门计算出是否进位 把这两个门电路打包,叫作半加器(Half Adder) 全加器 半加器只能解决一bit加法的问题,不能解决2bit或以上的加法(因为有进位信号) 二进制加法的竖式,从右往左,第二列称为二位,第三列称为四位,第四列称为八位 全加器:两个半加器和一个或门 把两个半加器的进位输出,作为一个或门的输入 只要两次加法中任何一次需要进位,那么在二位...
计算机组成 -- 电路
电报机 电报机的本质:蜂鸣器 + 电线 + 按钮开关 蜂鸣器装在接收方,开关留在发送方,双方通过电线连在一起 继电器 电线的线路越长,电线的电阻越大,当电阻很大,而电压不够时,即使按下开关,蜂鸣器也不会响的 继电器(Relay):为了实现接力传输信号 中继:不断地通过新的电源重新放大已经开始衰减的原有信号 中间所有小电报站都使用『螺旋线圈+磁性开关』的方式,来替代蜂鸣器+普通开关 只在电报的始发和终点用普通开关和蜂鸣器 这样就可以将长距离的电报线路,拆成一个个小的电报线路,接力传输电报信号 继电器别名:电驿,驿站的驿 二进制 有了继电器后,输入端通过开关的『开』和『关』表示1和0,输出端也能表示1和0 输出端的作用,不仅仅是通过一个蜂鸣器或者灯泡,提供一个供人观察的输出信号 还可以通过『螺旋线圈+磁性开关』,使得输出也有『开』和『关』两种状态,表示1和0,作为后续线路的输入信号 与(AND) 在输入端的电路上,提供串联的两个开关,只有两个开关都打开,电路才接通,输出的开关才能接通 或(OR) 在输入端的电路上,提供两条独立的线路到输出端 两条线路上各有一个开关,任意一个开关打...
计算机组成 -- 二进制编码
补码表示法 原码表示法 0011为3,1011为-3 缺点:0000和1000都表示为0 浪费 + 模凌两可 由此诞生了补码表示法,其实就是一个简单的翻转而已 用补码表示负数,使得整数的相加变得容易,不需要做任何特殊处理,当成普通的二进制相加即可 字符串 ASCII码类似一个字典,用8位二进制中的128个不同的数字,映射到128个不同的字符里 a在ASCII里面是第97个,二进制为0110 0001,对应的十六进制为0x61 字符串9用0011 1001来表示,字符串15用0011 0001和0011 0101来表示,占用更多的空间 因此存储数据的时候,要采用二进制序列化的形式, 而不是简单地把数据通过CSV或者JSON这样的文本格式存储来进行序列化 不管是整点数,还是浮点数,采用二进制序列化比存储文本能节省不少空间 字符集(Charset)和字符编码(Character Encoding) 字符集:字符的集合 Unicode是字符集,包含150种语言的14万个字符 字符编码:对于字符集里的这些字符,怎么用二进制表示出来的一个字典 Unicode可以用UTF-8、UTF-1...
计算机组成 -- 动态链接
静态链接 把对应的不同文件内的代码段合并在一起,成为最后的可执行文件,可以做到代码在开发阶段的复用 很多程序都需要通过装载器装载到内存里面,里面链接好的同样的功能代码,也都需要再装载一遍,再占一遍内存空间 动态链接 动态链接过程中,需要链接的不是存储在硬盘上的目标文件代码,而是加载到内存中的共享库(Shared Libraries) 加载到内存中的共享库会被很多程序的指令调用 Windows的共享库文件是.dll(Dynamic-Link Libary)文件;Linux的共享库文件是.so(Shared Object)文件 地址无关 + 相对地址 要在程序运行时共享代码,这些机器码必须是地址无关的,即编译出来的共享库文件的指令代码,是地址无关码 地址无关码(PIC):Position-Independent Code,无论加载到哪个物理内存地址,都能够正常执行 大部分函数库都能做到地址无关,因为都是接受特定的输入,进行确定的操作,然后给出返回结果 对于所有动态链接共享库的程序来讲 虽然共享库用的都是同一段物理内存地址 但在不同的应用程序里,共享库所在的虚拟内存地址是不同的 不应该...
计算机组成 -- 段 + 页
程序装载 背景 通过链接器,把多个目标文件合并成一个最终可执行文件 运行可执行文件时,其实是通过一个装载器,解析ELF或者PE格式的可执行文件 装载器会把对应的指令和数据加载到内存里面,让CPU去执行 装载器需要满足两个条件 可执行程序加载后占用的内存空间应该是连续的 执行程序时,程序计数器是顺序地一条一条指令执行下去 需要同时加载很多个程序,并且不能让程序自己规定在内存中加载的位置 内存地址 虚拟内存地址:指令里用到的内存地址 物理内存地址:内存硬件里的空间地址 一个思路 在物理内存里面找一段连续的内存空间,分配给装载的程序 然后把这段连续的内存空间地址和整个程序指令里指定的内存地址做一个映射 程序里有指令和各种内存地址,而我们只需要关心虚拟内存地址即可 对任何一个程序来说,它所看到的都是同样的内存地址 维护一个虚拟内存到物理内存的映射表 实际程序指令执行的时候,会通过虚拟内存地址,找到对应的物理内存地址,然后执行 因为是连续的内存地址空间,只需要维护映射关系的起始地址和对应的空间大小即可 内存分段 分段(Segmentation):找出一段连续的物理内存和虚拟内...
计算机组成 -- ELF + 静态链接
代码拆分源代码add_lib.c12345// add_lib.cint add(int a, int b){ return a+b;} link_example.c1234567891011// link_example.c#include <stdio.h>int main(){ int a = 10; int b = 5; int c = add(a, b); printf("c=%d\n", c);} gcc + objdump123$ gcc -g -c add_lib.c link_example.c$ objdump -d -M intel -S add_lib.o$ objdump -d -M intel -S link_example.o add_lib.o1234567891011121314151617add_lib.o: 文件格式 elf64-x86-64Disassembly of section .text:0000000000000000 <add>...
计算机组成 -- 函数调用
程序栈123456789101112131415// function_example.c#include <stdio.h>int static add(int a, int b){ return a+b;}int main(){ int x = 5; int y = 10; int u = add(x, y);} 12$ gcc -g -c function_example.c$ objdump -d -M intel -S function_example.o 1234567891011121314151617181920212223242526272829303132333435363738......0000000000000000 <add>:......int static add(int a, int b){ 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp 4...
计算机组成 -- goto
CPU执行指令 CPU是由一堆寄存器组成的,而寄存器是由多个触发器(Flip-Flop)或者锁存器(Latches)组成的简单电路 触发器和锁存器是两种不同原理的数字电路组成的逻辑门 N个触发器或者锁存器,就可以组成一个N位的寄存器,能保存N位的数据,64位的Intel服务器,寄存器就是64位的 寄存器分类 PC寄存器(Program Counter Register),也称为指令地址寄存器(Instruction Address Register) 用来存放下一条需要执行的计算机指令的内存地址 指令寄存器(Instruction Register) 用来存放当前正在执行的指令 条件码寄存器(Status Register) 用里面的一个个标志位(Flag),存放CPU进行算术或者逻辑计算的结果 其它 整数寄存器、浮点数寄存器、向量寄存器、地址寄存器、通用寄存器 程序执行 CPU会根据PC寄存器里面的地址,从内存里把需要执行的指令读取到指令寄存器里面执行 然后根据指令长度自增,开始顺序读取下一条指令,一个程序的指令,在内存里面是连续保存的,也会一条条顺序加载 特殊指令,如J类指令...
项目立项 -- 保证不亏钱
问题抽象一下:假设有n个坑,第$i$个坑投注了$X_i$,倍率为$Y_i$,怎样设置倍率才能保证不亏钱? 推导过程如下: 假设某场第$k$个坑赔最少,最少赔付记为$min = (X_k \times Y_k)$ 则$(X_k \times Y_k) \leq (X_l \times Y_l) \quad l \in [1,n]$ 易得$\frac{X_k \times Y_k}{Y_l} \leq X_l \quad l \in [1,n]$ 而收入为$(\sum_{i=1}^n{X_i}) \geq (\sum_{i=1}^n{\frac{X_k \times Y_k}{Y_i}}) = (\sum_{i=1}^n{\frac{1}{Y_i}}) \times (X_k \times Y_k) = (\sum_{i=1}^n{\frac{1}{Y_i}}) \times min$ 而不亏钱,只要保证$(\sum_{i=1}^n{X_i}) \geq min$即可,而由上易知,$(\sum_{i=1}^n{\fra...
计算机组成 -- 指令
CPU + 计算机指令 硬件的角度 CPU是一个超大规模集成电路,通过电路实现了加法、乘法乃至各种各样的处理逻辑 软件工程师的角度 CPU就是一个执行各种计算机指令的逻辑机器 计算机指令是一门CPU能听懂的语言,也称为机器语言 不同的CPU能够听懂的语言不太一样,两种CPU各自支持的语言,就是两组不同的计算机指令集 计算机程序平时是存储在存储器中,这种程序指令存储在存储器里面的计算机,叫作存储程序型计算机 代码 -> 机器码(编译 -> 汇编)1234567// test.cint main(){ int a = 1; int b = 2; a = a + b;} 编译(Compile)成汇编代码:把整个程序翻译成一个汇编语言(ASM,Assembly Language)的程序 汇编:针对汇编代码,用汇编器(Assembler)翻译成机器码(Machine Code) 机器码由0和1组成的机器语言表示,一串串的16进制数字,就是CPU能够真正认识的计算机指令 汇编代码 + 机器码12345678910$ gcc --help-c ...













