trap(系统调用)的几个疑问

62 views
Skip to first unread message

mei shaoming

unread,
Jan 1, 2011, 11:31:15 AM1/1/11
to xv6...@googlegroups.com
Hi all, 

现在在做lab3的ex4, 起动hello之后, cprintf->sys_cputs->int 0x30, 用qemu-gdb跟踪后发现执行完
这条int指令之后系统就跳到一个无关的位置上, 然后重启了. 跟踪的过程中发现自己对trap的过程仍然并
不了, 有两个疑问, 希望能与大家共同讨论下.

1. 每个任务都有自己的tss(我的理解是这样的), 第一个任务hello的tss是在哪里设置的?

2. 启动hello后, cprintf->sys_cputs->int 0x30, 此时从当前任务hello的tss中拿到ss0和esp0来设置
好堆栈, 然后将ss,esp,eflags,cs,eip,(error code)等压入堆栈, 接着cpu从idt[]的第0x30条拿到entry
的信息, 并根据entry里指定的cs:eip来跳到相应的地址执行. 
这个过程, 权限是如何从3变为0的?

--
boot

dave

unread,
Jan 2, 2011, 3:44:56 AM1/2/11
to xv6...@googlegroups.com
在 2011-01-02日的 00:31 +0800,mei shaoming写道:

> Hi all,
>
>
> 现在在做lab3的ex4, 起动hello之后, cprintf->sys_cputs->int 0x30, 用
> qemu-gdb跟踪后发现执行完
> 这条int指令之后系统就跳到一个无关的位置上, 然后重启了. 跟踪的过程中发
> 现自己对trap的过程仍然并
> 不了, 有两个疑问, 希望能与大家共同讨论下.
>
>
> 1. 每个任务都有自己的tss(我的理解是这样的), 第一个任务hello的tss是在哪
> 里设置的?
很不幸运的是JOS里面并不是这样,而是所有进程共用一个TSS,所有进程共用一个
内核栈,所以切换任务的时候不涉及TSS切换,具体代码在kern/trap.c
idt_init()里面设置的。


>
> 2. 启动hello后, cprintf->sys_cputs->int 0x30, 此时从当前任务hello的tss
> 中拿到ss0和esp0来设置
> 好堆栈, 然后将ss,esp,eflags,cs,eip,(error code)等压入堆栈, 接着cpu从
> idt[]的第0x30条拿到entry
> 的信息, 并根据entry里指定的cs:eip来跳到相应的地址执行.
> 这个过程, 权限是如何从3变为0的?

权限的改变是CPU自动完成的,不是程序员手工指定的。中间如果有任何段的页的
错误trap过程就会中断掉哦。
只根据你的描述我猜测是不是eip设置错误之类的仅仅是猜测,可以相关把代码如
trapentry.s的部分拿出来给大家看看。
> --
> boot


mei shaoming

unread,
Jan 2, 2011, 9:57:15 PM1/2/11
to xv6...@googlegroups.com
thx, 问题找到, 是由于idt描述符里的ss没有设置对, _alltraps  中也要把ds设置成内核态的数据段. 总结如下:
iret指令
* 实模式
从栈中弹出eip, cs和eflags

* 保护模式
NT==0, 从中断返回不会发生任务切换, 返回后的代码权限必须小于或等于当前代码
(CS的最低两位). 如果目标代码的权限更低, 则会从栈上再弹出esp和ss.
NT==1, iret会使得程序从call或int引起的任务切换中返回回来, 执行iret引起的更
新状态保存在任务的tss中.

- kernel -> hello, 启动第一个用户任务
  每个进程(env)在自己的Env结构里都有一个Trapframe, 并在env_alloc和load_icode
  里初始化这个Env.Trapframe结构, 最终将其传入 env_pop_tf中, 此时Trapframe如下:
{{{
struct Trapframe
+-------------+
|-----   ss   |  void env_pop_tf(struct Trapframe *tf)
+-------------+  {
|    esp      |   __asm __volatile("movl %0,%%esp\n"
+-------------+   "\tpopal\n"
|    eflags   |   "\tpopl %%es\n"
+-------------+   "\tpopl %%ds\n"
|----   cs    |   "\taddl $0x8,%%esp\n" /* skip tf_trapno and tf_errcode */
+-------------+   "\tiret"
|    eip      |   : : "g" (tf) : "memory");
+-------------+   panic("iret failed");  /* mostly to placate the compiler */
|    err      |  }
+-------------+  movl %0, %esp就把栈指针esp指向了Trapframe的最低地址, 接着弹出
|    trapno   |  各寄存器. 在iret执行时, esp指向Trapframe.eip; iret命令弹出
+-------------+  eip,cs,eflags,esp,ss. 此时切换到了用户层代码的cs和栈中.
|----   ds    |
+-------------+
|----   es    |
+-------------+
| ~~~regs~~~  |
+-------------+ <- esp
  }}}
- hello -> kernel, 系统调用trap进入内核态
  执行int 0x30命令后, cpu从idt的第0x30条目里取得信息. 该条目里内容:
{{{
  +-----------------------------------------+
  | vector[31...16]  |P|DPL| type |         |
  +-----------------------------------------+
  |        ss        |   vector[15...0]     |
  +-----------------------------------------+
  }}}
  cpu读取其中的段选择符ss到cs中, 然后再读取vector, 并跳到cs:vector地址开始执行
  如此, 便跳到了trapentry.S中的vector48上. 用DPL里的值作为新的特权级, 由于DPL
  小于CPL, 特权级发生切换, 引起堆栈切换. 从tr寄存器指定的tss里读取ss0和esp0作
  为新的栈, 并将旧的ss,esp,eflags,cs,eip等压入新栈(返回的时候要用到). 这里已经
  开在新栈中构建一个新的Trapframe了. 
  接着跳到trapentry.S中的vector48继续构建Trapframe, 构建完成之后 call trap跳入
  处理阶段.
  int 0x30执行时, 有了新的cs, 在trapentry.S中还要新的ds, 它们分别指向内核态的
  代码段和数据段.


Í



--
梅绍明

Yan Hong

unread,
Jan 5, 2011, 3:56:38 AM1/5/11
to xv6...@googlegroups.com
全局只有一个TSS。Intel搞出TSS是为从硬件上支持任务管理,也就是期待OS用这个机制来保存每个任务的硬件上下文。但是一般OS为了保持多平台上的可移植性,通常会用软件实现任务管理,但在Ring切换时必需从TSS里取得相应级别的栈信息,所以迫不得已使用一个TSS,这种情况有点类似分段机制。如果各进程使用不同的内核栈,也不非要很多TSS,切换进程时同时改掉TSS段里关于内核栈部分的字段即可。

Trapframe是保存环境的硬件上下文,env_create好像只会初始化几个可以确定的字段。徒手造进程时才会用到load_icode,以后通过sys_exofork产生新进程,进程的硬件上下文是copy父进程的。

IDT项中的DPL不是用来决定新的CPU级别的,决定新CPU执行级别的是IDT中segment
selector在GDT中索引得到的segment
descriptor中的DPL字段。IDT项中的DPL是用来设置是否允许通过编程来触发该异常(中断)。如果当前运行级别CPL大于IDT项中的DPL,则通过int之类的指令进到这里时会引发General
Protection异常。

关于中断发生时硬件详细的动作,可以看看ULK里关于中断的那一章。

Reply all
Reply to author
Forward
0 new messages