说说中断上下文的切换
2016-04-24 23:14
162 查看
在深入理解linux内核课程结束之际,我觉得对中断过程的理解,太浅薄。于是又重新将孟宁老师的系统中断部分再一次的听讲了一遍,并希望能够从中理解更多有关中断的处理过程。
当应用程序在应用层调用系统调用时,比如调用了read函数,那么经过库函数sys_call,将会通过int 0x80进入到内核中。此时会发生什么呢?
首先,因为是中断过程,硬件会保存部分现场。硬件保存的内容包括哪些呢?
用户态栈的栈顶值
当时的状态字
当时的cs:eip的值
保存的位置在栈中保存。保存到了用户的栈上。
如果保存到了用户的栈上,那么此时sp的值到底还是不是指向栈顶的位置呢?也许是,也许不是。
特别值得提出的是,对sp的保存,也保存到了栈中,这是如何完成的呢?在扩展中进一步的分析。
保存了状态值。这个状态是在eflag寄存器的值。看来这个寄存器的值对硬件是十分重要的。虽然在软件编程的过程中,我们不需要涉及到,但是他的值可能会影响很多程序的走向。保存eflag的值,接下来更换新的eflag的时候,将会指示当前程序的模式是内核模式了。
保存eip的值,当我们返回时将会继续执行应用程序,只要把他弹出来即可。这里进一步的分析。cs:eip的值,在应用层指向了用户空间的代码段。那么保存之后,将会指向内核的代码段中的语句。cs:eip的值更新后,将会自动的指向内核空间。cs:eip的值肯定会被更新到0xC000 000之上的位置中。
刚才我们看到int 0x80保存这些值到栈中,如何保存的呢? 老师在课程中讲到是硬件完成的。硬件是如何完成的呢?不知道。这需要查芯片手册估计才能明确。
另外,int 0x80保存了sp的值到栈中,但是没有保存bp的值。是不需要保存吗?显然不是,因为用户空间的栈和内核空间的栈是两个部分。现在推论不是硬件保存的,而是软件保存的,在后文中应该可以看到。此处存疑。
那么为什么这里还需要保存现场呢?
原因是在硬件保存现场中,保存的寄存器的值还不够,还要进一步的保存。
在代码中我们找到了SAVE_ALL的调用之处:
代码位置:
arch/x86/kernel/entry.s
代码的分析以后再分析。
贴出SAVE_ALL的实现:
在这段代码中,保存了相关寄存器的值。
他们一次是:ES,DS,EAX,EBP,EDI,ESI,EDX,ECX,EBX等等。从这里寄存器的顺序可以知道压栈的最后压入的是ebx,这里压入的栈是内核栈。
个人理解,什么时候将sp指向了内核中的sp呢?内核中的sp的位置在何处呢?
在开始,硬件只是保存了sp的值是不是除了保存sp的值之外,还将sp的值给更新了呢?更新成为了指向内核栈的栈顶的位置。因为只有这样,SAVE_ALL的时候,才会是保存的用户的寄存器的值,保存到了内核栈中。这样才能说得通。
目前没有查到,但是所有信息都在内核态中这个结论是和其他人说的一致的。
在老师的讲义中,老师提到了int 0x80的具体细节:
第一步硬件细节:
把cs:eip的值存储内核栈中,
把ss:esp的值存储内核栈中,
把eflags的值存储内核栈中,
第二步硬件细节:
更新cs:eip的值为0x80的中断服务函数
第三步硬件细节:
将ss:esp的值指向了内核栈中的栈顶位置。
第四步软件细节:
SAVE_ALL
在SAVE_ALL中我们看到了都是pushl的操作,其实是将这些值都存储进入了内核栈中。并且存储的部分寄存器的值,将会起到输入参数的功能。
当中断服务函数完成后,
第五步软件细节:
RESTORE_ALL
与SAVE_ALL的值是对应的。
第六步软件细节:
iret
这个iret与我们函数的ret是不同的,他是与上述第一步的硬件的细节相互对应的。
将会从内核栈中将:
eflags
ss:esp
cs:eip
这三个值依次恢复到寄存器中,并换回到用户态中来。
那么pop %esp的时候,是否能够完成呢?
pop的过程是
将值弹出到sp中,然后对sp的值+2.显然,这里恢复出来的sp的值不是存储在栈中的值而是栈中的值加上2.
但是,这是不是意味着我们这里的做法是错误的呢?也不一定。因为我们前面的压栈,如果是和这个对应的话,那么就是对的。
在硬件存储用户空间的sp的时候,将sp的值首先减去2,然后在存储到内核栈中,这样在iret的时候直接pop才不会出错。
现在的问题是,内核的sp如何知道他的位置的呢?因为内核栈是我们在fork的时候创建的。这是我还不懂的地方,需要继续查找。
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&id=4739291&uid=14528823
系统中断
在课程中,老师以系统中断为例子讲解。这里我也从系统中断开篇。当应用程序在应用层调用系统调用时,比如调用了read函数,那么经过库函数sys_call,将会通过int 0x80进入到内核中。此时会发生什么呢?
首先,因为是中断过程,硬件会保存部分现场。硬件保存的内容包括哪些呢?
用户态栈的栈顶值
当时的状态字
当时的cs:eip的值
保存的位置在栈中保存。保存到了用户的栈上。
如果保存到了用户的栈上,那么此时sp的值到底还是不是指向栈顶的位置呢?也许是,也许不是。
特别值得提出的是,对sp的保存,也保存到了栈中,这是如何完成的呢?在扩展中进一步的分析。
理解与扩展
通过上述的硬件保存的值,我们可以知道保存了栈顶值,从而可以推测出在内核态中栈顶值将会是另外一个,也就说,内核中仍然有一个栈,是和用户态中的栈不是一起的。保存了状态值。这个状态是在eflag寄存器的值。看来这个寄存器的值对硬件是十分重要的。虽然在软件编程的过程中,我们不需要涉及到,但是他的值可能会影响很多程序的走向。保存eflag的值,接下来更换新的eflag的时候,将会指示当前程序的模式是内核模式了。
保存eip的值,当我们返回时将会继续执行应用程序,只要把他弹出来即可。这里进一步的分析。cs:eip的值,在应用层指向了用户空间的代码段。那么保存之后,将会指向内核的代码段中的语句。cs:eip的值更新后,将会自动的指向内核空间。cs:eip的值肯定会被更新到0xC000 000之上的位置中。
刚才我们看到int 0x80保存这些值到栈中,如何保存的呢? 老师在课程中讲到是硬件完成的。硬件是如何完成的呢?不知道。这需要查芯片手册估计才能明确。
另外,int 0x80保存了sp的值到栈中,但是没有保存bp的值。是不需要保存吗?显然不是,因为用户空间的栈和内核空间的栈是两个部分。现在推论不是硬件保存的,而是软件保存的,在后文中应该可以看到。此处存疑。
软件保存现场
我们的硬件保存现场之后,在进入中断处理函数中,第一件事情是,SAVE_ALL做的事情。又继续保存现场。那么为什么这里还需要保存现场呢?
原因是在硬件保存现场中,保存的寄存器的值还不够,还要进一步的保存。
在代码中我们找到了SAVE_ALL的调用之处:
代码位置:
arch/x86/kernel/entry.s
# system call handler stub ENTRY(system_call) RING0_INT_FRAME # can't unwind into user space anyway ASM_CLAC pushl_cfi %eax # save orig_eax SAVE_ALL GET_THREAD_INFO(%ebp)# system call tracing in operation/emulation testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(NR_syscalls), %eax jae syscall_badsys syscall_call: call *sys_call_table(,%eax,4) syscall_after_call: movl %eax,PT_EAX(%esp) # store the return value
代码的分析以后再分析。
贴出SAVE_ALL的实现:
.macro SAVE_ALL cld PUSH_GS pushl_cfi %fs /*CFI_REL_OFFSET fs, 0;*/ pushl_cfi %es /*CFI_REL_OFFSET es, 0;*/ pushl_cfi %ds /*CFI_REL_OFFSET ds, 0;*/ pushl_cfi %eax CFI_REL_OFFSET eax, 0 pushl_cfi %ebp CFI_REL_OFFSET ebp, 0 pushl_cfi %edi CFI_REL_OFFSET edi, 0 pushl_cfi %esi CFI_REL_OFFSET esi, 0 pushl_cfi %edx CFI_REL_OFFSET edx, 0 pushl_cfi %ecx CFI_REL_OFFSET ecx, 0 pushl_cfi %ebx CFI_REL_OFFSET ebx, 0 movl $(__USER_DS), %edx movl %edx, %ds movl %edx, %es movl $(__KERNEL_PERCPU), %edx movl %edx, %fs SET_KERNEL_GS %edx .endm
在这段代码中,保存了相关寄存器的值。
他们一次是:ES,DS,EAX,EBP,EDI,ESI,EDX,ECX,EBX等等。从这里寄存器的顺序可以知道压栈的最后压入的是ebx,这里压入的栈是内核栈。
问题
什么时候切换到内核栈的呢?通篇代码,没有看到什么时候切换栈到了内核栈中。个人理解,什么时候将sp指向了内核中的sp呢?内核中的sp的位置在何处呢?
在开始,硬件只是保存了sp的值是不是除了保存sp的值之外,还将sp的值给更新了呢?更新成为了指向内核栈的栈顶的位置。因为只有这样,SAVE_ALL的时候,才会是保存的用户的寄存器的值,保存到了内核栈中。这样才能说得通。
目前没有查到,但是所有信息都在内核态中这个结论是和其他人说的一致的。
在老师的讲义中,老师提到了int 0x80的具体细节:
第一步硬件细节:
把cs:eip的值存储内核栈中,
把ss:esp的值存储内核栈中,
把eflags的值存储内核栈中,
第二步硬件细节:
更新cs:eip的值为0x80的中断服务函数
第三步硬件细节:
将ss:esp的值指向了内核栈中的栈顶位置。
第四步软件细节:
SAVE_ALL
在SAVE_ALL中我们看到了都是pushl的操作,其实是将这些值都存储进入了内核栈中。并且存储的部分寄存器的值,将会起到输入参数的功能。
当中断服务函数完成后,
第五步软件细节:
RESTORE_ALL
与SAVE_ALL的值是对应的。
第六步软件细节:
iret
这个iret与我们函数的ret是不同的,他是与上述第一步的硬件的细节相互对应的。
将会从内核栈中将:
eflags
ss:esp
cs:eip
这三个值依次恢复到寄存器中,并换回到用户态中来。
pop %esp问题
这里就是pop的过程那么pop %esp的时候,是否能够完成呢?
pop的过程是
将值弹出到sp中,然后对sp的值+2.显然,这里恢复出来的sp的值不是存储在栈中的值而是栈中的值加上2.
但是,这是不是意味着我们这里的做法是错误的呢?也不一定。因为我们前面的压栈,如果是和这个对应的话,那么就是对的。
在硬件存储用户空间的sp的时候,将sp的值首先减去2,然后在存储到内核栈中,这样在iret的时候直接pop才不会出错。
ebp的问题
在内核中没有ebp的问题。因为在内核中认为ebp的值是和esp的值是一致的,也就是说,每次进入到内核空间的时候,当前的进程的内核栈是空的。sp的值就是内核ebp的值。entry.s中内核栈指示:
0(%esp) - %ebx * 4(%esp) - %ecx * 8(%esp) - %edx * C(%esp) - %esi * 10(%esp) - %edi * 14(%esp) - %ebp * 18(%esp) - %eax * 1C(%esp) - %ds * 20(%esp) - %es * 24(%esp) - %fs * 28(%esp) - %gs saved iff !CONFIG_X86_32_LAZY_GS * 2C(%esp) - orig_eax * 30(%esp) - %eip * 34(%esp) - %cs * 38(%esp) - %eflags * 3C(%esp) - %oldesp * 40(%esp) - %oldss *
问题
在最后一个esp中,如果esp被pop出来的话,oldss将不能被拿到了啊?不能理解?解惑
在课堂讨论中,向老师提出了此问题。老师给出的明确的答复是,int指令iret指令虽然干了很多事情,但是他们是原子的操作,是不会被打断的。是纯粹的硬件完成的。没有我个人理解的先后次序,也就不存在上述的疑问了。现在的问题是,内核的sp如何知道他的位置的呢?因为内核栈是我们在fork的时候创建的。这是我还不懂的地方,需要继续查找。
参考
此外在这边文档中讲解的要比我的更加清楚http://blog.chinaunix.net/xmlrpc.php?r=blog/article&id=4739291&uid=14528823
相关文章推荐
- CompileSdkVersion和buildeToolVersion版本号关系
- ios开发总结之控制器的生命周期
- 20144303 第三次实验
- 监控页面所有 ajax请求
- js中如何获取该浏览器的类型
- MySQL单表多关键字模糊查询的实现
- LeetCode 237
- CreateProcessW的参数陷阱
- PHP 实现木桶算法
- 20145238-荆玉茗 《Java程序设计》实验三
- 每周收获(8-10)
- 第5天
- 链表的增删改查的操作
- cURL模拟登录教学管理系统
- 机房收费系统之泛型集合的使用
- LeetCode 226
- Uva 10905 Children's Game
- [Perl] 常见signals解析
- 第3章第1节练习题1 蛇形矩阵
- c++作业4