文章目录
中断分类
本地中断:软中断(0-3)和定时器中断(4-7)
全局中断:外部中断(8-10)
三类中断对应到CPU有三个引脚
mie mip
mstatus中的那个中断寄存器是全局,mie这个中断寄存器是二级的,相当于做个细化
mie:开启哪个模式下某个类型的中断
mip:标准当前哪个模式下的某个类型的中断是否在发生
中断处理流程
与异常处理流程类似,区别是设置mepc保存的是当前pc下一条指令的地址。因为中断认为是执行完当前指令后再去指向中断,所以中断处理完后应该回到之前指令的下一条指令
外部中断
外设产生中断给CPU,CPU响应,处理中断
对于每个CPU只有一个对应外部中断的引脚,但外设很多个
通过PLIC中断控制器管理,即产生外设产生中断源后,PLIC处理然后给某个CPU处理
中断源
0没有固定对应的中断源,预留
PLIC
PLIC也是一个外设,CPU访问其PLIC也是通过将PLIC映射到内存上进而实现访问内存即访问到PLIC寄存器的方式
Priority
每个中断源根据其中断源ID来对应到其Priority寄存器,每个寄存器内的值从0到7,0代表该中断源的优先级最低
Pending
PLIC可读可写,标注某个中断源是否发生,每个PLIC有两个是因为64位才能表示够所有的中断源
Enable
对应hart的,也是两个,也是因为64位才能表示够所有的中断源
Threshold
Claim/Complete
PLIC工作流程
1和2中断源分别优先级为1,2. hart0的所有设备对应的Enable打开而hart1关闭。hart0的threshold设置为零
- 当1和2中断源同时发生时,首先中断源的中断优先级互相比较,高优先级先进入
- 此时hart0的Enable允许进入,hart1不允许
- 然后hart0的threshold为零,允许进入
- pending对应的中断源位置为1,此时再传给CPU,CPU就知道某个中断发生了
- 然后Claim可以得知此时中断源的ID号,并把padding位关掉,然后走中断处理程序
- 然后再complete,通知PLIC当前中断处理完了,然后处理1号中断源,然后过程同上
设置uart寄存器IER
设置uart寄存器LSR
asm volatile(“mv %0, tp” : “=r” (x) );
这段代码是一个RISC-V架构上的内联汇编语句,用于C或C++编程环境。它具有以下含义:
asm volatile("mv %0, tp" : "=r" (x) );
-
asm volatile
: 这表明这是一个内联汇编指令,且标记为volatile
。这意味着编译器不会对此段代码进行优化,每次都会按照字面意义执行,且执行的结果可能会影响到程序的状态。 -
"mv %0, tp"
: 这是RISC-V架构的汇编指令。mv
是move(移动)指令,用于将一个值从源寄存器复制到目标寄存器。在这里,tp
表示线程指针寄存器,而%0
是一个占位符,代表将在输出约束中指定的C/C++变量。 -
: "=r" (x)
: 这是一个输出约束,描述了汇编指令如何与C/C++变量交互。=r
意味着需要一个寄存器作为输出,并且将该寄存器的值赋给变量x
。
头文件不能定义函数,不然每次导入都会定义一次
/usr/lib/riscv64-unknown-elf/bin/ld: plic.o: in function read_mie': /home/llk/Desktop/risllkos/github/Fullkenerl6/trap.h:14: multiple definition of read_mie'; Trap.o:/home/llk/Desktop/risllkos/github/Fullkenerl6/trap.h:14: first defined here /usr/lib/riscv64-unknown-elf/bin/ld: plic.o: in function write_mie': /home/llk/Desktop/risllkos/github/Fullkenerl6/trap.h:19: multiple definition of write_mie'; Trap.o:/home/llk/Desktop/risllkos/github/Fullkenerl6/trap.h:19: first defined here
这个链接错误提示说明项目中有两个对象文件(plic.o
和 Trap.o
)都在尝试定义相同的函数 read_mie
和 write_mie
,并且这些函数定义位于头文件 /home/llk/Desktop/risllkos/github/Fullkenerl6/trap.h
的第14行和第19行。
在C/C++中,函数不应该在头文件中直接定义,而应该只声明函数原型。否则,当包含这个头文件的源文件被编译后,就会导致每个源文件都生成一次该函数的定义,进而引发链接阶段的重复定义错误。
解决方法如下:
-
将
read_mie
和write_mie
函数的定义移到一个单独的源文件(如trap.c
或trap.s
,取决于它们是C函数还是汇编函数)中。 -
在头文件
trap.h
中只保留函数的声明(prototype):
#ifndef _TRAP_H_
#define _TRAP_H_
// 声明函数,而不是定义
extern uint32_t read_mie(void);
extern void write_mie(uint32_t value);
#endif // _TRAP_H_
-
确保在项目中只在一个源文件中定义这两个函数的实际实现,然后正常编译和链接该项目的所有源文件。
-
如果这些函数是非常底层的汇编函数,且你确实希望在头文件中放置定义,那么你需要确保使用
static inline
关键字来声明它们,以避免链接时的多重定义错误。但请注意,这种方法适用于非常小且适合内联展开的函数。对于较大的函数或涉及特殊寄存器访问的函数,通常不建议这样做。
static line
在C或C++中,当你在一个头文件中定义了一个常规(非inline)函数并且在多个源文件(.c或.cpp)中包含了这个头文件,编译器会在每个包含此头文件的源文件中生成这个函数的机器码。当链接器试图将所有目标文件链接成一个可执行文件时,会发现同一函数在多个地方都有定义,从而引发多重定义的链接错误。
然而,对于static inline
函数,情况有所不同:
-
Static:
static
关键字在这里用于限定函数的作用域。对于函数来说,这意味着函数是“内部链接”的,也就是它只在包含它的编译单元(通常是包含该函数定义的源文件)内部可见。尽管此处函数定义在头文件中,但由于它是static
的,每个包含此头文件的源文件都会得到该函数的一个独立副本,这些副本彼此之间互不影响,都在各自的作用域内有效。 -
Inline:
inline
关键字是用来请求编译器尝试将函数体直接嵌入到每个调用该函数的地方,就像宏那样,而不是生成一个单独的函数调用。这样一来,理论上就不需要在链接时处理此函数的外部引用。
结合两者,static inline
函数既限定了作用域(每个源文件视为一个独立的作用域),又要求编译器尽可能内联展开,即便编译器出于某种原因无法内联展开,由于static
的作用,各个源文件中生成的函数副本依然是独立的,并且只在其所在的编译单元内有效,所以在链接阶段就不会发生多重定义错误。
例如:
// trap.h
#ifndef TRAP_H
#define TRAP_H
static inline void handle_trap() {
// 函数体...
}
#endif // TRAP_H
// file1.c
#include "trap.h"
void some_function() {
handle_trap(); // 调用
}
// file2.c
#include "trap.h"
void another_function() {
handle_trap(); // 另一处调用
}
在这个例子中,尽管handle_trap()
函数在file1.c
和file2.c
中都包含了其定义,但由于它是static inline
的,每个源文件都有一个独立的内联版本(理想情况下编译器会内联展开,如果不是则在各自的编译单元内有独立的非内联副本),最终链接时不会出现多重定义的问题。
代码
外设通过uart借口连接到PLIC,PLIC连接到CPU上。当外设输入一个字符时会产生中断,此时会通过PLIC通知CPU进而处理中断。
而输出是直接CPU和uart连接,CPU将字符传给uart,uart再传给外设,显示屏自然就看到了
初始化plic
中断处理
根据发送的中断进入uart对应中断程序
代码
https://github.com/FULLK/risllkos/tree/main/Fullkenerl6