全球变量的Linux计时器

时间:2012-06-16 16:53:01

标签: c timer locking

我在互联网上找到了下面的代码,我试图理解Linux计时器是如何工作的,无论如何,正如你在下面看到的那样,counter1是全局var,如果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;
   }
}

2 个答案:

答案 0 :(得分: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;
 }

答案 1 :(得分:1)

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

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

如果我们添加锁会怎么样?现在代替加载,增量,存储,我们得到锁定,加载,增量,存储,解锁。但是如果信号在我们处于锁定和解锁之间时触发,则OS会将我们直接跳入处理程序 - 它不会让我们的代码先解锁。当处理程序试图锁定时,main()仍然持有锁,所以我们死锁。灾难!

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