LINUX.ORG.RU

Определить «реальность» времени (т.е. наличие CMOS-часов или NTP-синхронизации)

 , , ,


1

2

Предположим у нас есть некий unix-blackbox без часов реального времени (без батарейки в них) и без NTP (вообще без сети). Тогда при каждом запуске (включении и загрузки) на системных часах такого unix-box будет примерно одинаковое время (или совсем одинаковое).

Задача: Переносимым способом определять такую ситуацию (для тех кто в танке: «переносимо» == «не только на linux, а на любом POSIX/Unix»).

Дополнение в 2019-10-31 20:25 (исправляю упущение в исходной формулировке задачи): Очевидный вариант «увеличивать счетчик в файле при старте» не подходит, так как TL;DR должно работать в библиотеке без изменения её API и без вмешательства в процесс загрузки системы.

У меня есть вариант решения (назовём его «900»), но хочется услышать идеи от молодых и талантливых.

Для понимания: Всё это нужно (т.е. полная постановка задачи) для принятия решения «откатывать или нет» последние транзакции при открытии БД внутри libmdbx (встраиваемый движок БД, замена Berkeley DB), что требует переносимого аналога /proc/sys/kernel/random/boot_id, что в свою очередь требует определения boot time. И вот тут-то и нужно понять что это самое «boot time» не будет одинаковым при каждой загрузке.


Итоговый вариант решения был исходно обозначен как «900» и не изменился:

  • минимальная разница между часовыми поясами 900 секунд (15 минут);
  • Контролируем разницу между CLOCK_REALTIME и CLOCK_MONOTONIC;
  • Если она примерно кратна 900 секундам, то считаем что в системе нет CMOS-часов и/или синхронизации по NTP;

Как и почему это работает:

  • Если в системе нет cmos/NTP, то нет повода чтобы CLOCK_REALTIME и CLOCK_MONOTONIC шли в разнобой, т.е. разница будет равна некоторому часовому поясу (в том числе нулю в случае TZ=UTC).
  • Шаг «квантования» временных зон 15 минут, т.е. любая тайм-зона (в том числе еще не определенная) будет иметь смещение кратное 15 минутам.
  • Поэтому, если разность между CLOCK_REALTIME и CLOCK_MONOTONIC не кратна 15 минутам, то их разность не может быть объяснена только использованием какого-либо часового пояса. Следовательно CLOCK_REALTIME включает сдвиг для соответствия реальному времени и в системе где-то есть источник реального времени (CMOS или NTP).

Эта схема не идеальна и может давать ложно-отрицательный результат. Например, «алгоритм» посчитает что «время не настоящее»:

  • Система вправе инициализировать CLOCK_MONOTONIC от любого удобного источника, включая CMOS-часы. Тогда при наличии СMOS-часов работающих в нужной TZ получается всегда CLOCK_MONOTONIC == CLOCK_REALTIME.
  • С некоторой вероятностью разность может быть кратной 15 минутам.

Тем не менее, субъективно этот вариант лучше остальных (проверке условия «текущая время меньше даты исходников или сборки»). В контексте полной постановки задачи, важно что этот способ не даёт ложно-положительных результатов, и не имеет абсолютных точек привязки ко времени (идемпотентен по временной оси).

Исходники PoC для баловства = https://abf.io/erthink/poc4-boottime-nearly-constant


Добавлено 2019-11-01 в 12:18: В целом тредик/срач для меня полезен благодаря выводам:

  • на embedded-like платформах (как минимум на Linux) следует полагаться только на нативный bootid, ибо boottime может превращаться в тыкву распространенными рецептами в rc-скриптах.
  • генерацию bootid из boottime следует вынести в опцию, ибо нет 100% гарантии выявить все варианты предыдущего пункта.

Всем еще раз спасибо за дискус/срачик. Всё.

Deleted

Ответ на: комментарий от anonymous

смотри системное время и если оно из прошлого по отношению даты создания кода то синхронизации времени нет.

Эта эвристика входит в мой рецепт, но не всегда «прокатывает». Например, в embedded время может стартовать не c 1970-01-01, а с даты выпуска прошивки. Кроме этого, при наличии CMOS может быть выставлено неверное время (бывает относительно часто).

Deleted ()
Ответ на: комментарий от Deleted

в embedded время может стартовать не c 1970-01-01, а с даты выпуска прошивки

твой код в любом случае позднее создан

при наличии CMOS может быть выставлено неверное время

тогда задача вообще не решается - у тебя есть только правильная дата создания кода и определить правильность системного времени без эталона ты не сможешь

anonymous ()
Ответ на: комментарий от deep-purple

filemtime? тоже неплохо, правда, прокатит если ничего кроме только определения времени не нужно.

Во-первых, если время в системе неверное, то и mtime у всех файлов будет будет тоже кривое.

Во-вторых, mtime для какого файла и что это даст? Даже если filemtime(database_file) > current_time(), то это может быть из-за коррекции времени (в том числе по NTP).

Deleted ()

Где-то ошибка в логике

Понеслась

Нет батарейки, нет сети

Два варианта, таймер времени в оперативе, таймер времени в епруме

Первый вариант - состояние не определено по умолчанию

Второй вариант - при буте разница во времени не может быть определена, мы вычитываем из епрум последнее валидное время.

Собственно решали такую задачу для эмбеда, выхода только два - батарейка, либо синхронизация с устройством имеющее точное время. Второй вариант был несколько раз реализован, при помощи БТ, ГПС, ну и классика по сети к НТП

anonymous ()
Ответ на: комментарий от anonymous

в embedded время может стартовать не c 1970-01-01, а с даты выпуска прошивки

твой код в любом случае позднее создан

Это только «сегодня», а «завтра» будет наоборот и всё внезапно сломается при «обновлении прошивки». Короче, это эвристика, но только ёё мало.

при наличии CMOS может быть выставлено неверное время

тогда задача вообще не решается - у тебя есть только правильная дата создания кода и определить правильность системного времени без эталона ты не сможешь.

Есть еще одна цифра (мое решение «900»): Опуская делали, если разница между clock_gettime(CLOCK_REALTIME) и clock_gettime(CLOCK_MONOTONIC) кратна 900 секундам (15 минут = минимальная разница в часовых поясах), то системное время «не настоящее». Этот рецепт не будет работать только если кто-то сделает прошивку, где CLOCK_MONOTONIC будет считаться с нуля или 1970-01-01, а CLOCK_REALTIME с даты прошивки (но это как-то совсем дико и непонятно зачем).

Deleted ()
Ответ на: комментарий от Deleted

Этот рецепт не будет работать только если кто-то сделает прошивку,
CLOCK_MONOTONIC Clock that cannot be set and represents monotonic time since some unspecified starting point

с таким же успехом можно рандомно сказать, да - время синхронизировано.

anonymous ()
Ответ на: комментарий от anonymous

Где-то ошибка в логике

Тут не ошибка в логике, т.е. я знаю как делать правильно и что не нужно есть с ножа.

Но нужно в любой ситуации, в том числе плохой и не логичной, принимать рациональное решение.

Собственно, вопрос задан чтобы собрать коллективное мнение чтобы уменьшить вероятность что-нибудь упустить.

Deleted ()
Ответ на: комментарий от anonymous

Этот рецепт не будет работать только если кто-то сделает прошивку,

CLOCK_MONOTONIC Clock that cannot be set and represents monotonic time since some unspecified starting point

с таким же успехом можно рандомно сказать, да - время синхронизировано.

Задача другая и логическая цепочка другая.

  • НЕ нужно синхронизированное время, и НЕ нужно знать синхронизировано ли оно (по NTP) или нет.
  • НУЖНО знать будет ли boot time (примерно) таким-же при следующей перезагрузки.
Deleted ()
Ответ на: комментарий от Deleted

НУЖНО знать будет ли boot time (примерно) таким-же при следующей перезагрузки.

зачем тебе вообще знать boot time ? сохраняй системное время при ините в файл и в следующий раз ты точно будешь знать такое же оно или нет.

anonymous ()
Ответ на: комментарий от anonymous

зачем тебе вообще знать boot time ? сохраняй системное время при ините в файл и в следующий раз ты точно будешь знать такое же оно или нет.

В TC же написано, что boot time нужно для генерации bootid, с тем чтобы видеть перезапуск системы и откатывать или не откатывать транзакции внутри БД.

На всякий, упрощенно: если система перезагрузилась, то данные после крайнего fdatasync() / msync(MS_SYNC) могли не полностью дойти до диска и rollback нужен, иначе нет (просто упал или был убит процесс использующий движок БД).

Deleted ()

Для понимания: Всё это нужно (т.е. полная постановка задачи) для принятия решения «откатывать или нет» последние транзакции при открытии БД,

Стесняюсь спросить, а бинарные логи просто так придумали? Зачем тебе вообще время знать при такой постановке задачи? Либо завершение было корректным = не откатываем, либо не корректным - по бинлогам восстанавливаем консистентное состояние. Время - вообще дело десятое и нафиг не нужно.

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

Стесняюсь спросить, а бинарные логи просто так придумали?

его задача вообще не имеет переносимого решения - логов может не быть на встраиваемых системах после перезагрузки

anonymous ()
Ответ на: комментарий от anonymous

Всё это нужно (т.е. полная постановка задачи) для принятия решения «откатывать или нет» последние транзакции при открытии БД

его задача вообще не имеет переносимого решения - логов может не быть на встраиваемых системах после перезагрузки

Где-то здесь противоречие

anonymous ()
Ответ на: комментарий от anonymous

системный лог на встраиваемых системах очень часто в tmpfs

Риторические философские вопросы. Чем обеспечивается персистентность БД (сохранность данных)? Если БД в оперативке, то какой смысл в «реальности» времени вне «реальности» БД - между перезагрузками?

anonymous ()
Ответ на: комментарий от anonymous

Если БД в оперативке

БД и сислог - это разные объекты, сислог элементарно может быть в tmpfs, в buildroot например это по умолчанию, для примера то что там в /var

buildroot/output/target$ ls -l ./var
total 4
lrwxrwxrwx 1 user user    6 июн  2 11:01 cache -> ../tmp
drwxr-xr-x 2 user user 4096 июн  2 11:01 lib
lrwxrwxrwx 1 user user    6 июн  2 11:01 lock -> ../tmp
lrwxrwxrwx 1 user user    6 июн  2 11:01 log -> ../tmp
lrwxrwxrwx 1 user user    6 июн  2 11:01 run -> ../run
lrwxrwxrwx 1 user user    6 июн  2 11:01 spool -> ../tmp
lrwxrwxrwx 1 user user    6 июн  2 11:01 tmp -> ../tmp

база может быть в любой директории - хомяк например на физическом носителе

anonymous ()
Ответ на: комментарий от anonymous

И что мешает писать «реальное время» в БД, в то же место что БД?

ничего, только ему не время нужно а определить была ли нештатная перезагрузка системы универсальным для любой системы методом.

anonymous ()
Ответ на: комментарий от anonymous

Пишешь туда же, где «реальность БД», что система выключена штатным способом

ты просто вообще не понял сути, впрочем как и я по началу

Тут другой вопрос, почему БД не умеет делать это сама?

потому что он хочет сделать это в своей БД

anonymous ()
Ответ на: комментарий от anonymous

ты просто вообще не понял сути, впрочем как и я по началу

Да все там понятно - хочет сделать персистентые транзакции. Но почему-то хочет совместить разные персистентности - «реальности»: «реальное время» и «реальность БД». И возникают странные вопросы.

anonymous ()
Ответ на: комментарий от anonymous

Да все там понятно

видимо не всё, у Linux есть уникальный идентификатор загруженной сессии, для BSD используют системный вызов «время загрузки» который с работающими часами реального времени уникальный. Всё отлично но RTC не всегда работают правильно - могут для каждой сессии показывать одно и то же время, вот он и хочет даже для таких систем как-то определять что работает без перезагрузки.

anonymous ()
Ответ на: комментарий от anonymous

Ему не надо привязываться к другой реальности - к «реальному времени». Он может создать свою реальность для БД - атомарный персистентный счетчик.

Кстати «реальное время» - тоже счетчик, который хочет быть атомарным для всех наблюдателей его реальности.

anonymous ()
Ответ на: комментарий от anonymous

Как это решают mariadb/mysql с innodb? как это решается в pgsql? Если он свой код пишет - может там посмотреть. А из самого простого он может при штатном завершении работы создавать файл и удалять его после запуска - это работает на любой системе. К чему велосипед изобретать, тем более с квадратными колёсами - не ясно.

Deleted ()
Ответ на: комментарий от Deleted

Стесняюсь спросить, а бинарные логи просто так придумали? Зачем тебе вообще время знать при такой постановке задачи? Либо завершение было корректным = не откатываем, либо не корректным - по бинлогам восстанавливаем консистентное состояние. Время - вообще дело десятое и нафиг не нужно.

БД бывают разные… (на всякий - стоит отличать «БД» и «СУБД»).

В данном случае не СУБД, а встраиваемый движок с рядом особенностей. В частности нет WAL и полный mmap для данных, есть специфические режимы работы, в том числе «небезопасные» (без fdatasync/msync после каждой транзакции). Например:

  • данные отображаются в память в режиме чтения-записи, все работающие с БД процессы «просто» меняют данные в памяти (и читают их от-туда);
  • на диск данные пишутся ядром асинхронно, плюс явный msync() когда попросят;
  • если «всё падает», то движок логически откатывает БД к точке последнего msync().

Но тут есть «перестраховка»:

  • если был системный сбой (выключение питания, ядерный oops и т.п.), то откат нужен.
  • НО если просто упал (убит OOM-киллером) процесс работающий с БД, то откатывать не надо.

Поэтому нужен bootid, который в явном виде есть только в Linux, Darwin и кое-как неявно в масдае. В остальных случаях bootid несложно сделать своими силами из gethostid() и boot time, но вот с boot time нужно работать очень аккуратно…

Deleted ()
Ответ на: комментарий от Deleted

При завершении создавать файл не судьба? Нет, ну и что ты паришь тут со временем и т.п.? Если корректный шатдаун - делай touch somefile. При запуске если файла нет - ой, пипец, откатываем. Если файл есть - удаляем файл и работаем штатно. Зачем тут всякие огороды? Раз при OOM откатывать не надо -> делаешь это не в скрипте запуска сервиса, а, скажем, в cron по @reboot. Не, не катит?

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от anonymous

системный лог на встраиваемых системах очень часто в tmpfs

Риторические философские вопросы. Чем обеспечивается персистентность БД (сохранность данных)? Если БД в оперативке, то какой смысл в «реальности» времени вне «реальности» БД - между перезагрузками?

Системные журналы (полностью или частично) могут быть в tmpfs, но при этом может быть и БД «на флешке».

Причем в таких случаях внутри БД может хранится и конфигурация устройства (таблица маршрутизации и т.п.). В ТС речь идет о движке хранения, который примерно так и используют (в том числе потому-что в разы быстрее и компактнее SQLite).

Соответственно, такой движок БД должен вести себя максимально адекватно в самых странных ситуациях. Например, когда «села батарейка» или «пробило EEPROM» и т.п.

Для страховки от подобных ситуаций, в контексте обсуждаемой темы, резонно посоветовать «не есть с ножа» и sync-ать на диск/флешку каждую транзакцию. Но это должен быть выбор пользователя. Мне же, как разработчику, нужно дать ему выбор, т.е. максимально корректно реализовать фичу со стороны движка БД (в том числе поведение в самых нелогичных и плохих ситуациях).

Deleted ()
Ответ на: комментарий от Deleted

При завершении создавать файл не судьба? Нет, ну и что ты паришь тут со временем и т.п.? Если корректный шатдаун - делай touch somefile. При запуске если файла нет - ой, пипец, откатываем. Если файл есть - удаляем файл и работаем штатно. Зачем тут всякие огороды? Раз при OOM откатывать не надо -> делаешь это не в скрипте запуска сервиса, а, скажем, в cron по @reboot. Не, не катит?

Это всё уже сделано внутри без всяких внешних файлов, и какие-либо «скрипты» и прочие костыли не приемлемы.

На всякий: меня интересуют соображения только по теме обозначенной в ТС, а вот советов как делать БД, WAL и т.п. - совсем не надо.

Deleted ()
Ответ на: комментарий от Deleted

Эта эвристика входит в мой рецепт, но не всегда «прокатывает». Например, в embedded время может стартовать не c 1970-01-01, а с даты выпуска прошивки. Кроме этого, при наличии CMOS может быть выставлено неверное время (бывает относительно часто).

такой embedded уже не Unix, ты говорил про Unix-ы и POSIX, а там время начинается с 1970

Harald ★★★★★ ()
Ответ на: комментарий от Deleted

а с даты выпуска прошивки

у тебя есть дата релиза твоей софтинки, всё, что раньше этой даты - точно неправильное время, а позже - уже могут быть варианты

Harald ★★★★★ ()
Ответ на: комментарий от Deleted

Для страховки от подобных ситуаций, в контексте обсуждаемой темы, резонно посоветовать «не есть с ножа» и sync-ать на диск/флешку каждую транзакцию. Но это должен быть выбор пользователя. Мне же, как разработчику, нужно дать ему выбор, т.е. максимально корректно реализовать фичу со стороны движка БД (в том числе поведение в самых нелогичных и плохих ситуациях).

Заведи свои «альтернативные часы», которые показывают суммарное вермя работы. Перед стартом задач, зависящих от твоего «алтернативного времени», стартуешь свои «альтернативные часы», которые инициализируются временем, сохраненным в файле. Можешь через определенный промежуток времени записывать туда текущее «альтернативное время».

Будут своеобразные часы суммарного времени работы системы - сумма аптаймов.

anonymous ()
Ответ на: комментарий от Deleted

Ну раз советов не нужно и ты хочешь побредить - пожалуйста, удаляюсь. Но ты что-то делаешь не так )

На всякий - БД уже давно всё делает правильно, в том числе откат к крайней sync-фиксации. Сейчас же речь о том, чтобы не делать отката когда в нём нет необходимости. А так как это встраиваемая БД (libmdbx), то без изменения API это реализуется только посредством сверки bootid (и вот тут действительно один из вариантов - просто не давать такой возможности на платформах без нативного bootid).

Deleted ()
Ответ на: комментарий от anonymous

Заведи свои «альтернативные часы», которые показывают суммарное вермя работы. Перед стартом задач, зависящих от твоего «алтернативного времени», стартуешь свои «альтернативные часы», которые инициализируются временем, сохраненным в файле. Можешь через определенный промежуток времени записывать туда текущее «альтернативное время».

Есть более простые и очевидные решения:

  • поддерживать boot sequence, т.е. при старте системы увеличивать счетчик в каком-то персистентном файле.
  • при старте удалять какой-нибудь необязательный/вспомогательный файл БД (добавить такой файл).
  • явно добавить функцию/утилиту, которую нужно вызывать при старте системы…

Но всё это означает изменения API и/или поведение движка БД.

Deleted ()
Ответ на: комментарий от Deleted

Но всё это означает изменения API и/или поведение движка БД.

Ты тролль.

Все решения, которые здесь написали, в том числе и твои с определением «реальности», требуют изменения движка БД.

При этом ты даже не написал, что за БД и какое у него АПИ. То есть задача изначально была про определение (написание) этого АПИ. Естественно в такой «игре» все кроме тебя будут неправы.

Можешь при старте системы устанавливать системное время из файла (из моего предыдущего сообщения). У этого времени после инициализации есть важное свойство - монотонность. Это свойство можешь использовать для определения немонотонности (нереальности) времени, полученного другим путем.

anonymous ()

Ненавидимый всеми системдэ говорит на старте: время на ваших часах гораздо меньше, чем таймстэмп времени сборки моего бинаря, подкручиваю эти ваши часы с кукушкой.

// Узнал об этом как раз запустив дебиан 10 на такой железяке без батарейки.

aol ★★★★★ ()
Ответ на: комментарий от anonymous

Но всё это означает изменения API и/или поведение движка БД.

Ты тролль.

Все решения, которые здесь написали, в том числе и твои с определением «реальности», требуют изменения движка БД.

Соглашусь только с упущением в ТС о том, что нельзя вмешиваться в процесс старта ОС (что уже поправил). Из чего следует что сам процесс старта можно «заметить» только через POSIX API.

Остальное же последствия уже лишней, но еще не полной информации (какая БД, какое API, где что можно/нельзя менять и т.д.).

При этом ты даже не написал, что за БД и какое у него АПИ. То есть задача изначально была про определение (написание) этого АПИ. Естественно в такой «игре» все кроме тебя будут неправы.

Ссылки на libmdbx были в комментах, рядом с озвучиванием рецепта «900». Но условия и решения задачи это не влияет (после добавления поправки что нельзя вмешиваться в процесс старта системы).

Deleted ()