首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在linux中编写健壮的计时器

在linux中编写健壮的计时器
EN

Stack Overflow用户
提问于 2021-01-19 12:19:36
回答 2查看 215关注 0票数 0

我想为嵌入式linux应用程序实现一个健壮的定时器。这样做的目的是控制函数的执行时间,如果它们花费的时间太长,则生成一个中断来停止函数的循环。

我在网上到处搜索,第一条建议是使用clock()函数。

使用clock()函数的解决方案可以是:

代码语言:javascript
运行
复制
#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内核定时器,如下所示:

代码语言:javascript
运行
复制
#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一起使用,则不会出现溢出问题:

代码语言:javascript
运行
复制
#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的情况,最大时钟值是最大值。如上所述:

代码语言:javascript
运行
复制
elapsed_time = clock()*1000/CLOCKS_PER_SEC - initial_time;

让我们将clock()*1000/CLOCKS_PER_SEC重命名为x。如果存在溢出,则从理论上说是theoric_x > MAX,但由于存在溢出,x = theoric_x - MAX (希望是明确的':D)。因此:

代码语言:javascript
运行
复制
elapsed_time = (theoric_x - MAX) - initial_time;

它可以写成:

代码语言:javascript
运行
复制
elapsed_time = (theoric_x - initial_time) - MAX;

这相当于:elapsed_time = (theoric_x - initial_time),因为减法最大值就像回到相同的值(它的工作方式类似于模)。这是好的,当theoric_x低于initial_time + MAX,如果我们完成,经过的时间将重置。

我希望这已经足够清楚了。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-01-19 16:29:25

但谷歌告诉我,我们只能为每个信号设置一个处理程序,我不知道我的linux板中的其他处理器是否使用SIG_RTMIN。

不,它是每个信号每个进程的一个处理程序。

也就是说,在您自己的程序中为SIGRTMIN设置一个信号处理程序不会干扰任何其他进程的SIGRTMIN处理程序。类似地,创建计时器也不会影响任何其他进程的定时器。你所需要担心的就是你自己的过程。

(从技术上讲,只有有限数量的计时器可用,所以您不希望在一个进程中创建数百个定时器。)

如果进程中只有一个线程,请考虑以下超时方案:

代码语言:javascript
运行
复制
// 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堆,在时间等待()中等待下一个最快的超时,或者在条件变量上更新指示超时列表/数组/堆的信号。

票数 0
EN

Stack Overflow用户

发布于 2021-01-19 13:36:09

POSIX定义了clock_gettime。Linux也有它的扩展

函数clock_gettime()clock_settime()检索并设置指定时钟时钟的时间。

您可以简单地执行以下操作:

代码语言:javascript
运行
复制
#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指定的分辨率。有时这仅仅是微秒,甚至仅仅是毫秒。确保检查值并进行相应的调整。

代码语言:javascript
运行
复制
struct timespec res;
clock_getres(CLOCK_MONOTONIC, &res);
switch (res.tv_nsec) {
    case 1000: // microseconds
    case 10000000: // milliseconds
    // cases ...
}

编辑:

重读原作者的帖子,我意识到这并不能完全回答问题。不过,我还是把它留在这里,因为如果适用于这个问题,它可能是有用的。如果你想让实际的答案上升到顶端,你可以自由地否决这个问题。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65791396

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档