X86中断栈执行过程分析

目录

1. 中断是什么?

2. 中断现场状态具体指什么?

3. 函数调用栈实现

4. 中断栈实现

4.1 执行main函数压栈

4.2 X86硬件自动压栈

4.3 中断向量压栈

 4.4 压入通用状态寄存器

4.5 压入trapframe指针,以及Call函数调用

4.6 C语言访问trapframe指针

5. 中断形式的内核进入与退出

目录

1. 中断是什么?

2. 中断现场状态具体指什么?

3. 函数调用栈实现

4. 中断栈实现

4.1 执行main函数压栈

4.2 X86硬件自动压栈

4.3 中断向量压栈

 4.4 压入通用状态寄存器

4.5 压入trapframe指针,以及Call函数调用

4.6 C语言访问trapframe指针

5. 中断形式的内核进入与退出

 5.1 CPU正在执行用户程序,突然来了中断

​5.2 CPU正在执行用户程序,用户调用系统调用

5.3 内核栈出栈



1. 中断是什么?

        一个程序正在执行自己的过程中,突然被一个事件给打扰了,并且去处理这个事件,处理完后又执行自己。用比较形象的例子描述,就是我正在认真看书学习,突然我妈喊我吃饭,然后我去吃饭了,吃完饭我又回来学习。如图是中断的执行过程。

         那么对于顺序执行的程序来说,CPU是如何处理中断的呢?CPU虽然是一直取指令,执行指令;但是CPU并不是不管外部事件的,它会每执行一条指令后查询有没有外部事件发生,如果有就到该事件的处理函数中执行,执行完就回到顺序执行的程序中。就拿如下的程序片段举例子。

int main()
{
L1:	int a=1;
L2:	int b=2;
L3:	int c=a+b;
	return c;
}
void ISR()
{
	cout<<"I am a interrupt";
}

        正常的执行顺序是L1,L2,L3;假设突然来了中断,中断发生的时刻是在L1和L2之间,那么程序“隐式”的执行结果类似下面的样子。 

int main()
{
L1:	int a=1;
ISR();
L2:	int b=2;
L3:	int c=a+b;
	return c;
}
void ISR()
{
	cout<<"I am a interrupt";
}

2. 中断现场状态具体指什么?

        在L1和L2之间被CPU自动的加入了一段ISR()函数的调用,这种过程很类似函数调用,但是并不是函数调用。在用户程序执行中是感受不到中断的存在,因为中断作为一个事件随时可能发生,那么谁统一管理用户程序的执行和中断的执行呢?这个责任自然放在了CPU上。这里需要解决的核心问题就是,中断现场的保护和恢复。用户程序执行的过程中有它的状态,状态主要表现在三个方面:1)进行逻辑或者算术计算的通用状态,表现在通用寄存器EAX,EBX,ECX等。2)下一条需要执行的指令位置CS\IP等。3)栈位置栈顶ESP和栈底EBP。

3. 函数调用栈实现

        前两个状态很好理解,这里解释下栈状态。从C语言提出函数过程的概念后,就引入了栈的技术,栈底EBP指向某个函数第一行代码内存位置,栈顶ESP指向这个函数内部某行局部变量内存地址,拿以下函数调用举例子。在下面的例子执行过程中,函数传参和局部变量均使用栈内存作为变量的存储空间。因此栈的状态代表了函数的传参和局部变量的存储状态,这就是函数调用栈的目的。

int main()
{
L1:	int a=1;
L2:	int b=2;
L3: fun(a);
L4:	int c=a+b;
	return c;
}
void ISR()
{
	cout<<"I am a interrupt";
}
void int fun(int aa)
{
	int p = 1;
	int q = 2;
	aa = p+q;
}

        对应的汇编代码如下 

//%ebp=0,%esp=0
label main:
push %ebp;//main’s  EBP
mov %esp,%ebp;
pushal;//main’s  saved register
push $1;//Argument#a
push $2;//Argument#b
mov $1,%eax;
push %eax;//Argument#aa
push L4;//Return Address L4
jump fun;
push %ebp;//fun's  EBP
mov %esp,%ebp;
pushal;//fun’s saved register
push $1;//Argument#p,
push $2;//Argument#q,
mov 0x0c(%ebp),%ebx;
mov 0x10(%ebp),%edx;
add %ebx,%edx;
mov %ebx,-0x08(%ebp);//aa=p+q

         main函数和fun函数调用栈对应如下

 

         上面代码显示了main()函数中调用fun()函数过程,其中局部变量a\b\p\q均压入栈内存中,形式参数aa压入栈中。函数调用是通过栈顶指针ESP和栈底指针EBP来实现的。函数传参是通过访问栈变量内存位置实现的,如汇编代码在mov %ebx,-0x08(%ebp)。对于函数中的局部变量和形参寻址,在高级语言C中并不能看到这样的过程,然而在汇编语言中就能看到,这是因为从高级语言到汇编语言经过了重要的编译步骤,这其中需要解决局部变量存储和形参传递,这些都是编译器帮助我们完成的。如果不使用编译器,想要实现一个函数过程调用,就需要程序设计者自己实现函数局部参数入栈出栈、形参的内存寻址等。

4. 中断栈实现

        除了函数调用栈,用栈来描述传参的形参寻址和局部变量寻址,那么从用户程序到中断程序是否也有类似的地方呢?答案是肯定的,就是中断的实现也要在栈上实现。

        由于中断发生的时刻不确定,中断打断的代码位置也是未知的,所以栈的位置不确定。栈的位置只有中断发生时的CPU知道,因此CPU会自动进行入栈出栈如下信息:1)栈位置栈顶ESP和栈底EBP。2)下一条需要执行的指令位置CS\IP等。

        当栈的位置确定后,当前时刻的逻辑或者算术计算的通用状态(通用寄存器EAX,EBX,ECX等),用户可以通过汇编指令进行入栈出栈操作。

        假设不考虑内核状态和用户状态,看看如下代码突然来了中断,中断发生的时刻是在L1和L2之间,那么程序“隐式”的执行结果类似下面的样子。

int main()
{
L1:	int a=1;
ISR_timer();
L2:	int b=2;
L3:	int c=a+b;
	return c;
}
void ISR_timer()
{
	cout<<"I am a interrupt";
}

        这段程序的中断栈执行流程是什么?他又是如何与代码相关联起来的呢? 

4.1 执行main函数压栈

4.2 X86硬件自动压栈

4.3 中断向量压栈

        中断执行代码

.globl vector32
vector32:
  pushl $0
  pushl $32
  jmp __alltraps

        压栈结果如下

 4.4 压入通用状态寄存器

        汇编代码如下

.text
.globl __alltraps
__alltraps:
    # push registers to build a trap frame
    # therefore make the stack look like a struct trapframe
    pushl %ds
    pushl %es
    pushl %fs
    pushl %gs
    pushal

    # load GD_KDATA into %ds and %es to set up data segments for kernel
    movl $GD_KDATA, %eax
    movw %ax, %ds
    movw %ax, %es

    # push %esp to pass a pointer to the trapframe as an argument to trap()
    pushl %esp

    # call trap(tf), where tf=%esp
    call trap
    # pop the pushed stack pointer
L27:popl %esp

        压栈结果

4.5 压入trapframe指针,以及Call函数调用

        汇编代码如下

.text
.globl __alltraps
__alltraps:
	……
    # push %esp to pass a pointer to the trapframe as an argument to trap()
    pushl %esp

    # call trap(tf), where tf=%esp
    call trap
    # pop the pushed stack pointer
L27:popl %esp

备注:
call指令
call 0x12345
调用0x12345这个地址,可分解为:
pushl %eip ——> 将cpu下一条要执行的指令压入栈中
movl $0x12345, %eip ——> eip = 0x12345
注意:CPU下一条指令将会从地址0x12345中取。

        压栈结果

 

4.6 C语言访问trapframe指针

         根据函数调用栈中的分析结果,每个形式参数位置是固定的,因此trapframe的指针tf位置也是固定的。这个固定位置在汇编指令中是-8(EBP),那么tf指针代表了整个trapframe结构体的内容,也就不难理解tf=%esp了。

5. 中断形式的内核进入与退出

        在第4节中讨论了一般的内核态程序中断栈的执行过程,但是对于X86来说,具有用户态和内核态程序的区别。如果用户态的程序来了中断大致是怎样的流程呢?大致的流程如下:

 5.1 CPU正在执行用户程序,突然来了中断

        此时用户态的栈要切换到内核状态,与第4节不同的是在内核栈中压入用户态栈的位置信息,其他的过程基本上是相同的。

5.2 CPU正在执行用户程序,用户调用系统调用

        触发中断处理,此时用户态的栈要切换到内核状态,与第4节不同的是在内核栈中压入用户态栈的位置信息,其他的过程基本上是相同的。

5.3 内核栈出栈

        不管是什么形式触发的内核栈压栈,当内核中断程序执行完成之后,内核栈就会执行栈的退出和切换。在图中,tf_regs/gs/fs/es/ds是__trapret函数中内核程序出栈;图中iret指令触发CPU让内核栈出栈操作,将tf_ss/tf_esp/tf_eflags/tf_cs/tf_eip/tf_err出栈。

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值