【问题标题】:Handling 'intterupted system call' error when using timer使用计时器时处理“中断的系统调用”错误
【发布时间】:2012-07-25 11:51:02
【问题描述】:

我正在编写一个应用程序,它使用计时器以固定采样率 (200Hz) 进行一些数据采集和处理。 该应用程序就像一个服务器并在后台运行。它应该可以通过 UDP 被其他进程或其他机器控制。

为此,我使用 timer_create() API 定期生成 SIGUSR1 并调用执行获取和处理的处理程序。

配置定时器的代码如下(为了清楚起见减去错误检查):

sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGUSR1, &sa, NULL);
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
sev.sigev_value.sival_ptr = &timerid;
timer_create(CLOCK_REALTIME, &sev, &timerid);
timer_settime(...)

当收到来自 UDP 的“开始”命令时,会调用上面的代码。为了检查命令,我的主程序中有一个无限循环,它调用 recvfrom() 系统调用。

问题是,当收到“启动”命令,然后计时器正确启动并运行(使用上面的代码)时,我收到一个“系统调用中断”错误(EINTR)定时器发送的 SIGUSR1 信号中断 recvfrom() 调用。如果我检查这个特定的错误代码并忽略它,我最终会在调用 recvfrom() 时收到“连接被拒绝”错误。

所以这里是我的问题:

  1. 如何解决这个“中断的系统调用”错误,因为它似乎 忽略它并重新执行 recvfrom() 不起作用?
  2. 为什么我在尝试大约 20 次后收到“连接被拒绝”错误?
  3. 我觉得使用 SIGEV_THREAD 可能是一种解决方案,据我了解,创建一个新线程(如 phread_create)而不生成信号。我说的对吗?
  4. 信号编号在这里重要吗?使用实时信号有什么好处吗?
  5. 有没有其他方法可以做我想做的事:让后台循环检查来自 UDP 的命令和实时定期任务?

还有一个额外的问题:

  • 在处理程序中进行数据采集和处理是否安全,或者我应该使用信号量机制来唤醒执行此操作的线程?

解决方案: 正如答案和 cmets 中所建议的那样,使用 SA_RESTART 似乎可以解决主要问题。

解决方案 2: 在 SIGEV_SIGNAL 上使用 SIGEV_THREAD 也可以。我在某处读到使用 SIGEV_THREAD 可能需要比 SIGEV_SIGNAL 更多的资源。但是我没有看到关于任务时间的显着差异。

【问题讨论】:

  • 我不完全理解你的情况,但是避免在信号处理程序中做“实际工作”的一种优雅方法是打开一个管道给自己,让信号处理程序写入管道,并让主选择循环(我猜你有一个)从管道中读取并完成真正的工作。我不知道这是否会满足您的时间需求。至少管道避免了并发问题,因为从管道读取和写入是原子的(并且它们之间存在上下文切换)它还避免了连续切换 sigmask 的需要。
  • 我也不确定我是否理解了您的所有问题,但您可以尝试将SA_RESTART 添加到sa_flags 吗?
  • wildplasser> 我没有任何选择循环,因为我只有一件事要做:使用 recvfrom 检查通过 udp 套接字传入的新数据
  • ninjalj> 如下所述,我会试试的,明天第一件事。抱歉,如果我不清楚,哪些部分给您带来了困扰?

标签: c linux system


【解决方案1】:

定时器倾向于使用 SIGALARM 实现。

信号接收,包括 SIGALARM,往往会导致长时间运行的系统调用提前返回,并且 EINTR 在 errno 中。

SA_RESTART 是解决此问题的一种方法,因此因收到信号而中断的系统调用将自动重新启动。另一种方法是从系统调用的 errno 中检查 EINTR,并在收到 EINTR 时重新启动它们。

当然,使用 read() 和 write(),您不能只是重新启动,您需要从中断的地方继续。这就是为什么它们返回传输的数据长度。

【讨论】:

  • OP 没有使用alarm(2),他使用的是timer_create,因此他处理了SIGUSR1。
  • 正如吉尔所说,我使用的是 timer_create 而不是警报。无论如何,我会尝试使用 SA_RESTART 标志。
【解决方案2】:

鉴于您使用的是 Linux,我会选择使用 timerfd_create。

这样您就可以只使用select(2)、poll(2) 或epoll(7) 来处理计时器事件,而无需在主循环中使用信号处理程序。

至于EINTR(中断的系统调用),只需重新启动被中断的特定系统调用即可正确处理。

【讨论】:

  • 很遗憾timerfd_create 在我的平台上不可用。
【解决方案3】:

重新启动中断的系统调用是对EINTR的正确响应。您的“连接被拒绝”问题是一个不相关的错误 - 在 UDP 套接字上,它表明在该套接字上发送的先前数据包被目的地拒绝(通过 ICMP 消息通知)。

【讨论】:

    【解决方案4】:

    问题 5:您对消息和实时周期线程的使用完全没问题。但是,我建议您完全避免使用计时器,因为它们使用信号。我自己也遇到过这个问题,并最终用一个简单的clock_nanosleep() 替换了计时器,它使用TIMER_ABSTIME 更新时间以保持所需的速率(即将周期添加到绝对时间)。结果是更简单的代码,没有更多的信号问题,并且比基于信号的计时器更准确的计时器。顺便说一句,您应该在处理程序中测量计时器的周期,以确保它足够准确。我使用计时器的经验是 8 年前,因此可能会解决准确性问题。然而,信号的其他问题是信号本身固有的,因此无法“解决”——只能解决。

    此外,我认为从处理程序获取数据没有问题,它肯定会减少检索数据的延迟。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多