vdso - обзор
виртуального
динамически
компонуемого
общего
объекта ELF
#include <sys/auxv.h>
void *vdso = (uintptr_t) getauxval(AT_SYSINFO_EHDR);
«vDSO» (virtual dynamic shared object,
виртуальный
динамический
общий
объект) —
это
маленькая
общая
библиотека,
которую
ядро
автоматически
отображает
в адресное
пространство
всех
приложений
пользовательского
пространства.
Обычно,
приложениям
она не
нужна, так
как vDSO, чаще
всего,
вызывается
из
библиотеки
C. Вы можете
использовать
стандартные
функции
как обычно,
а
библиотека
C
самостоятельно
позаботится
об
использовании
возможностей
vDSO.
И всё же,
зачем
нужна vDSO?
Есть
несколько
системных
вызовов
ядра,
которые
используются
в
пользовательском
коде
настолько
часто, что
это сильно
влияет на
общую
производительность.
Это
происходит
из-за
частого
повторения
вызовов, а
также
затрат на
переключение
контекста,
которые
возникают
при выходе
из
пользовательского
пространства
и входа в
ядро.
Оставшаяся
часть этой
документации
предназначена
для
любопытных
и/или
авторов
библиотеки
C, а не для
обычных
разработчиков.
Если вы
попытаетесь
вызвать vDSO в
своём
приложении
не через
библиотеку
C, то,
наиболее
вероятно,
сделаете
это
неправильно.
Выполнение
системных
вызовов
может быть
медленным.
В 32-битных
системах x86
вы можете
использовать
программное
прерывание
(
int $0x80), чтобы
заставить
ядро
выполнить
системный
вызов.
Однако, эта
инструкция
очень
затратна:
она
проходит
по полному
маршруту
обработки
прерываний
в
микрокоде
процессора,
а также в
ядре. Новые
процессоры
содержат
более
быстрые
инструкции
(но обратно
совместимые)
для
запуска
системных
вызовов.
Вместо
того, чтобы
требовать
от
библиотеки
C выяснения
во время
выполнения
есть ли
такая
возможность,
библиотека
C может
использовать
функции,
предоставляемые
ядром в vDSO.
Заметим,
что можно
запутаться
в
терминологии.
В системах x86
функция vDSO,
используемая
для
определения
предпочтительного
метода
выполнения
системного
вызова,
называется
«__kernel_vsyscall», но в x86-64
термин «vsyscall»
также
ссылается
на
устаревший
метод
запроса
ядра о
времени
или на
каком ЦП
выполняется
вызывающий.
One frequently used system call is
gettimeofday(2). This system call is
called both directly by user-space applications as well as indirectly by the C
library. Think timestamps or timing loops or polling—all of these
frequently need to know what time it is right now. This information is also
not secret—any application in any privilege mode (root or any
unprivileged user) will get the same answer. Thus the kernel arranges for the
information required to answer this question to be placed in memory the
process can access. Now a call to
gettimeofday(2) changes from a system
call to a normal function call and a few memory accesses.
Базовый
адрес vDSO
(если есть)
передаётся
ядром
каждой
программе
во
вспомогательном
векторе
инициализации
(смотрите
getauxval(3)) через
тег
AT_SYSINFO_EHDR.
Вы не
должны
рассчитывать
на то, что vDSO
отображается
в каком-то
определённом
месте
карты
пользовательской
памяти.
Обычно,
базовый
адрес во
время
выполнения
выбирается
произвольным
образом
каждый раз
при
создании
нового
образа
процесса (с
помощью
execve(2)).
Это
делается в
целях
безопасности
для
предотвращения
атак
«возврат в
libc».
Для
некоторых
архитектур
также
существует
тег
AT_SYSINFO. Он
используется
только для
нахождения
точки
входа vsyscall и
часто
отсутствует
или равен 0
(то есть
недоступен).
Этот тег —
атавизм
первых
версий vDSO
(смотрите
История
ниже) и не
должен
использоваться.
Так как vDSO —
полноценный
образ ELF, вы
можете
искать в
нём
символы.
Это
позволяет
добавлять
новые
символы в
новых
версиях
ядра и
библиотеке
C находить
доступные
свойства
во время
выполнения
с
различными
версиями
ядер.
Зачастую,
библиотека
C выполняет
обнаружение
при первом
вызове и
затем
кэширует
результат
для
последующих
вызовов.
Все
символы
имеют
версии (в
формате
версий GNU).
Это
позволяет
ядру
обновлять
сигнатуру
функции
без
нарушения
обратной
совместимости.
Смена
версии
означает
изменение
аргументов
функции
или
возвращаемого
значения.
Таким
образом,
при поиске
символа в vDSO,
вы всегда
должны
включать
версию для
нахождения
ожидаемого
ABI.
Обычно, vDSO
удовлетворяет
соглашению
об
именовании,
начиная
все
символы с
«__vdso_» или «__kernel_»,
для их
выделения
среди
других
стандартных
символов.
Например,
функция
«gettimeofday»
называется
«__vdso_gettimeofday».
Для вызова
этих
функций
используйте
стандартные
соглашения
о вызове
языка Си.
Учитывать
поведение
регистров
и стека не
требуется.
Код vDSO
автоматически
компилируется
и
компонуется
при сборке
ядра.
Обычно, его
можно
найти в
каталоге
соответствующей
архитектуры:
find arch/$ARCH/ -name '*vdso*.so*' -o -name '*gate*.so*'
Имя vDSO
отличается
на разных
архитектурах.
Часто его
можно
увидеть в
выводе
утилит,
подобных
ldd(1)
из glibc. Точное
имя не
должно
влиять на
код,
поэтому
жёстко оно
нигде не
задаётся.
ABI
пользователя |
vDSO name |
|
aarch64 |
linux-vdso.so.1 |
arm |
linux-vdso.so.1 |
ia64 |
linux-gate.so.1 |
mips |
linux-vdso.so.1 |
ppc/32 |
linux-vdso32.so.1 |
ppc/64 |
linux-vdso64.so.1 |
riscv |
linux-vdso.so.1 |
s390 |
linux-vdso32.so.1 |
s390x |
linux-vdso64.so.1 |
sh |
linux-gate.so.1 |
i386 |
linux-gate.so.1 |
x86-64 |
linux-vdso.so.1 |
x86/x32 |
linux-vdso.so.1 |
При
трассировке
системных
вызовов с
помощью
strace(1),
символы
(системные
вызовы),
экспортируемые
vDSO,
не
показываются
в
результате
трассировки.
Также
данные
системные
вызовы
будут
невидимы в
фильтрах
seccomp(2).
Далее
приведены
замечания
по vDSO для
различных
архитектур.
Заметим,
что
используемая
vDSO
основывается
на ABI вашего
кода
пользовательского
пространства,
а не на ABI
ядра.
Например,
когда вы
запускаете
32-битный
исполняемый
файл ELF на i386, то
вы
получаете
ту же vDSO
независимо
от того,
выполняете
ли вы его
под 32-битном
ядром на i386
или под
64-битном
ядром под x86-64.
Таким
образом,
для
определения
нужного
раздела
ниже нужно
использовать
имя ABI
пользовательского
пространства.
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__vdso_gettimeofday |
LINUX_2.6
(экспортируется
начиная с Linux
4.1) |
__vdso_clock_gettime |
LINUX_2.6
(экспортируется
начиная с Linux
4.1) |
Также,
перенос ARM
содержит
страницу
кода со
вспомогательными
функциями.
Так как это
просто
страница с
кодом,
информация
ELF
отсутствует
и поиск
функций
невозможен
и
неизвестны
их версии.
Хотя в коде
есть
функции
нескольких
версий.
For information on this code page, it's best to refer to the kernel
documentation as it's extremely detailed and covers everything you need to
know:
Documentation/arm/kernel_user_helpers.rst.
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__kernel_rt_sigreturn |
LINUX_2.6.39 |
__kernel_gettimeofday |
LINUX_2.6.39 |
__kernel_clock_gettime |
LINUX_2.6.39 |
__kernel_clock_getres |
LINUX_2.6.39 |
As this CPU lacks a memory management unit (MMU), it doesn't set up a vDSO in
the normal sense. Instead, it maps at boot time a few raw functions into a
fixed location in memory. User-space applications then call directly into that
region. There is no provision for backward compatibility beyond sniffing raw
opcodes, but as this is an embedded CPU, it can get away with
things—some of the object formats it runs aren't even ELF based
(they're bFLT/FLAT).
Эта
страница с
кодом
хорошо
описана в
открытой
документации:
http://docs.blackfin.uclinux.org/doku.php?id=linux-kernel:fixed-code
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__kernel_gettimeofday |
LINUX_2.6
(экспортируется
начиная с Linux
4.4) |
__kernel_clock_gettime |
LINUX_2.6
(экспортируется
начиная с Linux
4.4) |
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__kernel_sigtramp |
LINUX_2.5 |
__kernel_syscall_via_break |
LINUX_2.5 |
__kernel_syscall_via_epc |
LINUX_2.5 |
Перенос на
Itanium, в
некоторой
степени,
неоднозначный.
Кроме vDSO,
показанной
выше, также
есть
«легковесные
системные
вызовы»
(также
называемые
как
«быстрые
syscall» или «fsys»).
Вы можете
вызвать их
через
вспомогательную
функцию vDSO
__kernel_syscall_via_epc.
Перечисленные
здесь
системные
вызовы
имеют ту же
семантику,
как если бы
вызывались
напрямую
через
syscall(2),
поэтому
обратитесь
к
соответствующей
документации
по каждому
из них. В
таблице
ниже
перечислены
функции,
доступные
через этот
механизм.
функция |
|
clock_gettime |
getcpu |
getpid |
getppid |
gettimeofday |
set_tid_address |
Перенос parisc
содержит
страницу
кода со
вспомогательными
функциями,
называемую
шлюзовой (gateway)
страницей.
Вместо
того, чтобы
использовать
обычный
вспомогательный
вектор ELF,
передаётся
адрес
страницы
для
обработки
через
регистр SR2.
Права на
страницу
таковы, что
простое
выполнение
этих
адресов
автоматически
выполняется
с правами
ядра и не в
пространстве
пользователя.
Подобный
способ
применяется
в HP-UX.
Так как это
просто
страница с
кодом,
информация
ELF
отсутствует
и поиск
функций
невозможен
и
неизвестны
их версии.
Просто
вызывайте
функцию по
соответствующему
смещению
через
инструкцию
ветвления,
например:
ble <смещение>(%sr2, %r0)
offset |
функция |
|
00b0 |
lws_entry
(операции
CAS) |
00e0 |
set_thread_pointer
(используется
в glibc) |
0100 |
linux_gateway_entry (syscall) |
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO. Функции,
помеченные
*, доступны
только в
ядре PowerPC64
(64-бита).
символ |
version |
|
__kernel_clock_getres |
LINUX_2.6.15 |
__kernel_clock_gettime |
LINUX_2.6.15 |
__kernel_clock_gettime64 |
LINUX_5.11 |
__kernel_datapage_offset |
LINUX_2.6.15 |
__kernel_get_syscall_map |
LINUX_2.6.15 |
__kernel_get_tbfreq |
LINUX_2.6.15 |
__kernel_getcpu *
|
LINUX_2.6.15 |
__kernel_gettimeofday |
LINUX_2.6.15 |
__kernel_sigtramp_rt32 |
LINUX_2.6.15 |
__kernel_sigtramp32 |
LINUX_2.6.15 |
__kernel_sync_dicache |
LINUX_2.6.15 |
__kernel_sync_dicache_p5 |
LINUX_2.6.15 |
Before Linux 5.6, the
CLOCK_REALTIME_COARSE and
CLOCK_MONOTONIC_COARSE clocks are
not supported by the
__kernel_clock_getres and
__kernel_clock_gettime interfaces; the
kernel falls back to the real system call.
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__kernel_clock_getres |
LINUX_2.6.15 |
__kernel_clock_gettime |
LINUX_2.6.15 |
__kernel_datapage_offset |
LINUX_2.6.15 |
__kernel_get_syscall_map |
LINUX_2.6.15 |
__kernel_get_tbfreq |
LINUX_2.6.15 |
__kernel_getcpu |
LINUX_2.6.15 |
__kernel_gettimeofday |
LINUX_2.6.15 |
__kernel_sigtramp_rt64 |
LINUX_2.6.15 |
__kernel_sync_dicache |
LINUX_2.6.15 |
__kernel_sync_dicache_p5 |
LINUX_2.6.15 |
Before Linux 4.16, the
CLOCK_REALTIME_COARSE and
CLOCK_MONOTONIC_COARSE clocks are
not supported by the
__kernel_clock_getres and
__kernel_clock_gettime interfaces; the
kernel falls back to the real system call.
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__vdso_rt_sigreturn |
LINUX_4.15 |
__vdso_gettimeofday |
LINUX_4.15 |
__vdso_clock_gettime |
LINUX_4.15 |
__vdso_clock_getres |
LINUX_4.15 |
__vdso_getcpu |
LINUX_4.15 |
__vdso_flush_icache |
LINUX_4.15 |
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__kernel_clock_getres |
LINUX_2.6.29 |
__kernel_clock_gettime |
LINUX_2.6.29 |
__kernel_gettimeofday |
LINUX_2.6.29 |
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__kernel_clock_getres |
LINUX_2.6.29 |
__kernel_clock_gettime |
LINUX_2.6.29 |
__kernel_gettimeofday |
LINUX_2.6.29 |
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__kernel_rt_sigreturn |
LINUX_2.6 |
__kernel_sigreturn |
LINUX_2.6 |
__kernel_vsyscall |
LINUX_2.6 |
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__kernel_sigreturn |
LINUX_2.5 |
__kernel_rt_sigreturn |
LINUX_2.5 |
__kernel_vsyscall |
LINUX_2.5 |
. |
|
. |
|
__vdso_clock_gettime |
LINUX_2.6
(экспортируется
начиная с Linux
3.15) |
__vdso_gettimeofday |
LINUX_2.6
(экспортируется
начиная с Linux
3.15) |
__vdso_time |
LINUX_2.6
(экспортируется
начиная с Linux
3.15) |
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO. Все эти
символы
также
доступны
без
префикса
«__vdso_», но вы не
должны
пользоваться
этим,
применяйте
имена,
перечисленные
ниже.
символ |
version |
|
__vdso_clock_gettime |
LINUX_2.6 |
__vdso_getcpu |
LINUX_2.6 |
__vdso_gettimeofday |
LINUX_2.6 |
__vdso_time |
LINUX_2.6 |
В таблице
ниже
перечислены
символы,
экспортируемые
vDSO.
символ |
version |
|
__vdso_clock_gettime |
LINUX_2.6 |
__vdso_getcpu |
LINUX_2.6 |
__vdso_gettimeofday |
LINUX_2.6 |
__vdso_time |
LINUX_2.6 |
The vDSO was originally just a single function—the vsyscall. In older
kernels, you might see that name in a process's memory map rather than
"vdso". Over time, people realized that this mechanism was a great
way to pass more functionality to user space, so it was reconceived as a vDSO
in the current format.
syscalls(2),
getauxval(3),
proc(5)
Документация,
примеры и
исходный
код в
дереве
исходного
кода Linux:
Documentation/ABI/stable/vdso
Documentation/ia64/fsys.rst
Documentation/vDSO/* (includes examples of using the vDSO)
find arch/ -iname '*vdso*' -o -iname '*gate*'
Русский
перевод
этой
страницы
руководства
был сделан
Azamat Hackimov <
[email protected]>, Dmitriy Ovchinnikov
<
[email protected]>, Dmitry Bolkhovskikh <
[email protected]>,
Katrin Kutepova <
[email protected]>, Yuri Kozlov
<
[email protected]> и Иван
Павлов <
[email protected]>
Этот
перевод
является
бесплатной
документацией;
прочитайте
Стандартную
общественную
лицензию GNU
версии 3
или более
позднюю,
чтобы
узнать об
условиях
авторского
права. Мы не
несем
НИКАКОЙ
ОТВЕТСТВЕННОСТИ.
Если вы
обнаружите
ошибки в
переводе
этой
страницы
руководства,
пожалуйста,
отправьте
электронное
письмо на
[email protected]