xv6 Traps, interrupts, and drivers

  • 学习Traps, interrupts, and drivers

1.保护性控制转移
  异常和中断都是保护性控制转移,让处理器从用户模式切换到内核模式(CPL=0),这样用户代码不会对内核或者其他环境造成影响。中断是由外部异步事件导致的处理器保护性控制转移,比如外设I/O的通知信号。异常是正在运行的代码同步事件导致的控制转移,比如除零或者访问无效内存。

  为了确保控制转移真的被保护,x86平台上有两种机制:

  • 中断描述符表。处理器确保中断和异常引起内核进入特定的入口点。

    • x86允许最多256个不同的中断和异常入口点,每个都有一个独特的中断向量。向量是0到255的数字,中断向量由中断源决定:不同设备,错误条件以及请求内核产生中断的不同向量。CPU使用向量作为索引访问处理器中断描述符表(IDT),这个是内核在内核私有内存设置的。处理器从合适的入口处加载:
      • 加载到EIP寄存器的值,这是个指向处理这种类型异常的内核代码的指针
      • 加载到CS寄存器的值,包含特权级0-1
  • 任务状态段。处理器需要存放中断异常发生之前的旧的处理器状态,比如原始EIP值和CS值,这样可以之后还原到之前的状态。保存这个的位置必须要受保护不能随意修改。

  因此,x86处理器处理中断时会导致特权级由用户转为内核,也会将堆栈切换到内核内存中。任务状态段具体指明段选择子和堆栈的地址。处理器将SS, ESP, EFLAGS, CS, EIP和可选错误码压入堆栈中,之后从中断描述符中加载CS和EIP,设置ESP和SS指向新的堆栈

1.1.Interrupts

  • External (hardware generated) interrupts.
    • External interrupts are received through pins on the processor.
  • Software-generated interrupts.
    • The INT n instruction permits interrupts to be generated from within software by supplying an interrupt vector number as an operand.

1.2.Exceptions

  • Processor-detected program-error exceptions.
  • Software-generated exceptions.
  • Machine-check exceptions

1.2.1.Program-Error Exceptions
  The processor generates one or more exceptions when it detects program errors during the execution in an application program or the operating system or executive. Intel 64 and IA-32 architectures define a vector number for
each processor-detectable exception. Exceptions are classified as faults, traps, and aborts.

  • Faults — A fault is an exception that can generally be corrected and that, once corrected, allows the program to be restarted with no loss of continuity. When a fault is reported, the processor restores the machine state to the state prior to the beginning of execution of the faulting instruction. The return address (saved contents of the CS and EIP registers) for the fault handler points to the faulting instruction, rather than to the instruction following the faulting instruction.
  • Traps — A trap is an exception that is reported immediately following the execution of the trapping instruction.Traps allow execution of a program or task to be continued without loss of program continuity. The return address for the trap handler points to the instruction to be executed after the trapping instruction.
  • Aborts — An abort is an exception that does not always report the precise location of the instruction causing the exception and does not allow a restart of the program or task that caused the exception. Aborts are used to report severe errors, such as hardware errors and inconsistent or illegal values in system tables.

1.2.2.Software-Generated Exceptions
  The INTO, INT1, INT3, and BOUND instructions permit exceptions to be generated in software. These instructions allow checks for exception conditions to be performed at points in the instruction stream. For example, INT3 causes a breakpoint exception to be generated.

1.2.3.Machine-Check Exceptions
  The P6 family and Pentium processors provide both internal and external machine-check mechanisms for checking the operation of the internal chip hardware and bus transactions.

2.xv6 trap/interrupts

  trap(实现系统调用)和中断主要区别:

  • trap是由当前进程触发的,中断是由设备产生的,可能和当前进程没有关系。
  • 中断门和trap门只有一位不相同,具体是中断门清除 EFLAGS 寄存器的 IF 位。所以运行trap处理程序时,还是会响应其它中断。

2.1.system call

  When a process needs to invoke a kernel service, it invokes a procedure call in the operating system interface. Such a procedure is called a system call. The system call enters the kernel; the kernel performs the service and returns. Thus a process alternates between executing in user space and kernel space.
  The kernel uses the CPU’s hardware protection mechanisms to ensure that each process executing in user space can access only its own memory. The kernel executes with the hardware privileges required to implement these protections; user programs execute without those privileges. When a user program invokes a system call, the hardware raises the privilege level and starts executing a pre-arranged function in the kernel.

  Each process’s address space maps the kernel’s instructions and data as well as the user program’s memory. When a process invokes a system call, the system call executes in the kernel mappings of the process’s address space. This arrangement exists so that the kernel’s system call code can directly refer to user memory. In order to leave room for user memory to grow, xv6’s address spaces map the kernel at high addresses, starting at 0x80100000.
在这里插入图片描述系统调用流程图:在这里插入图片描述
2.2.Interrupts
在这里插入图片描述
回答以上问题:
Q1: how to find interrupt handler?
1.Hardware maps interrupt type to interrupt number in the traps.h(xv6).
x86 Interrupt numbers:
在这里插入图片描述

  1 // x86 trap and interrupt constants.
  2 // Processor-defined:                                                                                                       
  4 #define T_DIVIDE         0      // divide error
  5 #define T_DEBUG          1      // debug exception
  6 #define T_NMI            2      // non-maskable interrupt
  7 #define T_BRKPT          3      // breakpoint
  8 #define T_OFLOW          4      // overflow
  9 #define T_BOUND          5      // bounds check
 10 #define T_ILLOP          6      // illegal opcode
 11 #define T_DEVICE         7      // device not available
 12 #define T_DBLFLT         8      // double fault
 13 // #define T_COPROC      9      // reserved (not used since 486)
 14 #define T_TSS           10      // invalid task switch segment
 15 #define T_SEGNP         11      // segment not present
 16 #define T_STACK         12      // stack exception
 17 #define T_GPFLT         13      // general protection fault
 18 #define T_PGFLT         14      // page fault
 19 // #define T_RES        15      // reserved
 20 #define T_FPERR         16      // floating point error
 21 #define T_ALIGN         17      // aligment check
 22 #define T_MCHK          18      // machine check
 23 #define T_SIMDERR       19      // SIMD floating point error
 24 
 25 // These are arbitrarily chosen, but with care not to overlap
 26 // processor defined exceptions or interrupt vectors.
 27 #define T_SYSCALL       64      // system call
 28 #define T_DEFAULT      500      // catchall
 29 
 30 #define T_IRQ0          32      // IRQ 0 corresponds to int T_IRQ
 31 
 32 #define IRQ_TIMER        0
 33 #define IRQ_KBD          1
 34 #define IRQ_COM1         4
 35 #define IRQ_IDE         14
 36 #define IRQ_ERROR       19
 37 #define IRQ_SPURIOUS    31

2.OS sets up Interrupt Descriptor Table ( also called interrupt vector) at boot.

  • IDT is in memory
  • Each entry is an interrupt handler
  • OS lets hardware know IDT base
  • Defines all kernel entry points
  • Hardware finds interrupt handler using interrupt number as index into IDT
    • handler = IDT[intr_number]

流程图:
在这里插入图片描述
2.1.Setting Up the IDT
  Set up the IDT to handle interrupt vectors 0-31 (the processor exceptions). The header files inc/trap.h and kern/trap.h contain important definitions related to interrupts and exceptions that you will need to become familiar with. The file kern/trap.h contains definitions that are strictly private to the kernel, while inc/trap.h contains definitions that may also be useful to user-level programs and libraries.

2.2.The overall flow of control

      IDT                   trapentry.S         trap.c
   
+----------------+                        
|   &handler1    |---------> handler1:          trap (struct Trapframe *tf)
|                |             // do stuff      {
|                |             call trap          // handle the exception/interrupt
|                |             // ...           }
+----------------+
|   &handler2    |--------> handler2:
|                |            // do stuff
|                |            call trap
|                |            // ...
+----------------+
       .
       .
       .
+----------------+
|   &handlerX    |--------> handlerX:
|                |             // do stuff
|                |             call trap
|                |             // ...
+----------------+

2.3.代码实现
  每个异常或中断都应该在 trapentry.S 有自己的处理函数handler,trap_init() 用来初始化IDT。每个异常处理是一个保存在堆栈上的 struct Trapframe ,调用 trap() 指向这个 Trapframe。trap() 之后处理异常中断。
  在kern/trapentry.S 中定义有两个宏TRAPHANDLER/TRAPHANDLER_NOEC,如下所示:

#define TRAPHANDLER(name, num)                      \
    .globl name;        /* define global symbol for 'name' */   \
    .type name, @function;  /* symbol type is function */       \
    .align 2;       /* align function definition */     \
    name:           /* function starts here */      \   
    pushl $(num);                           \
    jmp _alltraps

#define TRAPHANDLER_NOEC(name, num)                 \
    .globl name;                            \
    .type name, @function;                      \
    .align 2;                           \
    name:                               \
    pushl $0;                           \
    pushl $(num);                           \
    jmp _alltraps

  这两个宏的作用:
  利用TRAPHANDLER和TRAPHANDLER_NOEC宏来实现interrupt handler。这两个宏所生成的代码的作用是,根据传入参数定义handler的“标签”,然后进行一些压栈操作。如果系统没有压入错误码,就压入一个0;然后压入中断号,接下来跳转到一个名为_alltrap的函数。

  每个handler都要在栈上构造一个结构体 struct trapframe (见inc/trap.h),并调用 trap.c 中的 trap()。

 78 _alltraps:
 79     pushl %ds
 80     pushl %es
 81     pushal
 82     movw $GD_KD,%ax
 83     movw %ax,%ds
 84     movw %ax,%es
 85     pushl %esp
 86     call trap 
  • 值压入堆栈构造一个stuct Trapframe
  • 加载 GD_KD 到 %ds 和 %es
  • pushl %esp 将指向 Trapframe 的指针作为 trap() 的参数
  • 调用 trap function

  当中断发生后,CPU会自动切换到一个新的栈,然后自动地将当前运行程序的EFLAGS寄存器压入栈中,然后压入CS和IP,然后对于一些特殊的异常CPU还会压入error code,然后才会跳转到中断服务程序的第一条指令 – 这第一条指令就是在handler中。
  然后,handler就开始压栈了。为了保证Trapframe结构的一致性和完整性,对于CPU没有压入error code的情况,TRAPHANDLER_NOEC定义的handler首先压入一个0用于占位。 接着,TRAPHANDLER和TRAPHANDLER_NOEC都压入了trap number。
  最后,两者都跳转到_alltraps,这是每个handler都一样的代码,它的作用依然是继续补充Trapframe结构,注意压栈顺序要和Trapframe结构中的顺序一致。经过上述一系列操作之后,一个完整的Trapframe结构诞生了,也就意味着保存现场的工作完成。

  gdb 调试_alltraps:
  设置断点:

+ symbol-file obj/kern/kernel
(gdb) b *0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
(gdb) b *0x100025 
Breakpoint 2 at 0x100025
(gdb) b *0xf0105fbb          //查看obj/kern/kernel.asm: _alltraps 
Breakpoint 3 at 0xf0105f4e: file kern/trapentry.S, line 57.

在这里插入图片描述
如上所示,入栈之前ds=es=0x23,此时栈地址为0xefffffe0和0xefffffdc,执行push %ds 和push %es之前和之后,地址数据:

 //push %ds
(gdb) x/x 0xefffffe0 
0xefffffe0:     0xf01d3000
(gdb) si
=> 0xf0105fbc <_alltraps+1>:    push   %es
_alltraps () at kern/trapentry.S:84
(gdb) x/x 0xefffffe0
0xefffffe0:     0x00000023

//push %es
(gdb) x/x 0xefffffdc
0xefffffdc:     0xf01000e5
(gdb) si
=> 0xf0105fbd <_alltraps+2>:    pusha  
_alltraps () at kern/trapentry.S:85
(gdb) x/x 0xefffffdc
0xefffffdc:     0x00000023

trap_init() 使用这些handler的地址来初始化IDT。

74     SETGATE(idt[T_DIVIDE], true, GD_KT,t_divide, 0);
75     SETGATE(idt[T_DEBUG], true, GD_KT,t_debug, 0);
76     SETGATE(idt[T_NMI], false, GD_KT,t_nmi, 0);
77     SETGATE(idt[T_BRKPT], true, GD_KT,t_brkpt, 3);
78     SETGATE(idt[T_OFLOW], true, GD_KT,t_oflow, 0);
       ...

Q2:why switch stack?
在这里插入图片描述

参考资料:
Documents/work/code/jos/lab3
https://linbo.github.io/2017/10/22/xv6-syscall_1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值