【MIPS CPU 体系结构概述2】连载10:

/*以下这一段涉及比较微妙的问题,没有兴趣可以跳过*/

/* save_static_function宏是一个令人迷惑的东西,它定义了一个汇编函数,保存s0-s8 可是这个函数没有返回!实际上,它只是一个函数的一部分:

在arch/mips/kernel/signal.c中有:
save_static_function(sys_rt_sigsuspend);
static_unused int
_sys_rt_sigsuspend(struct pt_regs regs)
{
sigset_t *unewset, saveset, newset;
size_t sigsetsize;

这里用save_static_function定义了sys_rt_sigsuspend,而实际上如果
你调用sys_rt_sigsuspend的话,它保存完s0-s8后,接着就调用_sys_rt_sigsuspend! 看它链接后的反汇编片段:

80108cc8 :

80108cc8: afb00058 sw
80108ccc: afb1005c sw
80108cd0: afb20060 sw
80108cd4: afb30064 sw
80108cd8: afb40068 sw
80108cdc: afb5006c sw
80108ce0: afb60070 sw
80108ce4: afb70074 sw
80108ce8: afbe0090 sw

80108cec <_sys_rt_sigsuspend>:
80108cec: 27bdffc8 addiu
80108cf0: 8fa80064 lw
80108cf4: 24030010 li
80108cf8: afbf0034 sw
80108cfc: afb00030 sw

$s0,88($sp)
$s1,92($sp)
$s2,96($sp)
$s3,100($sp)
$s4,104($sp)
$s5,108($sp)
$s6,112($sp)
$s7,116($sp)
$s8,144($sp)

$sp,$sp,-56
$t0,100($sp)
$v1,16
$ra,52($sp)
$s0,48($sp) ---> notice

80108d00: afa40038 sw $a0,56($sp)
80108d04: afa5003c sw $a1,60($sp)
80108d08: afa60040 sw $a2,64($sp)

用到save_static_function的地方共有4处:

signal.c:save_static_function(sys_sigsuspend);
signal.c:save_static_function(sys_rt_sigsuspend);
syscall.c:save_static_function(sys_fork);
syscall.c:save_static_function(sys_clone);

我们知道s0-s8如果在子过程用到,编译器本来就会保存/恢复它的(如上面的s0), 那为何要搞这个花招呢?笔者分析之后得出如下结论:

(警告:以下某些内容是笔者的推测,可能不完全正确)

先看看syscall的处理,syscall也是mips的一种异常,异常号为8.上次我们说
了一般异常是如何工作的,但在handle_sys并非用BUILD_HANDLER生成,而是在 scall_o23.S中定义,因为它又有其特殊之处.

1.缺省情况它只用了SAVE_SOME,并没有保存at,t*,s*等寄存器,因为syscall
是由应用程序调用的,不象中断,任何时候都可以发生,所以一般编译器就可以 保证不会丢数据了(at,t*的值应该已经无效,s*的值会被函数保存恢复).
这样可以提高系统调用的效率

2.它还得和用户空间打交道(取参数,送数据)

还有个别系统调用需要在特定的时候手工保存s*寄存器,如上面的几个.为什么呢?
对sigsuspend来说,它将使进程在内核中睡眠等待信号到来,信号来了之后将直接
先回到进程的信号处理代码,而信号处理代码可能希望看到当前进程的寄存器
(sigcontext),这是通过内核栈中的pt_regs结构获得的,所以内核必需把s*寄存器
保存到pt_regs中.对于fork的情况,则似乎是为了满足vfork的要求.(vfork时,子进程
不拷贝页表(即和父进程完全共享内存),注意,连copy-on-write都没有!父进程挂起
一直到子进程不再使用它的资源(exec或者exit)).fork 系统调用使用ret_from_fork
返回,其中调用到了RESTORE_ALL_AND_RET(entry.S),需要恢复s*.

这里还有一个很容易混乱的地方: 在scall_o32.S和entry.S中有几个函数(汇编)是同名
的,如restore_all,sig_return等.总体来说scall_o32.S中是对满足o32(old 32bit)汇编
约定的系统调用处理,可以避免保存s*,而entry.S中是通用的,保存/恢复所由寄存器
scall_o32.S中也有一些情况需要保存静态寄存器s*,此时它就会到ret_from_syscall
而不是本文件中的o32_ret_from_syscall返回了,两者的差别就是恢复的寄存器数目
不同.scall_o32.S中一些错误处理直接用ret_from_syscall返回,笔者怀疑会导致s*寄存器 被破坏,有机会请各路高手指教.

--电子创新网--
粤ICP备12070055号