Why are many system calls (getpid) captured only once using strace?
I invoked getpid() in a program for many times (to test the efficiency of system calls), however when I use strace
to get the trace, only one getpid() call is captured.
The code is simple:
#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;
}
I used gdb
and got this:
(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
I don't quite understand the assembly code. It would also be helpful if somebody can give some detailed explanation about it. According to my observation, "call *%gs:0x10" (, which jumps into vdso) is not executed, except for the first getpid() call, that may be the reason why subsequent getpid() calls are not captured. But I don't know why.
The linux kernel: 2.6.24-29 gcc (GCC) 4.2.4 libc 2.7,
Thanks!
Glibc caches the result, since it can't change between calls. See the source code here for instance.
So the real syscall only gets executed once. The other calls just read from the cache. (The code is not very simple because it takes care of doing the Right Thing with threads.)
glibc caches the pid value. The first time you call getpid it asks the kernel for the pid, the next time it just returns value it got from the first getpid syscall.
glibc code:
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;
}
If you want to test the overhead of syscalls, gettimeofday()
is often used to do just that - the work done the kernel is very small, and neither the compiler nor the C library can optimize away calls to it.
Nowadays, with the introduction of pid_namespaces and numerous bug detected in applications on signal receipt or when they create child processes by calling syscall() instead of fork(), vfork() and clone(), the pid is no longer cached in the GLIBC. This is pointed out in the manual:
From glibc version 2.3.4 up to and including version 2.24, the
glibc wrapper function for getpid() cached PIDs, with the goal of
avoiding additional system calls when a process calls getpid()
repeatedly. Normally this caching was invisible, but its correct
operation relied on support in the wrapper functions for fork(2),
vfork(2), and clone(2): if an application bypassed the glibc
wrappers for these system calls by using syscall(2), then a call
to getpid() in the child would return the wrong value (to be
precise: it would return the PID of the parent process). In
addition, there were cases where getpid() could return the wrong
value even when invoking clone(2) via the glibc wrapper function.
(For a discussion of one such case, see BUGS in clone(2).)
Furthermore, the complexity of the caching code had been the
source of a few bugs within glibc over the years.
精彩评论