Solaris 动态跟踪指南

尾部调用优化

当一个函数以调用另一个函数而结束时,编译器可能会进行尾部调用优化,优化后,被调用的函数将重用调用方的栈帧。此过程通常用于 SPARC 体系结构中,在此情况下,编译器在被调用的函数中重用调用方的注册窗口,以便使注册窗口的压力降到最低。

进行此优化会使调用函数的 return 探测器在被调用函数的 entry 探测器之前触发。这种触发顺序会导致比较严重的混乱情况。例如,如果要记录从某个特定函数调用的所有函数,以及此函数调用的所有函数,则可以使用以下脚本:

fbt::foo:entry
{
	self->traceme = 1;
}

fbt:::entry
/self->traceme/
{
	printf("called %s", probefunc);
}

fbt::foo:return
/self->traceme/
{
	self->traceme = 0;
}

但是,如果 foo() 以优化的尾部调用结束,那么,在尾部调用的函数以及它调用的任何函数都不会被捕获。不能即时动态取消内核优化,DTrace 不希望总是考虑如何虚构代码结构。所以,应清楚什么时候可以使用尾部调用优化。

可以在类似以下示例的源代码中使用尾部调用优化:

	return (bar());

或者在类似以下示例的源代码中使用尾部调用优化:

	(void) bar();
	return;

相反地,结尾方式与以下示例类似的函数源代码不能优化对 bar() 的调用,因为对 bar() 的调用不是尾部调用:

	bar();
	return (rval);

可以使用以下方法确定是否已对某个调用进行了尾部调用优化:

由于指令集体系结构,尾部调用优化在 SPARC 系统上要比在 x86 系统上更常见。以下示例使用 mdb 发现内核的 dup() 函数中的尾部调用优化:


# dtrace -q -n fbt::dup:return'{printf("%s+0x%x", probefunc, arg0);}'

运行此命令时,将运行执行 dup(2) 的程序(如 bash 进程)。上面的命令会提供与以下示例类似的输出:


dup+0x10
^C

现在使用 mdb 检查函数:


# echo "dup::dis" | mdb -k
dup:                            sra       %o0, 0, %o0
dup+4:                          mov       %o7, %g1
dup+8:                          clr       %o2
dup+0xc:                        clr       %o1
dup+0x10:                       call      -0x1278       <fcntl>
dup+0x14:                       mov       %g1, %o7

该输出说明,dup+0x10 是对 fcntl() 函数的调用而不是 ret 指令。所以,fcntl() 就是尾部调用优化的一个示例。