2

我在互联网上找到了下面的代码,我试图了解 Linux 计时器是如何工作的,无论如何,正如您在 counter1 下方看到的那样,它是全局变量,如果 while 正在工作并且计时器关闭并更改会发生什么counter1 的值,我需要锁在那里吗?

// timertst1.c: Simple timer demo program. Use cross-gcc 
// Vers. 1.00 - 21.Oct. 2002
// k.d.walter@t-online.de

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>

// This is a timer handler.
int counter1 = 0;
void timerHandler (int signum)
{
   printf ("timerHandler: counter= %d\n", counter1++);
   fflush (stdout);
}

// This is the one and only main function.

int main (void)
{
   struct sigaction sa;
   struct itimerval timer;

   // Install the timer handler...

   memset (&sa, 0, sizeof (sa));
   sa.sa_handler= &timerHandler;
   sigaction (SIGALRM, &sa, NULL);

   // Configure the timer to expire every 100 msec...

   timer.it_value.tv_sec= 0;            // First timeout
   timer.it_value.tv_usec=  500000;
   timer.it_interval.tv_sec= 0;         // Interval
   timer.it_interval.tv_usec= 500000;

   // Start timer...

   setitimer (ITIMER_REAL, &timer, NULL);   setitimer (ITIMER_REAL, &timer, NULL);
   // Do noting...

   while (1) {
       printf("i'm here waiting to be interuppted = %d\n",counter1);
       //some work;
       counter1++;
       //some other work;
   }
}
4

2 回答 2

2

您还可以考虑使用sig_atomic_t用于从信号处理程序与程序的其余部分进行安全通信。但是,这在您发布的示例中不起作用,因为读取、递增和存储值不是原子操作。

在这里,您可以看到 GLIBC 手册的摘录解释sig_atomic_t

为避免中断对变量的访问的不确定性,您可以使用始终为原子访问的特定数据类型:sig_atomic_t。读取和写入这种数据类型保证在一条指令中发生,因此处理程序无法在访问的“中间”运行。

sig_atomic_t 类型始终是整数数据类型,但它是哪一种,以及它包含多少位,可能因机器而异。

— 数据类型:sig_atomic_t 这是一个整数数据类型。这种类型的对象总是以原子方式访问。

在实践中,您可以假设 int 是原子的。你也可以假设指针类型是原子的;这很方便。这两个假设在 GNU C 库支持的所有机器和我们知道的所有 POSIX 系统上都是正确的。

这里有一个使用示例:

 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>

 /* This flag controls termination of the main loop. */
 volatile sig_atomic_t keep_going = 1;

 /* The signal handler just clears the flag and re-enables itself. */
 void catch_alarm (int sig) {
   keep_going = 0;
   signal (sig, catch_alarm);
 }

 void do_stuff (void) {
   puts ("Doing stuff while waiting for alarm....");
 }

 int main (void) {
   /* Establish a handler for SIGALRM signals. */
   signal (SIGALRM, catch_alarm);

   /* Set an alarm to go off in a little while. */
   alarm (2);

   /* Check the flag once in a while to see when to quit. */
   while (keep_going)
     do_stuff ();

   return EXIT_SUCCESS;
 }
于 2012-06-16T21:16:28.313 回答
1

信号处理程序很危险。操作系统将中断您的程序,无论它在做什么,并运行处理程序;当处理程序返回时,操作系统会将您的程序带回它正在执行的操作。

假设它counter1++被编译为加载、增量和存储。如果在加载和存储之间触发中断,那么指令序列将是加载、加载、递增、递增、存储、存储。变量将增加一,而不是二;灾难!你是对的,这看起来像是一个经典的多线程问题。

如果我们添加一个锁会发生什么?现在不是加载、增量、存储,而是锁定、加载、增量、存储、解锁。但是如果信号在我们处于锁定和解锁之间时触发,操作系统会将我们直接跳转到处理程序中——它不会让我们的代码首先解锁。当处理程序尝试锁定时,main() 仍然持有锁定,所以我们死锁。灾难!

安全的做法是编写您的信号处理程序,以便它只记下必须做的事情,并编写代码来处理在其他地方实际执行的操作。但即使这样也不一定简单 - 如果您的信号处理程序本身被中断怎么办?在这种特殊情况下不是问题,但值得考虑一个兔子洞有多深的例子。编写正确的信号处理程序很难。有一些技巧,例如您可以阻止信号处理程序。但这仍然很难。

于 2012-06-16T18:04:45.543 回答