5

我在一个程序中多次调用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  

我不太明白汇编代码。如果有人可以对此进行详细解释,那也会很有帮助。根据我的观察,“call *%gs:0x10”(跳转到vdso)没有被执行,除了第一个getpid()调用,这可能是后续getpid()调用没有被捕获的原因。但我不知道为什么。

linux内核:2.6.24-29 gcc (GCC) 4.2.4 libc 2.7,

谢谢!

4

3 回答 3

5

Glibc 缓存结果,因为它不能在调用之间改变。例如,请参阅此处的源代码。

所以真正的系统调用只执行一次。其他调用只是从缓存中读取。(代码不是很简单,因为它负责用线程做正确的事情。)

于 2011-04-23T18:51:50.650 回答
4

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 库都无法优化对它的调用。

于 2011-04-23T18:55:27.700 回答
1

如今,随着 pid_namespaces 的引入,以及在应用程序中检测到信号接收或通过调用syscall()而不是fork()vfork()clone()创建子进程时检测到的大量错误,pid 不再缓存在 GLIBC 中. 手册中指出了这一点:

从 glibc 2.3.4 版到 2.24 版(包括 2.24 版),
用于 getpid() 的 glibc 包装函数缓存了 PID,目的是在进程 重复
调用 getpid() 时避免额外的系统调用。
通常这种缓存是不可见的,但它的正确
操作依赖于对 fork(2)、
vfork(2) 和 clone(2) 的包装器函数的支持:如果应用程序
通过使用 syscall(2) 绕过了这些系统调用的 glibc 包装器),然后在子进程中调用
getpid() 将返回错误的值(
准确地说:它将返回父进程的 PID)。此外, 即使通过 glibc 包装函数调用 clone(2)
,getpid() 也可能返回错误值。

(有关此类情况的讨论,请参见 clone(2) 中的 BUGS。)
此外,缓存代码的复杂性多年来一直是
glibc 中一些错误的根源。

于 2020-12-26T13:17:55.263 回答