Часы в Linux. Часть 1
Семён сохраняет полезное_)В Linux время не представляется как единый непрерывный поток — вместо этого используется концепция clock domains (доменов времени), каждый из которых служит для определённых задач и обладает уникальными свойствами. Разберём ключевые аспекты этой модели.
Основные типы часов (clock domains)
1.CLOCK_REALTIME
стандартное «настенное» время (wall‑clock), отсчитываемое от эпохи Unix (UTC). Используется для отображения текущего времени и даты. Подвержено корректировкам (например, при синхронизации по NTP).
2.CLOCK_MONOTONIC
монотонное время, начинающееся с момента загрузки системы. Не идёт назад, не подвержено скачкам. Идеально для измерения интервалов времени.
3.CLOCK_MONOTONIC_RAW
«сырое» монотонное время без каких‑либо корректировок со стороны ядра. Отражает чистое аппаратное время (hardware clock). Полезен для задач, требующих максимальной точности без влияния программных поправок.
4.CLOCK_BOOTTIME
аналогично CLOCK_MONOTONIC, но учитывает время, проведённое в режимах приостановки (suspend). Позволяет получить общее время работы системы, включая периоды «сна».
5.CLOCK_TAI
время по международной шкале атомного времени (TAI). Линейно, без високосных секунд (leap seconds), что делает его удобным для научных и высокоточных вычислений.
Аппаратные источники времени (clocksource)
Ядро Linux выбирает один активный источник времени из доступных аппаратных компонентов. Выбор основан на точности, стабильности и доступности устройства. Основные варианты:
- TSC (Time Stamp Counter) - счётчик тактов процессора. Очень точный и быстрый, но может быть ненадёжным на системах с изменяемой частотой процессора.
- HPET (High Precision Event Timer) высокоточный таймер событий. Обеспечивает стабильную частоту и хорошую точность.
- ACPI PM Timer-таймер управления питанием ACPI. Менее точный, но широко распространён.
- ARM arch timer -встроенный таймер архитектуры ARM. Стандартный источник для ARM‑систем.
Проверка текущего источника времени:
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
Как ядро считает время
Ядро преобразует аппаратные такты (cycles) в наносекунды по формуле:
time=base+(cycles×mult≫shift)
где:
base— базовое время (начальное смещение);cycles— количество тактов, отсчитываемых аппаратным источником;multиshiftкоэффициенты масштабирования, позволяющие точно преобразовать такты в наносекунды без использования операций с плавающей точкой.
vDSO: ускорение чтения времени
vDSO (virtual Dynamic Shared Object) - механизм, позволяющий приложениям получать текущее время без выполнения системного вызова (syscall). Это значительно ускоряет операции чтения времени, особенно для CLOCK_MONOTONIC.
Пример использования:
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
Вызов clock_gettime() выполняется в пространстве пользователя, минуя переключение контекста в ядро.
Синхронизация времени
Для поддержания точности системного времени используются два основных протокола:
- NTP (Network Time Protocol) - стандартный протокол синхронизации по сети:
- step - резкий скачок времени (используется при больших расхождениях);
- slew - плавная корректировка скорости хода часов (предпочтительный режим для малых отклонений).
- PTP (Precision Time Protocol) - протокол для высокоточной синхронизации (микросекундный и наносекундный диапазон):
- hardware timestam ping - аппаратная отметка времени пакетов, минимизирующая задержки в программном стеке;
- PHC (PTP Hardware Clock) аппаратные часы, поддерживающие PTP. Доступны через
/dev/ptpX.
Практическое использование часов
Для чтения времени из различных доменов используется функция clock_gettime():
struct timespec ts; // CLOCK_REALTIME clock_gettime(CLOCK_REALTIME, &ts); // CLOCK_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &ts); // CLOCK_MONOTONIC_RAW clock_gettime(CLOCK_MONOTONIC_RAW, &ts); // CLOCK_BOOTTIME clock_gettime(CLOCK_BOOTTIME, &ts); // CLOCK_TAI clock_gettime(CLOCK_TAI, &ts);
Kernel API: внутренние функции ядра
Ядро предоставляет набор функций для работы с различными доменами времени:
ktime_get()— возвращает монотонное время (CLOCK_MONOTONIC).ktime_get_raw()— возвращает «сырое» монотонное время (CLOCK_MONOTONIC_RAW).ktime_get_real()— возвращает реальное время (CLOCK_REALTIME).ktime_get_boottime()— возвращает время с учётом приостановки (CLOCK_BOOTTIME).
Внутренняя модель расчёта времени
Ядро поддерживает несколько слоёв времени, вычисляемых на основе базового «сырого» времени (RAW):
RAW =base+delta
MONOTONIC =RAW+freq_adjust
REALTIME =MONOTONIC+offs_real
BOOTTIME =MONOTONIC+offs_boot
TAI =REALTIME+offs_tai
где:
RAW— базовое аппаратное время;freq_adjust— поправки на дрейф частоты;offs_real— смещение для UTC (включая NTP‑корректировки);offs_boot— время, проведённое в suspend;offs_tai— смещение между UTC и TAI (учитывает leap seconds).
Эта модель обеспечивает гибкость и точность, позволяя использовать подходящий домен времени для каждой задачи от отображения даты на экране до синхронизации распределённых систем с наносекундной точностью.
Рекомендации по дальнейшему изучению:
- Документация ядра: Documentation/timers/
- Исходный код: kernel/time/
- Статья «Timekeeping in the Linux Kernel» (Thomas Gleixner)