开发一个RISC-V上的操作系统(六)—— 中断(interrupt)和异常(exception)

目录

往期文章传送门

一、控制流 (Control Flow)和 Trap

二、Exceptions, Traps, and Interrupts 

Contained Trap

Requested Trap

Invisible Trap

Fatal Trap

异常和中断的异同

三、RISC-V的异常处理

mtvec(Machine Trap-Vector Base-Address)

mepc(Machine Exception Program Counter) 

mcause(Machine Cause) 

mstatus(Machine Status)

RISC-V Trap 处理流程

四、实战

Trap 上半部

Trap 下半部

上板测试


往期文章传送门

开发一个RISC-V上的操作系统(一)—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客

开发一个RISC-V上的操作系统(二)—— 系统引导程序(Bootloader)_Patarw_Li的博客-CSDN博客

开发一个RISC-V上的操作系统(三)—— 串口驱动程序(UART)_Patarw_Li的博客-CSDN博客

开发一个RISC-V上的操作系统(四)—— 内存管理_Patarw_Li的博客-CSDN博客

开发一个RISC-V上的操作系统(五)—— 协作式多任务_Patarw_Li的博客-CSDN博客

本节的代码在仓库的 04_Trap 目录下,仓库链接:riscv_os: 一个RISC-V上的简易操作系统

本文代码的运行调试会在前面开发的RISC-V处理器上进行,仓库链接:cpu_prj: 一个基于RISC-V指令集的CPU实现

一、控制流 (Control Flow)和 Trap

控制流(Control Flow) 

  • branch(条件分支指令),jump(无条件跳转指令),由程序正常自主控制。

异常控制流(Exceptional Control Flow,简称ECP) 

  • exception(异常),interrupt(中断),不在程序的控制范围内。

在 RISC-V 中把异常控制流统称为 Trap

二、Exceptions, Traps, and Interrupts 

在RISC-V官方手册的描述中:

  • 异常(exception)指的是当前hart中运行的某条指令发生了一个非正常情况(unusual condition),如除法指令的除数为0,非法指令等。
  • 中断(interrupt)则被描述为一个可能导致hart经历意外的控制转移外部异步事件
  • 陷阱(trap)指的是由异常或中断引起的,到陷阱处理程序(trap handler)的控制转移

这里我翻译的可能不太准确,下面是官方手册的英文版描述:

从在执行环境中运行的软件的角度来看,hart 在运行时遇到的 trap 可能有四种不同的类型,官方的描述如下:  

Contained Trap

这类 trap 对执行环境中的软件可见,并且也由软件处理,如 user 模式下使用 ECALL 进行控制转移。

Requested Trap

这类 trap 是一个同步异常(synchronous exception),它是对执行环境的显式调用,代表执行环境中的软件请求操作,如系统调用(system call)。

Invisible Trap

执行环境会透明地(transparently)处理此类 trap(这里的透明地的意思是执行环境看不见,或者是意识不到这类 trap 的执行),并在处理后正常恢复执行(execution resume)。如缺页异常(page faults)。

Fatal Trap

这类 trap 一般代表发生了致命性的错误,会导致执行环境终止执行。

下表展示了各类 trap 的特征:

异常和中断的异同

一般来说,异常为当前执行的指令发生了“不正常的情况”,如除法指令的除数为0、缺页异常等等,异常发生后会跳转执行异常处理程序,视情况决定是否跳回发生异常的指令继续执行,如缺页异常在执行完缺页处理程序后会跳转到原指令继续执行,而非法指令引起的异常则不会跳回原指令继续执行。

而中断则是在当前执行流中发生了某个外部事件,需要暂停当前执行流去处理这个外部事件,和异常一样,处理完后也要视情况决定是否返回原执行流继续执行,但是中断一般是跳回发生中断的下一条指令继续执行,除了某些多周期指令被中断后需要重新执行,如除法指令。

中断又分本地(Local)中断和全局(Global)中断,本地中断又分为软件中断和定时器中断 ,系统调用就是一个典型的软件中断;全局中断为外部中断,如 uart、鼠标键盘等外设中断。

但是,站在处理器处理的过程来说,中断与异常其实并没有区别。当中断与异常发生时,处理器的表现形式就是,暂停当前执行的程序,转而执行处理中断或异常的处理程序,处理完后视情况恢复执行之前被暂停的程序。 通常我们所理解的中断与异常都可以被统称为广义上的异常

广义上的异常被分为两种:

  1. 同步(synchronous)异常:执行某个程序流,能稳定复现的的异常,能比较精确的确定是那条指令引发的异常。(例如程序流里有一条非法指令,或者是ecall、ebreak指令。属于内因)
  2. 异步(asynchronous)异常:异常产生的原因与当前的程序流无关,与外部的中断事件有关。(由外部事件引起的,比如由定时器或者uart、鼠标等外设引起的中断。属于外因)

三、RISC-V的异常处理

RISC-V 定义的三种模式 User、Supervisor 和 Machine,均可发生异常。但是只有特权模式 Supervisor 和 Machine 才能处理异常,因为处理异常需要 CSR 寄存器。下面简单介绍一下 trap处理中比较重要的 csr 寄存器。

mtvec(Machine Trap-Vector Base-Address)

mtvec 的高30位 BASE 用来保存发生异常时处理器需要跳转到的地址,低2位 MODE 用于控制入口函数的地址配置方式,所以基地址 BASE 必须保证四字节对齐。

根据 MODE 的配置,入口函数的配置分为 Direct 和 Vectored 两种方式:

Direct:所有的 exception 和 interrupt 发生后都跳转到 BASE 指定的地址处:

Vectored:exception 处理方式同 Direct;但 interrupt 的入口地址以数组方式排列:

 

mepc(Machine Exception Program Counter) 

当 trap 发生时,pc 会被替换为 mtvec 设定的地址,同时 hart 会设置 mepc 为当前指令或者下一条指令的地址,当需要退出 trap 时可以调用特殊的 mret 指令,该指令会将 mepc 中的值恢复到 pc 中,从而实现返回的效果。当我们不想回到发生 trap 的地方执行的时候,我们可以在 trap 处理程序中修改 mepc 的值来达到改变 mret 返回地址的目的。

mcause(Machine Cause) 

当 trap 发生时,hart 会设置该寄存器通知我们 trap 发生的原因,最高位 Interrupt 为1时说明当前 trap 为 interrupt,否则为 exception。

剩下的 Exception Code 用于标识具体的 interrupt 或 exception 的种类, 如下表:

mstatus(Machine Status)

用于跟踪和控制 hart 的状态,xIE(Interrupt Enable)用于打开或关闭对应模式下的全局中断,因为 riscv 默认是不支持嵌套中断的,所以在 trap 发生时,hart 会自动将 xIE 置0来关闭全局中断。xPIE(Previous Interrupt Enable),当 trap 发生时用于保存 trap 发生之前的 xIE 值。

RISC-V Trap 处理流程

在介绍完相关的 csr 寄存器后,接下来介绍一下 RISC-V 处理器在 Trap 发生后的处理流程。

Trap 处理又分为上半部分( Top Half )和下半部分( Bottom Half ):

上半部分由硬件执行, 执行流程如下:

  1. 处理器停止执行当前的程序流。
  2. 将 Trap 原因记录到 mcause 寄存器中。
  3. 将 Trap 的返回地址保存到 mepc 寄存器中。
  4. 将 Trap 发生时的存储器访问地址或者指令编码保存到 mtval 寄存器中。
  5. 更新 mstatus 状态寄存器(关闭全局中断位xIE,保存当前全局中断状态xIE到xPIE)。
  6. 最后 PC 跳转到 mtvec 寄存器定义的 BASE 地址开始执行( mtvec 寄存器的设置在 Trap 初始化步骤中完成)。

下半部分由软件执行,执行的程序基地址为 mtvec 寄存器中设置的 BASE 值,执行流程如下:

  1. 保存当前任务的上下文。
  2. 根据 mcause 中不同的 Trap 原因,跳转到不同的 Trap Handler 处理程序中执行。
  3. 从 Trap Handler 函数返回,mepc的值可能会有所调整。
  4. 恢复之前任务的上下文。
  5. 使用 mret 指令返回到 Trap 之前的状态。

四、实战

代码的 gitee 仓库如下:

cpu_prj: 一个基于RISC-V指令集的CPU实现

riscv_os: 一个RISC-V上的简易操作系统

Trap 上半部

在 cpu_prj 仓库的 FPGA/rtl/core/ 目录下的 clint.v 文件中,模块的输入输出引脚定义如下:

input    wire                    clk                 ,
input    wire                    rst_n               ,input    wire[`INST_DATA_BUS]    ins_i               ,     
input    wire[`INST_ADDR_BUS]    ins_addr_i          , // from ex
input    wire                    jump_flag_i         ,
input    wire[`INST_ADDR_BUS]    jump_addr_i         ,
input    wire                    div_req_i           , // 除法操作执行请求信号
input    wire                    div_busy_i          , // 除法操作忙信号// csr读写信号
output   reg                     wr_en_o             , // csr write enable
output   reg [`INST_ADDR_BUS]    wr_addr_o           , // csr write address
output   reg [`INST_REG_DATA]    wr_data_o           , // csr write data
output   reg [`INST_ADDR_BUS]    rd_addr_o           , // csr read address
input    wire[`INST_REG_DATA]    rd_data_i           , // csr read data// from csr 
input    wire[`INST_REG_DATA]    csr_mtvec           , // mtvec寄存器
input    wire[`INST_REG_DATA]    csr_mepc            , // mepc寄存器
input    wire[`INST_REG_DATA]    csr_mstatus         , // mstatus寄存器input    wire[`INT_BUS]          int_flag_i          , // 异步中断信号
output   wire                    clint_busy_o        , // 中断忙信号
output   reg [`INST_ADDR_BUS]    int_addr_o          , // 中断入口地址
output   reg                     int_assert_o          // 中断标志

中断仲裁逻辑代码如下, 首先判断 Trap 类型,是同步还是异步,还是特殊的 mret 指令,mret 指令用于返回到 mepc 中设置的地址,并且还原 mstatus的 内容。

    // 中断仲裁逻辑always @ (*) beginif (ins_i == `INS_ECALL || ins_i == `INS_EBREAK) begin// 如果执行阶段的指令为除法指令或者跳转指令,则先不处理同步中断if (div_req_i != 1'b1 && jump_flag_i != 1'b1) beginint_state = INT_SYNC_ASSERT;end else beginint_state = INT_IDLE;endend else if (int_flag_i != `INT_NONE && csr_mstatus[3] == 1'b1) beginint_state = INT_ASYNC_ASSERT;end else if (ins_i == `INS_MRET) beginint_state = INT_MRET;end else beginint_state = INT_IDLE;endend

根据不同的 Trap 类型,我们对 CSR 寄存器写不同的内容,如果是同步或异步 Trap ,我们要写 mepc、mcause、mstatus 寄存器;如果是 mret 返回指令,则只需要写 mstatus 寄存器。

   // 写CSR寄存器状态切换always @ (posedge clk or negedge rst_n) beginif (!rst_n) begincsr_state <= CSR_IDLE;cause <= `ZERO_WORD;ins_addr <= `ZERO_WORD;end else begincase (csr_state)CSR_IDLE: begin// 同步中断if (int_state == INT_SYNC_ASSERT) begincsr_state <= CSR_MEPC;// 在中断处理函数里会将中断返回地址加4ins_addr <= ins_addr_i;case (ins_i)`INS_ECALL: begincause <= 32'd11;end`INS_EBREAK: begincause <= 32'd3;enddefault: begincause <= 32'd10;endendcaseend // 异步中断else if (int_state == INT_ASYNC_ASSERT) begin// timer中断if (int_flag_i == `INT_TIMER) begincause <= 32'h80000004;end// uart中断,无总裁,目前这部分只用于测试else if (int_flag_i == `INT_UART_REV) begincause <= 32'h8000000b;endelse begincause <= 32'h0000000a;endcsr_state <= CSR_MEPC;if (jump_flag_i == 1'b1) beginins_addr <= jump_addr_i;end// 异步中断可以中断除法指令的执行,中断处理完再重新执行除法指令if (div_req_i == 1'b1 || div_busy_i == 1'b1) beginins_addr <= div_ins_addr;end else beginins_addr <= ins_addr_i;endend // 中断返回else if (int_state == INT_MRET) begincsr_state <= CSR_MSTATUS_MRET;endendCSR_MEPC: begincsr_state <= CSR_MSTATUS;endCSR_MSTATUS: begincsr_state <= CSR_MCAUSE;endCSR_MCAUSE: begincsr_state <= CSR_IDLE;endCSR_MSTATUS_MRET: begincsr_state <= CSR_IDLE;enddefault: begincsr_state <= CSR_IDLE;endendcaseendend// 发出中断信号前,先写几个CSR寄存器always @ (posedge clk or negedge rst_n) beginif (!rst_n) beginwr_en_o <= 1'b0;wr_addr_o <= `ZERO_WORD;wr_data_o <= `ZERO_WORD;end else begincase (csr_state)// 将mepc寄存器的值设为当前指令地址CSR_MEPC: beginwr_en_o <= 1'b1;wr_addr_o <= {20'h0, `CSR_MEPC};wr_data_o <= ins_addr;end// 写中断产生的原因CSR_MCAUSE: beginwr_en_o <= 1'b1;wr_addr_o <= {20'h0, `CSR_MCAUSE};wr_data_o <= cause;end// 关闭全局中断CSR_MSTATUS: beginwr_en_o <= 1'b1;wr_addr_o <= {20'h0, `CSR_MSTATUS};wr_data_o <= {csr_mstatus[31:4], 1'b0, csr_mstatus[2:0]};end// 中断返回CSR_MSTATUS_MRET: beginwr_en_o <= 1'b1;wr_addr_o <= {20'h0, `CSR_MSTATUS};wr_data_o <= {csr_mstatus[31:4], csr_mstatus[7], csr_mstatus[2:0]};enddefault: beginwr_en_o <= 1'b0;wr_addr_o <= `ZERO_WORD;wr_data_o <= `ZERO_WORD;endendcaseendend

最后再发出中断发生信号和跳转地址给EX_UNIT,来将PC的值改变为跳转地址的值,如果是同步或异步 trap ,则跳转地址为 mtvec 中的值;如果是 mret 指令,跳转地址则为 mepc 中的值。

    // 发出中断信号给ex模块always @ (posedge clk or negedge rst_n) beginif (!rst_n) beginint_assert_o <= 1'b0;int_addr_o <= `ZERO_WORD;end else begincase (csr_state)// 发出中断进入信号.写完mcause寄存器才能发CSR_MCAUSE: beginint_assert_o <= 1'b1;int_addr_o <= csr_mtvec;end// 发出中断返回信号CSR_MSTATUS_MRET: beginint_assert_o <= 1'b1;int_addr_o <= csr_mepc;enddefault: beginint_assert_o <= 1'b0;int_addr_o <= `ZERO_WORD;endendcaseendend

Trap 下半部

源码在 riscv_os 仓库的 04_Traps 目录下,主要是 entry.S 和 trap.c 两个文件。

在 entry.S 汇编文件中定义了 trap_vector 函数,mtvec 寄存器中的值要保存为 trap_vector 函数的地址。大致执行流程为先保存当前任务的上下文,然后调用 trap_handler 函数,并将 mepc 、mcause 寄存器的值作为参数传给它,trap_handler 函数将修改后的mepc的值返回,然后存入 mepc 寄存器中,最后在恢复之前任务的上下文,再使用 mret 指令返回。

trap_vector:# save context(registers).csrrw   t6, mscratch, t6        # swap t6 and mscratchreg_save t6# Save the actual t6 register, which we swapped into# mscratchmv      t5, t6          # t5 points to the context of current taskcsrr    t6, mscratch    # read t6 back from mscratchsw      t6, 120(t5)     # save t6 with t5 as base# Restore the context pointer into mscratchcsrw    mscratch, t5# call the C trap handler in trap.ccsrr    a0, mepccsrr    a1, mcausecall    trap_handler# trap_handler will return the return address via a0.csrw    mepc, a0# restore context(registers).csrr    t6, mscratchreg_restore t6# return to whatever we were doing before trap.mret

在 trap.c 文件中定义了 trap_init() 和 trap_handler() 函数,第一个用于初始化 mtvec 寄存器的内容为 trap_vector 函数的地址,并且开启 mstatus 寄存器中 machine 模式下的全局中断;第二个用于根据不同的 mcause 来进行相应的处理。

void trap_init()
{/** set the trap-vector base-address for machine-mode*/w_mtvec((reg_t)trap_vector);w_mstatus((reg_t)0x88);
}reg_t trap_handler(reg_t epc, reg_t cause)
{reg_t return_pc = epc;reg_t cause_code = cause & 0xfff;if(cause & 0x80000000) {/* Asynchronous trap - interrupt */switch(cause_code) {case 3:uart_puts("software interruption!\n");break;case 4:uart_puts("user timer interruption!\n");break;case 11:uart_puts("external interruption!\n");uart_int_handler();break;default:uart_puts("unknown async exception!\n");break;}} else {/* Synchronous trap - exception */printf("cause = %d\n", cause);uart_puts("OOPS! What can I do!\n");return_pc = return_pc + 4;}//printf("return_pc = %d\n", return_pc);return return_pc;
}

上板测试

使用 uart 的串口接收中断来模拟异步 trap,下面是 uart 在接收到数据后进行的中断处理函数。

void uart_int_handler(void)
{char begin_char = uart_getc();if (begin_char == 'e') {uart_puts("Input your command, and end with 'Enter':\n");while (1) {char c = uart_getc();uart_putc(c);if (c == '\n') {break;}}uart_puts("Received your command!\n");} else {uart_puts("Please send 'e' first, then enter your command\n");}
}

在 user.c 中定义了一个 ebreak 函数来模拟同步 trap 。

下面将程序 make 编译构建后,烧录到板子上,打开串口调试工具,可以看到如下输出,说明 ebreak 指令成功执行,进入 trap 处理程序执行后返回原来的任务继续执行:

接下来测试 uart 的异步 trap,首先发送字符 e ,可以看到 uart 中断处理程序成功执行,等待用户的进一步输入:

然后输入想要打印的指令,最后一定要加一个回车!!(因为程序中是通过回车来判断结尾的)

点击发送后可以看到指令成功被打印,打印之后程序继续返回原来的 task 继续执行:

至此,trap 的上板实验结束,有了 trap 里面的异常和中断,我们可以进一步实现操作系统里更加高级的功能——定时器中断和系统调用!!

接下来我也会继续更新对应文章,遇到问题欢迎加群 892873718 交流~

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

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

相关文章

内生安全构建数据存储

一、数据安全成为防护核心&#xff0c;存储安全防护不容有失 1、数据作为企业的核心资产亟需重点保护&#xff0c;数据安全已成网络空间防护核心 2、国家高度重视关键信息基础设施的数据安全&#xff0c;存储安全已成为审核重点 二、存储安全是数据安全的关键一环&#xff0c;应…

AIGC技术揭秘:探索火热背后的原因与案例

文章目录 什么是AIGC技术&#xff1f;为何AIGC技术如此火热&#xff1f;1. 提高效率与创造力的完美结合2. 拓展应用领域&#xff0c;创造商业价值3. 推动技术创新和发展 AIGC技术案例解析1. 艺术创作&#xff1a;生成独特的艺术作品2. 内容创作&#xff1a;实时生成各类内容3. …

SolidWorks不能使用选择如允许此选择将生成有冲突的前后关系

SolidWorks不能使用选择如允许此选择将生成有冲突的前后关系 1 SolidWorks不能使用选择如允许此选择将生成有冲突的前后关系 1 SolidWorks不能使用选择如允许此选择将生成有冲突的前后关系 https://www.swrjzxw.com/1556.html SolidWorks装配体时 显示 不能使用选择如允许此选…

哪些CRM的报价公开且透明?

企业在选型时&#xff0c;会发现很多品牌的CRM系统价格并不透明&#xff0c;往往都是需要跟产品顾问沟通后才能了解。下面推荐一款价格实在的CRM系统&#xff0c;所有报价公开透明&#xff0c;那就是Zoho CRM。 Zoho CRM是什么&#xff1f; Zoho CRM是一款在线CRM软件&#x…

分布式测试插件 pytest-xdist 使用详解

目录 使用背景&#xff1a; 使用前提&#xff1a; 使用快速入门&#xff1a; 使用小结&#xff1a; 使用背景&#xff1a; 大型测试套件&#xff1a;当你的测试套件非常庞大&#xff0c;包含了大量的测试用例时&#xff0c;pytest-xdist可以通过并行执行来加速整体的测试过…

如何使用ChatGPT设计LOGO,只需知道品牌名字就能完成傻瓜式操作

​独特且引人注目的LOGO对于引导用户/消费者快速识别并与你建立联系至关重要。然而&#xff0c;聘请专业的设计师来创建个性化LOGO可能非常昂贵。这里可以使用使用ChatGPT。[1] 你只需要&#xff1a; 准备好公司名称&#xff1b; 能用ChatGPT&#xff0c;用来给BingChat喂log…

学习总结(TAT)

好久都没交总结了&#xff0c;今天把之前的思路和错误整理了一下&#xff1a; 在服务器和客户端两侧&#xff0c;不可以同时先初始化获取输入流&#xff0c;否则会造成堵塞&#xff0c;同时为这位作者大大打call&#xff1a; (3条消息) 关于Java Socket和创建输入输出流的几点…

一、安全世界观

文章目录 1、 Web安全简史1.1 中国黑客简史1.2 黑客技术的发展历程1.3 web安全的兴起 2、黑帽子、白帽子3、安全的本质4、安全三要素5、如何实施安全评估5.1 资产等级划分5.2 威胁分析5.3 风险分析5.4 设计安全方案 6、白帽子兵法6.1 Secure By Default6.2 纵深防御原则6.3 数据…

学习51单片机怎么开始?

学习的过程不总是先打好基础&#xff0c;然后再盖上层建筑&#xff0c;尤其是实践性的、工程性很强的东西。如果你一定要先全面打好基础&#xff0c;再学习单片机&#xff0c;我觉得你一定学不好&#xff0c;因为你的基础永远打不好&#xff0c;因为基础太庞大了&#xff0c;基…

Oracle 知识篇+会话级全局临时表在不同连接模式中的表现

标签&#xff1a;会话级临时表、全局临时表、幻读释义&#xff1a;Oracle 全局临时表又叫GTT ★ 结论 ✔ 专用服务器模式&#xff1a;不同应用会话只能访问自己的数据 ✔ 共享服务器模式&#xff1a;不同应用会话只能访问自己的数据 ✔ 数据库驻留连接池模式&#xff1a;不同应…

探索数据之美:初步学习 Python 柱状图绘制

文章目录 一 基础柱状图1.1 创建简单柱状图1.2 反转x和y轴1.3 数值标签在右侧1.4 演示结果 二 基础时间线柱状图2.1 创建时间线2.2 时间线主题设置取值表2.3 演示结果 三 GDP动态柱状图绘制3.1 需求分析3.2 数据文件内容3.3 列表排序方法3.4 参考代码3.5 运行结果 一 基础柱状图…

Nodejs安装及环境变量配置(修改全局安装依赖工具包和缓存文件夹及npm镜像源)

本机环境&#xff1a;win11家庭中文版 一、官网下载 二、安装 三、查看nodejs及npm版本号 1、查看node版本号 node -v 2、查看NPM版本号&#xff08;安装nodejs时已自动安装npm&#xff09; npm -v 四、配置npm全局下载工具包和缓存目录 1、查看安装目录 在本目录下创建no…

瓴羊发布All in One 产品,零售SaaS的尽头是DaaS?

“打破烟囱、化繁为简&#xff0c;让丰富的能力、数据和智能All in One”&#xff0c;这是瓴羊新发布的产品瓴羊One承担的使命&#xff0c;也意味着瓴羊DaaS事业迈入了一个新阶段。 成立伊始&#xff0c;瓴羊就打出了“Not SaaS&#xff0c;But DaaS”旗号&#xff0c;将自己的…

小程序裂变怎么做?小程序裂变机制有哪些?

做了小程序就等于“生意上门”&#xff1f;其实并不是这样。小程序跟流量平台较为明显的区别就在于小程序并非“自带流量”&#xff0c;而是需要企业利用自己的营销推广能力来建立引流渠道&#xff0c;从而完成用户的拉新和留存、转化。因此&#xff0c;想要用小程序来增加自己…

[虚幻引擎] UE DTBase64 插件说明 使用蓝图对字符串或文件进行Base64加密解密

本插件可以在虚幻引擎中使用蓝图对字符串&#xff0c;字节数组&#xff0c;文件进行Base64的加密和解密。 目录 1. 节点说明 String To Base64 Base64 To String Binary To Base64 Base64 To Binary File To Base64 Base64 To File 2. 案例演示 3. 插件下载 1. 节点说…

自定义element-plus的弹框样式

项目中弹框使用频繁,需要统一样式风格,此组件可以自定义弹框的头部样式和内容 一、文件结构如下: 二、自定义myDialog组件 需求&#xff1a; 1.自定义弹框头部背景样式和文字 2.自定义弹框内容 3.基本业务流程框架 components/myDialog/index.vue完整代码&#xff1a; &…

采用pycharm在虚拟环境使用pyinstaller打包python程序

一年多以前&#xff0c;我写过一篇博客描述了如何虚拟环境打包&#xff0c;这一次有所不同&#xff0c;直接用IDE pycharm构成虚拟环境并运行pyinstaller打包 之前的博文&#xff1a; 虚拟环境venu使用pyinstaller打包python程序_伊玛目的门徒的博客-CSDN博客 第一步&#xf…

山西电力市场日前价格预测【2023-08-12】

日前价格预测 预测明日&#xff08;2023-08-12&#xff09;山西电力市场全天平均日前电价为330.52元/MWh。其中&#xff0c;最高日前电价为387.00元/MWh&#xff0c;预计出现在19: 45。最低日前电价为278.05元/MWh&#xff0c;预计出现在13: 00。 价差方向预测 1&#xff1a; 实…

伯俊ERP对接打通金蝶云星空表头表体组合查询接口与采购订单新增接口

伯俊ERP对接打通金蝶云星空表头表体组合查询接口与采购订单新增接口 数据源平台:伯俊ERP 伯俊科技&#xff0c;依托在企业信息化建设方面的领先技术与实践积累&#xff0c;致力于帮助企业实现全渠道一盘货。伯俊提供数字经营的咨询与系统实施&#xff0c;助力企业信息化升级、加…

vue3使用pinia和pinia-plugin-persist做持久化存储

插件和版本 1、安装依赖 npm i pinia // 安装 pinia npm i pinia-plugin-persist // 安装持久化存储插件2、main.js引入 import App from ./App.vue const app createApp(App)//pinia import { createPinia } from pinia import piniaPersist from pinia-plugin-persist //持…