linux vdso clock_gettime 获取时间方法

背景

有些进程需要频繁调用系统时间接口
导致大量的时间卡顿在时间获取的API上
Linux VDSO 提供了用户态的获取时间方法

VDSO 编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
chunli@ubuntu:~/source$ vim vdso_time.c
#include <stdio.h>
#include <sys/auxv.h>
#include <string.h>
#include <elf.h>

void* vdso_sym(const char* name) {
char* vdso_addr = (char*)getauxval(AT_SYSINFO_EHDR);
Elf64_Ehdr* elf_header = (Elf64_Ehdr*)vdso_addr;
Elf64_Shdr* section_header = (Elf64_Shdr*)(vdso_addr + elf_header->e_shoff);


char* dynstr = NULL;
for (int i=0; i<elf_header->e_shnum; i++) {
Elf64_Shdr *ss_ = section_header + elf_header->e_shstrndx;
Elf64_Shdr *s = section_header + i;
char *name = vdso_addr + ss_->sh_offset + s->sh_name;
if (strcmp(name, ".dynstr") == 0) {
dynstr = (char*)(vdso_addr + s->sh_offset);
break;
}
}

void *ret = NULL;
for (int i=0; i< elf_header->e_shnum; i++) {
Elf64_Shdr * s = section_header + i;
Elf64_Shdr *ss_ = section_header + elf_header->e_shstrndx;
char* name =vdso_addr + ss_->sh_offset + s->sh_name;
if (strcmp(name, ".dynsym") == 0) {
for (int si=0; si<(s->sh_size/s->sh_entsize); si++) {
Elf64_Sym * sym = ((Elf64_Sym*)(vdso_addr + s->sh_offset)) + si;
char *name = dynstr + sym->st_name;
if (strcmp(name, "clock_gettime") == 0) {
return vdso_addr + sym->st_value;
}
}
}
}
return ret;
}


#include <time.h> // for clockid_t, timespec, and CLOCK_REALTIME
typedef int (clock_gettime_t)(clockid_t clk_id, struct timespec *tp);
int main(int argc, char *argv[])
{
struct timespec now;
clock_gettime_t *my_clock_gettime = (clock_gettime_t*)vdso_sym("clock_gettime");
my_clock_gettime(CLOCK_REALTIME, &now);
printf("now: %ld\n", now.tv_sec);
return 0;
}

演示

1
2
3
4
5
6
7
chunli@ubuntu:~/source$
chunli@ubuntu:~/source$ gcc vdso_time.c && ./a.out
now: 1633682308
chunli@ubuntu:~/source$ gcc vdso_time.c && ./a.out
now: 1633682309
chunli@ubuntu:~/source$
`