当前位置: 首页 > news >正文

湖南大学-操作系统实验四

HUNAN  UNIVERSITY

操作系统实验报告

          

一、实验题目

实验四

中断、异常和陷阱指令是操作系统的基石,现代操作系统就是由中断驱动的。本实验和实验五的目的在于深刻理解中断的原理和机制,掌握CPU访问中断控制器的方法,掌握Arm体系结构的中断机制和规范,实现时钟中断服务和部分异常处理等。

  • 实验环境

ubuntu20

  • 实验步骤

一、陷入操作系统

如下图所示,操作系统是一个多入口的程序,执行陷阱(Trap)指令,出现异常、发生中断时都会陷入到操作系统。

二、ARMv8的中断与异常处理

ARMv8 架构定义了两种执行状态(Execution States),AArch64 和AArch32。分别对应使用64位宽通用寄存器或32位宽通用寄存器的执行。

上图所示为AArch64中的异常级别(Exception levels)的组织。可见AArch64中共有4个异常级别,分别为EL0,EL1,EL2和EL3。在AArch64中,Interrupt是Exception的子类型,称为异常。 AArch64 中有四种类型的异常:

Sync(Synchronous exceptions,同步异常),在执行时触发的异常,例如在尝试访问不存在的内存地址时。

IRQ (Interrupt requests,中断请求),由外部设备产生的中断

FIQ (Fast Interrupt Requests,快速中断请求),类似于IRQ,但具有更高的优先级,因此 FIQ 中断服务程序不能被其他 IRQ 或 FIQ 中断。

SError (System Error,系统错误),用于外部数据中止的异步中断。

当异常发生时,处理器将执行与该异常对应的异常处理代码。在ARM架构中,这些异常处理代码将会被保存在内存的异常向量表中。每一个异常级别(EL0,EL1,EL2和EL3)都有其对应的异常向量表。需要注意的是,与x86等架构不同,该表包含的是要执行的指令,而不是函数地址 3 。

异常向量表的基地址由VBAR_ELn给出,然后每个表项都有一个从该基地址定义的偏移量。 每个表有16个表项,每个表项的大小为128(0x80)字节(32 条指令)。 该表实际上由4组,每组4个表项组成。 分别是:

发生于当前异常级别的异常且SPSel寄存器选择SP0 4 , Sync、IRQ、FIQ、SError对应的4个异常处理。

发生于当前异常级别的异常且SPSel寄存器选择SPx 4 , Sync、IRQ、FIQ、SError对应的4个异常处理。

发生于较低异常级别的异常且执行状态为AArch64, Sync、IRQ、FIQ、SError对应的4个异常处理。

发生于较低异常级别的异常且执行状态为AArch32, Sync、IRQ、FIQ、SError对应的4个异常处理。

异常向量表

新建 src/bsp/prt_vector.S 文件,参照这里 3 定义异常向量表如下:

  1.     .section .os.vector.text, "ax"  
  2.   
  3.     .global  OsVectorTable  
  4.     .type  OsVectorTable,function  
  5.   
  6.     .align 13  
  7.   
  8. OsVectorTable:  
  9. .set    VBAR, OsVectorTable  
  10. .org VBAR                                // SynchronousCurrent EL with SP_EL0  
  11.     EXC_HANDLE  0 OsExcDispatch  
  12.   
  13. .org (VBAR + 0x80)                       // IRQ/vIRQ, Current EL with SP_EL0  
  14.     EXC_HANDLE  1 OsExcDispatch  
  15.   
  16. .org (VBAR + 0x100)                      // FIQ/vFIQ, Current EL with SP_EL0  
  17.     EXC_HANDLE  2 OsExcDispatch  
  18.   
  19. .org (VBAR + 0x180)                      // SERROR, Current EL with SP_EL0  
  20.     EXC_HANDLE  3 OsExcDispatch  
  21.   
  22. .org (VBAR + 0x200)                      // SynchronousCurrent EL with SP_ELx  
  23.     EXC_HANDLE  4 OsExcDispatch  
  24.   
  25. .org (VBAR + 0x280)                      // IRQ/vIRQ, Current EL with SP_ELx  
  26.     EXC_HANDLE  5 OsExcDispatch  
  27.   
  28. .org (VBAR + 0x300)                      // FIQ/vFIQ, Current EL with SP_ELx  
  29.     EXC_HANDLE  6 OsExcDispatch  
  30.   
  31. .org (VBAR + 0x380)                      // SERROR, Current EL with SP_ELx  
  32.     EXC_HANDLE  7 OsExcDispatch  
  33.   
  34. .org (VBAR + 0x400)                      // Synchronous, EL changes and the target EL is using AArch64  
  35.     EXC_HANDLE  8 OsExcDispatchFromLowEl  
  36.   
  37. .org (VBAR + 0x480)                      // IRQ/vIRQ, EL changes and the target EL is using AArch64  
  38.     EXC_HANDLE  9 OsExcDispatch  
  39.   
  40. .org (VBAR + 0x500)                      // FIQ/vFIQ, EL changes and the target EL is using AArch64  
  41.     EXC_HANDLE  10 OsExcDispatch  
  42.   
  43. .org (VBAR + 0x580)                      // SERROR, EL changes and the target EL is using AArch64  
  44.     EXC_HANDLE  11 OsExcDispatch  
  45.   
  46. .org (VBAR + 0x600)                      // Synchronous, L changes and the target EL is using AArch32  
  47.     EXC_HANDLE  12 OsExcDispatch  
  48.   
  49. .org (VBAR + 0x680)                      // IRQ/vIRQ, EL changes and the target EL is using AArch32  
  50.     EXC_HANDLE  13 OsExcDispatch  
  51.   
  52. .org (VBAR + 0x700)                      // FIQ/vFIQ, EL changes and the target EL is using AArch32  
  53.     EXC_HANDLE  14 OsExcDispatch  
  54.   
  55. .org (VBAR + 0x780)                      // SERROR, EL changes and the target EL is using AArch32  
  56.     EXC_HANDLE  15 OsExcDispatch  
  57.   
  58.     .text  

可以看到:针对4组,每组4类异常共16类异常均定义有其对应的入口,且其入口均定义为 EXC_HANDLE vecId handler 的形式。

prt_reset_vector.S 中的 OsEnterMain: 标号后加入代码

  1. OsVectTblInit// 设置 EL1 级别的异常向量表
  2.     LDR x0, =OsVectorTable
  3.     MSR VBAR_EL1, X0

上下文保存与恢复

EXC_HANDLE 实际上是一个宏,其定义如下

  1. .global OsExcHandleEntry
  2. .type   OsExcHandleEntryfunction
  3. .macro SAVE_EXC_REGS  // 保存通用寄存器的值到栈中
  4.     stp    x1x0[sp,#-16]!
  5.     stp    x3x2[sp,#-16]!
  6.     stp    x5x4[sp,#-16]!
  7.     stp    x7x6[sp,#-16]!
  8.     stp    x9x8[sp,#-16]!
  9.     stp    x11x10[sp,#-16]!
  10.     stp    x13x12[sp,#-16]!
  11.     stp    x15x14[sp,#-16]!
  12.     stp    x17x16[sp,#-16]!
  13.     stp    x19x18[sp,#-16]!
  14.     stp    x21x20[sp,#-16]!
  15.     stp    x23x22[sp,#-16]!
  16.     stp    x25x24[sp,#-16]!
  17.     stp    x27x26[sp,#-16]!
  18.     stp    x29x28[sp,#-16]!
  19.     stp    xzrx30[sp,#-16]!
  20. .endm
  21. .macro RESTORE_EXC_REGS  // 从栈中恢复通用寄存器的值
  22.     ldp    xzrx30[sp],#16
  23.     ldp    x29x28[sp],#16
  24.     ldp    x27x26[sp],#16
  25.     ldp    x25x24[sp],#16
  26.     ldp    x23x22[sp],#16
  27.     ldp    x21x20[sp],#16
  28.     ldp    x19x18[sp],#16
  29.     ldp    x17x16[sp],#16
  30.     ldp    x15x14[sp],#16
  31.     ldp    x13x12[sp],#16
  32.     ldp    x11x10[sp],#16
  33.     ldp    x9x8[sp],#16
  34.     ldp    x7x6[sp],#16
  35.     ldp    x5x4[sp],#16
  36.     ldp    x3x2[sp],#16
  37.     ldp    x1x0[sp],#16
  38. .endm
  39. .macro EXC_HANDLE vecId handler
  40.     SAVE_EXC_REGS // 保存寄存器宏
  41.     mov x1, #\vecId // x1 记录异常类型
  42.     b   \handler // 跳转到异常处理
  43. .endm

  EXC_HANDLE 宏的主要作用是一发生异常就立即保存CPU寄存器的值,然后跳转到异常处理函数进行异常处理。随后,我们继续在 src/bsp/prt_vector.S 文件中实现异常处理函数,包括 OsExcDispatchOsExcDispatchFromLowEl

  1.     .global OsExcHandleEntry
  2.     .type   OsExcHandleEntry, function
  3.     .global OsExcHandleEntryFromLowEl
  4.     .type   OsExcHandleEntryFromLowEl, function
  5.     .section .os.init.text, "ax"
  6.     .globl OsExcDispatch
  7.     .type OsExcDispatch, @function
  8.     .align 4
  9. OsExcDispatch:
  10.     mrs    x5, esr_el1
  11.     mrs    x4, far_el1
  12.     mrs    x3, spsr_el1
  13.     mrs    x2, elr_el1
  14.     stp    x4, x5, [sp,#-16]!
  15.     stp    x2, x3, [sp,#-16]!
  16.     mov    x0, x1  // x0 异常类型
  17.     mov    x1, sp  // x1: 栈指针
  18.     bl     OsExcHandleEntry  // 跳转到实际的 C 处理函数, x0, x1分别为该函数的第12个参数。
  19.     ldp    x2, x3, [sp],#16
  20.     add    sp, sp, #16        // 跳过far, esr, HCR_EL2.TRVM==1的时候,EL1不能写far, esr
  21.     msr    spsr_el1, x3
  22.     msr    elr_el1, x2
  23.     dsb    sy
  24.     isb
  25.     RESTORE_EXC_REGS // 恢复上下文
  26.     eret //从异常返回
  27.     .globl OsExcDispatchFromLowEl
  28.     .type OsExcDispatchFromLowEl, @function
  29.     .align 4
  30. OsExcDispatchFromLowEl:
  31.     mrs    x5, esr_el1
  32.     mrs    x4, far_el1
  33.     mrs    x3, spsr_el1
  34.     mrs    x2, elr_el1
  35.     stp    x4, x5, [sp,#-16]!
  36.     stp    x2, x3, [sp,#-16]!
  37.     mov    x0, x1
  38.     mov    x1, sp
  39.     bl     OsExcHandleFromLowElEntry
  40.     ldp    x2, x3, [sp],#16
  41.     add    sp, sp, #16        // 跳过far, esr, HCR_EL2.TRVM==1的时候,EL1不能写far, esr
  42.     msr    spsr_el1, x3
  43.     msr    elr_el1, x2
  44.     dsb    sy
  45.     isb
  46.     RESTORE_EXC_REGS // 恢复上下文
  47.     eret //从异常返回

 OsExcDispatch 首先保存了4个系统寄存器到栈中,然后调用实际的异常处理 OsExcHandleEntry 函数。当执行完 OsExcHandleEntry 函数后,我们需要依序恢复寄存器的值。这就是操作系统课程中重点讲述的上下文的保存和恢复过程。

OsExcDispatchFromLowEl OsExcDispatch 的操作除调用的实际异常处理函数不同外其它完全一致。

异常处理函数

新建 src/bsp/prt_exc.c 文件,实现实际的 OsExcHandleEntry OsExcHandleFromLowElEntry 异常处理函数。

  1. #include "prt_typedef.h"
  2. #include "os_exc_armv8.h"
  3. extern U32 PRT_Printf(const char *format, ...);
  4. // ExcRegInfo 格式与 OsExcDispatch 中寄存器存储顺序对应
  5. void OsExcHandleEntry(U32 excType, struct ExcRegInfo *excRegs)
  6. {
  7.     PRT_Printf("Catch a exception.\n");
  8. }
  9. // ExcRegInfo 格式与 OsExcDispatchFromLowEl 中寄存器存储顺序对应
  10. void OsExcHandleFromLowElEntry(U32 excType, struct ExcRegInfo *excRegs)
  11. {
  12.     PRT_Printf("Catch a exception from low exception level.\n");
  13. }

注意到上面两个异常处理函数的第2个参数是 struct ExcRegInfo * 类型,而在 src/bsp/prt_vector.S 中我们为该参数传递是栈指针 sp。所以该结构需与异常处理寄存器保存的顺序保持一致。新建 src/bsp/os_exc_armv8.h 文件,定义 ExcRegInfo 结构。

  1. #ifndef ARMV8_EXC_H
  2. #define ARMV8_EXC_H
  3. #include "prt_typedef.h"
  4. #define XREGS_NUM       31
  5. struct ExcRegInfo {
  6.     // 以下字段的内存布局与TskContext保持一致
  7.     uintptr_t elr;                  // 返回地址
  8.     uintptr_t spsr;
  9.     uintptr_t far;
  10.     uintptr_t esr;
  11.     uintptr_t xzr;
  12.     uintptr_t xregs[XREGS_NUM];     // 0~30 : x30~x0
  13. };
  14. #endif /* ARMV8_EXC_H */

触发异常

释掉 FPU 启用代码,构建系统并执行发现没有任何信息输出,通过调试将会观察到异常。

系统调用

系统调用是通用操作系统为应用程序提供服务的方式,理解系统调用对理解通用操作系统的实现非常重要。下面我们来实现1条简单的系统调用。

EL 0 是用户程序所在的级别,而在lab1中我们已经知道CPU启动后进入的是EL1或以上级别。

main 函数中我们首先返回到 EL0 级别,然后通过 SVC 调用一条系统调用.

  1. #include "prt_typedef.h"
  2. extern U32 PRT_Printf(const char *format, ...);
  3. extern void PRT_UartInit(void);
  4. S32 main(void)
  5. {
  6.     const char Test_SVC_str[] = "Hello, my first system call!";
  7.     PRT_UartInit();
  8.      PRT_Printf("   #####                                                \n");
  9.     PRT_Printf("  #     #  ######  #     #   ####   #    #  #  #     # \n");
  10.     PRT_Printf(" #         #       ##    #  #       #    #  #  ##    # \n");
  11.     PRT_Printf(" #   ####  #####   # #   #   ####   ######  #  # #   # \n");
  12.     PRT_Printf(" #      #  #       #  #  #       #  #    #  #  #  #  # \n");
  13.     PRT_Printf("  #     #  #       #   # #  #    #  #    #  #  #   # # \n");
  14.     PRT_Printf("   #####   ######  #     #   ####   #    #  #  #     # \n");
  15.     PRT_Printf("ctr-a h: print help of qemu emulator. ctr-a x: quit emulator.\n\n");
  16.     // 回到异常 EL 0级别,模拟系统调用,查看异常的处理,了解系统调用实现机制。
  17.     // Bare-metal Boot Code for ARMv8-A Processors
  18.     OS_EMBED_ASM(
  19.         "MOV    X1, #0b00000\n" // Determine the EL0 Execution state.
  20.         "MSR    SPSR_EL1, X1\n"
  21.         "ADR    x1, EL0Entry\n" // Points to the first instruction of EL0 code
  22.         " MSR    ELR_EL1, X1\n"
  23.         "eret\n"  // 返回到 EL 0 级别
  24.         "EL0Entry: \n"
  25.         "MOV x0, %0 \n" //参数1
  26.         "MOV x8, #1\n" //linux,x8传递 syscall number,保持一致。
  27.         "SVC 0\n"    // 系统调用
  28.         "B .\n" // 死循环,以上代码只用于演示,EL0级别的栈未正确设置
  29.         ::"r"(&Test_SVC_str[0])
  30.     );
  31.     //  EL1 级别上模拟系统调用
  32.     // OS_EMBED_ASM("SVC 0");
  33.     return 0;
  34. }

系统调用实现

在 src/bsp/prt_exc.c 修改 OsExcHandleFromLowElEntry 函数实现 1 条系统调用。

  1. extern void TryPutc(unsigned char ch);
  2. void MyFirstSyscall(char *str)
  3. {
  4.     while (*str != '\0') {
  5.         TryPutc(*str);
  6.         str++;
  7.     }
  8. }
  9. // ExcRegInfo 格式与 OsExcDispatch 中寄存器存储顺序对应
  10. void OsExcHandleFromLowElEntry(U32 excType, struct ExcRegInfo *excRegs)
  11. {
  12.     int ExcClass = (excRegs->esr&0xfc000000)>>26;
  13.     if (ExcClass == 0x15){ //SVC instruction execution in AArch64 state.
  14.         PRT_Printf("Catch a SVC call.\n");
  15.         // syscall number存在x8寄存器中, x0为参数1
  16.         int syscall_num = excRegs->xregs[(XREGS_NUM - 1)- 8]; //uniproton存储的顺序x0在高,x30在低
  17.         uintptr_t param0 = excRegs->xregs[(XREGS_NUM - 1)- 0];
  18.         PRT_Printf("syscall number: %d, param 0: 0x%x\n", syscall_num, param0);
  19.         switch(syscall_num){
  20.             case 1:
  21.                 MyFirstSyscall((void *)param0);
  22.                 break;
  23.             default:
  24.                 PRT_Printf("Unimplemented syscall.\n");
  25.         }
  26.     }else{
  27.         PRT_Printf("Catch a exception.\n");
  28.     }
  29. }

这段代码是一个异常处理程序和系统调用的实现。它展示了如何在异常处理过程中识别和处理系统调用。我们逐行分析代码:

  1. extern void TryPutc(unsigned char ch);:
    • 声明了一个外部函数 TryPutc,用于输出一个字符。这意味着函数的定义在别处,但在这里被引用。
  2. void MyFirstSyscall(char *str):
    • 声明了一个名为 MyFirstSyscall 的函数,该函数接受一个字符串指针作为参数。
  3. {:
    • 函数体的开始。
  4. while (*str != '\0') {:
    • 当字符串未结束时(即当前字符不是空字符 '\0'),继续循环。
  5. TryPutc(*str);:
    • 调用 TryPutc 函数输出当前字符。
  6. str++;:
    • 移动指针到下一个字符。
  7. }:
    • 循环结束。
  8. }:
    • MyFirstSyscall 函数结束。
  9. // ExcRegInfo 格式与 OsExcDispatch 中寄存器存储顺序对应:
    • 注释,解释 ExcRegInfo 结构的格式与 OsExcDispatch 中寄存器存储顺序相对应。
  10. void OsExcHandleFromLowElEntry(U32 excType, struct ExcRegInfo *excRegs):
    • 声明了一个名为 OsExcHandleFromLowElEntry 的函数,该函数接受一个异常类型和指向寄存器信息结构的指针。
  11. {:
    • 函数体的开始。
  12. int ExcClass = (excRegs->esr & 0xfc000000) >> 26;:
    • 提取异常类(Exception Class)字段,该字段位于异常状态寄存器(ESR)的高位。
  13. if (ExcClass == 0x15){ // SVC instruction execution in AArch64 state.:
    • 检查异常类是否为0x15(SVC指令执行)。
  14. PRT_Printf("Catch a SVC call.\n");:
    • 打印捕获到SVC调用的消息。
  15. // syscall number存在x8寄存器中, x0为参数1:
    • 注释,解释系统调用号在 x8 寄存器中,x0 为参数1。
  16. int syscall_num = excRegs->xregs[(XREGS_NUM - 1) - 8];:
    • 从寄存器数组中提取系统调用号,假设 x0 在高位,x30 在低位。
  17. uintptr_t param0 = excRegs->xregs[(XREGS_NUM - 1) - 0];:
    • 从寄存器数组中提取第一个参数。
  18. PRT_Printf("syscall number: %d, param 0: 0x%x\n", syscall_num, param0);:
    • 打印系统调用号和第一个参数。
    • 空行,分隔代码块。
  19. switch(syscall_num){:
    • 开始处理系统调用号的 switch 语句。
  20. case 1::
    • 如果系统调用号为1。
  21. MyFirstSyscall((void *)param0);:
    • 调用 MyFirstSyscall 函数,传递第一个参数。
  22. break;:
    • 跳出 switch 语句。
  23. default::
    • 默认情况(未实现的系统调用)。
  24. PRT_Printf("Unimplemented syscall.\n");:
    • 打印未实现的系统调用的消息。
  25. }:
    • 结束 switch 语句。
  26. } else {:
    • 如果异常类不是0x15。
  27. PRT_Printf("Catch a exception.\n");:
    • 打印捕获到异常的消息。
    • 空行,分隔代码块。
  28. }:
    • 结束 if-else 语句。
  29. }:
    • OsExcHandleFromLowElEntry 函数结束。

总结:

  • MyFirstSyscall 函数负责输出字符串。
  • OsExcHandleFromLowElEntry 函数负责处理异常,特别是识别和处理 SVC 调用(系统调用)。
  • 当捕获到 SVC 调用时,函数提取系统调用号和参数,调用相应的系统调用处理函数。
  • Lab4作业

作业

查找 启用FPU前异常出现的位置和原因。禁用FPU后PRT_Printf工作不正常,需通过调试跟踪查看异常发生的位置和原因elr_el1 esr_el1寄存器。

首先注释FPU,进入GDB调试:

进入异常向量表:

查看到ELR_EL1寄存器的值为0x400021e8,ESR_EL1寄存器的值为0x1fe0000。

五、总结

在实验过程中,对于 UniProton 系统更加熟悉了,并且通过自己去github 上查看了 libboundscheck 库,其选取并实现了常见的内存/字符串操作类的函数,如 memcpy_s、strcpy_s 等函数,并且处理边界检查函数的版本发布、更新以及维护,了解了这些开源项目的下载和如何自己去一步一步运行。

相关文章:

  • python——模块、包、操作文件
  • 如何选择 Flask 和 Spring Boot
  • 【数据结构入门训练DAY-21】信息学奥赛一本通T1334-围圈报数
  • 深入解析C++ STL Stack:后进先出的数据结构
  • 新书推荐——《游·思——看世界 上》孔祥超 著
  • React Ref引用机制解析
  • 指定文件夹随机筛出图像
  • 卷积神经网络常用结构
  • # 构建和训练一个简单的CBOW词嵌入模型
  • 密码学(1)LWE,RLWE,MLWE的区别和联系
  • 语法长难句
  • 星火燎原:Spark技术如何重塑大数据处理格局
  • 设计模式--工厂模式详解
  • ubuntu系统下部署使用git教程
  • 配置Intel Realsense D405驱动与ROS包
  • mysql数据库查看进程
  • 使用react的ant-design-pro框架写一个地图组件,可以搜索地图,可以点击地图获取点击的位置及经纬度
  • 【Deepseek学习大模型推理】MOONCAKE: A KVCache-centric Architecture 第一部分引言部分
  • springboot集成openfeign
  • How to install cuda-toolkit on Dell XPS 9560 with Linux mint 21
  • 2025年超长期特别国债24日首次发行
  • 人民日报整版聚焦第十个“中国航天日”:星辰大海,再启新程
  • 海南公布知识产权保护典型案例,一企业违规申请注册“中华”商标被处罚
  • 九江市人大常委会原党组成员、副主任戴晓慧主动交代问题,正接受审查调查
  • 消费维权周报丨上周合同纠纷类投诉多,合同未到期关闭门店等
  • 中远海运:坚决反对美方对中国海事物流及造船业301调查的歧视性决定