从0到1使用C++实现一个模拟器-1-【实现最简CPU】

文章目录

  • uint64_t
  • std
  • std::array
  • CPU和CU
  • 构造函数
  • size_t
  • static_cast
  • std::ifstream
  • riscv64-unknown-elf-objcopy -O binary add-addi add-addi.bin
  • riscv64-unknown-elf-gcc -Wl,-Ttext=0x0 -nostdlib -o add-addi add-addi.s
  • -wl
  • std::hex std::setw() std::setfill()各自的用法
  • hexdump
  • add与addi的risv编码
  • 简单CPU工作流程
  • 实现流程
  • 字节码
  • cpu.cpp

uint64_t

uint64_t是C/C++中的一种无符号整数类型,它表示一个64位的无符号整数。在计算机中,无符号整数是以二进制补码形式存储的,因此它的取值范围是从0到2^64-1(即18446744073709551615)。

uint64_t的全称是unsigned int 64-bit。

在C语言中,uint64_t是一个通过typedef定义的别名,代表了一个无符号的64位整型。这种命名方式是一种规范化的表示,其中"u"代表unsigned(无符号),"int"代表integer(整型),而"64"则指明了这是一个64位的整数类型。使用这些标准的数据类型别名可以提高代码的可读性和可维护性,因为它们明确地指出了数据类型的大小和属性。例如,uint8_t、uint16_t、uint32_t和uint64_t分别表示8位、16位、32位和64位的无符号整型。

这些数据类型都是在C99标准中定义的,它们并不是新的数据类型,而是已有数据类型的别名。这样做的好处是,即使在不同的平台上,这些类型的大小可能会有所不同,使用这些标准别名可以确保代码的一致性和移植性。

std

std是C++标准库的命名空间。

在C++编程语言中,std是一个特殊的命名空间,它包含了C++标准库的所有函数、类和其他模板。这个命名空间是为了区分标准库中的函数和对象与用户自定义的或其他库中的函数和对象。使用std命名空间,可以避免名称冲突,并且提供了一种组织代码的方式。以下是一些关于std命名空间的关键点:

标准库函数和对象:如std::cout用于输出,std::cin用于输入,以及各种数据结构和算法,如std::vector、std::sort等都在std命名空间中定义。
使用方式:在使用标准库中的任何函数或对象时,通常需要在前面加上std::来指明它们是来自std命名空间。例如,std::cout << "Hello, World!"中的std::就是必要的限定词。
简化使用:为了避免每次都要写std::,可以在代码中通过using namespace std;来告诉编译器在接下来的代码中使用std命名空间,这样就不需要重复写std::了。但这在某些情况下可能会导致命名冲突,所以需要谨慎使用。

std::array

std::array是一个固定大小的数组容器,提供了许多内置数组所不具备的功能,同时保持了与原生数组相同的随机访问能力和效率。以下是std::array的一些主要用法:

初始化:可以在声明时直接初始化std::array,也可以使用列表初始化的方式。例如:

std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
std::array<int, 5> arr2{6, 7, 8, 9, 10};

  • 访问元素:可以通过下标操作符[]来访问或修改std::array中的元素,就像使用原生数组一样。

  • 获取大小:std::array提供了一个size()成员函数,可以返回数组的大小(元素个数)。

  • 获取数据指针:可以使用data()成员函数获取指向第一个元素的指针,这对于需要以C风格数组形式传递数据的情况非常有用。

  • 迭代器支持:std::array支持随机访问迭代器,可以使用迭代器遍历数组中的元素。

  • 不支持动态变化:由于std::array的大小是固定的,它不支持添加或删除元素,也不支持改变其大小。

  • 自动推导数组大小:在C++17及以后的版本中,可以利用类模板参数推导特性来简化std::array的声明,如std::array arr = {1,2,3,4,5,6};,但需要注意的是,这种推导方式无法只提供数组元素类型让编译器推导数组长度。

总的来说,std::array是一个结合了原生数组优点并增加了更多功能的安全容器。它在需要固定大小数组且希望有更多安全性和便利性的场景下是一个很好的选择。

CPU和CU

CPU(中央处理单元)和CU(控制单元)是计算机中的核心组件,它们在功能上有所区别。具体如下:

CPU:是计算机的大脑,负责执行程序指令和处理数据。它由多个部件组成,包括算术逻辑单元(ALU)、控制单元(CU)、寄存器等。CPU内部采用复杂的电路来执行各种计算和逻辑操作,同时它还负责处理与内存、输入输出设备之间的数据交换。
CU:是CPU内部的一个部件,全称为Control Unit,中文叫做控制单元。CU的主要功能是发出各种控制命令或微指令,控制整个计算机系统的工作,包括CPU内部的各个部件。CU协调各个部件的工作,确保程序能够按照预定的流程顺利执行。

在C++中,public和class是两个关键字,它们用于定义类的成员的访问权限。

class:用于定义一个类,它表示一个抽象的数据类型,可以包含数据成员(属性)和成员函数(方法)。类的定义通常包括类名、类的属性和方法等。例如:

class MyClass {
    // 类的属性和方法
};

public:用于指定类的成员的访问权限为公共(public),即可以在类的外部直接访问这些成员。在类的定义中,使用public关键字修饰的成员可以被其他代码直接访问和操作。例如:

class MyClass {
    public:
        int x; // 公共属性
        void myFunction(); // 公共方法
};

在上面的例子中,x是一个公共属性,可以在类的外部直接访问和修改;myFunction()是一个公共方法,也可以在类的外部直接调用。

需要注意的是,在C++中,如果没有显式地指定访问权限,默认情况下,类的成员都是私有的(private),只能在类的内部访问和操作。如果需要让类的成员在类的外部可访问,就需要使用public关键字来声明。

构造函数

C++中的构造函数是一种特殊的成员函数,它在创建对象时被自动调用。构造函数的名称与类名相同,没有返回值类型,可以有参数。构造函数的主要作用是对对象进行初始化。

以下是一个简单的C++构造函数示例:

#include <iostream>
using namespace std;

class MyClass {
public:
    // 构造函数
    MyClass() {
        cout << "构造函数被调用" << endl;
    }
};

int main() {
    MyClass obj; // 创建对象,自动调用构造函数
    return 0;
}

在这个示例中,我们定义了一个名为MyClass的类,并在其中定义了一个构造函数。当我们在main函数中创建MyClass的对象obj时,构造函数会自动被调用,输出"构造函数被调用"。

size_t

size_t是C++中的一种无符号整数类型,通常用于表示对象的大小或数组的长度。它的定义取决于具体的编译器和操作系统,但通常足够大以表示任何可能的对象大小。在32位系统中,size_t通常是32位的,而在64位系统中,size_t通常是64位的。

static_cast

static_cast 是 C++ 中的一种类型转换操作符,用于将一个表达式从一种类型转换为另一种类型。它主要用于基本数据类型之间的转换,如 int 转 float、double 转 int 等。

std::ifstream

std::ifstream 是 C++ 标准库中的一个类,用于从文件中读取数据。它继承自 std::istream 类,提供了一些成员函数和操作符,可以方便地从文件中读取文本或二进制数据。

使用 std::ifstream 打开文件时,需要指定文件名和打开模式。其中,文件名可以是相对路径或绝对路径,而打开模式则决定了文件的访问方式。常用的打开模式包括:

std::ios::in:以只读方式打开文件;
std::ios::out:以写入方式打开文件;
std::ios::binary:以二进制方式打开文件;
std::ios::ate:打开文件后立即定位到文件末尾;
std::ios::app:以追加方式打开文件。

riscv64-unknown-elf-objcopy -O binary add-addi add-addi.bin

这个命令的作用是将名为"add-addi"的ELF格式文件转换为二进制格式,并将结果保存为"add-addi.bin"。

具体解释如下:

  • "riscv64-unknown-elf-objcopy"是一个用于处理目标文件的工具,它可以将一个文件从一种格式转换为另一种格式。
  • "-O binary"选项表示要将输入文件转换为二进制格式。
  • "add-addi"是要转换的输入文件名。
  • "add-addi.bin"是转换后的输出文件名。

riscv64-unknown-elf-gcc -Wl,-Ttext=0x0 -nostdlib -o add-addi add-addi.s

这个命令的作用是使用RISC-V 64位编译器将汇编文件"add-addi.s"编译为可执行文件"add-addi"。

具体解释如下:

  • "riscv64-unknown-elf-gcc"是一个用于编译和链接程序的工具,它是针对RISC-V架构的交叉编译器。
  • "-Wl,-Ttext=0x0"选项指定了程序的起始地址为0x0。
  • "-nostdlib"选项表示不链接标准库。
  • “-o add-addi"选项指定了输出文件名为"add-addi”。
  • "add-addi.s"是要编译的汇编源文件名。

-wl

-Wl 是一个常用的编译选项,它用于将命令行参数传递给链接器(linker)。在 GCC 编译器中,-Wl 后面通常跟着的是链接器的选项,这些选项可以影响链接过程和生成的可执行文件。

例如,-Wl,-Ttext=0x0 就是一个使用 -Wl 传递的链接器选项,其中 -Ttext=0x0 是链接器的一个具体选项,它告诉链接器将程序的文本段(通常包含程序的代码)的起始地址设置为 0x0。

其他常见的 -Wl 选项包括:

  • -Wl,–start-group 和 -Wl,–end-group:这两个选项用于在链接时指定一个输入文件的顺序,确保这些文件按照指定的顺序被链接。
  • -Wl,-Map:这个选项告诉链接器生成一个映射文件,该文件描述了输入对象和可执行文件之间的映射关系。
  • Wl,-rpath:这个选项用于设置运行时库搜索路径。

在使用 -Wl 选项时,通常需要确保链接器理解并支持你传递的选项。不同的目标平台和操作系统可能会有不同的链接器选项。

std::hex std::setw() std::setfill()各自的用法

std::hex、std::setw() 和 std::setfill() 是 C++ 标准库中的流操作符,用于格式化输出。

std::hex:将后面输出的数据都以十六进制形式显示。它通常与 std::cout 一起使用,例如:

#include <iostream>

int main() {
    int num = 255;
    std::cout << std::hex << num<<123; // 输出 "ff7b"
    return 0;
}

std::setw():设置下一次输出字段的宽度。它通常与 std::left 或 std::right 一起使用来指定对齐方式,但如果没有指定对齐方式,默认是右对齐。例如:

#include <iostream>

int main() {
    int num = 42;
    std::cout << std::setw(5) << num; // 输出 "   42"
    return 0;
}

std::setfill():设置填充字符。它通常与 std::setw() 一起使用,用于在输出字段不足指定宽度时用指定的字符进行填充。例如:

#include <iostream>

int main() {
    int num = 42;
    std::cout << std::setw(5) << std::setfill('0') << num; // 输出 "00042"
    return 0;
}

hexdump

在这里插入图片描述

add与addi的risv编码

在这里插入图片描述
在 RISC-V 中,指令编码格式分为几种类型,其中 addi 指令使用 I-type 格式,而 add 指令使用 R-type 格式。每种类型的指令机器码是32位宽,而且每一位都有特定的作用。

addi (Add Immediate) 指令的 I-type 格式: RISC-V 的 I-type 指令格式如下:

┌───────┬───────┬───────┬─────────────┬───────┬───────┐
│ funct7 │  rs2  │  rs1  │    funct3   │   rd  │ opcode│
├───────┼───────┼───────┼─────────────┼───────┼───────┤
│    立即数 (12 bits)rs1 (5 bits)  │  funct3 |   rd (5 bits)opcode (7 bits) │
└────────────────────────┴─────────────┴────────┴─────────────┘ 

opcode (7 bits): 操作码,对于 addi 是 0010011。
rd (5 bits): 目标寄存器的编号。
funct3 (3 bits): 功能码,对于 addi 是 000。
rs1 (5 bits): 源寄存器1的编号。
立即数 (12 bits): 要加的立即数,它是有符号的,并且在执行时会自动进行符号扩展到32位或更多。
例如,对于指令 addi x5, x10, 15,其二进制编码可能如下所示(只展示了操作相关的二进制位,不包括具体的立即数和寄存器编号):

00000000000011110110000010010011

add (Add) 指令的 R-type 格式: RISC-V 的 R-type 指令格式如下:

┌───────┬───────┬───────┬─────────────┬───────┬───────┐
│ funct7 │  rs2  │  rs1  │    funct3   │   rd  │ opcode│
├───────┼───────┼───────┼─────────────┼───────┼───────┤
│ funct7 (7 bits)rs2 (5 bits)rs1 (5 bits)funct3 (3 bits)rd (5 bits)opcode (7 bits)│
└───────┴───────┴───────┴─────────────┴───────┴───────┘

opcode (7 bits): 操作码,对于 add 是 0110011。
rd (5 bits): 目标寄存器的编号。
funct3 (3 bits): 功能码,对于 add 是 000。
rs1 (5 bits): 源寄存器1的编号。
rs2 (5 bits): 源寄存器2的编号。
funct7 (7 bits): 对于 add,这个字段是 0000000。
例如,对于指令 add x5, x10, x20,其二进制编码可能如下所示(只展示了操作相关的二进制位):

00000001010010100000010110110011

简单CPU工作流程

在现代计算机体系结构中,尤其是遵循冯·诺依曼架构的计算机系统,程序的执行可以分解为几个连续的阶段,这些阶段共同构成了 CPU 的指令周期。这些阶段包括取指(Instruction Fetch, IF)、解码(Instruction Decode, ID)、执行(Execute, EX)、访存(Memory Access, MEM)和写回(Write Back, WB)。这个过程是循环进行的,每个阶段完成特定的任务,确保计算机程序顺利执行。


+--------+   +--------+   +--------+   +--------+   +--------+
| 取指IF  |-->| 解码ID |-->| 执行EX  |-->| 访存MEM |-->| 写回WB |
+--------+   +--------+   +--------+   +--------+   +--------+

在这个示意图中,每行代表 CPU 中的一条指令随时间前进经过不同的处理阶段。每个方框代表流水线的一个阶段,箭头表示指令从一个阶段移动到下一个阶段的流程。这种设计使得在任何给定的时钟周期内,最多可以有五条指令处于不同的执行阶段,极大提高了 CPU 的效率和性能。

  • 取指 IF:从内存中读取指令。
    CPU 使用程序计数器(PC)指向的地址从内存中读取指令。读取后,PC 会更新到下一条指令的地址,为下一个周期准备。
  • 解码 ID:解析指令并准备必要的操作数。
    指令解码器(ID)解析取出的指令,确定需要执行的操作和操作数。这可能涉及到从指令中提取立即数、计算地址、确定寄存器编号等。
  • 执行 EX:执行计算或其他操作。
    执行阶段根据解码阶段的结果,进行相应的算术逻辑运算(ALU 操作)、分支决策或其他操作。此阶段可能需要使用到 ALU(算术逻辑单元)。
  • 访存 MEM:进行内存访问,如数据加载或存储。
    如果指令需要读取内存(如加载操作)或向内存写入数据(如存储操作),这一阶段将完成该操作。这一步是可选的,因为不是所有指令都需要访问内存。
  • 写回 WB:将执行结果写回寄存器。

将执行阶段的结果或访存阶段从内存读取的数据写回到指定的寄存器。这一步确保了指令的执行结果可以被后续指令使用。
这五个阶段共同构成了指令的完整执行周期,是现代 CPU 设计的基础。通过将指令执行分解为这些阶段,计算机能够以高效和有序的方式运行程序。每个阶段都由 CPU 的不同部件负责,使得计算机能够在任何给定时刻执行多条指令的不同阶段,这种设计是流水线处理的基础,极大提高了 CPU 的执行效率。

实现流程

创建 add-addi.s 并写入下面的内容

.global _start
_start:
    addi x29, x0, 100
    addi x30, x0, 200
    add x31, x30, x29

在汇编语言中,.global _start 和 _start: 的语句定义了程序的入口点。

  • .global _start:这条指令告诉链接器(linker),_start 标签是一个全局符号,可以被程序的其他部分或其他链接的文件访问。更重要的是,它标示 _start 为程序的入口点,即程序执行的起始位置。这对于操作系统(OS)来说非常关键,因为在程序被加载到内存并执行时,操作系统需要知道从哪里开始执行程序。

  • _start::这是一个标签,紧跟在它后面的是程序的入口点。在这个位置上编写的指令将会是程序执行的第一批指令。在一个裸机(bare-metal)环境或操作系统内核开发中,_start 是执行流程的起点,没有标准库或运行时环境的初始化过程。

综上所述,.global _start 和 _start: 一起定义了程序开始执行的地方,为操作系统提供了一个明确的起点来运行程序。如果不写的话会报错。

这段代码是使用 RISC-V 汇编语言编写的,它执行了一个非常简单的任务:计算两个数值的和,并将结果存储。具体来说,代码做了以下几件事情:

  • 将数字 100存储到寄存器 x29。
  • 将数字 200 存储到寄存器 x30。
  • 将寄存器 x29 和 x30 中的数值相加,将结果存储到寄存器 x31。

总结来说,这段代码简单地计算了 5 和 37 的和,然后将结果 42 存储到寄存器 x31 中。

将汇编转为二进制文件:

其中addi指令可能会被优化位li指令,此时将编译优化关闭,然后故意设置一些特定的值使得没有优化即可
在这里插入图片描述

字节码

地址低处才是opcode
在这里插入图片描述

在这里插入图片描述

        uint32_t code=static_cast<uint32_t>(recv_dram_addr[pc])
            |static_cast<uint32_t>(recv_dram_addr[pc+1]<<8)
            |static_cast<uint32_t>(recv_dram_addr[pc+2]<<16)
            |static_cast<uint32_t>(recv_dram_addr[pc+3]<<24);
            
        uint32_t opcode=(code)&0x7f;
        uint32_t rd=(code>>7)&0x1f;
        uint32_t funct3=(code>>12)&0x3;
        uint32_t rs1=(code>>15)&0x1f;
        uint32_t rs2=(code>>20)&0x1f;
        uint32_t funct7=(code>>25)&0x7f;
        uint32_t imm=(code>>20)&0xfff;

cpu.cpp

#include<bits/stdc++.h>

class DRAM
{   
    public:

    std::vector<uint8_t>code;

    public:
    
    DRAM(std::vector<uint8_t> paranment)
    {
        code=paranment;
    }
    
};

class CPU    //CPU 从存储器中读取指令,解析指令,然后执行指令
{
    std::array<uint64_t,32> registers={0};
    
    uint64_t pc;
    
    uint32_t ir;

    std::vector<uint8_t> recv_dram_addr;

    public:
    CPU(std::vector<uint8_t>positon)
    {
        pc=0;
        ir=0;
        recv_dram_addr=positon;
    }

    uint32_t get_code()
    {
        
        uint32_t code=static_cast<uint32_t>(recv_dram_addr[pc])
            |static_cast<uint32_t>(recv_dram_addr[pc+1]<<8)
            |static_cast<uint32_t>(recv_dram_addr[pc+2]<<16)
            |static_cast<uint32_t>(recv_dram_addr[pc+3]<<24);
        pc=pc+4;
        return code; //little-endian
    } 
    
    void decode_execute(uint32_t code)
    {
        uint32_t opcode=(code)&0x7f;
        uint32_t rd=(code>>7)&0x1f;
        uint32_t funct3=(code>>12)&0x3;
        uint32_t rs1=(code>>15)&0x1f;
        uint32_t rs2=(code>>20)&0x1f;
        uint32_t funct7=(code>>25)&0x7f;
        uint32_t imm=(code>>20)&0xfff;

        switch(opcode)
        {
            case 0x33: //add
            {
                registers[rd]=registers[rs1]+registers[rs2];
                break;
            }
            case 0x13: //addi
            {
                registers[rd]=registers[rs1]+imm;
                break;
            }
            default:
            {
                std::cout<<"invalid opcode";
                break;
            }
        }
    }

    const std::array<std::string,32>registers_name=
    {
        "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2",
        "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
        "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7",
        "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6",
    };
    
    void show_registers()
    {
        for(size_t i=0;i<32;i++)
        {
            std::cout<<registers[i];
        }
        
        for(size_t i=0;i<32;i+=4)
        {   
            std::cout<<std::hex
            <<" x"<<std::setw(1)<<std::setfill('0')<<i<<"("<<registers_name[i]<<") "<<registers[i]
            <<" x"<<std::setw(1)<<std::setfill('0')<<i+1<<"("<<registers_name[i+1]<<") "<<registers[i+1]
            <<" x"<<std::setw(1)<<std::setfill('0')<<i+2<<"("<<registers_name[i+2]<<") "<<registers[i+2]
            <<" x"<<std::setw(1)<<std::setfill('0')<<i+3<<"("<<registers_name[i+3]<<") "<<registers[i+3]
            <<std::endl;
        }
    }

    void run()
    {
        while(pc<recv_dram_addr.size())
        {
            uint32_t code=get_code();
            decode_execute(code);
        }
    }

};

int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        std::cout<<"please use like \n"<<"./nowfilename <filename>";
        return 0;
    }

    std::ifstream file(argv[1],std::ios::binary);

    std::vector<uint8_t> code(std::istreambuf_iterator<char>(file), {});
    std::cout<<code.size();
    DRAM dram(code);

    CPU cpu(dram.code);
    
    if(!file)
    {
        std::cout<<"can't open "<<argv[1]<<std::endl;
        return 0;
    }

    cpu.run();

    std::cout<<std::setw(50)<<std::setfill('~')<<"\n"<<std::endl;
    cpu.show_registers();
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/418297.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DOM 创建节点、添加节点和删除节点

创建元素节点 document.createElement(‘标签名’) 创建文本节点document.createTextNode ( 内容 ) 根据传入的标签名创建出一个空的元素对象创建出来的默认不显示&#xff0c;要成为别人的子元素才能显示&#xff0c;所以要结合appendChild使用 添加节点&#xff08;后面&am…

【数据结构与算法】回溯法解题20240301

这里写目录标题 一、78. 子集1、nums [1,2,3]为例把求子集抽象为树型结构2、回溯三部曲 二、90. 子集 II1、本题搜索的过程抽象成树形结构如下&#xff1a; 三、39. 组合总和1、回溯三部曲2、剪枝优化 四、LCR 082. 组合总和 II1、思路2、树形结构如图所示&#xff1a;3、回溯…

Kaggle 竞赛入门

打比赛不用写算法源码&#xff0c;应用的时候不用自己写。学习的时候可以自己写。 Kaggle 竞赛入门 认识 Kaggle 平台Kaggle竞赛知识前提结构化数据前提图像数据文本数据 Kaggle竞赛套路一个赛题的完整流程 认识 Kaggle 平台 Kaggle 官网 主页&#xff0c;比赛&#xff08;数据…

Git分布式版本控制系统——git学习准备工作

一、Git仓库介绍 开发者可以通过Git仓库来存储和管理文件代码&#xff0c;Git仓库分为两种&#xff1a; 本地仓库&#xff1a;开发人员自己电脑上的Git仓库 远程仓库&#xff1a;远程服务器上的Git仓库 仓库之间的运转如下图&#xff1a; commit&#xff1a;提交&#xff…

【HbuilderX】 uniapp实现 android申请权限 和 退出app返回桌面

目录 android申请权限&#xff1a; 监听用户是否开启权限或关闭权限&#xff1a; 退出app返回桌面&#xff1a; android申请权限&#xff1a; 首先在 manifest.json 内添加你所需要用到权限 添加权限插件 permission.js 一次就好1/权限插件 - Gitee.comhttps://gitee.co…

安装 docker 可视化工具 portainer

portainer 官方网站 https://www.portainer.io/ 一、portainer 介绍 Portainer是一款开源的容器管理平台&#xff0c;它提供了一个直观易用的Web界面&#xff0c;帮助用户管理Docker容器集群、镜像、卷等资源。Portainer 支持多种 Docker 环境&#xff0c;包括本地Docker、Sw…

k8s 存储卷详解与动静部署详解

目录 一、Volume 卷 1.1 卷类型 emptyDir &#xff1a; hostPath&#xff1a; persistentVolumeClaim (PVC)&#xff1a; configMap 和 secret&#xff1a; 二、 emptyDir存储卷 2.1 特点 2.2 用途&#xff1a; 2.3 示例 三、 hostPath存储卷 3.1 特点 3.2 用途 …

面试经典 150 题 ---- 轮转数组

面试经典 150 题 ---- 轮转数组 轮转数组方法一&#xff1a;使用额外的数组方法二&#xff1a;数组翻转 轮转数组 方法一&#xff1a;使用额外的数组 我们可以使用额外的数组来将每个元素放至正确的位置。用 n 表示数组的长度&#xff0c;我们遍历原数组&#xff0c;将原数组…

Jenkins笔记(一)

个人学习笔记&#xff08;整理不易&#xff0c;有帮助点个赞&#xff09; 笔记目录&#xff1a;学习笔记目录_pytest和unittest、airtest_weixin_42717928的博客-CSDN博客 目录 一&#xff1a;简单了解 二&#xff1a;什么是DevOps 三&#xff1a;安装Jenkins 四&#xff1…

OSCP靶场--DVR4

OSCP靶场–DVR4 考点(1.windows&#xff1a;路径遍历获取私钥getshell 2.ssh shell中runas切换用户) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap -sV -sC -p- 192.168.161.179 --min-rate 2000 Starting Nmap 7.92 ( https://nmap.org ) at 2024-02-29 07:14 EST…

nextjs13如何进行服务端渲染?

目录 一、创建一个新项目 二、动态获取后端数据进行服务端渲染出现的问题 三、nextjs13如何进行服务端渲染 nextjs13是nextjs的一个重大升级&#xff0c;一些原本在next12当中使用的API在nextjs13上使用十分不便。本文将着重介绍在nextjs13及以上版本当中进行服务端渲染的方…

一个基于增量同步数据库结构的工具 - Goose

嗨&#xff01;大家好&#xff0c;我是波罗学。本文是 Golang 三方库推荐第四篇&#xff0c;系列查看&#xff1a;Golang 三方库。 上篇文章&#xff0c;我讨论了数据库 schema 同步的两种方式&#xff1a;增量和差异。今天&#xff0c;推荐一个基于 Go 实现的增量同步数据库 …

图像处理基础——频域、时域

傅里叶分析不仅仅是一个数学工具&#xff0c;更是一种可以彻底颠覆一个人以前世界观的思维模式。 一、什么是频域 时域 时域是信号在时间轴随时间变化的总体概括&#xff1b;频域是把时域波形的表达式做傅立叶等变化得到复频域的表达式&#xff0c;所画出的波形就是频谱图&a…

Android Termux安装MySQL并实现公网远程连接本地数据库

文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前言 Android作为移动设备&#xff0c;尽管最初并非设计为服务器&#xff0c;但是随着技术的进步我们可以将Android配置为生产力工具&#xff0c;变成一个随身…

pix2pix-zero

pix2pix-zero&#xff1a;零样本图像到图像转换 论文介绍 Zero-shot Image-to-Image Translation 关注微信公众号: DeepGoAI 项目地址&#xff1a;https://github.com/pix2pixzero/pix2pix-zero 论文地址&#xff1a;https://arxiv.org/abs/2302.03027 本文介绍了一种名为…

live555学习 - 环境准备

环境&#xff1a;Ubuntu 16.04.7 ffmpeg-6.1 1 代码下载 最新版本&#xff1a; http://www.live555.com/liveMedia/public/ 历史版本下载 https://download.videolan.org/pub/contrib/live555/ 选择版本live.2023.01.19.tar.gz ps&#xff1a;没有选择新版本是新版本在…

深入理解计算机系统笔记

1.1 嵌套的数组 当我们创建数组的数组时&#xff0c;数组分配和引用的一般原则也是成立的。 例如&#xff0c;声明 int A[5][3]; 等价于下面的声明 typedef int row3_t[3]; row3_t A[5] 要访问多维数组的元素&#xff0c;编译器会以数组起始为基地址&#xff0c; (可能需…

MQTT协议解析:揭秘固定报头、可变报头与有效载荷的奥秘

MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;是一种轻量级的通讯协议&#xff0c;常用于远程传感器和控制设备的通讯。MQTT协议基于发布/订阅模式&#xff0c;为大量计算能力有限且工作在低带宽、不可靠网络环境中的设备…

【报名指南】2024年第九届数维杯数学建模挑战赛报名全流程图解

1.官方报名链接&#xff1a; 2024年第九届数维杯大学生数学建模挑战赛http://www.nmmcm.org.cn/match_detail/32 2.报名流程&#xff08;电脑与手机报名操作流程一致&#xff09; 参赛对象为在校专科生、本科生、研究生&#xff0c;每组参赛人数为1-3人&#xff08;指导老师不…

RDD简介与基础编程

1. 什么是RDD&#xff1f; RDD&#xff08;Resilient Distributed Dataset&#xff09;叫做弹性分布式数据集&#xff0c;是Spark中最基本的数据处理模型。在代码中&#xff0c;RDD是一个抽象类&#xff0c;他代表着一个弹性的、不可变的、可分区的、里面的元素可并行计算的集…