vDSO (Virtual Dynamic Shared Object)¶
vDSO — механизм ядра Linux, который отображает небольшую ELF-библиотеку непосредственно в адресное пространство каждого процесса. Это позволяет вызывать часто используемые функции ядра без полноценного системного вызова — без переключения в kernel mode.
Зачем нужен vDSO¶
Обычный системный вызов выполняет дорогостоящую последовательность операций:
- Переключение CPU из user mode в kernel mode (
syscall/sysenter) - Сохранение регистров
- Сброс TLB (частичный)
- Выполнение функции ядра
- Восстановление контекста и возврат
Для функций вроде clock_gettime(), которые вызываются тысячи раз в секунду, это накладные расходы существенны. vDSO
позволяет выполнить такую функцию полностью в user mode — как обычный вызов библиотеки.
Устройство vDSO¶
vDSO — стандартная shared ELF-библиотека, но она не существует на диске. Ядро автоматически отображает её в адресное пространство каждого нового процесса. Размер — обычно одна страница (4 KB на x86-64).
Адрес vDSO передаётся процессу через вспомогательный вектор (auxiliary vector) в поле AT_SYSINFO_EHDR. ASLR
рандомизирует адрес при каждом запуске.
Виртуальная память процесса (x86-64)
┌──────────────────────────────────┐ 0xffff...
│ kernel space │
├──────────────────────────────────┤
│ stack │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [vdso] (4 KB, r-x) │ ← ELF-библиотека, mapped ядром
│ ← AT_SYSINFO_EHDR │ адрес рандомизирован ASLR
│ │
│ [vvar] (~16 KB, r--) │ ← данные ядра: vvar + pvclock + hvclock
│ ← только чтение │ та же физ. страница что ядро пишет rw-
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ libc.so, ld-linux.so │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ ← program break
│ heap │
├──────────────────────────────────┤
│ .bss / .data / .text │
└──────────────────────────────────┘ 0x0000...
# Посмотреть, куда замаплен vDSO у текущего процесса
cat /proc/self/maps | grep vdso
# Или через вспомогательный вектор
LD_SHOW_AUXV=1 /bin/true | grep AT_SYSINFO_EHDR
Рядом с vDSO ядро отображает страницу vvar — область памяти только для чтения, через которую ядро передаёт процессу актуальные данные (текущее время, частоту TSC и т.д.).
Синхронизация времени без syscall¶
Механизм clock_gettime() через vDSO:
- Функция читает из
vvarпоследнее известное время и значение TSC на момент его записи - Считывает текущий TSC инструкцией
RDTSC - Вычисляет разницу TSC и пересчитывает её в наносекунды
- Прибавляет к базовому времени из vvar
Ядро обновляет данные в vvar через update_vsyscall() каждый раз, когда внутренние часы изменяются. Страница vvar
отображена дважды: rw- в kernel space и r-- в user space — обе ссылаются на одну физическую страницу.
Экспортируемые функции (x86-64)¶
| Функция | Назначение |
|---|---|
clock_gettime() |
Текущее время с высокой точностью |
clock_getres() |
Разрешение часов |
gettimeofday() |
Текущее время (POSIX legacy) |
time() |
Текущее время в секундах |
getcpu() |
Номер текущего CPU и NUMA-узла |
vDSO может экспортировать только функции, которые:
- работают с данными только для чтения (не изменяют состояние ядра)
- не требуют проверки прав доступа
- обращаются к публично доступным данным
Поэтому getuid(), open(), write() и подобные функции через vDSO не работают.
Fallback на syscall¶
vDSO автоматически делает fallback на обычный системный вызов в случаях:
- запрошен тип часов, недоступный через vvar (например,
CLOCK_BOOTTIMEна старых ядрах) - платформа виртуализации не предоставляет доступ к TSC
- наложены ограничения seccomp
Приложение не замечает разницы — интерфейс одинаков.
Использование из кода¶
libc автоматически использует vDSO для clock_gettime() и gettimeofday(). Явный доступ нужен редко:
#include <sys/auxv.h>
// Получить базовый адрес vDSO
unsigned long vdso_base = getauxval(AT_SYSINFO_EHDR);
// Дальше — разбор ELF-заголовка и поиск нужного символа
# Посмотреть символы vDSO
objdump -T /usr/lib/x86_64-linux-gnu/libdl.so.2
# Извлечь vDSO как файл
# (адрес берём из /proc/self/maps, затем читаем через /proc/self/mem)
Источники¶
man 7 vdso- The Linux vDSO — kernel.org
- vDSO — Wikichip