systemd-timesyncd 时间同步模块深度分析


  团队博客: 汽车电子社区


1. 源码结构和文件组织

1.1 目录结构

src/timesync/
├── timesyncd.c                    # 主守护进程入口 (9.63 KB)
├── timesyncd-manager.c            # 核心管理器实现 (45.09 KB)
├── timesyncd-manager.h            # 管理器头文件 (3.51 KB)
├── timesyncd-server.c             # 服务器管理 (4.81 KB)
├── timesyncd-server.h             # 服务器管理头文件 (1.25 KB)
├── timesyncd-bus.c                # D-Bus接口实现 (10.84 KB)
├── timesyncd-bus.h                # D-Bus接口头文件 (186 B)
├── timesyncd-conf.c               # 配置文件解析 (4.08 KB)
├── timesyncd-conf.h               # 配置解析头文件 (430 B)
├── timesyncd-ntp-message.h        # NTP协议消息定义 (1.15 KB)
├── timesyncd-forward.h             # 前向声明 (371 B)
├── timesyncd-gperf.gperf           # 配置解析表生成 (1.39 KB)
├── timesyncd.conf.in              # 配置文件模板 (1004 B)
├── test-timesync.c                # 单元测试 (1.11 KB)
├── wait-sync.c                    # 同步等待工具 (8.77 KB)
├── meson.build                    # 构建配置 (2.28 KB)
├── org.freedesktop.timesync1.conf    # D-Bus策略配置
├── org.freedesktop.timesync1.policy  # Polkit策略
├── org.freedesktop.timesync1.service # D-Bus服务配置
└── 80-systemd-timesync.list       # 系统服务列表

1.2 核心架构概览

┌─────────────────────────────────────────────────────────┐
│                 systemd-timesyncd                       │
├─────────────────────────────────────────────────────────┤
│ timesyncd.c (主进程)                                    │
│  ├─ 时间戳管理                                          │
│  ├─ 权限管理                                            │
│  └─ 事件循环启动                                        │
├─────────────────────────────────────────────────────────┤
│ timesyncd-manager.c (核心管理器)                        │
│  ├─ NTP协议处理                                         │
│  ├─ 时间同步算法                                        │
│  ├─ 服务器管理                                          │
│  ├─ 网络状态监控                                        │
│  └─ 时钟调整                                           │
├─────────────────────────────────────────────────────────┤
│ timesyncd-server.c (服务器管理)                         │
│  ├─ 服务器类型分类                                      │
│  ├─ 地址解析                                            │
│  └─ 连接管理                                           │
├─────────────────────────────────────────────────────────┤
│ timesyncd-bus.c (D-Bus接口)                             │
│  ├─ 属性导出                                           │
│  ├─ 方法调用                                           │
│  └─ 信号发送                                           │
└─────────────────────────────────────────────────────────┘

2. 核心功能和架构设计

2.1 Manager 结构体设计

typedef struct Manager {
    // 事件循环和总线
    sd_bus *bus;                          // D-Bus连接
    sd_event *event;                      // 事件循环
    sd_resolve *resolve;                  // DNS解析器

    // 服务器列表 (4种类型)
    LIST_HEAD(ServerName, system_servers);    // 系统配置的服务器
    LIST_HEAD(ServerName, link_servers);      // 网络链接配置的服务器  
    LIST_HEAD(ServerName, runtime_servers);   // 运行时配置的服务器
    LIST_HEAD(ServerName, fallback_servers);  // 回退服务器

    // 网络监控
    sd_network_monitor *network_monitor;      // 网络状态监控
    sd_event_source *network_event_source;   // 网络事件源

    // 当前连接状态
    ServerName *current_server_name;          // 当前服务器名称
    ServerAddress *current_server_address;    // 当前服务器地址
    int server_socket;                        // 服务器套接字
    bool talking;                            // 是否正在通信
    bool pending;                            // 是否有待处理的请求

    // NTP协议相关
    struct ntp_msg ntpmsg;                  // NTP消息
    struct timespec trans_time;              // 传输时间戳
    struct timespec origin_time, dest_time;  // 起源和目标时间
    struct ntp_ts request_nonce;             // 请求随机数

    // 时间同步算法数据
    struct {
        double offset;        // 偏移量
        double delay;         // 延迟
    } samples[8];             // 历史样本数组
    unsigned samples_idx;     // 样本索引
    double samples_jitter;    // 抖动值

    // 轮询和重试机制
    sd_event_source *event_timer;           // 轮询定时器
    usec_t poll_interval_usec;              // 轮询间隔
    usec_t poll_interval_min_usec;          // 最小轮询间隔
    usec_t poll_interval_max_usec;          // 最大轮询间隔
    usec_t retry_interval;                  // 重试间隔
    int missed_replies;                      // 错过的回复数

    // 时钟状态
    int64_t drift_freq;                     // 时钟漂移频率
    bool synchronized;                      // 是否已同步
    bool jumped;                            // 是否发生了时间跳跃
    bool rtc_local_time;                    // RTC是否为本地时间
} Manager;

2.2 服务器类型分类

typedef enum ServerType {
    SERVER_SYSTEM,     // 系统配置的服务器 (timesyncd.conf)
    SERVER_FALLBACK,   // 回退服务器 (编译时默认)
    SERVER_LINK,       // 网络链接配置的服务器
    SERVER_RUNTIME,    // 运行时设置的服务器 (D-Bus API)
    _SERVER_TYPE_MAX,
} ServerType;

  服务器优先级选择逻辑:
    1. 运行时服务器 (最高优先级)
    2. 系统配置服务器
    3. 网络链接服务器
    4. 回退服务器 (最低优先级)

3. NTP协议实现

3.1 NTP消息结构

struct ntp_msg {
    uint8_t field;                    // LI(2) + VN(3) + Mode(3)
    uint8_t stratum;                  // 层级
    int8_t poll;                      // 轮询间隔
    int8_t precision;                 // 精度
    struct ntp_ts_short root_delay;    // 根延迟
    struct ntp_ts_short root_dispersion; // 根离散
    char refid[4];                    // 参考标识
    struct ntp_ts reference_time;      // 参考时间
    struct ntp_ts origin_time;         // 起源时间 (T1)
    struct ntp_ts recv_time;          // 接收时间 (T2)
    struct ntp_ts trans_time;         // 传输时间 (T3)
} _packed_;

3.2 NTP时间戳计算

  核心算法实现 (RFC 5905):

/* 时间戳计算公式:
 * delay = (T4 - T1) - (T3 - T2)
 * offset = ((T2 - T1) + (T3 - T4)) / 2
 */

origin = ts_to_d(&m->trans_time) + OFFSET_1900_1970;    // T1
receive = ntp_ts_to_d(&ntpmsg.recv_time);               // T2
trans = ntp_ts_to_d(&ntpmsg.trans_time);                // T3
dest = ts_to_d(recv_time) + OFFSET_1900_1970;          // T4

offset = ((receive - origin) + (trans - dest)) / 2;
delay = (dest - origin) - (trans - receive);

root_distance = ntp_ts_short_to_d(&ntpmsg.root_delay) / 2 + 
                ntp_ts_short_to_d(&ntpmsg.root_dispersion);

3.3 NTP请求发送流程

static int manager_send_request(Manager *m) {
    struct ntp_msg ntpmsg = {
        .field = NTP_FIELD(0, 4, NTP_MODE_CLIENT), // LI=0, VN=4, Mode=Client
    };
    
    // 生成64位随机数作为传输时间戳
    random_bytes(&m->request_nonce, sizeof(m->request_nonce));
    ntpmsg.trans_time = m->request_nonce;
    
    // 记录发送时间 (尽可能接近实际发送时间)
    assert_se(clock_gettime(CLOCK_BOOTTIME, &m->trans_time_mon) >= 0);
    assert_se(clock_gettime(CLOCK_REALTIME, &m->trans_time) >= 0);
    
    // 发送UDP数据包
    len = sendto(m->server_socket, &ntpmsg, sizeof(ntpmsg), MSG_DONTWAIT, 
                 &m->current_server_address->sockaddr.sa, 
                 m->current_server_address->socklen);
}

4. 时间同步算法

4.1 样本管理和抖动检测

static bool manager_sample_spike_detection(Manager *m, double offset, double delay) {
    // 保存当前样本
    idx_cur = m->samples_idx;
    idx_new = (idx_cur + 1) % ELEMENTSOF(m->samples);
    m->samples_idx = idx_new;
    m->samples[idx_new].offset = offset;
    m->samples[idx_new].delay = delay;
    
    // 计算抖动 (基于最小延迟样本的RMS)
    for (idx_min = idx_cur, i = 0; i < ELEMENTSOF(m->samples); i++)
        if (m->samples[i].delay > 0 && m->samples[i].delay < m->samples[idx_min].delay)
            idx_min = i;
    
    j = 0;
    FOREACH_ELEMENT(sample, m->samples)
        j += pow(sample->offset - m->samples[idx_min].offset, 2);
    m->samples_jitter = sqrt(j / (ELEMENTSOF(m->samples) - 1));
    
    // 尖峰检测:偏差 > 3倍抖动视为异常
    return fabs(offset - m->samples[idx_cur].offset) > 3 * jitter;
}

4.2 动态轮询间隔调整

static void manager_adjust_poll(Manager *m, double offset, bool spike) {
    if (m->poll_resync) {
        // 重新同步时使用最小间隔
        m->poll_interval_usec = m->poll_interval_min_usec;
        return;
    }
    
    // 偏移量过大时使用最小轮询间隔
    if (!spike && fabs(offset) > NTP_ACCURACY_SEC) {
        m->poll_interval_usec = m->poll_interval_min_usec;
        return;
    }
    
    // 偏移量很小时增加轮询间隔
    if (fabs(offset) < NTP_ACCURACY_SEC * 0.25) {
        if (m->poll_interval_usec < m->poll_interval_max_usec)
            m->poll_interval_usec *= 2;
        return;
    }
    
    // 偏移量较大或检测到尖峰时减少轮询间隔
    if (spike || fabs(offset) > NTP_ACCURACY_SEC * 0.75) {
        if (m->poll_interval_usec > m->poll_interval_min_usec)
            m->poll_interval_usec /= 2;
    }
}

  轮询间隔范围:
    - 最小间隔:32秒 (符合NTP协议要求的最小15秒)
    - 最大间隔:2048秒
    - 精度目标:0.2秒

4.3 时钟调整策略

static int manager_adjust_clock(Manager *m, double offset, int leap_sec) {
    struct timex tmx;
    
    if (fabs(offset) < NTP_MAX_ADJUST) {
        // 小偏移量:渐进调整 (slew)
        tmx = (struct timex) {
            .modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST,
            .status = STA_PLL,
            .offset = offset * NSEC_PER_SEC,  // 转换为纳秒
            .constant = log2i(m->poll_interval_usec / USEC_PER_SEC) - 4,
        };
    } else {
        // 大偏移量:直接设置 (jump)
        tmx = (struct timex) {
            .modes = ADJ_STATUS | ADJ_NANO | ADJ_SETOFFSET,
            .time.tv_sec = (long)offset,
            .time.tv_usec = (offset - (double)(long)offset) * NSEC_PER_SEC,
        };
        m->jumped = true;
    }
    
    // 处理闰秒
    switch (leap_sec) {
    case 1:  tmx.status |= STA_INS; break;  // 插入闰秒
    case -1: tmx.status |= STA_DEL; break;  // 删除闰秒
    }
    
    return clock_adjtime(CLOCK_REALTIME, &tmx);
}

  调整阈值:
    - 渐进调整:< 0.4秒 (低于内核0.5秒限制)
    - 直接跳跃:≥ 0.4秒

5. 服务器管理

5.1 服务器连接管理流程

int manager_connect(Manager *m) {
    // 1. 断开当前连接
    manager_disconnect(m);
    
    // 2. 选择下一个服务器地址
    if (m->current_server_address && m->current_server_address->addresses_next)
        manager_set_server_address(m, m->current_server_address->addresses_next);
    else {
        // 3. 选择下一个服务器
        if (m->current_server_name && m->current_server_name->names_next)
            manager_set_server_name(m, m->current_server_name->names_next);
        else {
            // 4. 重新开始服务器轮询
            ServerName *f;
            if (!m->current_server_name || m->current_server_name->type == SERVER_LINK) {
                f = m->runtime_servers;    // 优先级1:运行时
                if (!f) f = m->system_servers;    // 优先级2:系统
                if (!f) f = m->link_servers;      // 优先级3:网络
            } else {
                f = m->link_servers;      // 优先级3:网络
                if (f) restart = false;
                else {
                    f = m->runtime_servers;    // 优先级1:运行时
                    if (!f) f = m->system_servers; // 优先级2:系统
                }
            }
            
            if (!f) f = m->fallback_servers; // 优先级4:回退
        }
    }
    
    // 5. DNS解析
    struct addrinfo hints = {
        .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG,
        .ai_socktype = SOCK_DGRAM,
        .ai_family = socket_ipv6_is_supported() ? AF_UNSPEC : AF_INET,
    };
    
    r = resolve_getaddrinfo(m->resolve, &m->resolve_query, 
                           m->current_server_name->string, "123", &hints,
                           manager_resolve_handler, NULL, m);
}

5.2 DNS解析处理

static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, Manager *m) {
    if (ret != 0) {
        log_debug("Failed to resolve %s: %s", m->current_server_name->string, gai_strerror(ret));
        return manager_connect(m); // 尝试下一个服务器
    }
    
    // 处理所有返回的地址
    for (; ai; ai = ai->ai_next) {
        if (!IN_SET(ai->ai_addr->sa_family, AF_INET, AF_INET6))
            continue; // 只支持IPv4/IPv6
            
        r = server_address_new(m->current_server_name, &a, 
                              (const union sockaddr_union*) ai->ai_addr, ai->ai_addrlen);
    }
    
    if (!m->current_server_name->addresses) {
        log_error("Failed to find suitable address for host %s.", m->current_server_name->string);
        return manager_connect(m);
    }
    
    // 使用第一个地址开始连接
    manager_set_server_address(m, m->current_server_name->addresses);
    return manager_begin(m);
}

5.3 服务器重试机制

  重试策略:
    - 最大错过的回复数:2次 (NTP_MAX_MISSED_REPLIES)
    - 重试间隔:从15秒开始,每次增加1/3,最大6分钟
    - 连接重试:默认30秒间隔
    - 服务器轮询:当所有服务器都尝试过后,根据轮询间隔等待

6. D-Bus接口实现

6.1 D-Bus服务定义

Service: org.freedesktop.timesync1
Object Path: /org/freedesktop/timesync1
Interface: org.freedesktop.timesync1.Manager

6.2 主要属性

// 服务器列表属性
SD_BUS_PROPERTY("LinkNTPServers", "as", property_get_servers, 
                offsetof(Manager, link_servers), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)
SD_BUS_PROPERTY("SystemNTPServers", "as", property_get_servers,
                offsetof(Manager, system_servers), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)  
SD_BUS_PROPERTY("RuntimeNTPServers", "as", property_get_servers,
                offsetof(Manager, runtime_servers), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)
SD_BUS_PROPERTY("FallbackNTPServers", "as", property_get_servers,
                offsetof(Manager, fallback_servers), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)

// 当前连接状态
SD_BUS_PROPERTY("ServerName", "s", property_get_current_server_name,
                offsetof(Manager, current_server_name), 0)
SD_BUS_PROPERTY("ServerAddress", "(iay)", property_get_current_server_address,
                offsetof(Manager, current_server_address), 0)

// 同步状态和配置
SD_BUS_PROPERTY("PollIntervalUSec", "t", bus_property_get_usec,
                offsetof(Manager, poll_interval_usec), 0)
SD_BUS_PROPERTY("NTPMessage", "(uuuuittayttttbtt)", property_get_ntp_message, 0,
                SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)
SD_BUS_PROPERTY("Frequency", "x", NULL, offsetof(Manager, drift_freq), 0)

6.3 主要方法

// 设置运行时NTP服务器
SD_BUS_METHOD_WITH_ARGS("SetRuntimeNTPServers",
                        SD_BUS_ARGS("as", runtime_servers),
                        SD_BUS_NO_RESULT,
                        method_set_runtime_servers,
                        SD_BUS_VTABLE_UNPRIVILEGED)

  方法实现:

static int method_set_runtime_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) {
    _cleanup_strv_free_ char **msg_names = NULL;
    Manager *m = ASSERT_PTR(userdata);
    
    // 验证权限
    r = bus_verify_polkit_async(message, "org.freedesktop.timesync1.set-runtime-servers",
                               NULL, &m->polkit_registry, error);
    if (r == 0) return 1; // Polkit异步回调
    
    // 清除现有运行时服务器
    manager_flush_runtime_servers(m);
    
    // 添加新服务器
    STRV_FOREACH(name, msg_names) {
        r = server_name_new(m, NULL, SERVER_RUNTIME, *name);
        if (r < 0) {
            manager_flush_runtime_servers(m);
            return log_error_errno(r, "Failed to add runtime server '%s': %m", *name);
        }
    }
    
    // 立即尝试连接新服务器
    m->exhausted_servers = true;
    manager_set_server_name(m, NULL);
    (void) manager_connect(m);
    
    return sd_bus_reply_method_return(message, NULL);
}

7. 时间源选择算法

7.1 服务器选择优先级

优先级1: Runtime Servers (运行时配置)
   ↓ 通过 D-Bus API 动态设置
   ↓ 立即生效,覆盖其他配置
   
优先级2: System Servers (系统配置)  
   ↓ /etc/systemd/timesyncd.conf
   ↓ 管理员手动配置
   
优先级3: Link Servers (网络配置)
   ↓ systemd-networkd 配置
   ↓ 每个网络接口独立配置
   
优先级4: Fallback Servers (回退配置)
   ↓ 编译时默认配置
   ↓ 确保基本可用性

7.2 服务器质量评估

// 服务器质量检查
if (NTP_FIELD_LEAP(ntpmsg.field) == NTP_LEAP_NOTINSYNC ||
    ntpmsg.stratum == 0 || ntpmsg.stratum >= 16) {
    log_debug("Server is not synchronized. Disconnecting.");
    return manager_connect(m);
}

// 检查版本支持
if (!IN_SET(NTP_FIELD_VERSION(ntpmsg.field), 3, 4)) {
    log_debug("Response NTPv%d. Disconnecting.", NTP_FIELD_VERSION(ntpmsg.field));
    return manager_connect(m);
}

// 检查根距离
root_distance = ntp_ts_short_to_d(&ntpmsg.root_delay) / 2 + 
                ntp_ts_short_to_d(&ntpmsg.root_dispersion);
if (root_distance > (double) m->root_distance_max_usec / (double) USEC_PER_SEC) {
    log_info("Server has too large root distance. Disconnecting.");
    return manager_connect(m);
}

  质量标准:
    - 层级:1-15 (0和16+无效)
    - 版本:NTPv3或NTPv4
    - 根距离:< 5秒 (可配置)
    - 同步状态:必须已同步

8. 同步状态管理

8.1 同步状态标志

// 状态指示文件
#define SYNCHRONIZED_FILE "/run/systemd/timesync/synchronized"

// 状态变量
bool synchronized;      // 是否曾经成功同步
bool talking;          // 是否正在与服务器通信
bool pending;          // 是否有待处理的请求
bool jumped;           // 是否发生了时间跳跃
bool spike;            // 当前样本是否为异常值

8.2 状态转换流程

启动状态
   ↓
选择服务器 → DNS解析 → 建立连接
   ↓
发送NTP请求
   ↓
接收响应
   ↓
质量检查 → 异常检测
   ↓     ↓
  通过   失败
   ↓     ↓
时钟调整  尝试下一服务器
   ↓
设置同步标志
   ↓
调整轮询间隔
   ↓
定时重复同步

8.3 初始同步处理

if (!spike && !m->synchronized) {
    m->synchronized = true;
    
    log_struct(LOG_INFO,
               LOG_MESSAGE("Initial clock synchronization to %s.",
                           FORMAT_TIMESTAMP_STYLE(dts.realtime, TIMESTAMP_US)),
               LOG_MESSAGE_ID(SD_MESSAGE_TIME_SYNC_STR),
               LOG_ITEM("MONOTONIC_USEC=" USEC_FMT, dts.monotonic),
               LOG_ITEM("REALTIME_USEC=" USEC_FMT, dts.realtime),
               LOG_ITEM("BOOTTIME_USEC=" USEC_FMT, dts.boottime));
    
    // 创建同步状态文件
    r = touch("/run/systemd/timesync/synchronized");
}

9. 与其他模块的集成

9.1 网络管理集成

// 监听网络状态变化
static int manager_network_monitor_listen(Manager *m) {
    r = sd_network_monitor_new(&m->network_monitor, NULL);
    if (r == -ENOENT) {
        log_info("systemd does not appear to be running, not listening for systemd-networkd events.");
        return 0;
    }
    
    fd = sd_network_monitor_get_fd(m->network_monitor);
    events = sd_network_monitor_get_events(m->network_monitor);
    
    return sd_event_add_io(m->event, &m->network_event_source, fd, events, 
                           manager_network_event_handler, m);
}

// 网络事件处理
static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
    bool changed = manager_network_read_link_servers(m);
    bool online = network_is_online();
    bool connected = manager_is_connected(m);
    
    if (connected && !online) {
        log_info("No network connectivity, watching for changes.");
        manager_disconnect(m);
    } else if ((!connected || changed) && online) {
        log_info("Network configuration changed, trying to establish connection.");
        // 重新建立连接
    }
}

9.2 systemd-networkd集成

// 从网络配置读取NTP服务器
static bool manager_network_read_link_servers(Manager *m) {
    _cleanup_strv_free_ char **ntp = NULL;
    
    r = sd_network_get_ntp(&ntp); // 获取网络配置的NTP服务器
    if (r < 0) goto clear;
    
    // 标记现有服务器
    LIST_FOREACH(names, n, m->link_servers)
        n->marked = true;
    
    // 添加新服务器,取消未匹配服务器的标记
    STRV_FOREACH(i, ntp) {
        bool found = false;
        LIST_FOREACH(names, n, m->link_servers)
            if (streq(n->string, *i)) {
                n->marked = false;
                found = true;
                break;
            }
        
        if (!found) {
            server_name_new(m, NULL, SERVER_LINK, *i);
            changed = true;
        }
    }
    
    // 删除标记的服务器
    LIST_FOREACH(names, n, m->link_servers)
        if (n->marked) {
            server_name_free(n);
            changed = true;
        }
}

9.3 内核时间管理集成

// 使用adjtimex系统调用调整时钟
static int manager_adjust_clock(Manager *m, double offset, int leap_sec) {
    struct timex tmx = {
        .modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR,
        .status = STA_PLL,  // 启用相位锁定环
        .offset = offset * NSEC_PER_SEC,
        .constant = log2i(m->poll_interval_usec / USEC_PER_SEC) - 4,
    };
    
    // 处理RTC本地时间
    if (m->rtc_local_time)
        tmx.status |= STA_UNSYNC; // 禁用内核11分钟模式
    
    // 调用内核接口
    if (clock_adjtime(CLOCK_REALTIME, &tmx) < 0)
        return -errno;
    
    // 保存频率漂移
    m->drift_freq = tmx.freq;
    return 0;
}

9.4 时间持久化

// 保存时间到文件系统
static int manager_save_time_and_rearm(Manager *m, usec_t t) {
    // 更新时间戳文件,用于系统重启后恢复
    r = touch_file(TIMESYNCD_CLOCK_FILE, false, t, UID_INVALID, GID_INVALID, MODE_INVALID);
    if (r < 0)
        log_debug_errno(r, "Failed to update "TIMESYNCD_CLOCK_FILE", ignoring: %m");
    
    return manager_setup_save_time_event(m);
}

// 启动时加载保存的时间
static int load_clock_timestamp(uid_t uid, gid_t gid) {
    usec_t epoch = TIME_EPOCH * USEC_PER_SEC, ct;
    
    // 打开时间戳文件
    fd = open(TIMESYNCD_CLOCK_FILE, O_RDWR | O_CLOEXEC, 0644);
    if (fd < 0) {
        // 文件不存在,使用编译时的epoch时间
        r = touch_file(TIMESYNCD_CLOCK_FILE, false, epoch, uid, gid, 0644);
    } else {
        // 使用文件时间戳中的较新时间
        epoch = MAX(epoch, timespec_load(&st.st_mtim));
    }
    
    // 如果当前时间早于保存的时间,前进时钟
    ct = now(CLOCK_REALTIME);
    if (ct <= epoch) {
        if (clock_settime(CLOCK_REALTIME, TIMESPEC_STORE(epoch + 1)) < 0)
            log_warning_errno(errno, "Failed to advance system clock, ignoring: %m");
    }
}

10. 配置系统

10.1 配置文件格式

# /etc/systemd/timesyncd.conf
[Time]
NTP=time1.example.com time2.example.com
FallbackNTP=0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org 3.pool.ntp.org
RootDistanceMaxSec=5
PollIntervalMinSec=32
PollIntervalMaxSec=2048
ConnectionRetrySec=30
SaveIntervalSec=60

10.2 配置解析实现

// 使用gperf生成的配置解析表
static const char * const server_type_table[_SERVER_TYPE_MAX] = {
    [SERVER_SYSTEM]   = "system",
    [SERVER_FALLBACK] = "fallback", 
    [SERVER_LINK]     = "link",
    [SERVER_RUNTIME]  = "runtime",
};

// timesyncd-gperf.gperf定义
Time.NTP,                config_parse_servers, SERVER_SYSTEM,   0
Time.FallbackNTP,        config_parse_servers, SERVER_FALLBACK, 0
Time.RootDistanceMaxSec,  config_parse_sec,     0,               offsetof(Manager, root_distance_max_usec)
Time.PollIntervalMinSec,  config_parse_sec,     0,               offsetof(Manager, poll_interval_min_usec)
Time.PollIntervalMaxSec,  config_parse_sec,     0,               offsetof(Manager, poll_interval_max_usec)

11. 数据流程图

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   启动时间同步   │───▶│   服务器选择     │───▶│   DNS解析       │
└─────────────────┘    └──────────────────┘    └─────────────────┘
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   建立UDP连接    │◀───│   选择服务器地址  │◀───│   地址解析完成   │
└─────────────────┘    └──────────────────┘    └─────────────────┘
         │                       │
         ▼                       ▼
┌─────────────────┐    ┌──────────────────┐
│   发送NTP请求    │───▶│   等待响应       │
└─────────────────┘    └──────────────────┘
         │                       │
         ▼                       ▼
┌─────────────────┐    ┌──────────────────┐
│   接收NTP响应    │◀───│   超时重试       │
└─────────────────┘    └──────────────────┘
         │
         ▼
┌─────────────────┐
│   消息验证       │
└─────────────────┘
         │
         ▼
┌─────────────────┐
│   时间计算       │
└─────────────────┘
         │
         ▼
┌─────────────────┐
│   异常检测       │
└─────────────────┘
         │
         ▼
┌─────────────────┐
│   时钟调整       │
└─────────────────┘
         │
         ▼
┌─────────────────┐
│   状态更新       │
└─────────────────┘
         │
         ▼
┌─────────────────┐
│   调整轮询间隔   │
└─────────────────┘

12. 软件架构图

┌─────────────────────────────────────────────────────────────────────────────┐
│                        systemd-timesyncd 架构                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────┐    ┌──────────────────┐    ┌──────────────────────┐  │
│  │   D-Bus API     │    │   配置管理       │    │   网络监控           │  │
│  │                 │    │                  │    │                      │  │
│  │ • 属性查询      │    │ • 配置文件解析   │    │ • 网络状态变化       │  │
│  │ • 方法调用      │    │ • 服务器列表管理 │    │ • DNS解析            │  │
│  │ • 信号发送      │    │ • 参数验证       │    │ • 链路配置读取       │  │
│  └─────────────────┘    └──────────────────┘    └──────────────────────┘  │
│           │                       │                       │              │
│           └───────────────────────┼───────────────────────┘              │
│                                   │                                      │
│  ┌─────────────────────────────────┼─────────────────────────────────────┐  │
│  │                    核心管理器 (Manager)                               │  │
│  │                                                                     │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │  │
│  │  │ 服务器管理   │  │ NTP协议处理  │  │ 时间同步算法 │  │ 状态管理     │ │  │
│  │  │             │  │             │  │             │  │             │ │  │
│  │  │ • 服务器选择 │  │ • 消息构建   │  │ • 样本收集   │  │ • 同步标志   │ │  │
│  │  │ • 地址解析   │  │ • 响应解析   │  │ • 抖动计算   │  │ • 状态持久化 │ │  │
│  │  │ • 连接管理   │  │ • 验证检查   │  │ • 异常检测   │  │ • 事件通知   │ │  │
│  │  │ • 故障转移   │  │ • 时间戳计算 │  │ • 间隔调整   │  │ • 监控报告   │ │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘ │  │
│  └─────────────────────────────────┼─────────────────────────────────────┘  │
│                                   │                                      │
│  ┌─────────────────────────────────┼─────────────────────────────────────┐  │
│  │                       系统接口层                                       │  │
│  │                                                                     │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │  │
│  │  │ 网络通信     │  │ 时钟控制     │  │ 存储管理     │  │ 事件循环     │ │  │
│  │  │             │  │             │  │             │  │             │ │  │
│  │  │ • UDP套接字  │  │ • adjtimex   │  │ • 时间戳文件 │  │ • sd-event  │ │  │
│  │  │ • IPv4/IPv6  │  │ • 时钟设置   │  │ • 状态文件   │  │ • 定时器     │ │  │
│  │  │ • 超时处理   │  │ • 频率调整   │  │ • 配置文件   │  │ • 信号处理   │ │  │
│  │  │ • 重试机制   │  │ • 闰秒处理   │  │ • 权限管理   │  │ • I/O多路复用│ │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘ │  │
│  └─────────────────────────────────┼─────────────────────────────────────┘  │
│                                   │                                      │
│  ┌─────────────────────────────────┼─────────────────────────────────────┐  │
│  │                       外部系统集成                                       │  │
│  │                                                                     │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │  │
│  │  │ 内核时钟     │  │ systemd-    │  │ systemd-    │  │ 外部NTP服务   │ │  │
│  │  │ 子系统       │  │ networkd    │  │ resolved    │  │ 器           │ │  │
│  │  │             │  │             │  │             │  │             │ │  │
│  │  │ • PLL控制    │  │ • 网络配置   │  │ • DNS解析   │  │ • 时间源     │ │  │
│  │  │ • 11分钟模式  │  │ • 接口状态   │  │ • 缓存管理   │  │ • 层级结构   │ │  │
│  │  │ • 漂移补偿   │  │ • 链路监控   │  │ • 域名解析   │  │ • 根距离     │ │  │
│  │  │ • 状态报告   │  │ • 事件通知   │  │ • 地址验证   │  │ • 精度保证   │ │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘ │  │
│  └─────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────┘

13. 总结

  systemd-timesyncd 是一个设计精良的轻量级NTP客户端,具有以下特点:

  设计优势:
    1. 模块化架构:清晰的职责分离,便于维护和扩展
    2. 多层次服务器配置:支持运行时、系统、网络、回退四种配置
    3. 智能时间同步算法:结合抖动检测、异常过滤、动态轮询
    4. 完善的集成机制:与systemd生态深度融合
    5. 轻量级实现:资源占用少,适合嵌入式和容器环境

  核心特性:
    - RFC 5905 NTP协议兼容
    - 渐进式和跳跃式时钟调整
    - 网络状态自适应
    - 时间戳持久化
    - D-Bus API接口
    - 完善的错误处理和重试机制

  适用场景:
    - 桌面和服务器系统的基础时间同步
    - 容器和虚拟机环境
    - 嵌入式设备和IoT设备
    - 对精度要求不是特别高的应用场景

  这个模块充分体现了systemd的设计理念:简洁、高效、可靠、易集成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值