1 Star 0 Fork 0

WHC/xv6-course

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
41-intro-interrupt.org 4.92 KB
一键复制 编辑 原始数据 按行查看 历史
殊蕤 提交于 2023-11-18 19:56 . add

中断初始化及调用流程

tvinit 初始中断向量 IDT

  1. 设置了 IDT 表中的 256 个表项
    • 中断 i 的处理向量为 vectors[i]
    • 0x40 设置成系统调用 T_SYSCALL
  2. xv6 中断编号 ../../study/os/xv6-public/traps.h
    • 0-31 软件中断: divide error、 page fault
    • 32-63 硬件中断
    • 64 系统调用: syscall
  3. syscall 特殊处理
    • 设置 trap 位为 1 指定 syscall 时陷阱门,即处理系统调用时可以同时处理其它中断
    • 设置 syscall 调用权限 DPL_USER,阻止用户程序产生其它中断
      1. 例如用户不能产生设备中断
      2. 如果用户产生不合法的中断,会抛出 general protection fault, 即 int 13
  4. 特权等级切换
    • 用户态切换内核态,内核不能使用用户的堆栈,可能存在恶意攻击
    • 内核态切换用户态,从 TSS 中恢复用户堆栈,见 switchuvm
  5. 当 trap 发生时,处理器会做下面一些事
    • 用户态时,它会从任务段描述符中加载 esp 和 ss,把老的 ss 和 esp 压入新的栈中
    • 内核态时,上面的事件就不会发生。处理器接下来会把 eflags,cs,eip 压栈
    • 另外,对于某些 trap 来说,处理器会压入一个 errno
(gdb) si
=> 0x80105859 <trapret+7>:	add    $0x8,%esp
31	  addl $0x8, %esp  # trapno and errcode
(gdb) xv-ctx
ctx: cs=0x8 eip=0x80105859 ss=0x10 esp=0x8dffffe4 eflags=0x282
(gdb) si
=> 0x8010585c <trapret+10>:	iret
32	  iret
(gdb) xv-ctx
ctx: cs=0x8 eip=0x8010585c ss=0x10 esp=0x8dffffec eflags=0x282
(gdb) si
=> 0x0:	push   $0x24
0x00000000 in ?? ()
(gdb) xv-ctx
ctx: cs=0x1b eip=0x0 ss=0x23 esp=0x1000 eflags=0x202
(gdb) si
=> 0x5:	push   $0x1c
0x00000005 in ?? ()
(gdb)
=> 0xa:	push   $0x0
0x0000000a in ?? ()
(gdb)
=> 0xc:	mov    $0x7,%eax
0x0000000c in ?? ()
(gdb)
=> 0x11:	int    $0x40
0x00000011 in ?? ()
(gdb) xv-ctx
ctx: cs=0x1b eip=0x11 ss=0x23 esp=0xff4 eflags=0x202
(gdb) si
=> 0x80105f57 <vector64>:	push   $0x0
319	  pushl $0
(gdb) xv-ctx
ctx: cs=0x8 eip=0x80105f57 ss=0x10 esp=0x8dffffec eflags=0x202
(gdb) x/8x 0x8dffffec
0x8dffffec:	0x00000013	0x0000001b	0x00000202	0x00000ff4
0x8dfffffc:	0x00000023	Cannot access memory at address 0x8e000000
(gdb)

int 指令

在 x86 中,中断处理程序的入口在中断描述符表(IDT)中被定义。这个表有 256 个表项, 每一个都提供了相应的 cs 和 eip

程序进行一个系统调用,它需要调用 int n 指令,这里 n 就是 IDT 的索引

int 0x40

int 指令进行下面一些步骤:

  1. 从 IDT 中获得第 n 个描述符,n 就是 int 的参数
  2. 检查 cs 的域 CPL <= DPL,DPL 是描述符中记录的特权级
  3. 如果目标段选择符的 PL < CPL,就在 CPU 内部的寄存器中保存 esp 和 ss 的值
  4. 从一个 TSS 任务段描述符中加载 ss 和 esp, 记做 esp0 cpus[0].ts.esp0
  5. push ss
  6. push esp
  7. push eflags
  8. push cs
  9. push eip
  10. 清除 eflags 的一些位
  11. 设置 cs 和 eip 为描述符中的值

Before int

ctx: cs=0x1b eip=0x11 ss=0x23 esp=0xff4 eflags=0x202

After int

(gdb) x/i $pc
=> 0x11:        int    $0x40
(gdb) si
=> 0x80105f57 <vector64>:       push   $0x0
319       pushl $0
(gdb) x/8x $esp
0x8dffffec:     0x00000013      0x0000001b      0x00000202      0x00000ff4
0x8dfffffc:     0x00000023      Cannot access memory at address 0x8e000000
(gdb) xv-ctx
ctx: cs=0x8 eip=0x80105f57 ss=0x10 esp=0x8dffffec eflags=0x202
(gdb)

Stack layout

                   | ??????            | <- esp0(0x8e000000) ss(0x10) from TSS
Present on       / | ss     0x00000023 |
privilege change \ | esp    0x00000ff4 |
                   | eflags 0x00000202 |
                   | cs     0x0000001b |
                   | eip    0x00000013 | <- esp(0x8dffffec)
                   |                   |

操作系统可以使用 iret 指令来从一个 int 指令中返回

  • 它从栈中弹出 int 指令保存的值
  • 然后通过恢复保存的 eip 的值来继续用户程序的执行

int 指令到 trap 流程分析

  1. int 0x40 => 跳转到中断处理向量
    • vector64
      vector64:
        pushl $0
        pushl $64
        jmp alltraps
              
    • vector64 跳转 alltraps
  2. alltraps 是所有 trap 的入口
    • 设置 trapframe
    • 修改寄存器
    • 返回内核态, call trap
  3. trap 中判断系统调用 syscall()

总结

  1. 初始化 IDT, tvinit 256 vectors.S
  2. 加载 IDTR, main.c -> mpinit -> idtinita
  3. int 语义
    • int 0x40
    • vector64
    • alltraps 准备 trampframe
    • trap(tf)
    • tf.trapno => syscall()
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C
1
https://gitee.com/whc_softHardware/xv6-course.git
git@gitee.com:whc_softHardware/xv6-course.git
whc_softHardware
xv6-course
xv6-course
master

搜索帮助

D67c1975 1850385 1daf7b77 1850385