我想为嵌入式linux应用程序实现一个健壮的定时器。这样做的目的是控制函数的执行时间,如果它们花费的时间太长,则生成一个中断来停止函数的循环。
我在网上到处搜索,第一条建议是使用clock()
函数。
使用clock()
函数的解决方案可以是:
#include <time.h>
int func(void){
//the starting time of the function
clock_t initial_time;
clock_t elapsed_time;
initial_time = clock()*1000/CLOCKS_PER_SEC;
do{
//some stuff
elapsed_time = clock()*1000/CLOCKS_PER_SEC - initial_time;
}while(elapsed_time < timeout_ms);
printf("time to get command : %ld\n", elapsed_time);
//send an error if a timeout was reached
if(elapsed_time >= timeout_ms){
return -1;
}
else{
return 1;
}
}
但这并不是真正的健壮,因为clock()可能会在函数计算之间造成溢出,因此,经过的时间将变为负值,并且永远不会脱离循环。--在下面的编辑部分中对此进行了更正
第二个解决方案是使用linux内核定时器,如下所示:
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/init.h> /* Needed for the macros */
#include <linux/timer.h>
int g_time_interval = 10000;
struct timer_list g_timer;
void timer_handler (unsigned long data)
{
// do your timer stuff here
}
int init_timer(void)
{
setup_timer(&g_timer, timer_handler, 0);
mod_timer( &g_timer, jiffies + msecs_to_jiffies(g_time_interval));
return 0;
}
void close_timer(void)
{
del_timer(&g_timer);
}
这个选项似乎不错,但我做了一些研究,jiffies (启动以来的滴答数)也可能溢出,我不知道这是否会影响我使用这个计时器。在下面的编辑部分中对此进行了更正
最后,我找到的最后一个选项是使用带有信号的timer_create。据我所知,如果与CLOCK_MONOTONIC一起使用,则不会出现溢出问题:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <stdbool.h>
#define SIG SIG_RTMIN
int init_timer((void *) handler(int, siginfo_t, void*)){
// Establish handler for timer signal
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIG, &sa, NULL) == -1)
printf("Error initializing timer\n");
// Block timer signal temporarily
printf("Blocking signal %d\n", SIG);
sigemptyset(&mask);
sigaddset(&mask, SIG);
// Create the timer
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIG;
sev.sigev_value.sival_ptr = &timerid;
}
static void handler(int sig, siginfo_t *si, void *uc)
{
//put a flag to 1 for example
signal(sig, SIG_IGN);
}
//Much other stuff ...
但谷歌告诉我,我们只能为每个信号设置一个处理程序,我不知道我的linux板中的其他处理器是否使用SIG_RTMIN。而且,由于我不想通过重新定义它的处理程序来破坏一切,所以它不是一个方便的解决方案。
我在这出什么事了吗?是否有一种方法可以在linux中定义计时器而不存在此问题?
(非常感谢大家:)
编辑
溢出不会导致问题,因此选项1和2是有效的。现在哪一个是最健壮的?
以下是我对溢出错误的解释。给出我们想要计算elapsed_time的情况,最大时钟值是最大值。如上所述:
elapsed_time = clock()*1000/CLOCKS_PER_SEC - initial_time;
让我们将clock()*1000/CLOCKS_PER_SEC
重命名为x。如果存在溢出,则从理论上说是theoric_x > MAX
,但由于存在溢出,x = theoric_x - MAX
(希望是明确的':D)。因此:
elapsed_time = (theoric_x - MAX) - initial_time;
它可以写成:
elapsed_time = (theoric_x - initial_time) - MAX;
这相当于:elapsed_time = (theoric_x - initial_time)
,因为减法最大值就像回到相同的值(它的工作方式类似于模)。这是好的,当theoric_x
低于initial_time + MAX
,如果我们完成,经过的时间将重置。
我希望这已经足够清楚了。
发布于 2021-01-19 16:29:25
但谷歌告诉我,我们只能为每个信号设置一个处理程序,我不知道我的linux板中的其他处理器是否使用SIG_RTMIN。
不,它是每个信号每个进程的一个处理程序。
也就是说,在您自己的程序中为SIGRTMIN设置一个信号处理程序不会干扰任何其他进程的SIGRTMIN处理程序。类似地,创建计时器也不会影响任何其他进程的定时器。你所需要担心的就是你自己的过程。
(从技术上讲,只有有限数量的计时器可用,所以您不希望在一个进程中创建数百个定时器。)
如果进程中只有一个线程,请考虑以下超时方案:
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <stdio.h>
#define TIMEOUT_SIGNAL (SIGRTMIN+0)
#define TIMEOUT_REPEAT_NS 1000000 /* Repeat every millisecond until canceled */
static volatile sig_atomic_t timeout_elapsed; /* Nonzero if timeout has elapsed */
static timer_t timeout_timer;
static void timeout_handler(int signum)
{
(void)signum; /* Silences warning about unused parameter; generates no code. */
timeout_elapsed = 1;
}
static int timeout_init(void)
{
struct sigaction act;
struct sigevent evt;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = timeout_handler;
act.sa_flags = 0;
if (sigaction(TIMEOUT_SIGNAL, &act, NULL) == -1)
return errno;
memset(&evt, 0, sizeof evt);
evt.sigev_notify = SIGEV_SIGNAL;
evt.sigev_signo = TIMEOUT_SIGNAL;
evt.sigev_value.sival_ptr = (void *)0;
if (timer_create(CLOCK_BOOTTIME, &evt, &timeout_timer) == -1)
return errno;
timeout_elapsed = 0;
return 0;
}
static void timeout_cancel(void)
{
struct itimerspec zero;
zero.it_value.tv_sec = 0;
zero.it_value.tv_nsec = 0;
zero.it_interval.tv_sec = 0;
zero.it_interval.tv_nsec = 0;
timer_settime(timeout_timer, 0, &zero, NULL);
}
static void timeout_set(double seconds)
{
struct itimerspec when;
sigset_t mask;
/* Block the timeout signal for now. */
sigemptyset(&mask);
sigaddset(&mask, TIMEOUT_SIGNAL);
sigprocmask(SIG_BLOCK, &mask, NULL);
/* Make sure any previous timeouts have been canceled. */
timeout_cancel();
/* Calculate the next (relative) timeout. */
if (seconds >= 0.000000001) {
long sec = (long)seconds;
long nsec = (long)(1000000000.0*(seconds - (double)sec));
if (nsec < 0)
nsec = 0;
if (nsec > 999999999) {
nsec = 0;
sec++;
}
when.it_value.tv_sec = sec;
when.it_value.tv_nsec = nsec;
} else {
when.it_value.tv_sec = 0;
when.it_value.tv_nsec = 1;
}
/* Set it to repeat, so that it is not easily missed. */
when.it_interval.tv_sec = 0;
when.it_interval.tv_nsec = TIMEOUT_REPEAT_NS;
/* Update the timer. */
timer_settime(timeout_timer, 0, &when, NULL);
/* Clear the flag, and unblock the signal. */
timeout_elapsed = 0;
sigprocmask(SIG_UNBLOCK, &mask, NULL);
}
int main(void)
{
char *line_ptr = NULL;
size_t line_max = 0;
ssize_t line_len;
if (timeout_init()) {
fprintf(stderr, "Cannot set up timeouts: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
timeout_set(5.0);
printf("Please type input lines. This will timeout in five seconds.\n");
fflush(stdout);
while (!timeout_elapsed) {
line_len = getline(&line_ptr, &line_max, stdin);
if (line_len > 0) {
/* Remove trailing newlines */
line_ptr[strcspn(line_ptr, "\r\n")] = '\0';
printf("Read %zd bytes: \"%s\".\n", line_len, line_ptr);
fflush(stdout);
}
}
timeout_cancel();
free(line_ptr);
line_ptr = NULL;
line_max = 0;
printf("Done.\n");
return EXIT_SUCCESS;
}
使用gcc -Wall -Wextra -O2 example1.c -lrt -o example1
编译并运行./example1
。
对于多线程进程,必须将信号传递到特定的线程,几乎总是设置超时的线程。在这里,我推荐了一种不同的方法:使用帮助线程、列表或数组,或者使用相应超时的CLOCK_REALTIME绝对时间的二进制min堆,在时间等待()中等待下一个最快的超时,或者在条件变量上更新指示超时列表/数组/堆的信号。
发布于 2021-01-19 13:36:09
POSIX定义了clock_gettime
。Linux也有它的扩展。
函数
clock_gettime()
和clock_settime()
检索并设置指定时钟时钟的时间。
您可以简单地执行以下操作:
#include <time.h>
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
// Your code here...
clock_gettime(CLOCK_MONOTONIC, &end);
然后,end.tv_nsec - start.tv_nsec
将为您提供由clock_getres
指定的分辨率。有时这仅仅是微秒,甚至仅仅是毫秒。确保检查值并进行相应的调整。
struct timespec res;
clock_getres(CLOCK_MONOTONIC, &res);
switch (res.tv_nsec) {
case 1000: // microseconds
case 10000000: // milliseconds
// cases ...
}
编辑:
重读原作者的帖子,我意识到这并不能完全回答问题。不过,我还是把它留在这里,因为如果适用于这个问题,它可能是有用的。如果你想让实际的答案上升到顶端,你可以自由地否决这个问题。
https://stackoverflow.com/questions/65791396
复制相似问题