Перейти к содержанию

vDSO (Virtual Dynamic Shared Object)

vDSO — механизм ядра Linux, который отображает небольшую ELF-библиотеку непосредственно в адресное пространство каждого процесса. Это позволяет вызывать часто используемые функции ядра без полноценного системного вызова — без переключения в kernel mode.

Зачем нужен vDSO

Обычный системный вызов выполняет дорогостоящую последовательность операций:

  1. Переключение CPU из user mode в kernel mode (syscall / sysenter)
  2. Сохранение регистров
  3. Сброс TLB (частичный)
  4. Выполнение функции ядра
  5. Восстановление контекста и возврат

Для функций вроде 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:

  1. Функция читает из vvar последнее известное время и значение TSC на момент его записи
  2. Считывает текущий TSC инструкцией RDTSC
  3. Вычисляет разницу TSC и пересчитывает её в наносекунды
  4. Прибавляет к базовому времени из 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)

Источники