为什么 Linux 在进行上下文切换时保存 %ebp?
Why does Linux save %ebp when doing a context switch?
在进行上下文切换时,x86 Linux(非常巧妙地)避免了保存和恢复 EAX、EBX、ECX、EDX、ESI 和 EDI。当然,当切换到内核模式时,用户态值会保存在内核堆栈中。但是 in 内核代码中的值不会被保存——相反,使用 GCC 指令告诉编译器不要在发生切换时保留那些寄存器中需要的任何值.
ESP自然要保存恢复。但这是我不明白的:在切换ESP之前,EBP被压入内核堆栈。我认为 EBP 被用作帧指针,但在我的内核调试器中,值肯定看起来不像:
(gdb) print $esp
= (void *) 0xc0025ec0
(gdb) print $ebp
= (void *) 0xcf827f3c
区别是way EBP 太大了,不能在这里作为帧指针。代码中的注释说 "EBP is saved/restored explicitly for wchan access",但我正在搜索代码,但无法弄清楚这是怎么回事。 Google 也无济于事。一些内核向导可以介入并提供帮助吗?
The difference is way too big for EBP to be a frame pointer here.
推测您编译的内核没有启用帧指针。查看相关配置选项:
config SCHED_OMIT_FRAME_POINTER
def_bool y
prompt "Single-depth WCHAN output"
depends on X86
---help---
Calculate simpler /proc/<PID>/wchan values. If this option
is disabled then wchan values will recurse back to the
caller function. This provides more accurate wchan values,
at the expense of slightly more scheduling overhead.
If in doubt, say "Y".
函数 get_wchan
将对 ebp
值进行健全性检查,仅当它看起来是帧指针时才使用它。
我认为最好在两个地方都使用上面的配置标志,这样 ebp
如果不是帧指针就不会被不必要地保存,还有 get_wchan
如果我们知道不会有帧指针,就不会打扰了。也就是说,saving/restoring ebp
只是增加了很少的开销,所以并不悲惨。
我想通了。 EBP是一个帧指针,但在我查看它的值时,ESP已经切换到新进程的内核栈,但EBP还没有恢复(所以它仍然具有前一过程的价值)。抱歉!!
存储帧指针的原因是其他人可以确定进程在内核代码中的哪个位置休眠。除其他事项外,/proc/PID/wchan
使用它,打印使进程休眠的内核函数的名称。
检查这个的代码如下(为简洁起见删除了细节):
unsigned long get_wchan(struct task_struct *p)
{
unsigned long sp, bp, ip;
sp = p->thread.sp;
bp = *(unsigned long *) sp;
do {
ip = *(unsigned long *) (bp+4);
if (!in_sched_functions(ip))
return ip;
bp = *(unsigned long *) bp;
} while (count++ < 16);
return 0;
}
由于EBP在切换内核堆栈之前被压入,睡眠进程的堆栈指针将指向保存的EBP(帧指针)值。那个帧指针指向调用者保存的帧指针,它指向前一个调用者的,它指向前一个调用者的……换句话说,保存的帧指针形成一个链表返回调用堆栈。
帧指针在函数入口处立即保存,因此它上面的值(向上 4 个字节)是调用函数的 return 地址。
get_wchan
中的循环遍历 "linked list" (bp = *bp
),检查每个保存的帧指针上方的 return 地址,直到找到函数内的地址像 ep_poll
或 futex_wait_queue_me
.
get_wchan
只是 return 一个函数内部的地址;为了在/proc
中显示,lookup_symbol_name
用于将该地址转换为函数名。
在进行上下文切换时,x86 Linux(非常巧妙地)避免了保存和恢复 EAX、EBX、ECX、EDX、ESI 和 EDI。当然,当切换到内核模式时,用户态值会保存在内核堆栈中。但是 in 内核代码中的值不会被保存——相反,使用 GCC 指令告诉编译器不要在发生切换时保留那些寄存器中需要的任何值.
ESP自然要保存恢复。但这是我不明白的:在切换ESP之前,EBP被压入内核堆栈。我认为 EBP 被用作帧指针,但在我的内核调试器中,值肯定看起来不像:
(gdb) print $esp
= (void *) 0xc0025ec0
(gdb) print $ebp
= (void *) 0xcf827f3c
区别是way EBP 太大了,不能在这里作为帧指针。代码中的注释说 "EBP is saved/restored explicitly for wchan access",但我正在搜索代码,但无法弄清楚这是怎么回事。 Google 也无济于事。一些内核向导可以介入并提供帮助吗?
The difference is way too big for EBP to be a frame pointer here.
推测您编译的内核没有启用帧指针。查看相关配置选项:
config SCHED_OMIT_FRAME_POINTER
def_bool y
prompt "Single-depth WCHAN output"
depends on X86
---help---
Calculate simpler /proc/<PID>/wchan values. If this option
is disabled then wchan values will recurse back to the
caller function. This provides more accurate wchan values,
at the expense of slightly more scheduling overhead.
If in doubt, say "Y".
函数 get_wchan
将对 ebp
值进行健全性检查,仅当它看起来是帧指针时才使用它。
我认为最好在两个地方都使用上面的配置标志,这样 ebp
如果不是帧指针就不会被不必要地保存,还有 get_wchan
如果我们知道不会有帧指针,就不会打扰了。也就是说,saving/restoring ebp
只是增加了很少的开销,所以并不悲惨。
我想通了。 EBP是一个帧指针,但在我查看它的值时,ESP已经切换到新进程的内核栈,但EBP还没有恢复(所以它仍然具有前一过程的价值)。抱歉!!
存储帧指针的原因是其他人可以确定进程在内核代码中的哪个位置休眠。除其他事项外,/proc/PID/wchan
使用它,打印使进程休眠的内核函数的名称。
检查这个的代码如下(为简洁起见删除了细节):
unsigned long get_wchan(struct task_struct *p)
{
unsigned long sp, bp, ip;
sp = p->thread.sp;
bp = *(unsigned long *) sp;
do {
ip = *(unsigned long *) (bp+4);
if (!in_sched_functions(ip))
return ip;
bp = *(unsigned long *) bp;
} while (count++ < 16);
return 0;
}
由于EBP在切换内核堆栈之前被压入,睡眠进程的堆栈指针将指向保存的EBP(帧指针)值。那个帧指针指向调用者保存的帧指针,它指向前一个调用者的,它指向前一个调用者的……换句话说,保存的帧指针形成一个链表返回调用堆栈。
帧指针在函数入口处立即保存,因此它上面的值(向上 4 个字节)是调用函数的 return 地址。
get_wchan
中的循环遍历 "linked list" (bp = *bp
),检查每个保存的帧指针上方的 return 地址,直到找到函数内的地址像 ep_poll
或 futex_wait_queue_me
.
get_wchan
只是 return 一个函数内部的地址;为了在/proc
中显示,lookup_symbol_name
用于将该地址转换为函数名。