1.用户态的syscall
级别
编码
名称
0
00
用户/应用模式 (U, User/Application)
1
01
监督模式 (S, Supervisor)
2
10
虚拟监督模式 (H, Hypervisor)
3
11
机器模式 (M, Machine)
riscv
的特权级如上,一共四个特权级,特权级越高,对硬件的控制能力越强。
opensbi
运行在M
模式下,从而S
模式下可以通过SBI
接口控制硬件
U
模式下可以通过ABI
调用S
模式的服务
从U
模式通过ecall
调用S
模式,并且S
模式通过SBI
调用M
模式硬件的流程如下:
riscv 系统调用简介 syscall
的调用参数和返回值传递通过遵循如下约定实现:
调用参数
a7
寄存器存放系统调用号,区分是哪个 Syscall
a0-a5
寄存器依次用来表示 Syscall
编程接口中定义的参数
返回值
ecall
指令会根据当前所处模式触发不同的执行环境切换异常:
in U-mode: environment-call-from-U-mode exception
in S-mode: environment-call-from-S-mode exception
in M-mode: environment-call-from-M-mode exception
Syscall
场景下是在 U-mode(用户模式)下执行 ecall
指令,主要会触发如下变更:
处理器特权级别由 User-mode(用户模式)提升为 Supervisor-mode(内核模式)
当前指令地址保存到 sepc
特权寄存器
设置 scause
特权寄存器
跳转到 stvec
特权寄存器指向的指令地址
具体的测试过程见:实现U模式的trap机制 | TimerのBlog (yanglianoo.github.io)
2.trap机制 **trap机制
**:
在riscv
架构中,异常,系统调用和中断的过程都被统称为trap
参考:RISC-V 32架构实践专题九(从零开始写操作系统-trap机制)_trap riscv 指令-CSDN博客
什么是ecall
ecall
指令是 RISC-V 指令集架构(ISA)中的一个指令,用于触发环境调用(environment call)。这在不同的特权级别下有不同的用途,但最常见的是用于从用户模式(U-mode)触发系统调用(system call),以请求操作系统内核提供服务。
工作流程:
**用户模式(U-mode)程序执行 ecall
**:当用户模式的程序执行 ecall
指令时,CPU 会产生一个陷阱(trap),跳转到内核模式(S-mode)进行处理。
保存上下文 :CPU 保存当前的执行上下文(如程序计数器、寄存器等),以便在处理完系统调用后能够恢复。
陷入内核 :CPU 跳转到内核中预定义的陷阱处理程序(trap handler)。
处理系统调用 :内核根据系统调用号和参数,执行相应的服务,如读写文件、分配内存等。
恢复上下文 :系统调用处理完成后,恢复之前保存的上下文。
返回用户模式 :CPU 返回用户模式,继续执行用户程序。
目标:
U模式下 通过ecall指令 调用S模式下 OS
OS 对 调用进行处理
处理完毕 返回U模式,并恢复调用时候的上下文继续执行U模式下的程序
注意:
切换前后需要保证程序的上下文不变
通常包含通用寄存器和栈空间
与S模式相关的异常寄存器 Supervisor Status Register (sstatus)
bit[8]:SPP
:表示在进入s模式之前正在执行的特权级别,
当接收到trap
时,如果该trap
来自用户模式,则SPP
设置为0
,否则设置为1
。
当执行一条SRET
指令从trap
处理程序返回时,如果SPP
位为0
,则特权级别被设置为U
模式,如果SPP
位为1
,则特权级别被设置为S
模式
Supervisor Trap Vector Base Address Register (stvec
)
stvec
寄存器用于设置发生trap
时,异常处理程序的地址。
MODE 位于 [1:0],长度为 2 bits;
当mode 字段为0时,stvec
被设置为 Direct 模式,此时进入 S 模式的 Trap 无论原因如何,处理 Trap 的入口地址都是 BASE<<2
,CPU 会跳转到这个地方进行异常处理。
当字段为1时,异常触发后会跳转到以BASE
字段对应的异常向量表,每个向量占4个字节。
BASE 位于 [63:2],长度为 62 bits。
Supervisor Scratch Register (sscratch)
sscratch寄存器是一个可读/写的辅助寄存器,通常,在hart执行用户代码时,sscratch用于切换上下文的栈。
Supervisor Exception Program Counter (sepc
)
sepc记录了 Trap 发生之前执行的最后一条指令的地址
Supervisor Cause Register (scause
):star:
这个寄存器记录了S模式下异常发生的原因。
最高位interrupt
为1
的时候表示触发的类型为中断,为0
的时候表示异常
其余位表示具体的trap原因Exception Code
Interrupt
Exception Code
Description
1
0
Reserved
1
1
Supervisor software interrupt
1
2–4
Reserved
1
5
Supervisor timer interrupt
1
6–8
Reserved
1
9
Supervisor external interrupt
1
10–15
Reserved
1
≥16
Designated for platform use
0
0
Instruction address misaligned
0
1
Instruction access fault
0
2
Illegal instruction
0
3
Breakpoint
0
4
Load address misaligned
0
5
Load access fault
0
6
Store/AMO address misaligned
0
7
Store/AMO access fault
0
8
Environment call from U-mode
0
9
Environment call from S-mode
0
10–11
Reserved
0
12
Instruction page fault
0
13
Load page fault
0
14
Reserved
0
15
Store/AMO page fault
0
16–23
Reserved
0
24–31
Designated for custom use
0
32–47
Reserved
0
48–63
Designated for custom use
0
≥64
Reserved
特权级切换流程机制 当cpu准备从 U特权级 trap 到 S特权级的时候,会执行如下流程:
sstatus
的 SPP
字段会被修改为 CPU
当前的特权级(U/S)
。
sepc
会被修改为 Trap
处理完成后默认会执行的下一条指令的地址。
scause/stval
分别会被修改成这次 Trap
的原因以及相关的附加信息。
CPU
会跳转到 stvec
所设置的 Trap
处理入口地址,并将当前特权级设置为 S
,然后从Trap
处理入口地址处开始执行。这里会根据scause
中保存的异常原因进行分发处理
当CPU完成trap 准备返回到 U特权级的时候,需要通过 一条S特权级的指令 sret
来完成:
CPU 会将当前的特权级按照 sstatus
的 SPP
字段设置为 U 或者 S ;
CPU 会跳转到 sepc
寄存器指向的那条指令,然后继续执行。 sepc
保存了trap之前待执行的下一条指令
上下文的保存和恢复需要用到栈空间
应用程序通过 ecall
进入到内核状态时,操作系统保存被打断的应用程序的 Trap
上下文;
操作系统根据Trap
相关的CSR
寄存器内容,完成系统调用服务的分发与处理;
操作系统完成系统调用服务后,需要恢复被打断的应用程序的Trap
上下文,并通 sret
让应用程序继续执行。
3.trap机制实现
OS/types.h
实现一个数据类型的定义,用typedef
定义了一个reg_t
的类型用于定义使用的寄存器
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __TYPES_H__ #define __TYPES_H__ typedef unsigned char uint8_t ;typedef unsigned short uint16_t ;typedef unsigned int uint32_t ;typedef unsigned long long uint64_t ; typedef uint64_t reg_t ;#endif
OS/riscv.h
定义了一些获取寄存器值的函数
需要在os.h 中 加入 types.h的头文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #ifndef __RISCV_H__ #define __RISCV_H__ #include "os.h" static inline reg_t r_sepc () { reg_t x; asm volatile ("csrr %0, sepc" : "=r" (x) ) ; return x; } static inline reg_t r_scause () { reg_t x; asm volatile ("csrr %0, scause" : "=r" (x) ) ; return x; } static inline reg_t r_stval () { reg_t x; asm volatile ("csrr %0, stval" : "=r" (x) ) ; return x; } static inline reg_t r_sstatus () { reg_t x; asm volatile ("csrr %0, sstatus" : "=r" (x) ) ; return x; } static inline void w_sstatus (reg_t x) { asm volatile ("csrw sstatus, %0" : : "r" (x)) ; } static inline void w_stvec (reg_t x) { asm volatile ("csrw stvec, %0" : : "r" (x)) ; } static inline reg_t r_stvec () { reg_t x; asm volatile ("csrr %0, stvec" : "=r" (x) ) ; return x; } #endif
OS/batch.c
在这个文件中 定义内核栈和用户栈,内核栈可以用来保存 trap情况下的用户程序上下文
KernelStack
和UserStack
的大小被定义为8kb。
1 2 3 4 5 6 7 8 #include <stddef.h> #include "os.h" #define USER_STACK_SIZE (4096 * 2) #define KERNEL_STACK_SIZE (4096 * 2) uint8_t KernelStack[KERNEL_STACK_SIZE];uint8_t UserStack[USER_STACK_SIZE];
OS/context.h
用来保存上下文寄存器信息,Trap上下文执行流的数据就是寄存器中的数据,有x0~x31
总共32
个通用寄存器以及sstatus
和sepc
等控制寄存器需要保存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #ifndef __CONTEXT_H__ #define __CONTEXT_H__ #include "os.h" typedef struct pt_regs { reg_t x0; reg_t ra; reg_t sp; reg_t gp; reg_t tp; reg_t t0; reg_t t1; reg_t t2; reg_t s0; reg_t s1; reg_t a0; reg_t a1; reg_t a2; reg_t a3; reg_t a4; reg_t a5; reg_t a6; reg_t a7; reg_t s2; reg_t s3; reg_t s4; reg_t s5; reg_t s6; reg_t s7; reg_t s8; reg_t s9; reg_t s10; reg_t s11; reg_t t3; reg_t t4; reg_t t5; reg_t t6; reg_t sstatus; reg_t sepc; }pt_regs; #endif
OS/kerneltrap.S
汇编文件中定义了两个函数::__alltraps 、__restore
用于对trap的上下文进行保存和恢复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 # __alltraps 函数: .globl __alltraps .align 4 #4*4 = 16字节对齐 __alltraps: # 从sscratch获取S模式下的SP,把U模式下的SP保存到sscratch寄存器中 csrrw sp, sscratch, sp # now sp->kernel stack, sscratch->user stack # allocate a TrapContext on kernel stack addi sp, sp, -34*8 # save general-purpose registers sd x1, 1*8(sp) # skip sp(x2), we will save it later sd x3, 3*8(sp) # skip tp(x4), application does not use it # save x5~x31 sd x4, 4*8(sp) #sd xN, N*8(sp):将通用寄存器 xN 的值存储到堆栈上 sp + N*8 的位置。 sd x5, 5*8(sp) sd x6, 6*8(sp) sd x7, 7*8(sp) sd x8, 8*8(sp) sd x9, 9*8(sp) sd x10,10*8(sp) sd x11, 11*8(sp) sd x12, 12*8(sp) sd x13, 13*8(sp) sd x14, 14*8(sp) sd x15, 15*8(sp) sd x16, 16*8(sp) sd x17, 17*8(sp) sd x18, 18*8(sp) sd x19, 19*8(sp) sd x20, 20*8(sp) sd x21, 21*8(sp) sd x22, 22*8(sp) sd x23, 23*8(sp) sd x24, 24*8(sp) sd x25, 25*8(sp) sd x26, 26*8(sp) sd x27, 27*8(sp) sd x28, 28*8(sp) sd x29, 29*8(sp) sd x30, 30*8(sp) sd x31, 31*8(sp) # we can use t0/t1/t2 freely, because they were saved on kernel stack csrr t0, sstatus csrr t1, sepc sd t0, 32*8(sp) sd t1, 33*8(sp) # read user stack from sscratch and save it on the kernel stack csrr t2, sscratch sd t2, 2*8(sp) # set input argument of trap_handler(TrapContext) mv a0, sp call trap_handler
__alltraps
函数就是发生异常时的处理函数,在此函数中:
csrrw sp, sscratch, sp
中将sscratch
和sp
的值进行了交换,
在进入此函数之前sp
指向的是用户栈,sscratch
中的值保存的是内核栈的栈顶。
进行交换后,由于此时进入了S
态,所以需要切换栈,由此就切换到了内核栈。
然后就是将寄存器的值保存进内核栈中,在context.h
上下文的定义可以看见pt_regs
中定义了34
个寄存器,所以通过addi sp, sp, -34*8
指令来压栈,然后依次保存寄存器的值
最后两行将内核栈的sp
保存进a0
寄存器用于传参,所以将用户态寄存器保存进内核栈后,调用了trap_handler
函数,在此函数中可通过a0
传入的参数访问内核栈中储存的寄存器的值。
__restore
函数:将内核栈中的存储的寄存器的值恢复,然后通过sret
指令返回从S态到用户态继续执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 .globl __restore .align 4 __restore: # case1: start running app by __restore # case2: back to U after handling trap mv sp, a0 # now sp->kernel stack(after allocated), sscratch->user stack # restore sstatus/sepc ld t0, 32*8(sp) ld t1, 33*8(sp) ld t2, 2*8(sp) csrw sstatus, t0 csrw sepc, t1 csrw sscratch, t2 # restore general-purpuse registers except sp/tp ld x1, 1*8(sp) ld x3, 3*8(sp) ld x4, 4*8(sp) ld x5, 5*8(sp) ld x6, 6*8(sp) ld x7, 7*8(sp) ld x8, 8*8(sp) ld x9, 9*8(sp) ld x10,10*8(sp) ld x11, 11*8(sp) ld x12, 12*8(sp) ld x13, 13*8(sp) ld x14, 14*8(sp) ld x15, 15*8(sp) ld x16, 16*8(sp) ld x17, 17*8(sp) ld x18, 18*8(sp) ld x19, 19*8(sp) ld x20, 20*8(sp) ld x21, 21*8(sp) ld x22, 22*8(sp) ld x23, 23*8(sp) ld x24, 24*8(sp) ld x25, 25*8(sp) ld x26, 26*8(sp) ld x27, 27*8(sp) ld x28, 28*8(sp) ld x29, 29*8(sp) ld x30, 30*8(sp) ld x31, 31*8(sp) # release TrapContext on kernel stack addi sp, sp, 34*8 # now sp->kernel stack, sscratch->user stack 交换内核态和用户态的指针 csrrw sp, sscratch, sp # now sp->user stack, sscratch->kernel stack 返回 sret
__restore
函数的定义为__restore(pt_regs *next)
,所以在第一行传入内核栈地址,然后将内核栈中存放的寄存器的值恢复,然后切换sp
,最后通过sret
返回用户态继续执行
在最后两行会将sp
指向用户栈,sscratch
指向内核栈
4.测试
OS/trap.c
在汇编文件中的__alltraps
中调用了中断处理函数trap_handler
对异常进行处理,下面时对这个函数的简单实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include "os.h" #include "context.h" #include "riscv.h" extern void __alltraps(void );pt_regs* trap_handler (pt_regs* cx) { reg_t scause = r_scause() ; printf ("cause:%x\n" ,scause); printf ("a0:%x\n" ,cx->a0); printf ("a1:%x\n" ,cx->a1); printf ("a2:%x\n" ,cx->a2); printf ("a7:%x\n" ,cx->a7); printf ("sepc:%x\n" ,cx->sepc); printf ("sstatus:%x\n" ,cx->sstatus); printf ("sp:%x\n" ,cx->sp); while (1 ) { } return cx; } void trap_init () { w_stvec((reg_t )__alltraps); }
pt_regs* cx
是一个指向 pt_regs
结构体的指针,包含了所有保存的寄存器值(上下文)
r_scause()
函数读取 scause
寄存器,获取 Trap 的原因。
trap_init
函数用于初始化 Trap 机制,设置 Trap 向量基地址为 __alltraps
OS/batch.c
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include <stddef.h> #include "os.h" #include "context.h" #define USER_STACK_SIZE (4096 * 2) #define KERNEL_STACK_SIZE (4096 * 2) #define APP_BASE_ADDRESS 0x80600000 size_t syscall (size_t id, reg_t arg1, reg_t arg2, reg_t arg3) { long ret; asm volatile ( "mv a7, %1\n\t" "mv a0, %2\n\t" "mv a1, %3\n\t" "mv a2, %4\n\t" "ecall\n\t" "mv %0, a0" : "=r" (ret) : "r" (id), "r" (arg1), "r" (arg2), "r" (arg3) : "a7" , "a0" , "a1" , "a2" , "memory" ) ; return ret; } void testsys () { int ret = syscall(2 ,3 ,4 ,5 ); } uint8_t KernelStack[KERNEL_STACK_SIZE];uint8_t UserStack[USER_STACK_SIZE]={0 };extern void __restore(pt_regs *next);struct pt_regs tasks ;void app_init_context () { reg_t user_sp = &UserStack + USER_STACK_SIZE; printf ("user_sp:%p\n" , user_sp); reg_t stvec = r_stvec(); printf ("stvec:%x\n" , stvec); trap_init(); reg_t sstatus = r_sstatus(); sstatus &= (0U << 8 ); w_sstatus(sstatus); printf ("sstatus:%x\n" , sstatus); tasks.sepc = (reg_t )testsys; printf ("tasks sepc:%x\n" , tasks.sepc); tasks.sstatus = sstatus; tasks.sp = user_sp; pt_regs* cx_ptr = &KernelStack[0 ] + KERNEL_STACK_SIZE - sizeof (pt_regs); printf ("pt_regs: %d\n" ,sizeof (pt_regs)); cx_ptr->sepc = tasks.sepc; printf ("cx_ptr sepc :%x\n" , cx_ptr->sepc); printf ("cx_ptr sepc adress:%x\n" , &(cx_ptr->sepc)); cx_ptr->sstatus = tasks.sstatus; cx_ptr->sp = tasks.sp; printf ("cx_ptr adress:%x\n" , cx_ptr); __restore(cx_ptr); }
解释:
1 2 3 4 5 6 7 8 #include <stddef.h> #include "os.h" #include "context.h" #define USER_STACK_SIZE (4096 * 2) #define KERNEL_STACK_SIZE (4096 * 2) #define APP_BASE_ADDRESS 0x80600000
包含了必要的头文件
定义了用户堆栈和内核堆栈的大小 4k * 2 = 8k
定义了应用程序的起始地址 0x80600000
系统调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 size_t syscall (size_t id, reg_t arg1, reg_t arg2, reg_t arg3) { long ret; asm volatile ( "mv a7, %1\n\t" "mv a0, %2\n\t" "mv a1, %3\n\t" "mv a2, %4\n\t" "ecall\n\t" "mv %0, a0" : "=r" (ret) : "r" (id), "r" (arg1), "r" (arg2), "r" (arg3) : "a7" , "a0" , "a1" , "a2" , "memory" ) ; return ret; }
该函数执行一个系统调用,传递系统调用号和三个参数。
使用内联汇编将参数传递给适当的寄存器,并执行 ecall
指令。 — 执行__alltrap
保存上下文 ,然后调用handler
函数处理ecall
,最后恢复
1 2 3 4 void testsys () { int ret = syscall(2 , 3 , 4 , 5 ); }
1 2 3 4 5 6 7 uint8_t KernelStack[KERNEL_STACK_SIZE];uint8_t UserStack[USER_STACK_SIZE] = {0 };extern void __restore(pt_regs *next);struct pt_regs tasks ;
定义了用户和内核堆栈
声明了外部的汇编函数restore
,用于恢复上下文
定义一个 pt_regs
结构体实例 tasks
,用于保存任务上下文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void app_init_context () { reg_t user_sp = (reg_t )(&UserStack) + USER_STACK_SIZE; printf ("user_sp:%p\n" , user_sp); reg_t stvec = r_stvec(); printf ("stvec:%x\n" , stvec); trap_init(); reg_t sstatus = r_sstatus(); sstatus &= ~(1U << 8 ); w_sstatus(sstatus); printf ("sstatus:%x\n" , sstatus); tasks.sepc = (reg_t )testsys; printf ("tasks sepc:%x\n" , tasks.sepc); tasks.sstatus = sstatus; tasks.sp = user_sp; pt_regs* cx_ptr = (pt_regs*)((uint8_t *)&KernelStack[0 ] + KERNEL_STACK_SIZE - sizeof (pt_regs)); printf ("pt_regs: %d\n" , sizeof (pt_regs)); cx_ptr->sepc = tasks.sepc; printf ("cx_ptr sepc: %x\n" , cx_ptr->sepc); printf ("cx_ptr sepc address: %p\n" , &(cx_ptr->sepc)); cx_ptr->sstatus = tasks.sstatus; cx_ptr->sp = tasks.sp; printf ("cx_ptr address: %p\n" , cx_ptr); __restore(cx_ptr); }
目的: 通过app_init...
初始化用户态程序的上下文,并最后通过调用 __restore(cx_ptr)
将控制权移交给用户态程序, 最后在用户态程序中才是真的ecall
reg_t user_sp
初始化用户堆栈指针,并打印出来
因为栈是从高地址往低地址向下增长的,所以用户栈的地址为&UserStack + USER_STACK_SIZE
然后调用trap_init
函数来设置stvec
寄存器的值为__alltraps
,这里告诉cpu发生trap时去哪里执行
stvec
寄存器用于设置发生trap
时,异常处理程序的地址。
然后设置sstatus寄存器的SPP位为0。这是为啥呢?在上面对寄存器的介绍中提到“当执行一条SRET指令从trap处理程序返回时,如果SPP位为0,则特权级别被设置为U模式,如果SPP位为1,则特权级别被设置为S模式;”所以我们为了从S模式返回用户模式去执行testsys()中的代码,我们需要将SPP位设置为0。
然后就是构造一段内核栈,设置sstatus
、sepc
、sp
的值,这里由于下一阶段为用户模式,所以sepc
会设置成用户态程序的地址(testsys
),sp
设置为用户栈的地址。
最后恢复上下文,返回用户态执行
OS/os.h
还需要添加头文件以及函数声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #ifndef __OS_H__ #define __OS_H__ #include <stddef.h> #include <stdarg.h> #include "types.h" #include "context.h" #include "riscv.h" extern int printk (const char * s, ...) ;extern void panic (char *s) ;extern void sbi_console_putchar (int ch) ;extern void app_init_context () ;extern void trap_init () ;#endif
OS/main.c
添加打印测试的U模式下函数
1 2 3 4 5 6 7 8 9 10 11 #include "os.h" void os_main () { printk("hello!!!\n" ); app_init_context(); while (1 ) { } }
OS/Makefle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 CROSS_COMPILE = riscv64-unknown-elf- CFLAGS = -nostdlib -fno-builtin -I/opt/riscv/riscv64-unknown-elf/include -mcmodel=medany CC = ${CROSS_COMPILE}gcc OBJCOPY = ${CROSS_COMPILE}objcopy OBJDUMP = ${CROSS_COMPILE}objdump SRCS_ASM = \ entry.S \ kerneltrap \ SRCS_C = \ sbi.c \ main.c \ printk.c \ batch.c \ trap.c \ OBJS = $(SRCS_ASM:.S=.o) OBJS += $(SRCS_C:.c=.o) os.elf: ${OBJS} ${CC} ${CFLAGS} -T os.ld -Wl,-Map=os.map -o os.elf $^ ${OBJCOPY} -O binary os.elf os.bin %.o : %.c ${CC} ${CFLAGS} -c -o $@ $< %.o : %.S ${CC} ${CFLAGS} -c -o $@ $< .PHONY : clean clean: rm -rf *.o *.bin *.elf
测试结果
可以看到 cause的值为8,查看scause
8表示 U模式的下的ecall
trap
处理的总体流程
首先通过 __alltraps
将 Trap 上下文保存在内核栈上,
然后跳转到编写的 trap_handler
函数完成 Trap 处理。
当 trap_handler
返回之后,使用 __restore
从保存在内核栈上的 Trap 上下文恢复寄存器。
最后通过一条 sret
指令回到应用程序执行。
本节添加函数逻辑:star:
S模式:
首先在app_init_context()
这个函数中,初始化用户态栈指针,初始化trap
处理程序,初始化内核栈,最后通过__restore(cx_ptr);
恢复到用户态(testsys
)执行
在trap_init
中 初始化了 __alltrap
汇编函数 这个函数用于保存上下文信息 以及调用 trap_handler
对trap进行处理
U模式:
用户态程序 testsys
执行,函数调用了syscall
函数
U模式:
在syscall
函数,对寄存器进行操作,并且通过内联汇编的方式调用ecall
,使得操作系统陷入了trap
S模式:
在trap
中,首先调用了__alltrap
对上下文进行了保存,并调用trap_handler
对trap进行处理(也是打印相关的寄存器信息)
S模式:
最后处理完 会调用__restore
函数