Watchdog — это устройство, предназначенное для обнаружения и устранения проблем оборудования. Обычно для этого используется таймер, периодический перезапуск которого предотвращает отправку сигнала на перезагрузку.
Целевой сервер на Gentoo используется мной в основном для экспериментов, однако на нём работает ряд сервисов, которые, по возможности, должны быть доступны без перебоев. К сожалению, последствия некоторых экспериментов приводят к kernel panic, 100% загрузке CPU и другим неприятностям в самый не подходящий момент. Так что идея добавить watchdog давно требовала внимания и наконец материализовалась в данное устройство.
После пристального осмотра того, что было в наличии и оценки доступного времени, оптимальным вариантом стал watchdog собранный на базе Arduino Nano. Примерно также появился и список требований:
Таким образом, «микроскоп» был найден, «гвоздь» обозначен… можно забивать.
Основой устройства стал китайский клон Arduino Nano, выполненный на базе чипа Ch440. Свежие Linux ядра (проверял начиная с 3.16) имеют подходящий драйвер, так что устройство легко обнаруживается как USB последовательный порт.
При каждом подключение терминала, Arduino перезагружается. Причина в отправке терминалом сигнала DTR (Data Terminal Ready), который вызывает перезагрузку устройства. Таким образом Arduino IDE переводит устройсво в режим для загрузки скетчей.
Существует несколько вариантов решения проблемы, но рабочим оказался только один — необходимо установить электролит 10µF (C1 на схеме ниже) между контактами RST и GND.
К сожалению, это также блокирует загрузку скетчей на устройство.Как итог — схема получилась следующий:
Нарисовано с помощью KiCad
Пояснения к схеме
Микроконтроллеры ATmega имеют встроенный механизм перезагрузки по таймеру WDT (WatchDog Timer). Однако все попытки использовать данную функцию приводили к boot-loop, выйти из которого можно было только отключив питание.
Не долгие поиски выявили, что загрузчики большинства клонов Arduino не поддерживают WDT. К счастью, данная проблема была решена в альтернативном загрузчике Optiboot.
Для того, чтобы прошить загрузчик, необходим программатор умеющий работать по протоколу SPI, также желательно, чтобы Arduino IDE знала это устройство «в лицо». В данном случае идеально подойдёт ещё одна Arduino.
Если взять Arduino UNO, в качестве программатора, и последнию на данный момент версию Arduino IDE v1.6.5, то алгоритм будет следующий:
Arduino Uno (программатор) | Arduino Nano (ICSP разъём) | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
На фото это выглядит так
После этой процедуры, загружать скетчи в Arduino Nano нужно будет выбирая те-же настройки — Board: Optiboot on 32 pin cpus, Processor: ATmega328p, CPU Speed: 16MHz.
Далее необходимо всё спаять, так чтобы выглядело одним куском.
Здесь USB штекер понадобился из-за того, что у меня mini-ITX мат.плата только с одним разъем на пару USB2.0, которые нужны на передней панели, а к контактной площадке USB3.0 нечем было подключиться. По возможности такие устройства нужно подключать прямо к мат.плате, чтобы провода наружу не торчали.
Пайка, как правило, проблем не вызывает, но в данном случае используется макетная плата, и тут есть своя специфика.
Как паять дорожки на макетной плате
Сначала необходимо напаять шарики на отверстия (долго греть нельзя, иначе олово вытечет с обратной стороны). Затем напаять перемычки между парами соседних шариков и закончить дорожку спаяв оставшиеся сегменты.
Выглядеть должно примерно так:
Результат:
Здесь может показаться, что некоторые контакты плохо пропаяны, но это не так, проблема в припое, он содержал 40% флюса.
Объективно говоря, код этого проекта особого интереса не представляет. Вводные далеко не экстремальные, а архитектура описывается одной фразой: отправить команду — подождать ответ. Для порядка опишу здесь основной функционал и кратко остановлюсь на самых интересных моментах, с моей точки зрения.
Весь код опубликован на GitHub, так-что если вы знакомы с Bash и С/C++ (в контексте Arduino скетчей), чтение на этом месте можно закончить. При наличии интереса, с готовым результатом можно ознакомиться здесь.
При подключении watchdog создается файл устройства, содержащий порядковый номер. Если в системе есть другие ttyUSB устройства (в моём случае — модем), то возникает проблема с нумерацией.
Чтобы однозначно идентифицировать устройство, необходимо создать симлинк с уникальным именем. Для этого предназначен udev, который наверняка уже есть в системе.Для начала нужно визуально найти подключённый watchdog, например, подсмотрев в системный лог файл. Затем, заменив /dev/ttyUSB0 на нужное устройство, написать в терминале:
udevadm info -a -p "$(udevadm info -q path -n /dev/ttyUSB0)"
Пример вывода
looking at device ‘/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.4/1-1.4:1.0/ttyUSB0/tty/ttyUSB0’:
KERNEL==«ttyUSB0»
SUBSYSTEM==«tty»
…
looking at parent device ‘/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.4/1-1.4:1.0/ttyUSB0’:
KERNELS==«ttyUSB0»
SUBSYSTEMS==«usb-serial»
looking at parent device ‘/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1.4/1-1.4:1.0’:
…
looking at parent device ‘/devices/pci0000:00/0000:00:14. 0/usb1/1-1/1-1.4′:
SUBSYSTEMS==«usb»
DRIVERS==«usb»
ATTRS{idVendor}==«1a86»
ATTRS{idProduct}==«7523»
ATTRS{product}==«USB2.0-Serial»
…
В данном случае, правило будет иметь следующий вид:
ACTION=="add", KERNEL=="ttyUSB[0-9]*", SUBSYSTEM=="tty" SUBSYSTEMS=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="ttyrst-watchdog"
Разместить его нужно в отдельном файле в директории /etc/udev/rules.d, например 51-ttyrst-watchdog.rules и скомандовать udev перезагрузить правила:
udevadm control --reload-rules
С этого момента, при подключении watchdog будет создаваться ссылка /dev/ttyrst-watchdog на нужное устройство, которая и будет использоваться далее.
Общение с watchdog производится на скорости 9600 бод. Arduino без проблем работает с терминалами на больших скоростях, но команды для работы с текстом (cat, echo и т.п.), получают и отправляют только мусор. Не исключено, что это особенность только моего экземпляра Arduino Nano.
Для основного цикла перезапуска таймера и для функций командной строки, используется один скрипт. Причина в том, что оба компонента используют общий ресурс — файл устройства, и к нему необходимо обеспечить синхронный доступ.
Синхронизация по сути состоит цикла ожидания:
while fuser ${DEVICE} >/dev/null 2>&1; do true; done
и захвата устройства на необходимое время:
cat <${DEVICE}
Очевидно, такая схема подвержена состоянию гонки (race condition). Бороться с этим можно по взрослому (например, организовать очередь сообщений), но в данном случае, достаточно грамотно расставить таймауты, чтобы гарантированно получать результат за приемлемое время. По сути весь скрипт и есть работа с таймаутами.
Демонизация (запуск в фоновом режиме) производится средствами пакета OpenRC. Предполагается, что данный скрипт находится в файле /usr/local/bin/ttyrst-watchdog.sh, а OpenRC скрипт в /etc/init.d/ttyrst-watchdog.
При остановке демона требуется корректная дезактивации watchdog. Для этого в скрипте устанавливается обработчик сигналов, требующих завершение работы:
trap deactivate SIGINT SIGTERM
И тут всплывает проблема — OpenRC не может остановить демон, точнее может, но не часто.
Дело в том, что команда kill, отправляет сигнал скрипту, а программа sleep, которая используется для приостановки работы скрипта, выполняется в другом процессе и сигнал не получает. В результате функция deactivate запускается только после завершения работы sleep, а это слишком долго.
Решение заключается в том, чтобы запустить sleep в фоне и ждать завершения процесса в скрипте:
sleep ${SLEEP_TIME} & wait $! # переменная $! содержит ID последнего запущенного процесса
Основные константы:
WATCHDOG_ACTIVE — YES или NO, соответственно, отправлять сигнал на перезагрузку при срабатывании таймера или нет.
WATCHDOG_TIMER — время в секундах на которое устанавливается таймер.
SLEEP_TIME — время в секундах через которое необходимо перезапускать таймер. Должно быть много меньше, чем WATCHDOG_TIMER, но не сильно маленькое, что бы не создавать чрезмерную нагрузку на систему и устройство. При текущих таймаутах разумный минимум — примерно 5 секунд.
DEFAULT_LOG_LINES — число последних записей лога устройства, возвращаемых командой log по умолчанию.
Команды скрипта:
start — запуск основного цикла перезапуска таймера. В функцию is_alive можно добавить код дополнительных проверок, например проверить возможность подключения по ssh.
status — вывод статуса устройства.
reset — обнуление EEPROM (данных лога) и перезагрузка устройства для приведения watchdog в исходное состояние.
log <число записей> — вывод заданного числа последних записей лога.
Для успешной компиляции скетча потребуется сторонняя библиотека Time, необходимая для синхронизации времени.
Скетч состоит из двух файлов. Это связанно с тем, что Arduino IDE не воспринимает структуры (struct) объявленные в основном файле, их необходимо выносить во внешней файл заголовков. Также для объявления структуры не обязательно ключевое слово typedef, вероятно даже вредно… проверив стандартные варианты, подобрать подходящий синтаксис у меня не получилось. В остальном это более или менее стандартный C++.
Функции wdt_enable и wdt_reset работают со встроенным в микроконтроллер watchdog. После инициализации WDT главное не забывать сбрасывать его в основном цикле и внутри циклов всех длительных операций.
Записи лога пишутся в энергонезависимую память EEPROM, доступный её размер можно указать в logrecord.h, в данном случае это число 1024. Лог выполнен в виде кольца, разделителем служит структура с нулевыми значениями. Максимальное число записей для 1 KiB EEPROM — 203.
Запись о загрузке устройства попадает в лог только после синхронизации времени. Синхронизация производится одновременно с перезапуском таймера и перед выполнением любой команды во время инициализации устройства. По другому сопоставить корректное время данному событию не получится, да и информация о перезагрузках устройства, в отрыве от работающего демона, не сильно интересна.
На этом всё, спасибо за внимание!
Исходные файлы проекта расположены на GitHub
Автор: custos
Источник
Сторожевой таймер (WDT) — это аппаратный таймер, который автоматически производит сброс системы, если основная программа не выполняет его периодическое обслуживание. Он часто используется для автоматического сброса встроенного устройства, которое зависает из-за программного или аппаратного сбоя. Плата Arduino Uno имеет микросхему ATmega328P в качестве блока управления, который имеет сторожевой таймер, который помогает системе восстанавливаться после сценариев, когда система зависает или зависает из-за ошибок в коде или из-за условий, которые могут возникнуть из-за проблем с оборудованием.
Как работает сторожевой таймер?
Сторожевой таймер использует внутренний источник тактового сигнала 128 кГц, и его необходимо настроить в соответствии с потребностями приложения. Когда он включен, он начинает отсчет от 0 до значения, выбранного пользователем. Если сторожевой таймер не сбрасывается к тому времени, когда он достигает выбранного пользователем значения, таймер сбрасывает микроконтроллер.
Основная программа обычно имеет цикл, через который она постоянно проходит, выполняя различные функции. Сторожевой таймер загружается с начальным значением, превышающим наихудшую временную задержку в основном цикле программы, поэтому каждый раз, когда он проходит через основной цикл, код сбрасывает сторожевой таймер. Если возникает ошибка, и основная программа не возвращается для сброса таймера до того, как он совершит обратный отсчет до нуля, генерируется прерывание для сброса процессора. Таким образом, сторожевой таймер может обнаружить сбой в программе Arduino без присмотра и предпринять корректирующие действия с помощью сброса. После сброса также можно прочитать регистр, чтобы определить, сгенерировал ли сторожевой таймер сброс или это был обычный сброс. Этот регистр на Arduino называется регистром флага сброса сторожевого таймера (WDRF).
Сторожевой таймер ATmega328P может быть сконфигурирован для 10 различных настроек времени (время, после которого сторожевой таймер переполняется, вызывая сброс). Различные значения времени: 16 мс, 32 мс, 64 мс, 0,125 с, 0,25 с, 0,5 с, 1 с, 2 с, 4 с и 8 с.
Теперь, когда у нас есть общее представление о том, как работает таймер, научимся его настраивать. Мы будем использовать простой пример мигания встроенного светодиода на Arduino UNO.
После запуска вышеупомянутой программы светодиод на плате будет мигать в течение определенного времени перед входом в цикл while. Цикл while здесь заменяет систему в зависшем состоянии. Поскольку сторожевой таймер не сбрасывается в цикле while, таймер вызывает сброс системы, и светодиод снова начинает мигать, прежде чем система зависнет и снова перезапустится. Это продолжается в цикле.
Примечание. Сторожевой таймер отключается в начале кода, и перед включением таймера используется задержка в 3 секунды. Задержка важна для того, чтобы позволить загрузчику в Arduino проверить, загружается ли новый код, и дать ему время записать код во флэш-память.
В этом блоге мы узнали о сторожевом таймере в Arduino UNO, о том, как он работает, и увидели простой скетч для его включения.
/* ———————————————————— ——————————— | |||
* Лицензия на программный пакет SAM | |||
* — ————————————————— ———————— | |||
* Copyright (c) 2011, Atmel Corporation | |||
* | |||
* Все права защищены. | |||
* | |||
* Распространение и использование в исходном и бинарном виде, с модификацией или без модификации | |||
* разрешены при соблюдении следующих условий: | |||
7 700299 | * — Распространение исходного кода должны сохранить вышеуказанное уведомление об авторских правах, | ||
* этот список условий и заявление об отказе от ответственности ниже. | |||
* | |||
* Имя Atmel не может использоваться для поддержки или продвижения продуктов, производных от | |||
* данного программного обеспечения, без специального предварительного письменного разрешения. | |||
* | |||
* ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ КОМПАНИЕЙ ATMEL «КАК ЕСТЬ» И ЛЮБЫМИ ЯВЛЕННЫМИ ИЛИ | |||
* КОММЕРЧЕСКАЯ ПРИГОДНОСТЬ, ПРИГОДНОСТЬ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ И НЕНАРУШЕНИЕ ПРАВ | |||
* ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ. НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ ATMEL НЕ НЕСЕТ ОТВЕТСТВЕННОСТИ ЗА КАКИЕ-ЛИБО ПРЯМЫЕ, КОСВЕННЫЕ, | |||
* СЛУЧАЙНЫЕ, ОСОБЫЕ, ПРИМЕРНЫЕ ИЛИ КОСВЕННЫЕ УБЫТКИ (ВКЛЮЧАЯ, НО НЕ | |||
, ПРОГРАММЫ ЗАЩИТЫ ТОВАРЫ ИЛИ УСЛУГИ, ПОТЕРИ ИСПОЛЬЗОВАНИЯ, ДАННЫХ, | |||
* ИЛИ ПРИБЫЛИ; ИЛИ ПРЕРЫВАНИЯ ДЕЯТЕЛЬНОСТИ) ОДНАКО ВЫЗВАННАЯ И НА ЛЮБОЙ ТЕОРИИ | |||
*0028 | |||
* НЕБРЕЖНОСТЬ ИЛИ ИНЫМ ОБРАЗОМ), ВОЗНИКШИЕ В РЕЗУЛЬТАТЕ ИСПОЛЬЗОВАНИЯ ДАННОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, | |||
* ДАЖЕ ЕСЛИ УВЕДОМЛЕНО О ВОЗМОЖНОСТИ ТАКОГО ПОВРЕЖДЕНИЯ. | |||
* ———————————————————— ——————————— | |||
*/ | |||
/* * | |||
* \файл | |||
* | |||
* Реализация контроллера сторожевого таймера (WDT). | |||
* | |||
*/ | |||
* Драйвер WDT предоставляет интерфейс для настройки и использования WDT | |||
* периферийный. | |||
* | |||
* WDT можно использовать для предотвращения блокировки системы, если программное обеспечение становится | |||
* в тупике. Он может генерировать только общий сброс или сброс процессора | |||
*. Он синхронизируется путем деления медленных часов на 128. | |||
* | |||
* WDT работает в режиме сброса с 16-секундным периодом сторожевого таймера (медленные часы на частоте 32,768 кГц) | и внешним сбросом включено. Пользователь должен либо отключить его, либо | ||
* перепрограммировать его в соответствии с требованиями приложения. | |||
* | |||
* Чтобы использовать WDT, пользователь может выполнить следующие несколько шагов: | |||
* | * способный сторожевой таймер с заданным режимом, используя \ ссылка WDT_Enable(). | ||
* | |||
* | |||
* | |||
* Для получения более точной информации см. раздел WDT в техническом описании | |||
*. | |||
* | |||
* \note | |||
* Регистр режима сторожевого таймера (WDT_MR) может быть записан только один раз.\n | 28 | * | |
* Связанные файлы: \n | |||
* \ref wdt.c\n | |||
* \ref wdt.h.\n | |||
*/ | |||
/*@{*/ | |||
/*@}*/ | |||
/*———- ————————————————— ————— | |||
* Заголовки | |||
*———————- ————————————————— —*/ | |||
#include «chip. h» | |||
————————————————— — | |||
* Экспортированные функции | |||
*——————————— ———————————————————-*/ | |||
/** | |||
* \кратко Включить сторожевой таймер с заданным режимом. | |||
* | |||
* \note Регистр режима сторожевого таймера (WDT_MR) может быть записан только один раз. | |||
* Сбрасывает только сброс процессора. | |||
* | |||
* \param dwMode Устанавливаемый режим WDT | |||
*/ | |||
{ | |||
pWDT->WDT_MR = dwMode ; | |||
} | |||
/** | |||
* \brief Отключить сторожевой таймер. | |||
* | |||
* \note Регистр режима сторожевого таймера (WDT_MR) может быть записан только один раз. | |||
* Сбрасывает только сброс процессора. | |||
*/ | |||
extern void WDT_Disable( Wdt* pWDT ) | |||
{ | |||
pWDT->WDT_MR = WDT_MR_WDDIS; | |||
} | |||
/** | |||
* \brief Watchdog. | |||
*/ | |||
extern void WDT_Restart(Wdt* pWDT) | |||
{ | |||
pWDT->WDT_CR = 0xA5000001; | |||
} | |||
/** | |||
* \brief Watchdog получить статус. |