Вместо введения, или попытка ответить на вопрос «Зачем?»

Время – одно из важнейших понятий современного мира. Все события имеют определенные координаты на временной оси. Трудно переоценить важность синхронизации времени в IT структуре в различных аспектах управления, безопасности, планирования, отладки, а также, при анализе причин аварий, в особенности, когда требуется определение времени происхождения событий. Асинхронность во времени может приводить к деградации производительности и полному останову кластерных систем. Для распределенных систем измерения и регистрации не синхронность времени приводит к некорректности одномоментных измерений, и последующих расчетов. Для финансовых систем неточность времени может приводить к проблемам с законом.

Один из способов синхронизации времени – это использование внешнего пула серверов времени. Списки серверов можно получить на www.ntp.org. Однако, есть ряд минусов:

  • это определенного рода уязвимость вашей системы – должен быть открыт UDP порт 123, по которому осуществляется синхронизация;
  • потеря точности, при использовании внешних каналов связи;
  • негарантированность постоянной поддержки внешних сервисов времени;
  • некоторые сети не могут быть подключены к глобальной сети, по различным причинам

Другой способ синхронизации – собственный сервер времени c использованием GPS датчика. При этом данный способ лишен вышеописанных недостатков, а также, такой источник, как GPS имеет stratum 0, т.е. минимально возможную удаленность от эталона.

Далее рассмотрим создание собственного сервера времени, на основе датчика Garmin GPS16-HVS, и сервера с ОС Linux.

Аппаратная часть

Подключение Garmin GPS16-HVS

Информационные интерфейсы, а их в датчике два, реализованы в виде RS-232. Garmin GPS16-HVS поставляется с 5 метровым кабелем оконеченным разъемом RJ-45 (Таблица – 1). Для подключения устройства к серверу необходимо изготовить кабель, как показано на рисунке 1. В отличие, от официальной инструкции, для подключения сигнала PPS, с целью повышения точности до 1 микросекунды, необходимо подключить 6 контакт разъема RJ-45 на 1 контакт разъема DB-9 (на рисунке указано синим цветом). В качестве блока питания используется источник постоянного тока, согласно инструкции допустимый диапазон напряжений от 8 до 40 вольт. Порт 1 используется для передачи сигналов с стандарте «NMEA 0183, Version 3.0″, порт 2 – «RTCM Recomeded Standards For Differential Navstar GPS Service, Version 2.2, RTCM Special Committee No.104″. В данном описании используется порт 1.

Таблица – 1. Описание разъема RJ-45 датчика Garmin GPS16-HVS

RJ-45 Провод Наименование

1 Красный POWER

2 Черный GROUND

3 Желтый REMOTE POWER ON/OFF

4 Синий Port 1 DATA IN

5 Белый Port 1 DATA OUT

6 Серый PPS

7 Зеленый Port 2 DATA IN

8 Фиолетовый Port 2 DATA OUT

Рисунок – 1. Схема подключения Garmin GPS16-HVS к RS-232
Схема подключения

Настройка порта и конфигурирование датчика

С помощью minicom c опцией «-s» настроим работу соответствующего COM порта (для COM1 – /dev/ttyS0) для работы с Garmin GPS16-HVS. Для этого в секции Serial port setup установим Serial Device соответственно /dev/ttyS0 и следующие параметры порта 4800 8N1 (4800 baud, no parity, 8 data bits, 1 stop bit.), именно такие заводские установки датчика. Если на предыдущих этапах все было выполнено корректно, то после конфигурирования порта увидим NMEA поток похожий на такой:

$GPRMC,022708,A,3303.6672,N,09640.3395,W,000.0,266.2,090406,004.8,E*6D
$GPGGA,022708,3303.6672,N,09640.3395,W,1,04,2.1,214.0,M,-24.9,M,,*72
$GPGSA,A,3,01,,,,,16,20,23,,,,,5.1,2.1,2.0*32
$GPGSV,3,1,11,01,54,060,40,06,02,065,00,07,07,114,00,11,11,241,00*7D
$GPGSV,3,2,11,14,20,094,00,16,51,155,36,20,51,303,43,23,25,306,39*7D
$GPGSV,3,3,11,24,08,310,00,25,58,027,00,37,00,000,00*4E
$PGRME,6.7,M,17.3,M,19.5,M*27
$GPGLL,3303.6672,N,09640.3395,W,022708,A*36
$GPVTG,266,T,261,M,000.0,N,0000.0,K*79
$PGRMV,0.0,0.0,0.0*5C
$PGRMF,346,8842,090406,022708,14,3300.6672,N,09643.3395,W,A,2,0,266,5,3*0B
$PGRMB,,,,,,K,,,*2D
$PGRMM,WGS 84*06

Примечание: Если подобного не увидели, а на вывод идет «мусор» по структуре напоминающий NMEA, проверьте 5 и 2 контакты могут быть некорректно вами разведены.

Из всех последовательностей NMEA достаточно использовать $GPRMC, остальные отключим. Для задействования сигналов PPS включим режим распознавания PPS. Для этого сформируем командный файл gpsedit следующего содержания:

$PGRMO,,2^M
$PGRMO,GPRMC,1^M
$PGRMC,,,,,,,,,,,,2,,,^M

Обратите внимание, что строка заканчивается <CR><LF>. Отправим его на устройство:

$ cat ./gpsedit > /dev/ttyS0

После этого NMEA поток приобретает следующий вид:

$GPRMC,074824,A,4818.5002,N,03801.8239,E,000.0,294.4,080909,006.9,E*7F
$GPRMC,074825,A,4818.5002,N,03801.8239,E,000.0,294.4,080909,006.9,E*7E
$GPRMC,074826,A,4818.5002,N,03801.8239,E,000.0,294.4,080909,006.9,E*7D

Анализируя NMEA поток, можно понять, получает ли датчик сигналы от спутников, см. инструкцию. Для успешной синхронизации необходимо установить связь со спутниками. Сигнал не может проникать через стены, поэтому расположить датчик необходимо вне помещения или хотя бы у окна.

Программная часть

Компиляция ядра с поддержкой PPS

Чтобы максимально приблизится к заявленной точности GPS датчика (10E-6 сек), необходима поддержка PPSAPI (RFC-2783) на уровне ядра операционной системы. Для обеспечения поддержки ядром потребуется две вещи, во-первых, исходные коды ядра Linux, которые можно получить на kernel.org (предпочтительнее использовать наиболее свежую версию ядра), и во-вторых, соответствующий ядру patch, при написании данной статьи были использованы: ядро linux-2.6.30.4 и patch ntp-pps-v2.6.30-rc5-bis.diff.

К распакованным исходным кодам ядра применим полученный patch

$ cd /usr/src/kernels/linux-<ваша версия ядра>
$ patch -p1 < ntp-pps-<ваша версия patch>.diff

Далее необходимо скомпилировать ядро с поддержкой PPS. Выполним команду конфигурирования ядра, и как один из множества вариантов рассмотрим:

$ make menuconfig

В разделе Device Drivers, находим добавленную нами секцию PPS support, в которой выполняем следующие установки.

<M> PPS support
[ ]   Use low level IRQ timestamps
[ ]   PPS debugging messages
      *** PPS clients support ***
< >   Kernel timer client (Testing client, use for debug)                                     
<M>   PPS line discipline
      *** Parallel printer support (forced off) ***

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

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

Для удобства эксплуатации в разделе General setup можно использовать суффикс добавляемый к ядру

(.pps) Local version - append to kernel release

Записываем конфигурацию ядра, компилируем и устанавливаем.

# make
# make modules_install install

На данном этапе перезагружаем систему с новым ядром.

Инструменты

Перед тем как приступать к компиляции инструментов обновим заголовки, в соответствии с новым ядром

$ cd /usr/include
$ mv linux linux.old
$ mv asm asm.old
$ mv asm-generic asm-generic.old
$ ln -s /lib/modules/$(uname -r)/build/include/linux linux
$ ln -s /lib/modules/$(uname -r)/build/arch/x86/include/asm asm
$ ln -s /lib/modules/$(uname -r)/build/include/asm-generic asm-generic
$ cp /lib/modules/$(uname -r)/build/Documentation/pps/timepps.h timepps.h

Теперь можем приступать к компиляции ppstest

$ cd /usr/src/kernels/linux-<ваша версия ядра>/Documentation/pps
$ make

Для удобства использования копируем ppstest в один из каталогов, прописанный в переменной окружения PATH

cp /usr/src/kernels/linux-<ваша версия ядра>/Documentation/pps/ppstest /usr/sbin/

Кроме этого необходим инструмент ldattach, содержащийся в пакете util-linux-ng версии 2.14 и выше. Исходные коды util-linux-ng можно взять на kernel.org. При написании данной статьи был использован пакет версии 2.16.
Исходный файл ldattach.c, после блока

    switch (parity) {
    case 'n':
        ts.c_cflag &= ~(PARENB|PARODD);
        break;
    case 'e':
        ts.c_cflag |= PARENB;
        ts.c_cflag &= ~PARODD;
        break;
    case 'o':
        ts.c_cflag |= (PARENB|PARODD);
        break;
    }

необходимо дополнить следующими строками:

    if (ldisc == 18)
        ts.c_iflag |= (IGNBRK|ICRNL);

Это обеспечивает корректную работу с PPS сигналами, совместно ldattach и ntpd.

Включение PPS

При работе ntpd будет необходима сиволическая ссылка на устройство pps. Для этого в файл /etc/udev/rules.d/50-udev.rules добавим следующую строку:

KERNEL=="pps*", NAME="%k", SUBSYSTEM=="pps", MODE="0660", SYMLINK+="gps%k",GROUP="uucp"
# ldattach PPS /dev/ttyS0

Тестирование

Нижеследующие команды демонстрируют, что устройство pps существует и функционирует

# ls -l /dev/*pps*
lrwxrwxrwx 1 root root      4 Sep  3 08:28 /dev/gpspps0 -> pps0
crw-rw---- 1 root uucp 251, 0 Sep  3 08:28 /dev/pps0

# find  /sys/class/pps/
/sys/class/pps/
/sys/class/pps/pps0
/sys/class/pps/pps0/uevent
/sys/class/pps/pps0/dev
/sys/class/pps/pps0/subsystem
/sys/class/pps/pps0/assert
/sys/class/pps/pps0/clear
/sys/class/pps/pps0/mode
/sys/class/pps/pps0/echo
/sys/class/pps/pps0/name
/sys/class/pps/pps0/path
/sys/class/pps/pps0/power
/sys/class/pps/pps0/power/wakeup

# tree /sys/class/pps/pps0
/sys/class/pps/pps0
|-- assert
|-- clear
|-- dev
|-- echo
|-- mode
|-- name
|-- path
|-- power
|   `-- wakeup
|-- subsystem -> ../../pps
`-- uevent

2 directories, 9 files

# ppstest /dev/pps0
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1252061746.999986333, sequence: 106548
                                                   - clear  1252061746.099989543, sequence: 106547
source 0 - assert 1252061746.999986333, sequence: 106548
                                                   - clear  1252061747.099987409, sequence: 106548
source 0 - assert 1252061747.999986200, sequence: 106549
                                                   - clear  1252061747.099987409, sequence: 106548
source 0 - assert 1252061747.999986200, sequence: 106549
                                                   - clear  1252061748.099988578, sequence: 106549
source 0 - assert 1252061748.999987260, sequence: 106550
                                                   - clear  1252061748.099988578, sequence: 106549

ntp

После успешной подготовки ядра и необходимого инструментария можно приступить к компиляции ntp, исходные коды берем на www.ntp.org. Для использования PPSAPI ядра, реализованного на предыдущем этапе необходимо установить
LinuxPPS NMEA Patch.
Применяем patch к файлу refclock_nmea.c, после чего конфигурируем ntp, кроме подключения используемых далее драйверов, настраиваем каталоги в которых после установки будут размещены файлы поддержки протокола ntp. Компилируем.

$ cd /usr/src/ntp-<ваша версия>
$ patch -p1 < nmea.patch
$ ./configure --sysconfdir=/etc --prefix=/usr --libdir=/var/lib --enable-ATOM --enable-NMEA \
              --enable-LOCAL-CLOCK
$ make

Установим несколько позже, а именно после успешного тестирования.

Конфигурирование ntpd

Поддержка большинства широко распространенных аппаратных источников времени включена в стандартную конфигурацию NTP демона для UNIX-систем. Аппаратные часы согласно соглашению имеют адрес вида 127.127.t.u, где t - тип драйвера аппаратных часов, u - номер экземпляра устройства в диапазоне 0-3, для обеспечения возможности использования нескольких устройств одного типа. Для подключения Garmin можно использовать два типа драйверов:

  • 20 - Generic NMEA GPS Receiver (NMEA)
  • 22 - PPS Clock Discipline (PPS)

Хотя достаточно только 20-го, при совместном использовании 20 и 22 драйверов преимуществ не выявлено, таким образом рассмотрим подробно остановимся на 20 - Generic NMEA GPS Receiver (NMEA).
Для обеспечения работы драйвера NMEA, необходимо создать символические ссылки вида /dev/gpsU и /dev/gpsppsU, где u - номер экземпляра, таким образом:

# ln -s /dev/ttyS0 /dev/gps0
# ln -s /dev/pps0 /dev/gpspps0

Этот драйвер поддерживает GPS приемники с выводом последовательностей NMEA $GPRMC по умолчанию, и $GPGGA или $GPGLL как альтернативные. Точность приема зависит от используемого приемника. Тем не менее, в большинстве случаев реальная точность ограничена точностью тайм-кода и латентностью последовательного интерфейса и операционной системы.
Драйвер может использовать поддержку PPSAPI (RFC-2783) уровня операционной системы.
Алтернативные последовательности NMEA могут быть использованы путем задания опции mode в конфигурационной строке server, допускается использование нескольких последовательностей:

server 127.127.20.u mode X
  • bit 0 - задействует $GPRMC (значение = 1)
  • bit 1 - задействует $GPGGA (значение = 2)
  • bit 2 - задействует $GPGLL (значение = 4)

Конфигурационная строка fudge:

  • time1 time
    Параметер калибровки - указывает время смещения, в секундах с плавающей точкой, по умолчанию 0.0.
  • stratum number
    Указывает stratum драйвера, целочисленное от 0 до 15, по умолчанию 0.

  • refid string
    Указывает идентификатор драйвера, ASCII строка от 1 до 4 знаков, по умолчанию GPS.
  • flag2 0 | 1
    Указывает какой фронт сигнала PPS использовать: 0 - передний (по умолчанию), 1 - задний
  • flag3 0 | 1
    Использование PPS дисциплины ядра: 0 - выключено (по умолчанию) , 1 - включено.

Таким образом, добавим следующие сроки в файл /etc/ntp.conf

server  127.127.20.0 minpoll 4 prefer
fudge   127.127.20.0 flag3 1

Запускаем ntpd и в messages видим следующее

Sep  8 11:20:04 db ntpd[441]: ntpd 4.2.4p7@1.1607-o Mon Sep  7 10:46:54 UTC 2009 (3)
Sep  8 11:20:04 db ntpd[442]: precision = 1.000 usec
Sep  8 11:20:04 db ntpd[442]: Listening on interface #0 wildcard, 0.0.0.0#123 Disabled
Sep  8 11:20:04 db ntpd[442]: Listening on interface #1 lo, 127.0.0.1#123 Enabled
Sep  8 11:20:04 db ntpd[442]: Listening on interface #2 eth0, 10.1.2.36#123 Enabled
Sep  8 11:20:04 db ntpd[442]: Listening on interface #3 eth0:0, 192.168.0.254#123 Enabled
Sep  8 11:20:04 db ntpd[442]: kernel time sync status 0040
Sep  8 11:20:04 db ntpd[442]: refclock_nmea: found GPS source "/dev/gps0"
Sep  8 11:20:04 db ntpd[442]: refclock_nmea: try "/dev/gpspps0" for PPS
Sep  8 11:20:04 db ntpd[442]: refclock_nmea: found PPS source "/dev/gpspps0"
Sep  8 11:20:04 db ntpd[442]: refclock_nmea: time_pps_kcbind failed: Operation not supported
Sep  8 11:20:04 db ntpd[442]: frequency initialized 45.546 PPM from /var/lib/ntp/drift

Примечание: refclock_nmea: time_pps_kcbind failed: Operation not supported - это нормально, согласно RFC 2783 данная функция является опциональной, и в LinuxPPS не поддерживается.

После запуска ntpd, если ntp.drift не существует или его данные относятся к другим источникам времени, ntpd вводит специальный режим, позволяющий откорректировать осциллятор системных часов и частоту ошибок, что занимает около 15 минут, после чего ntpd начинает работать в нормальном режиме. Файл ntp.drift - содержит последнюю расчетную частоту ошибок, и записывается каждый час.
После этого можем проверить результаты:

# ntpq -pn
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*127.127.20.0    .GPS.            0 l   12   16  377    0.000    0.014   0.001
 10.1.2.254      10.1.2.36        2 u    7   64  377    1.173   -1.069   0.839
 10.1.2.2        10.1.2.36        2 u   59   64  377    0.139   -8.971   0.117
 127.127.1.0     .LOCL.          10 l    4   64  377    0.000    0.000   0.001

Как указывает строка 127.127.20.0, расхождение с эталоном составляет 14 микросекунд, дрожание 1 микросекунда, что соответствует заявленным характеристикам точности GPS приемника. Результат достигнут.