【跟我学RISC-V】(二)RISC-V的基础知识学习与汇编练习

news2024/12/29 9:59:20

写在前面:

这篇文章是跟我学RISC-V的第二期,是第一期的延续,第一期主要是带大家了解一下什么是RISC-V,是比较大体、宽泛的概念。这一期主要是讲一些基础知识,然后进行RISC-V汇编语言与c语言的编程。在第一期里我们搭建了好几个环境,你可以任意选一个你喜欢的RISC-V环境(能够执行RV机器码的平台),然后进行代码编写、编译、汇编、链接、运行、观察现象的这一过程。同样地,在这一篇里我也会拿x86的知识与RISC-V进行对比,这样也可以促进对两种指令集的学习。

一、RISC-V指令集的基础信息

1、RISC-V的通用寄存器

在第一期里我讲过,无论是RV32还是RV64,它的通用寄存器的数量都是32个。32真是一个好数字,刚好是2的5次方,实际上伯克利大学的研究员在设计RV的时候就非常讲究,这么做的好处是颇多的,也体现了RISC-V指令集的特色,这个我们在学习之后再讨论这个问题。

这32个寄存器分别是x0 x1 x2 ... x31这样去编号,但是就单纯的这样去写汇编的话,是非常不方便的,因此每一个寄存器又有自己的别名,这个别名就代表了这个寄存器的含义,以及函数调用时候的规则。也就是说,你在实际汇编编程的时候,既可以使用编号名,也可以使用别名,实际上使用别名更好,这样能够把寄存器的含义和在这里的作用绑定起来,别人看你的代码就知道你要做什么了。

寄存器名别名作用在函数调用过程中的维护
x0zero零寄存器,永远是0不需要维护
x1rareturn address在函数调用时存放返回地址caller
x2spstack pointer栈指针寄存器callee
x3gpglobal pointer全局寄存器(用于联接器松弛优化)经常使用基于gp的寻址模式来访问全局变量和静态数据,从而提高访问速度和效率caller/不需要保存
x4tpthread pointer线程寄存器(保存pcb的地址)与线程相关
x5t0temporaries临时寄存器,相当于c语言的临时用一下变量,callee可能会改变他们的值,caller根据实际情况看是否要保存caller
x6t1
x7t2
x8s0saved保存寄存器,在函数调用过程中必须保存的寄存器callee
x9s1
x10a0

argumeng参数寄存器,在函数调用过程中传递参数和返回值。同时,a0和a1又会在函数返回时的传递返回值。

caller
x11a1
x12a2
x13a3
x14a4
x15a5
x16a6
x17s7
x18

s2

saved保存寄存器,在函数调用过程中必须保存的寄存器callee
x19s3
x20s4
x21s5
x22s6
x23s7
x24s8
x25s9
x26s10
x27s11
x28t3临时寄存器,总共有7个临时寄存器caller
x29t4
x30t5
x31t6

上图描述了32个通用寄存器在编程中作用的约定,特别是c编程时候的默认调用约定。其实这些寄存器本身来说想咋用,但是如果这样的话,你写一套使用寄存器的风格,他也有一套自己的风格,这样的话我写的函数你就没法调用了,因为寄存器安排不同,这样就非常麻烦,根本不利于开发。于是RISC-V指令集在设计之处,就把这些寄存器的作用和安排都规定好了,别名也取好了。你不需要自己想一套函数调用的法则,你只需要遵守约定就好。这样,我写的函数,你也可以直接调用,而不需要考虑参数保存在哪个寄存器,因为这都已经规定好了。

比如说A函数调用的B函数,那么caller就是A函数,callee就是B函数。

这个寄存器的别名是很有用的,你不需要去记忆x寄存器名到别名的映射,你只需要记住别名中前缀的含义,你在汇编语言编程的时候就知道该使用哪一个寄存器来保存什么信息了。

如果你是第一次看见这个表格,你可能会感觉很抽象,不过只要编程练习一下,那么也就不抽象了。不过想进行RISC-V汇编语言的编程,光是知道通用寄存器还是不够的,你还得知道一些指令,所以我在这里先分析一下RV的寄存器和x86的不同之处。

我们都知道,x86是CISC,而RISC-V在RISC,这二者在寄存器的安排上就有非常大的不同。在x86架构中,有一种说法是“寄存器较弱的体系结构”,意思就是x86架构的通用寄存器的数量是非常少的。在实模式下,也就ax,bx,cx,dx,bp,sp,si,di, 就是搞来搞去就这么几个寄存器,并且比如bx,bp还要拿来作为offset偏移量寻址、cx还要拿来作为循环次数的保存、sp还是指向栈顶。总结来说就是能够程序员使用的通用寄存器的数量是在是太少太少了,我在大一的时候学习8086汇编就比较难受,寄存器满打满算就这么几个,一下子就用掉了,总感觉不太够用(你可以看看我之前的blog)。进入IA-32e的长模式感觉就好多了,通用寄存器又加上了r8 ~ r15 ,Intel终于是不挤牙膏了。而对于RISC-V而言,有整整32个通用寄存器,其中临时寄存器的数量就有7个,相比x86真是太爽了,随便拿一个就能临时保存一下我计算过程中的数据(你可以认为是打草稿)。对程序员来说,这太方便、舒服了。

还有一些区别是,在x86中(保护模式),函数调用时候参数非常依赖于内存。也就是参数都是保存在栈里的,保存在栈里问题倒是不大,就是读写内存的速度相比读写寄存器的数据差距太大了。在RISC-V有中专门的a系列的寄存器可以用户保存函数调用时候的参数,a0 ~ a7 整整8个寄存器呢!基本上来说,你一个函数的参数也很少会超过8个,当然如果超过了那还是要保存在栈里。总的来说,参数保存在寄存器里那速度是快了好几倍。(当然在Intel IA-32e中也是使用寄存器保存参数了,Intel在多年的迭代过程中算是学聪明了,而RISC-V是一开始就这么聪明,这就是后发的优势)

还有一点就是在RISC-V体系结构中,专门可以拿出一个寄存器tp, 来存放指向当前进程task_struct的指针,用于加快访问速度。而像x86这样的体系结构(通用寄存器数量不多)就只能在内存栈顶创建thread_info结构,通过计算偏移量间接的查找task_struct(也就是pcb)。也就是x86体系中每一次调用current去查找当前进程的pcb都需要访问多次内存,还要通过偏移量去找到task_struct的地址,这转来转去速度就会变慢。而RISC-V则直接通过tp寄存器直接就能找到pcb ,那访问寄存器的速度快很多,并且也不需要通过偏移量去寻址。这又是一大优势。

还有就是在x86中通用寄存器是可拆解的,比如IA-32e的rax寄存器是64位的,你可以拆解它。rax、eax、ax、ah、al, 从8位到16位到32位到64位,是可以拆分的。这都是Intel为了兼容性而设计的这么一套东西,因为早期的8088是8位的CPU,8086是16位的,老奔腾是32位的,酷睿又是64位的,它为了兼容就用这种方法,你即便进入了长模式,仍然可以使用al寄存器。但是在RISC-V中,RV32寄存器的大小就是32位的,你不可能说拆成hx和hl,没有这样的用法。所以说RISC-V指令集里面,你在使用load系列指令的时候,把一个不到32位的数值放置到32位的寄存器,会进行符号扩展或者零扩展,而不是直接把这个数直接放到寄存器里,这样会损失符号的。

在上面表格中有一个非常特殊的寄存器x0 zero寄存器,它类似于LInux里的/dev/null这个设备,你往里面写入任何数据都没用,再怎么写都是0,写入任何数都会被丢弃掉;如果你把这个寄存器的值给读出来,也还是0。你可能觉得这个寄存器好像没啥用啊,难道我就不能用立即数0去替代这个x0寄存器吗?实际上,这个x0 寄存器是非常有用的,有很多地方都会用到它。特别是伪指令在转换成汇编指令的时候,会经常用到zero寄存器,这个我会在后面讲到。


2、RISC-V的指令格式与特点

(一)RISC-V指令的特点

RISC-V的每条指令宽度都是32位(固定的4B),如果有c扩展使用指令压缩后会变成2B ,这个我们先不提。RV的指令格式如图所示分为6种。

  1. R-type:寄存器与寄存器算术指令,这里的R就是register寄存器的意思;
  2. I-type:寄存器与立即数算术指令或者加载指令;
  3. S-type:存储指令(和上面的加载指令刚好是反义词);
  4. B-type:条件跳转指令;
  5. U-type:长立即数操作指令;
  6. J-type:无条件跳转指令。

大家从图里可以清晰地看到:无论是什么类型的指令,确实都是4B的,并且共同点就是opcode操作码都在低7位。操作码这个概念相信学过计算机组成原理的都知道。

  • 足够的编码空间:使用7位操作码可以提供128种不同的可能值,这允许定义多种不同的基本操作和指令格式。对于一个旨在可扩展和支持多种扩展模块(如整数、浮点、原子操作等)的现代处理器架构来说,这一点非常重要。

  • 简化解码:RISC-V的指令长度固定为32位,这使得硬件能够更加简单和高效地解码指令。opcode位于指令的最低7位,硬件可以快速地读取这7位并确定如何进一步解析整个指令,这对提高指令解码速度和处理器整体性能至关重要。

  • 支持指令格式多样性:RISC-V使用不同的指令格式(如R、I、S、B、U、J格式)来支持不同类型的操作。这些格式有不同的字段组合和长度,opcode的7位设计帮助区分这些格式,并指导如何解析随后的字段。

  • 扩展性:RISC-V架构被设计为可扩展的,以支持新的功能和指令集扩展。7位opcode为未来可能的指令集扩展留出了空间,使得可以轻松加入新的操作码而不会干扰现有的指令解码逻辑。

在图中的rd就是目的寄存器,rs就是源寄存器。这个概念类似于x86中的rdi和rsi。大家可以总结出来,rd要么是1个要么是0个(有的指令是不用把值输出到目的寄存器的),rs最多支持两个,就是最多放两个源寄存器进来。无论是rd还是rs,它所占用的位数都是5位。这个事情我们之前提到过,因为寄存器总共就32个,2的5次方等于32,那么设置成5位这样,是非常巧妙的。

图片中还经常出现imme,这个就是立即数的意思,immediately.

还有就是占用3位的funct3与占用7位的funct7,就是说单纯的opcode还不足以确定这条指令究竟是哪一条指令。而是要opcode和funct功能码,这二者一起才能共同决定这条指令对应的具体的汇编指令手动反汇编的时候要用到。

实际上对照这张表格,你就很容易做到反汇编了。拿到一个4B的16进制数,你先把他转换成32位二进制数,然后对照opcode先确定是什么类型,确定好之后再根据具体的funct(如果存在)就能确定是哪一条指令了。确定指令之后,再通过rs,rd推出对应的寄存器号,有立即数的话把立即数也带进去。这样,一整条汇编指令就出现了。


(二)RISC-V每条指令详解

接下来,我要对每一条指令进行说明,大家耐心看一看吧。为了让现象更加明显,我使用c语言内联汇编的方法,把指令执行后的现象给展示出来,方便大家查看,那么大家如果能够跟着实践一遍这样更好。这里我还没有讲到c语言内联汇编的东西,不过有编程基础的应该能够看懂asm语句,我会在c代码后面讲述这么做的目的。

①加载指令

加载指令load就是把数据从内存加载到寄存器的这一过程。

指令格式数据位宽说明
lb rd,offset(rs)8把rs寄存器里的值指向的地址作为基地址,在偏移offset的地址处,加载1B的数据经过符号扩展之后放入到rd寄存器里面
lbu rd,offset(rs)8作为无符号加载,经过零扩展放入到寄存器rd
lh rd,offset(rs)16符号扩展加载2B
lhu rd,offset(rs)16零扩展加载2B
lw rd,offset(rs)32符号扩展加载4B
lwu rd,offset(rs)32零扩展加载4B
ld rd,offset(rs)64直接加载到rd寄存器里,不用扩展了
lui rd,imme64把立即数imme左移12位,然后符号扩展,再把结果写入到rd寄存器(这里的u是upper的意思,不是unsigned的意思)

RISC-V的指令都挺有规律的,l就是load加载的意思,代表数据从内存加载到寄存器;b是byte的意思,表示1个字节;h是halfword的意思,表示半字,2个字节;w表示word,一个字,4个字节;d表示double word表示双字,就是8个字节。跟在b/h/w/d后面的u是unsigned的意思,表示这是无符号数,不存在符号扩展;直接跟在l后面的是u ,表示这是upper,需要左移。记忆是比较容易的。

我们先进行一些区分:

#include <stdio.h>

int main(void)
{
    long rd = 0;

    char rs[3];
    rs[0] = 'a';
    rs[1] = 'b';
    rs[2] = 'c';

    asm volatile(
        "lb %0,1(%1)    \n\t"
        :"=r"(rd)
        :"r"(rs)
    );
    printf("%c\n",rd);
        return 0;
}

这是一段非常简单的c语言内联汇编的代码,意思就是把rs作为地址传入到寄存器里,再通过lb指令把rs指向的地址作为基地址,偏移了1B的地址里面取出来1B,把这个数据经过符号扩展放入寄存器里,在输出到rd变量。我们打印rd变量,确实是字符b.由于字符b是一个正数,因此符号扩展之后值就是本身。


lb.c

#include <stdio.h>


int main(void)
{
    long rd = 0;
    char rs = -20;

    asm volatile(
        "lb %0,0(%1)    \n\t"
        :"=r"(rd)
        :"r"(&rs)
    );
    printf("%d\n",rd);
        return 0;
}

lbu.c

#include <stdio.h>

int main(void)
{
    long rd = 0;
    char rs = -20;

    asm volatile(
        "lbu %0,0(%1)    \n\t"
        :"=r"(rd)
        :"r"(&rs)
    );
    printf("%d\n",rd);
        return 0;
}

可以看见,即便rs变量的值是-20,如果你使用的是lbu指令,那么就会进行零扩展,符号位就无效了。

从以上这个例子我们不难看出:符号扩展是计算机系统中把小字节转换成大字节的规则之一,它会将符号扩展到所需要的位数。

比如一个1字节的数0x8A,它的最高位也就是第7位是1,那么就需要进行符号扩展,高字节使用1来填充。如果扩展到64位,那么它的值就是0xffff ffff ffff ff8a

而零扩展的就是当成无符号来处理,既然是无符号数,高字节部分使用0来填充。

还有一点要注意的是,符号扩展是小字节往大字节扩展的时候进行的,而ld这一条指令,它本身就是从内存加载一个64位的数到寄存器,没有从小字节到大字节的过程,因此是不需要符号扩展的。


我再测试一下lui指令:

lui.c

#include <stdio.h>

int main(void)
{
    long rd = 0;

    asm volatile(
        "lui %0,0xff    \n\t"
        :"+r"(rd)
    );

    printf("rd = %lx\n",rd);
    return 0;
}

确实是左移了12位,1个16进制的0代表2进制的4位。你也许会很困惑,为什么要左移12位?为什么不是左移13位?为什么不是干脆不左移?

  1. 寻址能力的扩展

lui 指令将 20 位立即数置于寄存器的高 20 位。这样做的目的是允许程序能够引用位于较高地址范围内的内存地址或数据。考虑到 RISC-V 的寄存器是 32 位的,这种设计使得使用 lui 加上一个后续的加法或其他指令(比如 addi),可以访问整个 32 位地址空间。

  1. 高效的常数加载

通过将立即数左移 12 位,lui 指令可以快速地设置寄存器中的高位,这对设置大的常数值非常有效。如果需要加载的立即数不仅仅是高位,可以通过随后的 addi(Add Immediate)等指令来设置剩余的低 12 位。

  1. 指令编码的简化

在 RISC-V 的指令格式中,立即数字段(imm字段)经常被复用以适应不同类型的指令。lui 指令的设计使得指令的立即数字段直接对应于寄存器的高 20 位,从而简化了指令的解码和执行过程。

  1. 支持编译器优化

这种左移 12 位的设计也有助于编译器生成更优化的代码,尤其是在进行全局地址或大范围数据定位的时候。编译器可以更容易地生成用于初始化大数组或访问静态变量的代码。

总之就是,在RISC-V中一条指令总共就4B,能够分配给立即数imme的部分是很有限的,为了能够寻址到“高地址的地方”,于是很多指令都是具有upper的性质,即把其中的立即数左移12位,然后低于12位的部分你可以使用add系列的指令加上来,这样你的寻址能力大大提升,不用再受限于4B指令有限的imme位数能够表示的最大值了。此时你可能会觉得这也太麻烦了,我寻址一下难道还要把一个完成的地址给拆分成高位和低12位,这样组合成地址吗?实际上,你可以手动这样去组合、去拼凑,因为精简指令集本身就是多条指令的组合才能完成一个功能的,而不像x86那样,一条MOV指令打天下。当然,RISC-V的设计者为了程序员方便,它提供了大量的“伪指令”,你使用伪指令之后,伪指令会再拆分成真正的RISC-V汇编指令。有了这些伪指令,编程是不会太麻烦了。

在这个例子你,你可以使用一条伪指令叫做li,这个li就可以把一个立即数放进寄存器里。

li.c

#include <stdio.h>

int main(void)
{
    long rd = 0;

    asm volatile(
        "li %0,0xff    \n\t"
        :"+r"(rd)
    );

    printf("rd = %lx\n",rd);
    return 0;
}

不过你的记得,这是一条伪指令,它不是真正的RISC-V汇编指令,它是多条指令的组合。


②存储指令

存储指令就是加载指令的反义词 --把数据从寄存器移动到内存里。只是它更加简单了,没有符号扩展,直接移动数据即可。

指令位宽说明
sb rs2,offset(rs1)8把rs2寄存器的低8位的值存储到以rs1寄存器的值为基地址,offset为偏移量的地址处。
sh rs2,offset(rs1)16低16位
sw rs2,offset(rs1)32低32位
sd rs2,offset(rs1)64整个rs2寄存器的值

这个存储指令就是store,把寄存器的值往内存里存,对应的指令类型是S-type.

大家其实也发现了,这个store指令系列对于load来说,简单太多了,没有什么又是u啊又是i的,就是非常单纯的把寄存器值的一部分或者整个寄存器的值,放置到指定的内存地址里面去。这里不需要什么符号扩展、零扩展的。

#include <stdio.h>

int main(void)
{
    char rs[3] = {0};

    asm volatile(
        "li t0,'b'  \n\t"
        "sb t0,1(%0)    \n\t"
        :
        :"r"(rs)
        :"t0","memory"
    );
    printf("rs[1] = %c\n",rs[1]);
    return 0;
}

注意这里我们在扩展内联汇编里直接使用到了寄存器t0,因此在损坏部分要把它写进去,这样在asm嵌入的代码块执行结束的时候会把t0原先的值给恢复回去。


③算术指令

算术指令相对来说是比较重要、用到的场景也是比较多的。

指令指令格式说明
addadd rd,rs1,rs2把rs1寄存器的值和rs2寄存器的值相加,并把加法的结果放到rd寄存器里
addiadd rd,rs,imme把rs寄存器的值和立即数imme相加,把结果放到rd寄存器里
addwaddw rd,rs1,rs2截取rs1和rs2寄存器的低32位,相加后把结果进行符号扩展并放到rd寄存器里
addiwaddiw rd,rs,imme截取rs寄存器的低23位并与imme立即数相加,把结果进行符号扩展并放到rd寄存器里
subsub rd,rs1,rs2把rs1寄存器里的值减去rs2寄存器里的值,把结果放到rd寄存器里
subwsubw rd,rs1,rs2把rs1寄存器的低32位减去rs2寄存器的低32位,把结果放到rd寄存器里

这个看起来比较简单,实践起来也不复杂。

add.c

#include <stdio.h>

int main(void)
{
    long rs1 = 20;
    long rs2 = 30;
    long rd = 0;

    asm volatile(
        "add %0,%1,%2   \n\t"
        :"=r"(rd)
        :"r"(rs1),"r"(rs2)
    );

    printf("rd = %d\n",rd);
    return 0;
}

sub.c

#include <stdio.h>

int main(void)
{
    long rs1 = 20;
    long rs2 = 30;
    long rd = 0;

    asm volatile(
        "sub %0,%1,%2   \n\t"
        :"=r"(rd)
        :"r"(rs1),"r"(rs2)
    );

    printf("rd = %d\n",rd);
    return 0;
}

怎么样,这样的汇编风格写起来,相比x86来说是不是简单太多了。


到目前为止,我们已经学习了load加载指令和store存储指令,这些都是真汇编指令,但有的时候,这些指令用起来会不太方便,毕竟不像x86那样一个MOV就能够达到目的。因此,对伪指令的学习也是非常重要的。

程序计数器(Program Counter,PC)是用来指示下一条指令的地址。为了保证CPU能够正确地执行程序的指令代码。就会使用一套PC寄存器来存储这个地址,那么硬件上就只需要把PC指针指向的地址里面的数据当作是代码,然后由指令领取单元IFU把指令送入预译码器并进行预译码。在这里面我们可以看到这个PC寄存器的重要作用,不同指令集给出的PC实现方式也不太一样。比如在x86架构中是使用CS:IP这一对寄存器来指定代码段的位置。而在RISC-V中简化了这一过程,它单纯使用PC寄存器来指定下一条指令的地址。这个PC寄存器,我们不能去读它的位置,但是可以用别的指令去相对PC寄存器进行寻址。

auipc rd,imme

auipc指令就是这么一条,通过PC寄存器进行相对寻址的指令。它的英文名是Add upper immediate to PC.

其中有upper,也就是说这里面的imme立即数也是要左移12位的,这里和上面是一样的。因此它只能寻址到与4KB对齐的地址,如果一个地址是在4KB内存块的内部,则auipc寻址不到它。不过我们也有相应的伪指令可以很方便地去寻址。这个auipc指令,我们用到的其实不太多,程序员用到的更多的是基于它的伪指令,当然这些基于它的伪指令展开还是auipc.

伪指令指令组合说明


二、RISC-V指令集的编程理论


三、RISC-V指令集的编程实践

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

虚拟化技术 安装并配置ESXi服务器系统

安装并配置ESXi服务器系统 一、实验目的与要求 1.掌握创建VMware ESXi虚拟机 2.掌握安装VMware ESXi系统 3.掌握配置VMware ESXi系统的管理IP 4.掌握开启VMware ESXi的shell和ssh功能的方法 二、实验内容 1.安装VMware workstation 15或更高版本 2.创建VMware ESXi虚拟…

Python数据分析案例44——基于模态分解和深度学习的电负荷量预测(VMD+BiGRU+注意力)

案例背景 承接之前的案例&#xff0c;说要做模态分解加神经网络的模型的&#xff0c;前面纯神经网络的缝合模型参考数据分析案例41和数据分析案例42。 虽然我自己基于各种循环神经网络做时间序列的预测已经做烂了.....但是还是会有很多刚读研究生或者是别的领域过来的小白来问…

【算法与数据结构】哈希表

文章目录 引入哈希函数介绍便利店的例子Python3 中的哈希表C 中的哈希表 应用将散列表用于查找防止重复将散列表用作缓存 哈希冲突与解决链地址法开放寻址 总结参考资料写在最后 引入 假设你在一家便利店上班&#xff0c;你不熟悉每种商品的价格&#xff0c;在顾客需要买单是时…

WPF之XmlDataProvider使用

1&#xff0c;WPF XAML支持数据提供&#xff08;DataProvider&#xff09;&#xff0c;但其提供的数据只供查看不可进行修改&#xff0c;删除&#xff0c;添加等。 数据提供者都继承自System.Windows.DataSourceProvider类&#xff0c;目前&#xff0c;WPF只提供两个数据提供者…

一键自动化博客发布工具,chrome和firfox详细配置

blog-auto-publishing-tools博客自动发布工具现在已经可以同时支持chrome和firefox了。 很多小伙伴可能对于如何进行配置和启动不是很了解&#xff0c;今天带给大家一个详细的保姆教程&#xff0c;只需要跟着我的步骤一步来就可以无障碍启动了。 前提条件 前提条件当然是先下…

c++ 红黑树学习及简单实现

1. 了解红黑树 1.1. 概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个节点增加一个存储位表示节点的颜色&#xff0c;可以是红色&#xff0c;或是黑色&#xff0c;通过对任何一条从根到叶子的路径上各个节点的着色方式进行限制&#xff0c;红黑树确保没有一条路…

DIM层数据处理

一、了解DIM层 这个就是数仓开发的分层架构 我们现在是在DIM层&#xff0c;从ods表中数据进行加工处理&#xff0c;导入到dwd层&#xff0c;但是记住我们依然是在DIM层&#xff0c;而非是上面的ODS和DWD层。 二、处理维度表数据 ①先确认hive的配置 -- 开启动态分区方案 -- …

ubuntu20文件安装和卸载cuda11.6

搜索cuda 11.6 nvidia&#xff0c;进入官网https://developer.nvidia.com/cuda-11-6-0-download-archive 选择linux --> runfile 用安装包安装 wget https://developer.download.nvidia.com/compute/cuda/11.6.0/local_installers/cuda_11.6.0_510.39.01_linux.run sudo s…

飞书API(7):MySQL 入库通用版本

一、引入 在上一篇介绍了如何使用 pandas 处理飞书接口返回的数据&#xff0c;并将处理好的数据入库。最终的代码拓展性太差&#xff0c;本篇来探讨下如何使得上一篇的最终代码拓展性更好&#xff01;为什么上一篇的代码拓展性太差呢&#xff1f;我总结了几点&#xff1a; 列…

开源免费的网盘项目Cloudreve,基于Go云存储个人网盘系统源码(七牛、阿里云 OSS、腾讯云 COS、又拍云、OneDrive)

项目简介&#xff1a; 在现今的网盘服务中&#xff0c;用户经常遭遇限速和价格上涨的问题&#xff0c;这无疑增加了使用上的困扰。 为此&#xff0c;我今天要介绍一款开源且免费的网盘项目——Cloudreve。 这个项目是基于Go语言开发的云存储个人网盘系统&#xff0c;支持多种…

免费开源,无需 GPU,本地化部署大语言模型的对话系统

免费开源&#xff0c;无需 GPU&#xff0c;本地化部署大语言模型的对话系统 分类 编程技术 项目名: FreeAskInternet -- 本地化部署大语言模型的对话系统 Github 开源地址&#xff1a; https://github.com/nashsu/FreeAskInternet FreeAskInternet 是一个免费开源的工具&am…

「 网络安全常用术语解读 」通用漏洞报告框架CVRF详解

1. 背景 ICASI在推进多供应商协调漏洞披露方面处于领先地位&#xff0c;引入了通用漏洞报告框架&#xff08;Common Vulnerability Reporting Format&#xff0c;CVRF&#xff09;标准&#xff0c;制定了统一安全事件响应计划&#xff08;USIRP&#xff09;的原则&#xff0c;…

Python中无法pip的解决办法和pip的介绍

什么是pip&#xff1f; PIP是通用的Python包管理工具&#xff0c;提供了对 Python 包的查找、下载、安装、卸载、更新等功能。安装诸如Pygame、Pymysql、requests、Django等Python包时&#xff0c;都要用到pip。 注意&#xff1a;在Python3.4&#xff08;一说是3.6&#xff09…

Electron 对 SQLite 进行加密

上一篇讲了如何在 Electron使用 SQLite&#xff0c;如果 SQLite 中存有敏感数据&#xff0c;客户端采用明文存储风险很高&#xff0c;为了保护客户数据&#xff0c;就需要对数据进行加密&#xff0c;由于 electron 对代码并不加密&#xff0c;所以这里排除通过逆向工程进行数据…

ArcGIS软件:地图投影的认识、投影定制

这一篇博客介绍的主要是如何在ArcGIS软件中查看投影数据&#xff0c;如何定制投影。 1.查看地图坐标系、投影数据 首先我们打开COUNTIES.shp数据&#xff08;美国行政区划图&#xff09;&#xff0c;并点击鼠标右键&#xff0c;再点击数据框属性就可以得到以下的界面。 我们从…

深入理解分布式事务⑨ ---->MySQL 事务的实现原理 之 MySQL 中的XA 事务(基本原理、流程分析、事务语法、简单例子演示)详解

目录 MySQL 事务的实现原理 之 MySQL 中的XA 事务&#xff08;基本原理、流程分析、事务语法、简单例子演示&#xff09;详解MySQL 中的 XA 事务1、XA 事务的基本原理1-1&#xff1a;XA 事务模型图&#xff1a;1-2&#xff1a;XA 事务模型的两阶段提交操作&#xff1a;Prepare …

MLP手写数字识别(3)-使用tf.data.Dataset模块制作模型输入(tensorflow)

1、tensorflow版本查看 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())2、MNIST数据集下载与预处理 (train_images,train_labels),(test_images,test_labels) tf.keras.datasets.mnist.load_data()…

02_Java综述

目录 面向对象编程两种范式抽象OOP 三原则封装继承多态多态、封装与继承协同工作 面向对象编程 面向对象编程(Object-Oriented Programming&#xff0c;OOP)在Java中核心地位。几乎所有的Java程序至少在某种程度上都是面向对象的。OOP与java是密不可分的。下面说一下OOP的理论…

【已解决】VSCode 连接远程 Ubuntu :检测到 #include 错误。请更新 includePath。

文章目录 1. 环境声明2. 解决过程 1. 环境声明 即使是同一个报错&#xff0c;在不同的环境中&#xff0c;报错原因、解决方法都是不同的&#xff0c;本文只能解决跟我类似的问题&#xff0c;如果你发现你跟我遇到的问题不太一样&#xff0c;建议寻找其他解法。 必须要吐槽的是…

吴恩达2022机器学习专项课程C2(高级学习算法)W1(神经网络):2.1神经元与大脑

目录 神经网络1.初始动机*2.发展历史3.深度学习*4.应用历程 生物神经元1.基本功能2.神经元的互动方式3.信号传递与思维形成4.神经网络的形成 生物神经元简化1.生物神经元的结构2.信号传递过程3.生物学术语与人工神经网络 人工神经元*1.模型简化2.人工神经网络的构建3.计算和输入…