如何在Linux中轻松运行高分辨率、高精度的周期循环,任意频率(例如:最高可达10 KHz〜100 KHz),使用软实时调度程序和纳秒延迟
这样做相当复杂。但是,借助我编写的一些辅助函数,这非常容易。因此,让我首先向您展示简单部分。
以下是如何在Linux中设置以固定频率10 KHz运行的循环(根据您的代码和系统,可能可以实现高达10 KHz至100 KHz的频率)。在此处获取timinglib.h。
#include "timinglib.h"
void * pthread_task(void * argument)
{
use_realtime_scheduler();
const uint64_t PERIOD_US = 100;
uint64_t last_wake_time_us = micros();
while (true)
{
sleep_until_us(&last_wake_time_us, PERIOD_US);
}
}
就是这样!非常简单。把你的套接字代码放在那里以发送UDP数据包。
我在这里有一个UDP套接字演示:
- 客户端
- 服务器
现在,让我们谈谈上面的定时问题。
sleep_until_us()
的工作方式就像 FreeRTOS函数vTaskDelayUntil()
一样。我喜欢它使用起来非常容易,所以我让我的函数在Linux中也像它一样工作。
为了做到这一点,我必须使用带有标志 TIMER_ABSTIME
的 clock_nanosleep()
来睡眠,以等待未来(您期望的周期)设置的绝对时间,而不是从现在开始计算的相对时间。
在这里阅读相关内容:https://man7.org/linux/man-pages/man2/clock_nanosleep.2.html
查看我在timinglib.c中实现该函数的调用:
void sleep_until_us(uint64_t * previous_wake_time_us, uint64_t period_us)
{
if (previous_wake_time_us == NULL)
{
printf("ERROR: NULL ptr.\n");
return;
}
uint64_t previous_wake_time_ns = US_TO_NS(*previous_wake_time_us);
uint64_t period_ns = US_TO_NS(period_us);
sleep_until_ns(&previous_wake_time_ns, period_ns);
*previous_wake_time_us = NS_TO_US(previous_wake_time_ns);
}
void sleep_until_ns(uint64_t * previous_wake_time_ns, uint64_t period_ns)
{
if (previous_wake_time_ns == NULL)
{
printf("ERROR: NULL ptr.\n");
return;
}
uint64_t time_wakeup_ns = *previous_wake_time_ns + period_ns;
*previous_wake_time_ns = time_wakeup_ns;
const struct timespec TS_WAKEUP =
{
.tv_sec = (__time_t)(time_wakeup_ns / NS_PER_SEC),
.tv_nsec = (__syscall_slong_t)(time_wakeup_ns % NS_PER_SEC),
};
int retcode = EINTR;
while (retcode == EINTR)
{
retcode = clock_nanosleep(CLOCK_TYPE, TIMER_ABSTIME, &TS_WAKEUP, NULL);
if (retcode != 0)
{
print_nanosleep_failed(retcode);
}
}
}
但这还不够。使用常规的Linux SCHED_OTHER
调度程序,clock_nanosleep()
的最小睡眠间隔约为55微秒,误差高达1毫秒。这对于1~10+ KHz循环来说不够好。因此,我们激活了SCHED_RR
软实时轮询调度程序,以获得约4微秒的最小睡眠间隔,误差高达0.4毫秒。这样就好多了。
请查看我的答案:如何睡眠几微秒
这是我的调度程序和内存锁定配置代码,来自timinglib.c:
void use_realtime_scheduler()
{
int retcode;
pthread_t this_thread = pthread_self();
const struct sched_param priority_param =
{
.sched_priority = REALTIME_SCHEDULER_PRIORITY_LOWEST,
};
retcode = pthread_setschedparam(this_thread, SCHED_RR, &priority_param);
if (retcode != 0)
{
printf("ERROR: in file %s: %i: Failed to set pthread scheduler. "
"retcode = %i: %s.\n",
__FILE__, __LINE__, retcode, strerror(retcode));
if (retcode == EPERM)
{
printf(" You must use `sudo` or run this program as root to "
"have proper privileges!\n");
}
}
else
{
}
retcode = mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT);
if (retcode == -1)
{
printf("ERROR: in file %s: %i: Failed to lock memory into RAM. "
"errno = %i: %s.\n",
__FILE__, __LINE__, errno, strerror(errno));
if (errno == EPERM)
{
printf(" You must use `sudo` or run this program as root to "
"have proper privileges!\n");
}
}
else
{
}
}
这里有一个带有时间仪器的完整演示,用于测试您系统上可能达到的最高速度:timinglib_pthread_periodic_loop.c。
当设置为 100 微秒 的循环周期(10 KHz)时,这是输出和误差。请注意误差的好坏!大多数循环迭代的误差都小于 1%,最坏情况下偶尔会出现 +/- 20% 的误差。对于 10 KHz,这非常好!
eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=c17 timinglib_pthread_periodic_loop.c timinglib.c -o bin/a -pthread && time sudo bin/a
Activating realtime scheduler.
Starting pthread at fixed interval using `sleep_until_us()`.
thread_name = some thread name
loop period = 100 us; freq = 10000.0 Hz
0: dt_ns = 85717 ns; error = 14283 **ns** ( 14.283%)
1: dt_ns = 98792 ns; error = 1208 **ns** ( 1.208%)
2: dt_ns = 99712 ns; error = 288 **ns** ( 0.288%)
3: dt_ns = 100196 ns; error = -196 **ns** ( -0.196%)
4: dt_ns = 99679 ns; error = 321 **ns** ( 0.321%)
5: dt_ns = 100680 ns; error = -680 **ns** ( -0.680%)
6: dt_ns = 99396 ns; error = 604 **ns** ( 0.604%)
7: dt_ns = 100265 ns; error = -265 **ns** ( -0.265%)
8: dt_ns = 99716 ns; error = 284 **ns** ( 0.284%)
9: dt_ns = 100183 ns; error = -183 **ns** ( -0.183%)
10: dt_ns = 99864 ns; error = 136 **ns** ( 0.136%)
11: dt_ns = 100031 ns; error = -31 **ns** ( -0.031%)
12: dt_ns = 100001 ns; error = -1 **ns** ( -0.001%)
13: dt_ns = 99970 ns; error = 30 **ns** ( 0.030%)
14: dt_ns = 99984 ns; error = 16 **ns** ( 0.016%)
15: dt_ns = 100047 ns; error = -47 **ns** ( -0.047%)
16: dt_ns = 99861 ns; error = 139 **ns** ( 0.139%)
17: dt_ns = 100281 ns; error = -281 **ns** ( -0.281%)
18: dt_ns = 99909 ns; error = 91 **ns** ( 0.091%)
19: dt_ns = 99985 ns; error = 15 **ns** ( 0.015%)
20: dt_ns = 99736 ns; error = 264 **ns** ( 0.264%)
21: dt_ns = 100312 ns; error = -312 **ns** ( -0.312%)
22: dt_ns = 100013 ns; error = -13 **ns** ( -0.013%)
23: dt_ns = 100014 ns; error = -14 **ns** ( -0.014%)
24: dt_ns = 99834 ns; error = 166 **ns** ( 0.166%)
25: dt_ns = 99951 ns; error = 49 **ns** ( 0.049%)
26: dt_ns = 100267 ns; error = -267 **ns** ( -0.267%)
27: dt_ns = 99735 ns; error = 265 **ns** ( 0.265%)
28: dt_ns = 100174 ns; error = -174 **ns** ( -0.174%)
29: dt_ns = 100028 ns; error = -28 **ns** ( -0.028%)
30: dt_ns = 99942 ns; error = 58 **ns** ( 0.058%)
31: dt_ns = 99747 ns; error = 253 **ns** ( 0.253%)
32: dt_ns = 100226 ns; error = -226 **ns** ( -0.226%)
33: dt_ns = 99994 ns; error = 6 **ns** ( 0.006%)
34: dt_ns = 99969 ns; error = 31 **ns** ( 0.031%)
35: dt_ns = 99857 ns; error = 143 **ns** ( 0.143%)
36: dt_ns = 100386 ns; error = -386 **ns** ( -0.386%)
37: dt_ns = 99813 ns; error = 187 **ns** ( 0.187%)
38: dt_ns = 100042 ns; error = -42 **ns** ( -0.042%)
39: dt_ns = 99706 ns; error = 294 **ns** ( 0.294%)
40: dt_ns = 100238 ns; error = -238 **ns** ( -0.238%)
41: dt_ns = 99886 ns; error = 114 **ns** ( 0.114%)
42: dt_ns = 100160 ns; error = -160 **ns** ( -0.160%)
43: dt_ns = 99867 ns; error = 133 **ns** ( 0.133%)
44: dt_ns = 100092 ns; error = -92 **ns** ( -0.092%)
45: dt_ns = 99878 ns; error = 122 **ns** ( 0.122%)
46: dt_ns = 100085 ns; error = -85 **ns** ( -0.085%)
47: dt_ns = 100112 ns; error = -112 **ns** ( -0.112%)
48: dt_ns = 99764 ns; error = 236 **ns** ( 0.236%)
49: dt_ns = 100212 ns; error = -212 **ns** ( -0.212%)
50: dt_ns = 99989 ns; error = 11 **ns** ( 0.011%)
51: dt_ns = 100010 ns; error = -10 **ns** ( -0.010%)
52: dt_ns = 99759 ns; error = 241 **ns** ( 0.241%)
53: dt_ns = 100206 ns; error = -206 **ns** ( -0.206%)
54: dt_ns = 100002 ns; error = -2 **ns** ( -0.002%)
55: dt_ns = 99938 ns; error = 62 **ns** ( 0.062%)
56: dt_ns = 99746 ns; error = 254 **ns** ( 0.254%)
57: dt_ns = 100569 ns; error = -569 **ns** ( -0.569%)
58: dt_ns = 99724 ns; error = 276 **ns** ( 0.276%)
59: dt_ns = 100101 ns; error = -101 **ns** ( -0.101%)
60: dt_ns = 99650 ns; error = 350 **ns** ( 0.350%)
61: dt_ns = 100250 ns; error = -250 **ns** ( -0.250%)
62: dt_ns = 100001 ns; error = -1 **ns** ( -0.001%)
63: dt_ns = 100052 ns; error = -52 **ns** ( -0.052%)
64: dt_ns = 99853 ns; error = 147 **ns** ( 0.147%)
65: dt_ns = 99832 ns; error = 168 **ns** ( 0.168%)
66: dt_ns = 100017 ns; error = -17 **ns** ( -0.017%)
67: dt_ns = 100001 ns; error = -1 **ns** ( -0.001%)
68: dt_ns = 100227 ns; error = -227 **ns** ( -0.227%)
69: dt_ns = 99840 ns; error = 160 **ns** ( 0.160%)
70: dt_ns = 99876 ns; error = 124 **ns** ( 0.124%)
71: dt_ns = 99938 ns; error = 62 **ns** ( 0.062%)
72: dt_ns = 100469 ns; error = -469 **ns** ( -0.469%)
73: dt_ns = 100162 ns; error = -162 **ns** ( -0.162%)
74: dt_ns = 100724 ns; error = -724 **ns** ( -0.724%)
75: dt_ns = 106371 ns; error = -6371 **ns** ( -6.371%)
76: dt_ns = 93393 ns; error = 6607 **ns** ( 6.607%)
77: dt_ns = 100476 ns; error = -476 **ns** ( -0.476%)
78: dt_ns = 99400 ns; error = 600 **ns** ( 0.600%)
79: dt_ns = 99948 ns; error = 52 **ns** ( 0.052%)
80: dt_ns = 99938 ns; error = 62 **ns** ( 0.062%)
81: dt_ns = 100204 ns; error = -204 **ns** ( -0.204%)
82: dt_ns = 100026 ns; error = -26 **ns** ( -0.026%)
83: dt_ns = 100236 ns; error = -236 **ns** ( -0.236%)
84: dt_ns = 99252 ns; error = 748 **ns** ( 0.748%)
85: dt_ns = 100272 ns; error = -272 **ns** ( -0.272%)
86: dt_ns = 99745 ns; error = 255 **ns** ( 0.255%)
87: dt_ns = 101421 ns; error = -1421 **ns** ( -1.421%)
88: dt_ns = 99283 ns; error = 717 **ns** ( 0.717%)
89: dt_ns = 100878 ns; error = -878 **ns** ( -0.878%)
90: dt_ns = 99288 ns; error = 712 **ns** ( 0.712%)
91: dt_ns = 99430 ns; error = 570 **ns** ( 0.570%)
92: dt_ns = 99673 ns; error = 327 **ns** ( 0.327%)
93: dt_ns = 100080 ns; error = -80 **ns** ( -0.080%)
94: dt_ns = 99945 ns; error = 55 **ns** ( 0.055%)
95: dt_ns = 99950 ns; error = 50 **ns** ( 0.050%)
96: dt_ns = 99985 ns; error = 15 **ns** ( 0.015%)
97: dt_ns = 100418 ns; error = -418 **ns** ( -0.418%)
98: dt_ns = 100050 ns; error = -50 **ns** ( -0.050%)
99: dt_ns = 99361 ns; error = 639 **ns** ( 0.639%)
100: dt_ns = 99627 ns; error = 373 **ns** ( 0.373%)
101: dt_ns = 99641 ns; error = 359 **ns** ( 0.359%)
102: dt_ns = 100025 ns; error = -25 **ns** ( -0.025%)
103: dt_ns = 100820 ns; error = -820 **ns** ( -0.820%)
104: dt_ns = 100723 ns; error = -723 **ns** ( -0.723%)
105: dt_ns = 98815 ns; error = 1185 **ns** ( 1.185%)
106: dt_ns = 100250 ns; error = -250 **ns** ( -0.250%)
107: dt_ns = 100216 ns; error = -216 **ns** ( -0.216%)
108: dt_ns = 99683 ns; error = 317 **ns** ( 0.317%)
109: dt_ns = 100966 ns; error = -966 **ns** ( -0.966%)
110: dt_ns = 100357 ns; error = -357 **ns** ( -0.357%)
111: dt_ns = 100022 ns; error = -22 **ns** ( -0.022%)
112: dt_ns = 98966 ns; error = 1034 **ns** ( 1.034%)
113: dt_ns = 99517 ns; error = 483 **ns** ( 0.483%)
114: dt_ns = 99973 ns; error = 27 **ns** ( 0.027%)
115: dt_ns = 99841 ns; error = 159 **ns** ( 0.159%)
116: dt_ns = 101627 ns; error = -1627 **ns** ( -1.627%)
117: dt_ns = 100344 ns; error = -344 **ns** ( -0.344%)
118: dt_ns = 99767 ns; error = 233 **ns** ( 0.233%)
119: dt_ns = 100106 ns; error = -106 **ns** ( -0.106%)
120: dt_ns = 101530 ns; error = -1530 **ns** ( -1.530%)
121: dt_ns = 99844 ns; error = 156 **ns** ( 0.156%)
122: dt_ns = 98751 ns; error = 1249 **ns** ( 1.249%)
123: dt_ns = 100082 ns; error = -82 **ns** ( -0.082%)
124: dt_ns = 99979 ns; error = 21 **ns** ( 0.021%)
125: dt_ns = 101888 ns; error = -1888 **ns** ( -1.888%)
126: dt_ns = 99798 ns; error = 202 **ns** ( 0.202%)
127: dt_ns = 98897 ns; error = 1103 **ns** ( 1.103%)
128: dt_ns = 100091 ns; error = -91 **ns** ( -0.091%)
129: dt_ns = 99992 ns; error = 8 **ns** ( 0.008%)
130: dt_ns = 100077 ns; error = -77 **ns** ( -0.077%)
131: dt_ns = 99306 ns; error = 694 **ns** ( 0.694%)
132: dt_ns = 100008 ns; error = -8 **ns** ( -0.008%)
133: dt_ns = 100690 ns; error = -690 **ns** ( -0.690%)
134: dt_ns = 100179 ns; error = -179 **ns** ( -0.179%)
135: dt_ns = 97880 ns; error = 2120 **ns** ( 2.120%)
136: dt_ns = 99795 ns; error = 205 **ns** ( 0.205%)
137: dt_ns = 100787 ns; error = -787 **ns** ( -0.787%)
138: dt_ns = 102552 ns; error = -2552 **ns** ( -2.552%)
139: dt_ns = 99397 ns; error = 603 **ns** ( 0.603%)
140: dt_ns = 99718 ns; error = 282 **ns** ( 0.282%)
141: dt_ns = 99864 ns; error = 136 **ns** ( 0.136%)
142: dt_ns = 101029 ns; error = -1029 **ns** ( -1.029%)
143: dt_ns = 104776 ns; error = -4776 **ns** ( -4.776%)
144: dt_ns = 94933 ns; error = 5067 **ns** ( 5.067%)
145: dt_ns = 99679 ns; error = 321 **ns** ( 0.321%)
146: dt_ns = 99559 ns; error = 441 **ns** ( 0.441%)
147: dt_ns = 100669 ns; error = -669 **ns** ( -0.669%)
148: dt_ns = 100517 ns; error = -517 **ns** ( -0.517%)
149: dt_ns = 98934 ns; error = 1066 **ns** ( 1.066%)
150: dt_ns = 98797 ns; error = 1203 **ns** ( 1.203%)
151: dt_ns = 99370 ns; error = 630 **ns** ( 0.630%)
152: dt_ns = 99447 ns; error = 553 **ns** ( 0.553%)
153: dt_ns = 99903 ns; error = 97 **ns** ( 0.097%)
154: dt_ns = 101088 ns; error = -1088 **ns** ( -1.088%)
155: dt_ns = 99971 ns; error = 29 **ns** ( 0.029%)
156: dt_ns = 99980 ns; error = 20 **ns** ( 0.020%)
157: dt_ns = 99390 ns; error = 610 **ns** ( 0.610%)
158: dt_ns = 102007 ns; error = -2007 **ns** ( -2.007%)
159: dt_ns = 99097 ns; error = 903 **ns** ( 0.903%)
160: dt_ns = 98546 ns; error = 1454 **ns** ( 1.454%)
161: dt_ns = 99841 ns; error = 159 **ns** ( 0.159%)
162: dt_ns = 100830 ns; error = -830 **ns** ( -0.830%)
163: dt_ns = 100135 ns; error = -135 **ns** ( -0.135%)
164: dt_ns = 101267 ns; error = -1267 **ns** ( -1.267%)
165: dt_ns = 103445 ns; error = -3445 **ns** ( -3.445%)
166: dt_ns = 99046 ns; error = 954 **ns** ( 0.954%)
167: dt_ns = 99528 ns; error = 472 **ns** ( 0.472%)
168: dt_ns = 100012 ns; error = -12 **ns** ( -0.012%)
169: dt_ns = 100580 ns; error = -580 **ns** ( -0.580%)
170: dt_ns = 97971 ns; error = 2029 **ns** ( 2.029%)
171: dt_ns = 99363 ns; error = 637 **ns** ( 0.637%)
172: dt_ns = 100817 ns; error = -817 **ns** ( -0.817%)
173: dt_ns = 101567 ns; error = -1567 **ns** ( -1.567%)
174: dt_ns = 100112 ns; error = -112 **ns** ( -0.112%)
175: dt_ns = 99775 ns; error = 225 **ns** ( 0.225%)
176: dt_ns = 100885 ns; error = -885 **ns** ( -0.885%)
177: dt_ns = 99555 ns; error = 445 **ns** ( 0.445%)
178: dt_ns = 101252 ns; error = -1252 **ns** ( -1.252%)
179: dt_ns = 99116 ns; error = 884 **ns** ( 0.884%)
180: dt_ns = 99471 ns; error = 529 **ns** ( 0.529%)
181: dt_ns = 98410 ns; error = 1590 **ns** ( 1.590%)
182: dt_ns = 100764 ns; error = -764 **ns** ( -0.764%)
183: dt_ns = 99709 ns; error = 291 **ns** ( 0.291%)
184: dt_ns = 99505 ns; error = 495 **ns** ( 0.495%)
185: dt_ns = 101294 ns; error = -1294 **ns** ( -1.294%)
186: dt_ns = 98697 ns; error = 1303 **ns** ( 1.303%)
187: dt_ns = 101129 ns; error = -1129 **ns** ( -1.129%)
188: dt_ns = 99346 ns; error = 654 **ns** ( 0.654%)
189: dt_ns = 100789 ns; error = -789 **ns** ( -0.789%)
190: dt_ns = 97991 ns; error = 2009 **ns** ( 2.009%)
191: dt_ns = 101046 ns; error = -1046 **ns** ( -1.046%)
192: dt_ns = 98505 ns; error = 1495 **ns** ( 1.495%)
193: dt_ns = 99308 ns; error = 692 **ns** ( 0.692%)
194: dt_ns = 99995 ns; error = 5 **ns** ( 0.005%)
195: dt_ns = 100440 ns; error = -440 **ns** ( -0.440%)
196: dt_ns = 100826 ns; error = -826 **ns** ( -0.826%)
197: dt_ns = 102797 ns; error = -2797 **ns** ( -2.797%)
198: dt_ns = 97970 ns; error = 2030 **ns** ( 2.030%)
199: dt_ns = 98622 ns; error = 1378 **ns** ( 1.378%)
average time error per iteration = 69.690 ns ( 0.070%)
minimum time error per iteration = -6371 ns ( -6.371%)
maximum time error per iteration = 14283 ns ( 14.283%)
Final message from thread = Done!
real 0m0.049s
user 0m0.004s
sys 0m0.015s
你可以自己运行它。克隆我的仓库并运行代码。以下是从文件顶部开始的gcc构建和运行命令:
gcc -Wall -Wextra -Werror -O3 -std=c17 timinglib_pthread_periodic_loop.c \
timinglib.c -o bin/a -pthread && time sudo bin/a
另请参阅:
- [我的回答] 如何睡眠几微秒并配置
SCHED_RR
- 如何配置Linux的SCHED_RR
软实时轮询调度程序,以便clock_nanosleep()
可以从~55 us提高分辨率到~4 us
- [我的回答] pthread_create与pthread_attr_setschedparam无法正常工作
- https://www.drdobbs.com/soft-real-time-programming-with-linux/184402031?pgno=1