开发一个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、鼠标键盘等外设中断。

但是,站在处理器处理的过程来说,中断与异常其实并没有区别。当中断与异常发生时,处理器的表现形式就是,暂停当前执行的程序,转而执行处理中断或异常的处理程序,处理完后视情况恢复执行之前被暂停的程序。 通常我们所理解的中断与异常都可以被统称为广义上的异常。
广义上的异常被分为两种:
- 同步(synchronous)异常:执行某个程序流,能稳定复现的的异常,能比较精确的确定是那条指令引发的异常。(例如程序流里有一条非法指令,或者是ecall、ebreak指令。属于内因)
- 异步(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 ):

上半部分由硬件执行, 执行流程如下:
- 处理器停止执行当前的程序流。
- 将 Trap 原因记录到 mcause 寄存器中。
- 将 Trap 的返回地址保存到 mepc 寄存器中。
- 将 Trap 发生时的存储器访问地址或者指令编码保存到 mtval 寄存器中。
- 更新 mstatus 状态寄存器(关闭全局中断位xIE,保存当前全局中断状态xIE到xPIE)。
- 最后 PC 跳转到 mtvec 寄存器定义的 BASE 地址开始执行( mtvec 寄存器的设置在 Trap 初始化步骤中完成)。
下半部分由软件执行,执行的程序基地址为 mtvec 寄存器中设置的 BASE 值,执行流程如下:
- 保存当前任务的上下文。
- 根据 mcause 中不同的 Trap 原因,跳转到不同的 Trap Handler 处理程序中执行。
- 从 Trap Handler 函数返回,mepc的值可能会有所调整。
- 恢复之前任务的上下文。
- 使用 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 交流~
相关文章:
开发一个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 Ba…...
心跳跟随的心形灯(STM32(HAL)+WS2812+MAX30102)
文章目录 前言介绍系统框架原项目地址本项目开发开源地址硬件PCB软件功能 详细内容硬件外壳制作WS2812级联及控制MAX30102血氧传感器0.96OLEDFreeRTOS 效果视频总结 前言 在好几年前,我好像就看到了焊武帝 jiripraus在纪念结婚五周年时,制作的一个心跳跟…...
5. 服务发现
当主机较少时,在抓取配置中手动列出它们的IP地址和端口是常见的做法,但不适用于较大规模的集群。尤其不适用使用容器和基于云的实例的动态集群,这些实例经常会变化、创建或销毁的情况。 Prometheus通过使用服务发现解决了这个问题࿱…...
算法备案背后的原因:确保技术透明度与公正
随着现代技术的发展,算法逐渐渗透到我们日常生活的各个方面,从金融决策到个性化的商品推荐,再到医疗诊断和司法系统。然而,这种无所不在的应用也带来了一系列的社会和伦理问题,尤其是在算法的透明度和公正性上。这正是…...
Linux centos 常用命令 【持续更新】
一、查看文件信息 indoe和目录项 # df命令查看每个硬盘分区的inode总数和已经使用的数量 df -i# 查看inode的大学 xfs_growfs /dev/sda1|grep "isize"# 查看文件的indoe号码 ls -istat查看文件信息 # 文件的详细信息 stat anaconda-ks.cfg # -t参数是在一行内输出…...
《使用 VMware 在 Windows 上搭建 Linux 系统的完整指南》
《使用 VMware 在 Windows 上搭建 Linux 系统的完整指南》 1、准备工作1.1 安装 VMware 软件1.2 下载 Linux 发行版镜像文件1.3 安装SSH工具 2、创建新的虚拟机2.1 VMware页面2.2 打开VMware页面并点击创建新的虚拟机,选择自定义2.3 选择系统兼容性,默认…...
大数据Flink(六十):Flink 数据流和分层 API介绍
文章目录 Flink 数据流和分层 API介绍 一、Flink 数据流...
软件测试面试题——如何测试App性能?
为什么要做App性能测试? 如果APP总是出现卡顿或网络延迟的情况,降低了用户的好感,用户可能会抛弃该App,换同类型的其他应用。如果APP的性能较好,用户体验高,使用起来丝滑顺畅,那该应用的用户粘…...
玩转C链表
链表是C语言编程中常用的数据结构,比如我们要建一个整数链表,一般可能这么定义: struct int_node {int val;struct int_node *next;}; 为了实现链表的插入、删除、遍历等功能,另外要再实现一系列函数,比如:…...
MySQL表的基础的增删改查
增(insert into) 插入所有列的数据 不写具体列名要确保字段都对应正确 -- 假设你有一个名为 "employees" 的表,有多个列 INSERT INTO employees VALUES (101, Alice, Manager, 50000);插入指定列的数据 -- 假设你有一个名为 "students" 的表&…...
数字化车间
一、数字化车间概述 数字化车间是以现代化信息、网络、数据库、自动识别等技术为基础,通过智能化、数字化、MES系统信息化等手段融合建设的数字化生产车间,精细地管理生产资源、生产设备和生产过程。随着工业4.0概念的提出,未来的工业和制造…...
基础堆排序
目录 基础堆排序 一、概念及其介绍 二、适用说明 三、过程图示 基础堆排序...
ISC 2023 | 赛宁网安验证评估 重磅发布
8月9日-10日,第十一届互联网安全大会(简称ISC 2023)在北京国家会议中心隆重举办。作为本次大会的战略合作伙伴(最高级别),赛宁网安主办 “安全验证评估论坛”,邀请邬江兴院士与业界专家共同…...
浅谈AI浪潮下的视频大数据发展趋势与应用
视频大数据的发展趋势是多样化和个性化的。随着科技的不断进步,人们对于视频内容的需求也在不断变化。从传统的电视节目到现在的短视频、直播、VR等多种形式,视频内容已经不再是单一的娱乐方式,更是涉及到教育、医疗、商业等各个领域。 为了满…...
github 无语的问题,Host does not existfatal: Could not read from remote repository.
Unable to open connection: Host does not existfatal: Could not read from remote repository. image.png image.png image.png Please make sure you have the correct access rights and the repository exists. 如果github desktop和git pull 和git clone全部都出问题了&…...
机器学习基础之《特征工程(4)—特征降维—案例》
一、探究用户对物品类别的喜好细分 1、找到用户和物品类别的关系 数据如下: (1)order_products__prior.csv:订单与商品信息 字段:order_id,product_id,add_to_cart_order,reordered…...
docker 删除镜像文件
docker 容器里面太多镜像,D盘满了 四 查看和移除镜像 1 查看镜像 docker images 2 移除镜像命令 docker rmi 镜像名称 # 只输入前四位即可 五 实际有效操作 清除所有不使用的资源 docker system prune 这个命令将会删除所有不使用的镜像、容器和数据卷等资…...
ArcGIS Pro 基础安装与配置介绍
ArcGIS Pro ArcGIS Pro作为ESRI面向新时代的GIS产品,它在原有的ArcGIS平台上继承了传统桌面软件(ArcMap)的强大的数据管理、制图、空间分析等能力,还具有其独有的特色功能,例如二三维融合、大数据、矢量切片制作及发布…...
剑指 Offer 13. 机器人的运动范围
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如&am…...
技术应用:Docker安全性的最佳实验|聊聊工程化Docker
🔥 技术相关:《技术应用》 ⛺️ I Love you, like a fire! 文章目录 首先,使用Docker Hub控制访问其次,保护密钥写在最后 不可否认,能生存在互联网上的软件都是相互关联的,当我们开发一款应用程序时&#x…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
