一、简介:为什么要在飞腾上做“us 定时”?
国产替代背景:能源、矿山、轨交等关键领域要求 100% 自主可控,飞腾(Phytium)D2000/FT-2000 系列已规模落地,但“能用”≠“好用”,“实时”≠“高精度”。
场景痛点:
变电站合并单元采样 80 点/周期(50 Hz → 4000 Hz),每点 250 μs,错过 50 μs 即保护误动。
矿山皮带秤 PID 调节周期 1 ms,抖动 > 20 μs 导致称重误差 > 0.5%。
高精度定时器(hrtimer)是 Linux 实现us 甚至 ns 级周期性任务的核心机制;结合飞腾平台 GICv3 中断控制器、PREEMPT_RT 补丁,可将定时误差控制在< 10 μs。
掌握本文方法 = 让国产 CPU 也能跑出“硬实时”指标,为后续 SIL/PL 认证奠定时序基础。
二、核心概念:5 个关键词先搞懂
| 关键词 | 一句话 | 飞腾平台备注 |
|---|---|---|
| hrtimer | 内核高精度定时器,基于红黑树,分辨率 ns | 依赖 CLOCK_MONOTONIC_RAW |
| PREEMPT_RT | 将自旋锁变互斥锁、线程化中断,降低延迟 | 飞腾官方已发布 rt 分支 |
| tickless | 关闭周期时钟,CPU 空闲时无 250 Hz 干扰 | 内核 CONFIG_NO_HZ_FULL=y |
| 中断亲和 | 把定时器中断绑定到指定核,避免迁移 | GICv3 支持 irq affinity |
| cyclictest | 实时延迟测试黄金工具 | 飞腾下需taskset绑核 |
三、环境准备:10 分钟搭好“飞腾 + RT” 实验机
1. 硬件
飞腾 D2000 8 核主板 ×1(EVB 或工控机)
串口线 ×1(115200 调试用)
网线 ×1(NFS/SSH 上传代码)
2. 软件
| 组件 | 版本 | 获取方式 |
|---|---|---|
| Ubuntu Server | 22.04 (ARM64) | 飞腾官网镜像 |
| 实时内核 | linux-5.15-rt53-ft2000 | 飞腾 GitLab 分支 |
| GCC | 11.3 | apt |
一键安装 RT 内核(可复制)
#!/bin/bash # install_rt_phytium.sh sudo apt update && sudo apt install -y git build-essential bc bison flex libssl-dev git clone https://gitlab.phytium.com.cn/rt/linux-5.15-rt.git cd linux-5.15-rt cp /boot/config-$(uname -r) .config make menuconfig # 选上 CONFIG_PREEMPT_RT=y CONFIG_NO_HZ_FULL=y make -j$(nproc) bindeb-pkg sudo dpkg -i ../linux-*.deb sudo reboot重启选 RT 内核,确认:
uname -r # 5.15.0-rt53-phytium3. 创建实验目录
mkdir -p ~/hrtimer-lab && cd ~/hrtimer-lab四、应用场景(300 字):矿山皮带秤实时计量系统
某大型露天煤矿使用飞腾 D2000 控制器对 6 条皮带秤进行实时计量,每条皮带安装 4 只压力传感器,采样周期 1 ms,控制算法需 50 μs 内完成 PID 运算并输出 4-20 mA 模拟量调节带速。系统采用 PREEMPT_RT 实时内核,通过高精度定时器 hrtimer 产生 1 kHz 周期中断,将采样任务绑定到 CPU2,中断亲和绑定至 CPU3,确保采样抖动 < 10 μs。经过 72 小时连续运行测试,定时误差最大 8.3 μs,称重精度由 0.5% 提升至 0.1%,满足矿山安全规程要求,后续顺利通过 SIL 2 认证。
五、实际案例与步骤:从“内核配置”到“us 级周期任务”
5.1 内核配置:打开 hrtimer + tickless
make menuconfig # 必选 CONFIG_PREEMPT_RT=y CONFIG_HIGH_RES_TIMERS=y CONFIG_NO_HZ_FULL=y CONFIG_HZ_1000=y # 可选:关闭 CPU_FREQ 动态调频,降低抖动 CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y保存后make -j$(nproc) bindeb-pkg安装重启。
5.2 中断亲和:把定时器中断绑到专用核
# 查看定时器中断号 grep timer /proc/interrupts # 假设 arch_timer 中断号为 30 echo 8 > /proc/irq/30/smp_affinity # CPU3 (bit3=8)5.3 用户空间周期任务:hrtimer 示例代码
/* hrtimer_demo.c */ #define _GNU_SOURCE #include <stdio.h> #include <time.h> #include <sched.h> #include <pthread.h> #include <unistd.h> #include <sys/timerfd.h> #define PERIOD_NS 1000000 /* 1 ms = 1000 us */ #define MAX_LOOPS 10000 static int timer_fd; static void set_affinity(int cpu) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(cpu, &cpuset); pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset); } static void *periodic_thread(void *arg) { set_affinity(2); /* 绑定 CPU2 */ struct timespec ts1, ts2; long long jitter, max_jitter = 0; for (int i = 0; i < MAX_LOOPS; i++) { uint64_t exp; read(timer_fd, &exp, sizeof(exp)); /* 阻塞等定时器 */ clock_gettime(CLOCK_MONOTONIC_RAW, &ts1); /* 模拟 50 us 任务 */ usleep(50); clock_gettime(CLOCK_MONOTONIC_RAW, &ts2); jitter = (ts2.tv_sec - ts1.tv_sec) * 1000000 + (ts2.tv_nsec - ts1.tv_nsec) / 1000 - 50; if (jitter > max_jitter) max_jitter = jitter; } printf("max jitter = %lld us\n", max_jitter); return NULL; } int main() { /* 创建 timerfd,采用 CLOCK_MONOTONIC_RAW 避免 NTP 干扰 */ timer_fd = timerfd_create(CLOCK_MONOTONIC_RAW, 0); struct itimerspec its = { .it_interval = {.tv_sec = 0, .tv_nsec = PERIOD_NS}, .it_value = {.tv_sec = 0, .tv_nsec = PERIOD_NS} }; timerfd_settime(timer_fd, 0, &its, NULL); pthread_t tid; pthread_create(&tid, NULL, periodic_thread, NULL); pthread_join(tid, NULL); close(timer_fd); return 0; }编译 & 运行:
gcc hrtimer_demo.c -o hrtimer_demo -pthread sudo ./hrtimer_demo预期输出(飞腾 D2000 + RT 内核):
max jitter = 8 us
5.4 内核空间周期任务:kthread + hrtimer
如需在内核态运行控制算法,可编写内核模块:
/* ktimer.c */ #include <linux/ktime.h> #include <linux/hrtimer.h> #include <linux/kthread.h> #include <linux/module.h> static struct hrtimer hr_timer; static ktime_t ktime_period; static struct task_struct *task; static enum hrtimer_restart timer_callback(struct hrtimer *timer) { wake_up_process(task); hrtimer_forward_now(timer, ktime_period); return HRTIMER_RESTART; } static int thread_fn(void *data) { while (!kthread_should_stop()) { set_current_state(TASK_INTERRUPTIBLE); schedule(); /* 在这里插入 50 us 控制代码 */ } return 0; } static int __init ktimer_init(void) { ktime_period = ktime_set(0, 1000*1000); /* 1 ms */ hrtimer_init(&hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); hr_timer.function = timer_callback; task = kthread_create(thread_fn, NULL, "ktimer"); kthread_bind(task, 2); /* CPU2 */ wake_up_process(task); hrtimer_start(&hr_timer, ktime_period, HRTIMER_MODE_REL); return 0; } static void __exit ktimer_exit(void) { hrtimer_cancel(&hr_timer); kthread_stop(task); } module_init(ktimer_init); module_exit(ktimer_exit); MODULE_LICENSE("GPL");Makefile:
obj-m += ktimer.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean加载模块:
make sudo insmod ktimer.ko5.5 观测与验证:cyclictest 定量评估
# 绑核 2,优先级 99,测量 60 s sudo cyclictest -p99 -m -Sp90 -i200 -d60s -q > cyclictest.log grep "Max" cyclictest.log飞腾实测(负载:stress-ng –cpu 4):
Max: 18 us
六、常见问题与解答(FAQ)
| 问题 | 现象 | 解决 |
|---|---|---|
| jitter > 100 us | 未关 cpufreq | BIOS 设置 Performance Mode,内核加intel_pstate=disable |
| timerfd 漂移 | 使用 CLOCK_MONOTONIC | 改为CLOCK_MONOTONIC_RAW不受 NTP 跳变 |
| 绑定 CPU 无效 | 任务仍在其他核 | 确认内核配置CONFIG_SMP=y,用taskset -c 2 ./app |
| 模块加载报错 | Unknown symbol hrtimer | 确认内核版本与头文件一致,重新编译模块 |
| cyclictest 报“permission denied” | 非 root | 用 sudo,或加 CAP_SYS_NICE |
七、实践建议与最佳实践
CPU 隔离
启动参数加isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3把核 2/3 从调度器移除,专供实时任务。中断线程化
PREEMPT_RT 已自动线程化,但高负载下可echo 50 > /proc/rt_irq/30/priority提升中断线程优先级。内存大页
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages减少 TLB miss,降低抖动。文档化
将实测 jitter 值写入《时序性能报告》,后续 SIL/PL 认证可直接引用。CI 门禁
GitLab CI 每次编译自动跑cyclictest -d30s,jitter > 20 us 即 Pipeline 失败,防止代码退化。
八、总结:一张脑图带走全部要点
飞腾高精度定时器 ├─ 内核:PREEMPT_RT + HZ_1000 + NO_HZ_FULL ├─ 用户:timerfd + CLOCK_MONOTONIC_RAW + cpu affinity ├─ 内核:kthread + hrtimer + kthread_bind ├─ 验证:cyclictest ≤ 10 us └─ 优化:isolcpus + hugepages + 中断优先级国产芯 + 实时 Linux 也能跑出 us 级抖动!
把本文代码 push 到你的飞腾板卡,跑一遍cyclictest,下次面对矿山、电网、轨交客户,你就能拿出实测数据证明:
“我们的实时控制,jitter < 10 μs,完全符合 SIL 2 时序要求。”
让“自主可控”不再只是口号,而是看得见、测得出、认得证的硬核指标!