1. Overview

When we dump the memory of a process in Linux, we observe two memory regions in the output, namely vdso and vsyscall.

In this tutorial, we’ll discuss what vdso and vsyscall are. Firstly, we’ll examine the address space of an example application, and see the vdso and vsyscall regions. Then, after a brief discussion of system calls, we’ll learn the importance of vdso and vsyscall in speeding up system calls for user space applications.

2. An Example

We’ll use the following C program, wait_enter.c:

#include <stdio.h>

void main()
{ 
    printf("Press enter to exit\n");
    getchar();
}

The program waits until we press Enter. Let’s build it using gcc to generate the executable wait_enter:

$ gcc -o wait_enter wait_enter.c

Having built the executable, let’s run it:

$ ./wait_enter
Press enter to exit

The running process waits for us to press Enter as expected. Now, let’s find the PID of the running process using ps and grep:

$ ps -ef | grep wait_enter | grep -v grep
centos      3095    2659  0 13:07 pts/0    00:00:00 ./wait_enter

We used grep wait_enter and grep -v grep to filter the output of ps -ef. The PID of the process is 3095.

Now, let’s list the memory regions of the process using the /proc filesystem:

$ cat /proc/3095/maps | grep -E 'vdso|vsyscall'
7fff5c1a8000-7fff5c1aa000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

The /proc//maps file contains the memory regions of a process with the PID . Since the PID of our process is 3095, we list the contents using cat /proc/3095/maps. Additionally, we filter the output to list memory regions having names either vdso or vsyscall using grep -E ‘vdso|vsyscall’.

As is apparent from the output, there are two memory regions with the names vdso and vsyscall. Additionally, after running wait_enter once more, let’s check its memory regions:

$ ps -ef | grep wait_enter | grep -v grep
centos      3095    2659  0 13:07 pts/0    00:00:00 ./wait_enter
centos      3213    2706  0 13:08 pts/1    00:00:00 ./wait_enter
$ cat /proc/3213/maps | grep -E 'vdso|vsyscall'
ffcaa7fe000-7ffcaa800000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                 [vsyscall]

The process with PID 3213 is the second process spawned by running wait_enter. The address ranges of the vdso memory regions are different for the two processes. However, the address ranges of the vsyscall memory regions are the same in the two processes, which is ffffffffff600000-ffffffffff601000.

3. Understanding vdso and vsyscall

Before discussing vdso and vsyscall in this section, we’ll shortly see what system calls are.

3.1. System Calls

System calls are service requests of applications from the kernel. They can be slow because of the context switches between the user and kernel spaces. If there are frequently used system calls, the performance of an application might get even worse.

An example of a frequently used system call is gettimeofday(). An application might call gettimeofday() directly, but it might also be called indirectly by the C standard library.

3.2. vdso

Since system calls like gettimeofday() might be frequently called, the kernel automatically maps the most frequently used system calls into the address space of an application using the memory region vdso, which is the abbreviation of virtual dynamic shared object. A call to gettimeofday() becomes like a normal library call in this case.

Let’s extract the vdso region of the already running process with PID 3095 using the dd command:

$ dd if=/proc/3095/mem of=out.vdso bs=1 count=$((0x7fff5c1aa000-0x7fff5c1a8000)) skip=$((0x7fff5c1aa000)) 
8192+0 records in
8192+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00695626 s, 1.2MB/s

The if option of dd specifies the input file, which is /proc/3095/mem in our case. Having extracted vdso to out.vdso, let’s check its type using the file command:

$ file out.vdso
out.vdso: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=4caa2879ca7056c7706fe028473d304d44c0febd, stripped

As we see, the extracted memory region is a shared library in the ELF format. Let’s list the symbols in the ELF file using readelf:

$ readelf -Ws out.vdso
Symbol table '.dynsym' contains 13 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000c20     5 FUNC    WEAK   DEFAULT   11 clock_gettime@@LINUX_2.6
     2: 0000000000000be0     5 FUNC    GLOBAL DEFAULT   11 __vdso_gettimeofday@@LINUX_2.6
     3: 0000000000000c30    96 FUNC    WEAK   DEFAULT   11 clock_getres@@LINUX_2.6
     4: 0000000000000c30    96 FUNC    GLOBAL DEFAULT   11 __vdso_clock_getres@@LINUX_2.6
     5: 0000000000000be0     5 FUNC    WEAK   DEFAULT   11 gettimeofday@@LINUX_2.6
     6: 0000000000000bf0    41 FUNC    GLOBAL DEFAULT   11 __vdso_time@@LINUX_2.6
     7: 0000000000000cc0   156 FUNC    GLOBAL DEFAULT   11 __vdso_sgx_enter_enclave@@LINUX_2.6
     8: 0000000000000bf0    41 FUNC    WEAK   DEFAULT   11 time@@LINUX_2.6
     9: 0000000000000c20     5 FUNC    GLOBAL DEFAULT   11 __vdso_clock_gettime@@LINUX_2.6
    10: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  ABS LINUX_2.6
    11: 0000000000000c90    37 FUNC    GLOBAL DEFAULT   11 __vdso_getcpu@@LINUX_2.6
    12: 0000000000000c90    37 FUNC    WEAK   DEFAULT   11 getcpu@@LINUX_2.6

gettimeofday() exists within the symbols. Additionally, clock_gettime(), clock_getres(), getcpu() and time() are other system calls in vdso.

Let’s check the soname in the extracted vdso region again using readelf:

$ readelf -s out.vdso | grep SONAME
0x000000000000000e (SONAME)             Library soname: [linux-vdso.so.1]

The soname is linux-vdso.so.1. This is listed in the shared library dependencies of wait_enter:

$ ldd wait_enter
        linux-vdso.so.1 (0x00007ffc7d955000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fc723af3000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fc723d0a000)

As the output of ldd wait_enter shows, the first line in the output contains the vdso region, namely, linux-vdso.so.1. But it isn’t a physical file on the file system. The other dependencies, libc.so.6 and ld-linux-x86-64.so.2, are the C standard library and the run-time linker that runs the program, respectively.

3.3. vsyscall

vsyscall is like vdso. It’s the oldest mechanism in the kernel to accelerate the execution of some system calls. It speeds up the calls to gettimeofday(), time() and getcpu() system calls.

vsyscall has been replaced by vdso over time. The main difference between them is that vsyscall is static in memory, i.e., it has always the same address.

Having always the same address might have security risks, so the address of vdso is random. vdso also maps memory pages into processes in a shared object form as we’ve already seen.

vsyscall still exists in memory mappings just as a fallback for vdso.

4. Conclusion

In this article, we discussed what vdso and vsycall are. Firstly, we saw that these are memory regions belonging to the address space of every process in the user space.

Then, we learned what system calls are. Finally, we saw that vdso and vsyscall are mechanisms for speeding up the most frequently used system calls.


» 下一篇: Linux CLI日历指南