• [No0000166]CPU的组成结构及其原理


    中央处理器(Central Processing Unit, CPU)

    CPU
    的基本架构和工作原理其实百科上讲得已经相当清楚了,不过我觉得有些事情呢还是给个例子出来比较方便学习。
    本文会先从内存地址,计算机的一般架构之类的基础知识出发,然后逐步为读者"拼装"出一个超级简单的8-bit CPU。。。就像下图这样(大图点开)

    这就是本文的目标:拼装这样一个结构的CPU

    -----------------------------------------------------------------------------------------------------------------------

    上面那个大图里有几个梯形的符号
    它们叫做数据选择器(Multiplexer),也叫多路选择器或多路开关
    已经知道这个东西是干啥的童鞋直接跳过此楼吧。。

    这图是一个21选择器,A,B,S为输入,Z为输出,它们可取的值当然都只有01
    怎么工作的呢?
    以该图为例:
    S=1的时候,输出值Z = 输入值B
    S=0的时候,输出值Z = 输入值A
    比如说A=1,B=0,S=0的时候输出是多少?S=0就是说:选择A的值输出,也就是说输出值Z=A=1
    就这么简单

    同样地,我们也可以有41选择器

    只不过控制输入S变成了两位(00,01,10,11,分别对应一二三四),道理还是一样的

    如果你对这个东东怎么做成的感兴趣的话。。。下面就是41选择器的其中一种电路 = =

    哦,还有。。本文使用的逻辑门符号均是ANSI/IEEE Std 91-1984中的Distinctive shape,不是用方框符号。。。= =

    你只要知道数据选择器是干啥的就好,不用惦记上边那电路。。

    ------------------------------------------------------------------------------------------------------------

    1. 计算机架构(Computer Architecture)

    CPU
    、内部存储器(Internal Storage Device)和输入/输出设备(Input/Output Device, I/O)是电子计算机三大核心部件。内部储存器可以是硬盘,内存,缓存等;输入设备可以是鼠标,键盘;输出设备可以有屏幕,音箱等等。。

    当你打开电脑硬盘上安装的某个程序时,你的操作系统会把硬盘上的相应内容放入内存中。至于怎么放,在内存的什么地方放那可是一门大学问,光这个就够一般人喝一壶的。。相关知识可以在大学的操作系统课程里学到

    比如说你放个音乐。要放音乐,先用鼠标点开一个mp3文件,于是你就使用了一个输入设备。这个输入设备会把一个中断请求(Interrupt Request)送到CPU那边,结合来自操作系统的信息后CPU就知道:哦,你用鼠标点开那个mp3文件了!于是:
    1. CPU
    执行操作系统里关于文件关联的代码,于是你的电脑就知道要用WMP打开文件了
    2.
    你的操作系统开始把WMP这个程序里含有的指令和mp3文件的内容从硬盘上拉进内存里(还是CPU的工作)
    3.
    然后你的CPU开始一条一条地(双核的话那你就当成两条两条地好了)执行内存里的WMP程序指令(也就是如何解码mp3),并且把解码后的PCM比特流传到声卡上,再由声卡把数字信号转换成模拟信号送到音箱/耳机(输出设备)里。So now you have music

    又比如说你要编辑一个txt文件。还是先得用鼠标点开文件,又用了一次输入设备。于是:
    1. CPU
    执行操作系统里关于文件关联的代码,于是你的电脑就知道要用notepad打开文件了
    2.
    你的操作系统把notepad的程序指令和文件内容拉进内存
    3.
    然后你的CPU又开始执行Notepad程序的指令了
    4.
    每当你敲一次键盘(还是输入设备!!),都会向CPU发送一个中断请求好让CPU知道你敲了某个键。比如说你敲个Y,那么CPU就会把Y这个字符写进内存里。然后你要保存的时候操作系统就会把内存里改过的东东倒进硬盘里!~

    。。。好吧我承认实际过程跟这儿说的不大一样并且复杂得多,有些细节会在后面详细讲,but that's the basic idea.

    这大约就是CPU,输入/输出设备和内存之间的互动方式了。

    ---------------------------------------------------------------------------------------------------

    2.内存(Memory)

    内存就是暂时存储程序以及数据的地方,比如当我们在使用WPS处理文稿时,当你在键盘上敲入字符时,它就被存入内存中,当你选择存盘时,内存中的数据才会被存入硬盘。一断电内存上的东东就没了

    内存里的数据是根据内存地址(Memory Address)来组织的。每个地址都是独特的,每个地址一般来说对应着一个字节(byte)=8 bit,我们管这叫Byte addressable memory.

    32 bit的系统上,内存地址的长度就是32 bit。那么一个32 bit长度的二进制数最大可以表示的数是多少呢?很简单,2^32 = 4294967296。也就是说,32 bit的内存地址最大可以对应4294967296字节的内存!

    这个数字换算一下就可以得出它相当于4GB。现在你知道为什么32位系统不支持4GB以上的内存了吗?

    ------------------------------------------------------------------------------------------------------

    3. 指令编码(Instruction Encoding)

    终于要说点正经的了。。前面说过CPU会执行内存/缓存中的程序指令,可是这些指令是以什么样的形式储存在内存里的呢?要知道所谓的指令其实就是一长串的01而已。那CPU如何从这些01里知道指令是什么呢?这就是指令编码的内容了。

    先说说CPU。您说,CPU能干啥?其实很简单,无非就是加减乘除,读写内存,逻辑运算什么的。若是复杂些的CPU可能指令集要大些,不过基本的指令大概就这些。CPU内部也有自己的储存单元,叫做寄存器(Register),也是暂时用来放数据的地方,速度特别快,容量特别小。
    就拿我经常用的NIOS II来说,它内部有32个寄存器,它可以执行的指令包括(不好意思我要用汇编语言了=_=):

    add rA,rB,rC #
    把寄存器rB,rC里的数加起来,结果放入寄存器rC
    addi rB, rA, IMM16 #
    rA里的数跟一个16位的数相加,结果放入rB
    beq rB,rA,LABEL #
    rA=rB,则跳到LABEL指定的内存地址开始执行指令,否则继续按照内存地址顺序执行指令
    stwio rB, b_o(rA) #
    从内存地址rA+b_o处读取一个字节,数据放入rB
    ldwio rB, b_o(rA) #
    从内存地址rA+b_o处开始写入一个字节,写入的数据在rB


    等等

    总结起来,CPU可以有以下几个功能:
    1.
    进行寄存器之间的运算和比较
    2.
    由寄存器内指定的地址读写内存
    3.
    分支指令,类似于C语言里的if语句。比如跳到某个寄存器里指定的内存地址开始读取并执行指令

    当然,更复杂的指令集是有可能的,不过这里就不说了

    ---------------------------------------------------------------------------------------------

    我知道读者可能好几楼没见着个图有点烦躁了,不过请有些耐心,等开始拼装CPU的时候图片绝对多。。。

    Anyway
    ,继续说指令编码
    大致来说,上面的指令可以分为三大类:I-type,R-type,J-type
    P.S.
    这种分类适用于MIPS架构的处理器,其他我就不知道了

    1. I-type
    32 bit为例。一条I-type指令包括四个元素:
    两个寄存器编号,一个16位数字和一个操作码

    31-27
    位代表指令里寄存器rA的编号
    26-22
    位代表寄存器rB的编号
    21-6
    位是一个16位的二进制数
    5-0
    位是操作码

    例子:
    NIOS II
    汇编指令 addi r6,r7,310表示把寄存器r7里的数加上310,结果放入寄存器r6。如果我们规定addi运算对应的六位操作码是000011,那么请问整条指令的编码是?
    解答:
    寄存器r6的编号是6,即00110
    寄存器r7的编号是7,即00111
    数字310对应的二进制数是0000000100110110
    addi
    的操作码是000011
    所以整条指令的编码就是00110 00111 0000000100110110 000011
    -----------------------r6-----r7--------310---------addi------
    32位!
    这就是I-type指令在内存里存在的形式!!~

    ----------------------------------------------------------------------------

    2. R-type

    还是以32 bit为例。一条R-type指令通常包括四个元素:
    三个寄存器编号,一个操作码

    31-27
    位是寄存器rA的编号
    26-22
    位是寄存器rB的编号
    21-17
    位是寄存器rC(一般来说这个是目标寄存器)的编号
    16-6
    位是OPX,是操作码
    5-0
    位。。你当它没用吧,写上000000就好 = =

    例子:
    汇编指令 add r10,r9,r8是典型的R-type指令。它表示把寄存器r9,r8里的数加起来,然后把结果写入寄存器r10(目标寄存器)。若规定add运算的操作码为00000011111,请问整条指令的编码是?
    解答:
    寄存器r10的编号是10,即01010
    寄存器r9的编号是9,即01001
    寄存器r8的编号是8,即01000
    add
    运算操作码是00000011111
    OP = 000000
    所以整条指令的二进制编码是01001 01000 01010 00000011111 000000
    ---------------------------r9-----r8---r10------add-------OP
    32位!

    -----------------------------------------------------------------------------------------

    3. J-Type

    一条32bitJ-Type指令包含两个元素:
    一个26位的数字(通常是内存地址)和一个6位的操作码

    31-6
    位是数字
    5-0
    位是操作码

    例子:
    汇编指令 call ROUTINE_3是典型的J-Type指令,它表示该指令执行完毕后CPU将从ROUTINE_3开始的内存地址读取并执行其他指令。若ROUTINE_3开头指令的内存地址是0x00002b3ccall的操作码是000000,请问整条指令的二进制编码是?
    解答:
    16
    进制数0x00002b3c = 0000 0000 0000 0000 0010 1011 0011 1101
    call
    操作码是000000
    所以整个指令的编码是00000000000000000010101100111101 000000
    ------------------------------ROUTINE_3--------------call--
    还是32位!

    4.再说些CPU的事情

    让我试着用图片总结一下前面的基础知识。。

    上图概括了CPU和计算机其他部分的互动方式。该图与实际的计算机有很大差距,but you get the idea...现在让我们把注意力集中在CPU身上!

    CPU
    只知道执行指令,而指令是在内存里的(实际上不一定,但是为了让事情简单些,我们假设指令都是在内存里的)。所以CPU需要从内存里取出指令,这一步叫做提取(Fetch)

    CPU
    还需要知道这条指令是干什么的,所以被编码过的指令会被传到CPU的控制电路那边解码以正确设置控制信号,这样CPU才能正确执行指令,这一步叫解码(Decode)

    上面两步完成后CPU就可以执行该条指令了,也就是执行(Execute)
    运算后的结果经常需要保存,用来进行下一个指令的运算。那保存在哪里呢?无非就是寄存器组和内存。这一步叫做写回(Writeback)

    CPU
    的结构简图(省略了到输入/输出设备的连接)

    时钟信号(Clock Signal)说白了其实就是一个频率很高的方波,就像这样:

    它控制着CPU内核的工作节奏,每当时钟信号由01(rising edge)的时候,CPU里面的元件就会做点什么。

    数据通路(Datapath)是一个能够执行任何指令集内的指令的电路,但是它需要控制电路告诉它应该在什么时候执行什么指令。数据通路包括了寄存器组,算术逻辑单元(Arithmetic logic unit, ALU)以及很多其他的元件。

    控制电路负责解码指令并且正确设置控制信号,于是数据通路就能根据这些控制信号知道应当执行哪一条指令。

    ----------------------------------------------------------------------------------------------------------

    5. 好了,开始搭CPU = =

    如果读者到目前为止都还能懂的话,那么恭喜!你终于有了足够的基础知识来搭建一个简单的CPU了。
    当然了,CPU这玩意不是说搭就搭的。我们的CPU能干些什么?能执行些什么指令?指令是怎么编码的?它由哪些小模块组成?都有哪些控制信号?这些问题都必须有明确的回答。

    从现在开始,我强烈建议读者拿几张空白的纸出来记下这些问题的回答,因为我们即将面对的是众多的指令,模块以及控制信号。这可比拼装家具复杂多了,如果不记下来的话到时大概会头晕目眩。

    当初LZ对这个CPU做一丁点儿小改动的时候,可得对着一张电路图,大大的控制信号表格以及超长的Verilog HDL代码,花了不少时间和草稿纸呢

    I will be back tomorrow or after 3 hours...~

    --------------------------------------------------------------------------------------------------------------

    我们的CPU能做什么?

    从现在开始将进入本文最复杂,最能绕晕人的部分,请做好准备。。
    下面要开始说明这个CPU的规格,信息量略大,推荐写在纸上记着。

    现在我们对下面的行**几个简化约定(要是不简化的话,读者就会看到一大堆密密麻麻的描述文字),请务必记好。

    TMP = MEM[R2]
    这个语句表示从寄存器R2指定的内存地址读取数据,然后把读到的数据赋值给TMP
    举个例子:如果寄存器R2里的数字是0001 0011
    而内存地址0001 0011处所存的数据是1111 1111
    那么这个语句就表示TMP被赋值1111 1111TMP = 1111 1111

    MEM[R2] = TMP
    这个语句表示TMP的值被写入内存,写入的位置是内存地址R2
    举个例子:如果TMP = 1111 1110R2 = 0000 0001
    那么这个语句就表示内存地址0000 0001处的数据变成了1111 1110

    R1 = TMP
    这个语句表示寄存器R1写入TMP的值
    举个例子:如果TMP = 0000 1111
    那么这个语句表示寄存器R1里的数字变成了0000 1111

    PC = PC + 1
    几乎每个指令都会带有这个语句,意思是PC寄存器里的数字加1
    PC
    寄存器中有指令所在的内存地址。每执行完一条指令后,这个内存地址一般都会加1,好让CPU调出下一条指令

    -------------------------------------------------------------------------------------------------

    前面说过,我们的CPU8-bit的,也就是说它最多只能支持2^8=256个内存地址。我们的CPU内部将会有四个通用寄存器(General Purpose Register)R0~R3,一个PC寄存器(Program Counter Register),每个寄存器容量为8 bit。这个CPU不支持中断,意味着它不接受键盘和鼠标的输入,只会从内存里读取并执行指令。另外,CPU内部还有两个特殊的比特位,NZ。如果某个运算的结果是负数,那么N就会被设定为1;如果某个运算结果为零,那么Z就被设定为1。我们将会在跳转指令里用到这两个比特位。

    CPU可以执行10种指令:

    1. LOAD R1 (R2)
    实现方法:
    TMP = MEM[R2]
    R1 = TMP
    PC = PC + 1
    这条指令是把内存地址R2处的数据读出来,然后放进寄存器R1里。接着PC寄存器加一为下一条指令做准备。看出来了吗?下面将不再有这种文字描述,全部使用简写。

    2. STORE R1 (R2)
    实现方法:
    MEM[R2] = R1
    PC = PC + 1

    3. ADD R1 R2 [
    加法运算]
    实现方法: 
    TMP = R1 + R2
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    4. SUB R1 R2 [
    减法运算]
    实现方法:
    TMP = R1 - R2
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    5. NAND R1 R2 [NAND
    逻辑运算]
    实现方法:
    TMP = R1 NAND R2
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    6. ORI IMM5 [OR
    逻辑运算]
    实现方法:
    TMP = R1 OR IMM5, IMM5
    是一个5-bit的二进制数
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    7. SHIFT L/R R1 IMM2 [
    移位运算]
    实现方法:
    IF (L) THEN TMP = R1 << IMM2
    ELSE TMP = R1 >> IMM2
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    8. BZ IMM4 [
    如果Z=1,就跳过IMM4个指令]
    实现方法:
    IF (Z == 1) PC = PC + 1 + (SIGN-EXTEND8(IMM4))
    ELSE PC = PC + 1

    9. BNZ IMM4 [
    跟上一条指令相反]
    实现方法:
    IF (Z == 0) PC = PC + 1 + (SIGN-EXTEND8(IMM4))
    ELSE PC = PC + 1

    10. BPZ IMM4 [
    N = 0,就跳过IMM4个指令]
    实现方法:
    IF (N == 0) PC = PC + 1 + (SIGN-EXTEND8(IMM4))
    ELSE PC = PC + 1

    这些描述都比较抽象,做拼装的时候这些东西应该会表现得更具体些。

    ---------------------------------------------------------------------------------------------------

    CPU的指令编码

    5个指令的编码方式都是:
    7-6
    位是寄存器R1的编号
    5-4
    位是寄存器R2的编号
    3-0
    位是操作码


    ORI
    指令的编码


    SHIFT
    指令的编码


    三个跳转指令的编码

    --------------------------------------------------------------------------------------------------------------------------

    数据通路的设计

    这个CPU的数据通路将由以下部件组成:
    寄存器

    data in ----------
    写入寄存器数据,8条线,因为是8-bit
    data out ----------
    输出寄存器的数据,8条线,因为是8-bit
    控制信号write ---------- 是否允许写入数据。是的话write = 1, 否则 write = 0
    clock ----------
    时钟信号
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    数据选择器

    相信诸位都应该知道这玩意怎么工作的吧?当然输入输出都是8条线
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    通用寄存器组

    通用寄存器R0-R3都在这里面。
    reg1,reg2 ----------
    说明指令里涉及哪两个寄存器
    regw ----------
    指明要往哪个寄存器里写数据(应该是2条线,没画出来)
    data0 ----------
    reg1指定的寄存器中输出数据
    data1 ----------
    reg2指定的寄存器中输出数据
    dataw ----------
    实际写入寄存器的数据从这里进去(应该是8条线,没画出来)
    控制信号write ---------- 是否允许写入数据?是的话write = 1,否则write = 0
    clock ----------
    时钟信号
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    算术逻辑单元(ALU)

    这个模块就是CPU做运算的地方了。它能实现加减法,NANDOR运算,以及移位运算。
    In0,In1 ----------
    输入
    控制信号ALUop ---------- 告诉ALU应该做哪个运算
    Z,N ----------
    前面提过的特殊比特位,ALU要负责根据运算结果设置ZN
    OUT ----------
    运算结果输出

    -----------------------------------------------------------------------------------------------------------------

    继续说CPU组件。。。

    指令内存

    addr ----------
    指定从哪个内存地址读取指令
    Out ----------
    从内存里输出的指令在这里去往CPU
    控制信号Read ---------- 是否允许读取指令?是的话Read = 1, 否则 Read = 0
    我们假设指令内存是只读(Read Only)
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    数据内存

    addr ----------
    指定从哪个内存地址读取数据
    Din ----------
    往内存里写的数据从这里进去
    Dout ----------
    从内存里读取的数据从这里出去
    Clock ----------
    时钟信号
    控制信号MemWrite ---------- 是否允许数据写入内存?是的话MemWrite = 1, 否则为0
    控制信号MemRead ---------- 是否允许读取?是的话MemRead = 1,否则为0
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    现在我们要用上面的模块去组装CPU的数据通路。

    首先,无论执行的是哪一条指令,CPU都必须先从内存里把指令调出来,前面说过这一步叫做提取。此步骤由下图的电路执行。

    这个电路由一个PC寄存器(Program Counter Register)和指令内存组成。PC寄存器里储存的是该条指令所在的内存地址,然后指令内存会根据PC寄存器指定的地址向CPU输出相应的指令。控制信号PCWrite决定是否允许更改PC寄存器里的数字,如果允许那么PCWrite = 1, 否则为0

    -------------------------------------------------------------------------------------------------

    能执行加法指令的电路

    前面说过加法指令ADD R1 R2的编码形式如下

    该指令会把寄存器R1R2里的数字加起来,然后把结果写回R1
    INST
    线的3-0位是操作码,会被送到控制电路那边解码。解码后控制电路会设置好各个控制信号使得CPU的数据通路执行加法运算。我们以后再详细说说控制电路的事情,现在让我们来看看在数据通路里,INST线7-4位是如何使用的。


    上图是一个可以执行加法指令的电路(图里的数字有点小错误,不要在意)
    INST 7-6
    位代表R1的编号,作为通用寄存器组的reg1regw输入
    INST 5-4
    位代表R2的编号,作为reg2的输入
    然后R1R2里的数据从data0,data1输出,送到ALU做加法运算(ALUop会告诉ALU做加法运算)
    TMP = R1 + R2
    完成
    然后加法运算的结果被送到寄存器组的dataw输入。这时RFWrite = 1。由于此时regw指定的寄存器编号是R1,所以加法运算的结果就被写回了寄存器R1
    R1 = TMP
    完成
    此外,ALU还会把NZ这两个特殊的比特位根据运算结果设置好
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    完成

    这条指令还没完,我们需要把PC寄存器里的数字加一,这样CPU才能取得下一条指令。因此还得加点东西。

    有了上面那个电路之后
    PC = PC + 1
    就可以完成了

    同样的电路也可以用作执行SUBNAND指令。唯一不同的是控制信号ALUop会让ALU做减法或者NAND运算。

    -----------------------------------------------------------------------------------------

    执行ORI指令的电路

    ORI IMM5
    的编码

    这个指令会把寄存器R1里的数字与一个5-bit的二进制数做OR运算,然后把结果写回R1
    要实现这个指令只需把上面的电路稍作更改即可


    可以看到这个电路增加了两个数据选择器(图中的reg0,reg1应分别为reg1,reg2)
    首先,因为ORI指令总是在寄存器R1上进行操作,不像ADD,SUB,NAND等其他指令需要指定在哪些寄存器上进行操作,所以我们加入一个控制信号为R1Sel的选择器。
    当执行ORI指令时,R1Sel = 1,这样reg1的输入在执行ORI指令时总会是01
    所以data0输出也总是会输出R1的数据到ALU
    这时,ALU的另外一个输入应当是指令里的IMM5,而不是从寄存器组那边过来的输入
    于是我们加入另一个控制信号为ALU2的选择器,这样我们就可以选择是从寄存器组还是从INST线那边输入ALU数据。
    当执行ORI指令时,ALU2 = 1,这样ALU就会把INST 7-3位的5-bit二进制数作为输入
    然后控制信号ALUop告诉ALU进行OR运算
    TMP = R1 OR IMM5
    完成
    结果写回R1
    R1 = TMP
    完成
    ALU
    根据运算结果设置NZ
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    完成
    PC
    寄存器加一,CPU为下一条指令做好准备
    PC = PC + 1
    完成

    -----------------------------------------------------------------------------------------------------------------


    -----------------------------------------------------------------------------------------------------

    能执行内存读取以及SHIFT指令的电路

    LOAD R1 (R2)
    STORE R1 (R2)
    由于这两条指令里的R2部分总是作为地址使用,所以寄存器R2的输出要连到数据内存的addr输入;而R1STORE指令中是作为数据源的寄存器使用的,所以连接到Datain输入。R1LOAD指令中是作为放内存读出数据的寄存器使用的,所以连回到寄存器组的dataw输入。中间加了一个RFin选择器,这样寄存器组就可以选择是从ALU还是从数据内存那边写入数据。

    现在我们要把数据内存加入我们的电路里,如下图:

    最后我们还把ALU2选择器扩展了一下,使得执行SHIFT指令时ALU能够选择从INST线读到运算需要的数据(图中的INST 5-2应当为INST 4-3,因为SHIFT指令的IMM2在指令编码的4-3)

    然后这个CPU的数据通路就基本完成了!!

  • 相关阅读:
    pdf 去水印 比较好用
    ffmpeg从视频中每隔几秒提取一张图片
    tampermonkey 网盘直链下载助手
    淘工厂买的usb 蓝牙5.0 外置台式机 适配器(下载)
    Apache Doris 轻松入门和快速实践
    一步一步编译最新版Apache Doris 0.15版本的详细过程
    go使用msgpack
    服务接口杂谈
    关于真伪3层所讨论的
    jattach最近的一些更新
  • 原文地址:https://www.cnblogs.com/Chary/p/No0000166.html
Copyright © 2020-2023  润新知