PTP (IEEE 1588) 정밀 시각 동기화
PTP(Precision Time Protocol, IEEE 1588)는 이더넷 네트워크 내에서 나노초 수준의 시각 동기화를 달성하기 위한 표준 프로토콜입니다. TSN(Time-Sensitive Networking), 산업 자동화, 5G 프론트홀, 방송 미디어, 금융 거래 시스템에 핵심으로 쓰입니다. 리눅스 커널의 PTP 서브시스템(drivers/ptp/)은 NIC의 하드웨어 클럭(PHC)을 ptp_clock으로 노출하고, ptp4l/phc2sys 유저스페이스 툴이 클럭 계층 구성·보정·동기화를 담당합니다.
핵심 요약
- Grandmaster Clock — GPS나 원자 시계로부터 정확한 시각을 제공하는 최상위 클럭입니다.
- Boundary Clock (BC) — 업스트림 포트는 슬레이브로 동기화하고, 다운스트림 포트로 시각을 재분배합니다. 네트워크 스위치에 구현됩니다.
- Ordinary Clock (OC) — 단일 포트로 GM에 동기화되는 일반 호스트입니다.
- PHC (PTP Hardware Clock) — NIC에 내장된 하드웨어 클럭 카운터로, TX/RX 타임스탬프를 나노초 해상도로 기록합니다.
- ptp4l — PTP 프로토콜 상태 기계를 구현하는 유저스페이스 데몬입니다. PHC를 GM에 동기화합니다.
- phc2sys — PHC와 시스템 클럭(CLOCK_REALTIME) 간 오프셋을 보정하는 데몬입니다.
- gPTP (802.1AS) — IEEE 1588을 TSN 브리지에 맞게 프로파일링한 버전으로, 자동차(AUTOSAR) 및 오디오/비디오 브리징에 사용됩니다.
- BMCA (Best Master Clock Algorithm) — 네트워크에서 가장 좋은 클럭 소스를 자동으로 선출하는 알고리즘입니다.
단계별 이해
- 하드웨어 요구사항 확인 — NIC가 하드웨어 타임스탬핑을 지원하는지(
ethtool -T eth0) 확인합니다. 지원하지 않으면 소프트웨어 타임스탬프로 마이크로초 수준만 가능합니다. - 클럭 계층 설계 — 네트워크 토폴로지에서 GM, BC, OC의 위치를 결정합니다.
- ptp4l로 PHC 동기화 —
ptp4l -i eth0 -m으로 PHC를 GM에 동기화합니다. - phc2sys로 시스템 클럭 동기화 —
phc2sys -s eth0 -c CLOCK_REALTIME -m으로 CLOCK_REALTIME을 PHC에 동기화합니다. - 정밀도 측정 —
ptp4l의master offset로그로 나노초 오프셋과 주파수 보정량을 모니터링합니다.
IEEE 1588 프로토콜 개요
IEEE 1588은 2002년 제정(v1), 2008년 개정(v2), 2019년 확장(v2.1)된 네트워크 시각 동기화 표준입니다. 핵심 메커니즘은 지연 측정 + 오프셋 보정입니다.
동기화 메커니즘 (Delay Request-Response)
하드웨어 타임스탬핑을 사용하면 t1~t4를 NIC 레벨에서 기록하므로 소프트웨어 지터(OS 스케줄링 지연)가 제거됩니다. 일반 NTP의 마이크로초 정확도 대비 수십 나노초 정확도가 가능합니다.
클럭 유형과 계층
| 클럭 유형 | 설명 | 포트 수 |
|---|---|---|
| Grandmaster Clock (GM) | GPS/원자 시계 기반 최상위 시각 소스 | 1 (master) |
| Ordinary Clock (OC) | 단일 PTP 포트, 슬레이브 또는 마스터로 동작 | 1 |
| Boundary Clock (BC) | 업스트림 슬레이브 + 다운스트림 마스터(들) | 2+ |
| Transparent Clock (TC) | 체류 시간(residence time)을 보정하는 스위치 | 다수 |
| End-to-End TC (E2ETC) | 전체 경로 지연 측정 지원 TC | 다수 |
리눅스 PTP 서브시스템
리눅스 커널의 PTP 서브시스템(drivers/ptp/ptp_clock.c)은 NIC 드라이버가 PHC(PTP Hardware Clock)를 /dev/ptp0와 같은 문자 디바이스로 노출하는 표준 인터페이스를 제공합니다.
ptp_clock_ops 구조체
/* include/linux/ptp_clock_kernel.h */
struct ptp_clock_ops {
/* 필수 */
int (*adjfine)(struct ptp_clock_info *ptp, long scaled_ppm);
int (*adjtime)(struct ptp_clock_info *ptp, s64 delta);
int (*gettime64)(struct ptp_clock_info *ptp, struct timespec64 *ts);
int (*settime64)(struct ptp_clock_info *ptp,
const struct timespec64 *ts);
/* 선택 — 하드웨어 타임스탬프 큐 */
int (*getcrosststamp)(struct ptp_clock_info *ptp,
struct system_device_crosststamp *cts);
/* Pulse Per Second / 외부 타임스탬프 / 주기적 출력 */
int (*enable)(struct ptp_clock_info *ptp,
struct ptp_clock_request *request, int on);
};
ptp_clock_info 선언
static struct ptp_clock_info my_nic_ptp_info = {
.owner = THIS_MODULE,
.name = "my_nic PHC",
.max_adj = 500000, /* 최대 주파수 보정량 (ppb) */
.n_ext_ts = 0, /* 외부 타임스탬프 입력 수 */
.n_per_out = 0, /* 주기적 출력 수 */
.n_pins = 0, /* 재구성 가능한 핀 수 */
.pps = 1, /* PPS (Pulse Per Second) 지원 */
.adjfine = my_nic_ptp_adjfine,
.adjtime = my_nic_ptp_adjtime,
.gettime64 = my_nic_ptp_gettime,
.settime64 = my_nic_ptp_settime,
.enable = my_nic_ptp_enable,
.getcrosststamp = my_nic_ptp_getcrosststamp,
};
핵심 ops 구현
static int my_nic_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct my_nic_priv *priv = container_of(ptp, struct my_nic_priv, ptp_info);
s64 adj;
u32 diff, incval;
/* scaled_ppm은 65536 * ppm 단위 */
adj = (s64)scaled_ppm * MY_NIC_CLK_PERIOD_NS;
adj = div_s64(adj, 65536);
incval = MY_NIC_CLK_BASE_INCVAL + (s32)adj;
iowrite32(incval, priv->base + MY_NIC_REG_TIMADJ);
return 0;
}
static int my_nic_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct my_nic_priv *priv = container_of(ptp, struct my_nic_priv, ptp_info);
unsigned long flags;
spin_lock_irqsave(&priv->ptp_lock, flags);
iowrite32((u32)(delta >> 32), priv->base + MY_NIC_REG_TIMEADJ_H);
iowrite32((u32)(delta & 0xFFFFFFFF), priv->base + MY_NIC_REG_TIMEADJ_L);
iowrite32(MY_NIC_CMD_ADJTIME, priv->base + MY_NIC_REG_CMD);
spin_unlock_irqrestore(&priv->ptp_lock, flags);
return 0;
}
static int my_nic_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
{
struct my_nic_priv *priv = container_of(ptp, struct my_nic_priv, ptp_info);
unsigned long flags;
u64 ns;
spin_lock_irqsave(&priv->ptp_lock, flags);
/* 래치 명령으로 현재 시각 캡처 */
iowrite32(MY_NIC_CMD_LATCH, priv->base + MY_NIC_REG_CMD);
ns = (u64)ioread32(priv->base + MY_NIC_REG_TIME_H) << 32;
ns |= ioread32(priv->base + MY_NIC_REG_TIME_L);
spin_unlock_irqrestore(&priv->ptp_lock, flags);
*ts = ns_to_timespec64(ns);
return 0;
}
PHC 등록과 해제
/* probe에서 */
priv->ptp_clock = ptp_clock_register(&priv->ptp_info, &pdev->dev);
if (IS_ERR(priv->ptp_clock)) {
dev_err(&pdev->dev, "ptp_clock_register failed\n");
priv->ptp_clock = NULL;
}
/* remove에서 */
if (priv->ptp_clock)
ptp_clock_unregister(priv->ptp_clock);
linuxptp 유저스페이스 도구
ptp4l — PTP 프로토콜 데몬
ptp4l은 PTP 프로토콜 상태 기계(BMCA, 메시지 교환)를 구현하고 PHC를 GM에 동기화합니다.
# 기본 실행 (하드웨어 타임스탬프, eth0)
ptp4l -i eth0 -m
# 설정 파일 기반 (권장)
ptp4l -f /etc/linuxptp/ptp4l.conf -m
# 소프트웨어 타임스탬프 (HW 미지원 시)
ptp4l -i eth0 -m -S
# Slave only 모드 (GM이 될 수 없는 장치)
ptp4l -i eth0 -m -s
# /etc/linuxptp/ptp4l.conf
[global]
tx_timestamp_timeout 10
logMinDelayReqInterval -6
logSyncInterval -4
domainNumber 0
slaveOnly 1
time_stamping hardware
transportSpecific 0x0
[eth0]
phc2sys — PHC↔시스템 클럭 동기화
# PHC → CLOCK_REALTIME 동기화
phc2sys -s eth0 -c CLOCK_REALTIME -m -O 0
# 또는 PHC 인덱스로 직접 지정
phc2sys -s /dev/ptp0 -c CLOCK_REALTIME -m
# TAI 시각 오프셋 설정 (GPS 기반 GM에서 TAI↔UTC 차이)
phc2sys -s eth0 -c CLOCK_REALTIME -m -O -37
# 시스템 클럭 → PHC (반대 방향, Grandmaster 역할)
phc2sys -s CLOCK_REALTIME -c eth0 -m
pmc — PTP Management Client
# 포트 상태 확인
pmc -u -b 0 'GET PORT_DATA_SET'
# 현재 클럭 정보
pmc -u -b 0 'GET CURRENT_DATA_SET'
# GM 정보
pmc -u -b 0 'GET PARENT_DATA_SET'
# 시각 프로퍼티 (UTC 오프셋, 윤초 등)
pmc -u -b 0 'GET TIME_PROPERTIES_DATA_SET'
GPS Grandmaster 설정 (ts2phc)
ts2phc는 GPS PPS 신호를 사용하여 PHC를 GPS 시각에 동기화하고 Grandmaster 역할을 수행합니다.
# GPS PPS를 /dev/ptp0에 동기화
ts2phc -f /etc/linuxptp/ts2phc.conf -m
# ts2phc + ptp4l 동시 운용 (GM 구성)
ts2phc -f /etc/ts2phc.conf -m &
ptp4l -f /etc/ptp4l-master.conf -m &
# /etc/linuxptp/ts2phc.conf
[global]
use_syslog 1
verbose 1
time_stamp_mode=L
[eth0]
ts2phc.master 1 # 이 포트가 GM
[/dev/ptp_pps0]
ts2phc.master 0 # GPS PPS 입력
PTP 프로파일 변종
| 프로파일 | 표준 | 주요 용도 | 정밀도 |
|---|---|---|---|
| Default Profile | IEEE 1588-2019 | 일반 네트워크 | ~100ns |
| gPTP (802.1AS) | IEEE 802.1AS-2020 | TSN, 자동차, AV 브리징 | <1μs |
| ITU-T G.8275.1 | ITU-T G.8275 | 통신 네트워크 (5G 프론트홀) | ±30ns |
| ITU-T G.8275.2 | ITU-T G.8275 | 부분 타임스탬프 네트워크 | ±1.5μs |
| SMPTE ST 2059-2 | SMPTE | 방송 미디어 | 방송 동기 수준 |
커널 내 PTP 클럭 API
커널 드라이버에서 PHC 시각을 직접 읽거나 비교할 때 사용합니다.
#include <linux/ptp_clock_kernel.h>
/* PHC 인덱스로 ptp_clock 획득 */
struct ptp_clock *ptp_clock;
ptp_clock = ptp_clock_get(phc_index);
/* 시스템 클럭과 PHC의 교차 타임스탬프 */
struct system_device_crosststamp cts;
ret = get_device_system_crosststamp(ptp_getcrosststamp, ptp_info,
NULL, &cts);
/* cts.device: PHC 타임스탬프, cts.sys_realtime: 동시 CLOCK_REALTIME */
동기화 품질 검증
PTP 동기화 품질은 주로 master offset과 path delay의 두 지표로 평가합니다. ptp4l의 로그에서 master offset은 로컬 PHC와 Grandmaster 사이의 시각 차이(나노초)를 나타내고, freq는 발진기 주파수 보정량(ppb)을 의미합니다. 안정적인 하드웨어 타임스탬핑 환경에서는 offset이 수십~수백 나노초 범위를 유지합니다. 수 마이크로초를 초과하거나 진동이 크면 네트워크 체류 시간 변동(지터) 또는 PHC 발진기 품질 문제를 의심합니다.
# ptp4l 실시간 로그에서 오프셋 모니터링
# master offset: GM과의 오프셋(ns), s2: 2step sync
# freq: 주파수 보정량(ppb), path delay: 경로 지연(ns)
# ptp4l[12345.678]: master offset -42 s2 freq -1543 path delay 2312
# PHC 시각 직접 확인
phc_ctl /dev/ptp0 get
# PHC와 CLOCK_REALTIME 오프셋
phc_ctl /dev/ptp0 cmp
# 나노초 오프셋 히스토그램 (long-term 통계)
ts2phc -m 2>&1 | grep offset | awk '{print $NF}' | sort -n | uniq -c
TSN과 PTP 통합
IEEE 802.1Qbv(Time-Aware Shaper), 802.1Qbu(Frame Preemption) 등 TSN 표준은 PTP로 정의된 공통 시각을 사용하여 패킷 전송 창(transmission window)을 열고 닫습니다.
# TSN TAS (Time-Aware Shaper) 설정 예 (tc taprio)
tc qdisc replace dev eth0 parent root handle 100 taprio \
num_tc 4 \
map 0 1 2 3 0 0 0 0 \
queues 1@0 1@1 1@2 1@3 \
base-time 1000000000 \
sched-entry S 0x8 200000 \ # 큐 3 개방 200μs
sched-entry S 0x4 300000 \ # 큐 2 개방 300μs
sched-entry S 0xF 500000 \ # 모든 큐 개방 500μs
clockid CLOCK_TAI # TAI 기반 스케줄
One-Step 타임스탬핑
2-step PTP는 Sync 후 Follow_Up을 별도 패킷으로 전송하여 t1을 전달합니다. One-step 타임스탬핑은 NIC 하드웨어가 Sync 패킷이 전선에 나가는 순간 패킷 내부에 직접 타임스탬프를 기록하므로 Follow_Up이 필요 없습니다. 패킷 수와 지터가 절반으로 줄어듭니다.
| 항목 | 2-Step | One-Step |
|---|---|---|
| Sync 패킷 이후 | Follow_Up 별도 전송 | Follow_Up 불필요 |
| t1 기록 위치 | Follow_Up 페이로드 | Sync 패킷 originTimestamp 필드 (NIC가 직접 기록) |
| 패킷 수 | 많음 | 적음 |
| 하드웨어 요구사항 | TX 타임스탬프 읽기 | TX 패킷 내용 수정 능력 (egress edit) |
| Linux 지원 | 표준 HW TS | HWTSTAMP_TX_ONESTEP_SYNC |
/* One-step TX 타임스탬프 모드 설정 */
struct hwtstamp_config cfg = {
.tx_type = HWTSTAMP_TX_ONESTEP_SYNC, /* one-step */
.rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT,
};
struct ifreq ifr;
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
ifr.ifr_data = (char *)&cfg;
ioctl(sock, SIOCSHWTSTAMP, &ifr);
/etc/ptp4l.conf에서 tx_timestamp_timeout 1을 설정하고 NIC가 one-step을 지원하는 경우 one_step_sync 1을 활성화합니다.
IEEE 1588-2019 (v2.1) 주요 개선
2019년 발표된 v2.1(IEEE 1588-2019)은 v2(2008) 대비 아래 기능을 추가했습니다.
| 기능 | 설명 |
|---|---|
| CMLDS (Common Mean Link Delay Service) | 여러 PTP 인스턴스가 동일 링크의 지연 측정값을 공유 |
| Enhanced Accuracy TLV | 클럭 정확도를 서브-나노초 단위로 전달 |
| Port State Decision (PSDN) | 경로 선택 알고리즘 개선 |
| Alternate Master | 결함 내성을 위한 다중 마스터 동시 추적 |
| Signaling TLV 확장 | 로그 주기·임계값 동적 협상 |
# ptp4l에서 v2.1 CMLDS 활성화 (linuxptp 3.x+)
# /etc/ptp4l.conf
[global]
dataset_comparison ieee1588 # v2.1 데이터셋 비교 알고리즘
G.8275.defaultDS.localPriority 128
boundary_clock_jbod 1 # Boundary Clock JBOD 모드
cmlds 1 # CMLDS 활성화
문제 해결
| 증상 | 원인 | 해결 |
|---|---|---|
| 오프셋이 수백 μs 이상 | 소프트웨어 타임스탬핑 사용 | ethtool -T eth0으로 HW 타임스탬핑 확인 |
| ptp4l LISTENING 상태 고착 | PTP 패킷이 방화벽에 차단 | UDP 319/320 포트 허용 |
| phc2sys 오프셋 발산 | TAI/UTC 오프셋 불일치 | -O 옵션으로 TAI-UTC 차이(37초) 설정 |
| TX 타임스탬프 타임아웃 | 드라이버 TX TS 지원 불완전 | tx_timestamp_timeout 값 증가 |
| gPTP 동기화 실패 | 802.1AS 요구 사항 미충족 | transportSpecific 0x1, peer delay 활성화 |