我在程序中多次调用“getpid()”(以测试系统调用的效率),但是当我使用strace获取跟踪时,只捕获了一个getpid调用。
代码很简单:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void print_usage(){
printf("Usage: program count\n");
exit(-1);
}
int main(int argc, char** argv){
if(argc != 2)
print_usage();
int cnt = atoi(argv[1]);
int i = 0;
while(i++<cnt)
getpid();
return 0;
}
我使用了gdb并得到了这个:
(gdb) disasse
Dump of assembler code for function getpid:
0xb76faac0 <getpid+0>: mov %gs:0x4c,%edx
0xb76faac7 <getpid+7>: cmp $0x0,%edx
0xb76faaca <getpid+10>: mov %edx,%eax
0xb76faacc <getpid+12>: jle 0xb76faad0 <getpid+16>
0xb76faace <getpid+14>: repz ret
0xb76faad0 <getpid+16>: jne 0xb76faadc <getpid+28>
0xb76faad2 <getpid+18>: mov %gs:0x48,%eax
0xb76faad8 <getpid+24>: test %eax,%eax
0xb76faada <getpid+26>: jne 0xb76faace <getpid+14>
0xb76faadc <getpid+28>: mov $0x14,%eax
0xb76faae1 <getpid+33>: call *%gs:0x10
0xb76faae8 <getpid+40>: test %edx,%edx
0xb76faaea <getpid+42>: mov %eax,%ecx
0xb76faaec <getpid+44>: jne 0xb76faace <getpid+14>
0xb76faaee <getpid+46>: mov %ecx,%gs:0x48
0xb76faaf5 <getpid+53>: ret
我不太了解汇编代码。如果有人可以给出一些详细的解释也会有所帮助。根据我的观察,除了第一次getpid()调用之外,不会执行“call *%gs:0x10”(跳转到vdso),这可能是未捕获其他getpid调用的原因。但我不知道为什么。
linux内核:2.6.24-29 gcc(GCC)4.2.4 libc 2.7,
谢谢!
答案 0 :(得分:4)
Glibc缓存结果,因为它不能在调用之间改变。例如,请参阅源代码here。
所以真正的系统调用只执行一次。其他调用只是从缓存中读取。 (代码不是很简单,因为它负责用线程做正确的事情。)
答案 1 :(得分:3)
glibc缓存pid值。第一次调用getpid时,它会向内核询问pid,下次它只返回从第一个getpid系统调用获得的值。
glibc代码:
pid_t
__getpid (void)
{
#ifdef NOT_IN_libc
INTERNAL_SYSCALL_DECL (err);
pid_t result = INTERNAL_SYSCALL (getpid, err, 0);
#else
pid_t result = THREAD_GETMEM (THREAD_SELF, pid);
if (__builtin_expect (result <= 0, 0))
result = really_getpid (result);
#endif
return result;
}
如果你想测试系统调用的开销,gettimeofday()
经常被用来做 - 内核完成的工作非常少,编译器和C库都不能优化对它的调用。
答案 2 :(得分:1)
如今,随着 pid_namespaces 的引入以及应用程序在接收信号或通过调用 syscall() 而不是 fork() 创建子进程时检测到的大量错误>、vfork() 和 clone(),pid 不再缓存在 GLIBC 中。 manual 中指出了这一点:
<块引用>从 glibc 版本 2.3.4 到并包括版本 2.24,
用于 getpid() 缓存 PID 的 glibc 包装函数,目标是
当进程调用 getpid() 时避免额外的系统调用
反复。通常这种缓存是不可见的,但它是正确的
操作依赖于 fork(2) 的包装函数的支持,
vfork(2) 和 clone(2):如果应用程序绕过 glibc
使用 syscall(2) 封装这些系统调用,然后调用
子进程中的 getpid() 会返回错误的值(要
精确:它将返回父进程的PID)。在
此外,在某些情况下 getpid() 可能会返回错误的
即使在通过 glibc 包装器函数调用 clone(2) 时也值。
(有关此类情况的讨论,请参阅 clone(2) 中的 BUGS。)
此外,缓存代码的复杂性一直是
多年来 glibc 中一些错误的来源。