Signal handler won't see global variable
Asked Answered
D

2

6

Here's the problem: this program should receive input from stdin and count the bytes inserted; the SIGUSR1 signal whill stop the main program and will print on file standard error how many bytes have been copied when I send the SIGUSR1.

That's how my teacher wants me to do this: in one terminal run

cat /dev/zero | ./cpinout | cat >/dev/null

while from a second terminal send signals with

kill -USR1 xxxx

where xxxx is the pid of cpinout.

I updated my previous code:

/* cpinout.c */

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

#define BUF_SIZE 1024   

volatile sig_atomic_t countbyte = 0;
volatile sig_atomic_t sigcount = 0;

/* my_handler: gestore di signal */
static void sighandler(int signum) {
    if(sigcount != 0)
        fprintf(stderr, "Interrupted after %d byte.\n", sigcount);
    sigcount = countbyte;    
}

int main(void) {
  
    int c;
    char buffer[BUF_SIZE];
    struct sigaction action;

    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    action.sa_handler = sighandler;
    if(sigaction(SIGUSR1, &action, NULL) == -1) {
        fprintf(stderr, "sigusr: sigaction\n");
        exit(1);
    }
    while( c=getc(stdin) != EOF ) {
        countbyte++;
        fputc(c, stdout);
    }
    return(0);
}
Diaphanous answered 19/3, 2013 at 13:28 Comment(1)
Won't work even with sigaction >.< I still get millions of bytes copiedDiaphanous
B
2


EDIT

In the comments you mentioned that you are running the command as:

cat /dev/zero | ./namefile | cat >/dev/null

The behaviour is actually fine. /dev/zero is an endless stream of zeros, which are being sent to the program. So it's counting them up very quickly. When you interrupt, it stops and you're left with a large number.


The problem may be related to the fact that the signal handler may be called while the global variable is being updated (if this takes more than one instruction). However, the GNU documentation states that it's safe to assume that an int is always atomic on a POSIX system.

The only other possibility I can think of is that you're calling fputc in the loop, with printf in the handler (it should however be safe to call printf in a handler if it's not being called by the program). Try removing fputc from the loop to see if it resolves the problem.

EDIT:

This appears to explain the problem. This relates to the kind of functions that are safe to call from within a signal handler:

Functions can also be nonreentrant if they use static data structures for their internal bookkeeping. The most obvious examples of such functions are the members of the stdio library (printf(), scanf(), and so on), which update internal data structures for buffered I/O. Thus, when using printf() from within a signal handler, we may sometimes see strange output—or even a program crash or data corruption— if the handler interrupts the main program in the middle of executing a call to printf() or another stdio function. (The Linux Programming Interface)

Your program is interrupting a stdio function, which seems to fit this perfectly.


Here's an alternative approach:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

int countbyte = 0;  // for main program
int sigcount = 0;   // for signal handler

/* my_handler: signal handler */
static void sighandler(int signum)
{
   sigcount = countbyte;
}

int main(void)
{ 
   int c;
   struct sigaction sigact;

   sigemptyset(&sigact.sa_mask);
   sigact.sa_flags = 0;
   sigact.sa_handler = sighandler;
   sigaction(SIGUSR1, &sigact, NULL);
   while ((c = getc(stdin)) != EOF) {
      countbyte++;
      fputc(c, stdout);
   }
   if (sigcount != 0) {
      printf("Interrupted after %d bytes\n", sigcount);
   }

   return 0;
}
Bever answered 19/3, 2013 at 14:26 Comment(17)
Could you give an hint why you think the differences between signal and sigaction are pertinent here?Hellraiser
@Hellraiser - the documentation states 'The behavior of signal() varies across UNIX versions, and has also varied historically across different versions of Linux. Avoid its use: use sigaction(2) instead ... The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN. The semantics when using signal() to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.Bever
Yes, there are differences in behavior between unix variants, I know of none related to the reported problem.Hellraiser
@AndreaMazzocchi - what's your operating system?Bever
Linux Xubuntu 12.10 3.5.0-23-genericDiaphanous
@AndreaMazzocchi - reason found, I think.Bever
@teppic, I'll try this later and I'll find an alternative to the fprintf when I want to print the number of bytes copied before receiving the SIGUSR1Diaphanous
@AndreaMazzocchi - you can set a global variable in your handler function to the number of bytes copied, and then access that when you return from the handler.Bever
Sorry @Bever I didn't understand what you said :(Diaphanous
@AndreaMazzocchi - have two global variables. One is updated in your main program in the loop, and the other you set from the handler function, e.g. countsignal = countbyte;, then when you return, if count_signal != 0, you know it was interrupted.Bever
@teppic, the real problem I can't figure out is that the exercise asks me to launch this program like this: cat /dev/zero | ./namefile | cat >/dev/null and because of /dev/null your last printf won't print anything on stdout...I don't really know what to do :(Diaphanous
@AndreaMazzocchi - you could write to stderr, that would still workBever
Indeed! But, again, the number of bytes read doesn't match (I get millions of bytes)Diaphanous
@AndreaMazzocchi - update your question with your current codeBever
@AndreaMazzocchi - actually, I see why. I'll update the answer.Bever
Thanks @teppic, but why does he want me to use this endless stream of zeroes? do i have to count also the zeroes from cat /dev/zero? I don't get it XD Anyway the program seems to do what it should doDiaphanous
@AndreaMazzocchi - yep, the program will be counting the number of zeroes. You'll have to ask him why :) Your program is ok.Bever
H
4

Signals may only write volatile sig_atomic_t variables according to the C89 and POSIX 7 standards:

the behavior is undefined if the signal handler refers to any object [CX] [Option Start] other than errno [Option End] with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t

Implementations often offer more, but I doubt that using non-volatile global variables or printf is something provided by yours.

Hellraiser answered 19/3, 2013 at 14:21 Comment(0)
B
2


EDIT

In the comments you mentioned that you are running the command as:

cat /dev/zero | ./namefile | cat >/dev/null

The behaviour is actually fine. /dev/zero is an endless stream of zeros, which are being sent to the program. So it's counting them up very quickly. When you interrupt, it stops and you're left with a large number.


The problem may be related to the fact that the signal handler may be called while the global variable is being updated (if this takes more than one instruction). However, the GNU documentation states that it's safe to assume that an int is always atomic on a POSIX system.

The only other possibility I can think of is that you're calling fputc in the loop, with printf in the handler (it should however be safe to call printf in a handler if it's not being called by the program). Try removing fputc from the loop to see if it resolves the problem.

EDIT:

This appears to explain the problem. This relates to the kind of functions that are safe to call from within a signal handler:

Functions can also be nonreentrant if they use static data structures for their internal bookkeeping. The most obvious examples of such functions are the members of the stdio library (printf(), scanf(), and so on), which update internal data structures for buffered I/O. Thus, when using printf() from within a signal handler, we may sometimes see strange output—or even a program crash or data corruption— if the handler interrupts the main program in the middle of executing a call to printf() or another stdio function. (The Linux Programming Interface)

Your program is interrupting a stdio function, which seems to fit this perfectly.


Here's an alternative approach:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

int countbyte = 0;  // for main program
int sigcount = 0;   // for signal handler

/* my_handler: signal handler */
static void sighandler(int signum)
{
   sigcount = countbyte;
}

int main(void)
{ 
   int c;
   struct sigaction sigact;

   sigemptyset(&sigact.sa_mask);
   sigact.sa_flags = 0;
   sigact.sa_handler = sighandler;
   sigaction(SIGUSR1, &sigact, NULL);
   while ((c = getc(stdin)) != EOF) {
      countbyte++;
      fputc(c, stdout);
   }
   if (sigcount != 0) {
      printf("Interrupted after %d bytes\n", sigcount);
   }

   return 0;
}
Bever answered 19/3, 2013 at 14:26 Comment(17)
Could you give an hint why you think the differences between signal and sigaction are pertinent here?Hellraiser
@Hellraiser - the documentation states 'The behavior of signal() varies across UNIX versions, and has also varied historically across different versions of Linux. Avoid its use: use sigaction(2) instead ... The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN. The semantics when using signal() to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.Bever
Yes, there are differences in behavior between unix variants, I know of none related to the reported problem.Hellraiser
@AndreaMazzocchi - what's your operating system?Bever
Linux Xubuntu 12.10 3.5.0-23-genericDiaphanous
@AndreaMazzocchi - reason found, I think.Bever
@teppic, I'll try this later and I'll find an alternative to the fprintf when I want to print the number of bytes copied before receiving the SIGUSR1Diaphanous
@AndreaMazzocchi - you can set a global variable in your handler function to the number of bytes copied, and then access that when you return from the handler.Bever
Sorry @Bever I didn't understand what you said :(Diaphanous
@AndreaMazzocchi - have two global variables. One is updated in your main program in the loop, and the other you set from the handler function, e.g. countsignal = countbyte;, then when you return, if count_signal != 0, you know it was interrupted.Bever
@teppic, the real problem I can't figure out is that the exercise asks me to launch this program like this: cat /dev/zero | ./namefile | cat >/dev/null and because of /dev/null your last printf won't print anything on stdout...I don't really know what to do :(Diaphanous
@AndreaMazzocchi - you could write to stderr, that would still workBever
Indeed! But, again, the number of bytes read doesn't match (I get millions of bytes)Diaphanous
@AndreaMazzocchi - update your question with your current codeBever
@AndreaMazzocchi - actually, I see why. I'll update the answer.Bever
Thanks @teppic, but why does he want me to use this endless stream of zeroes? do i have to count also the zeroes from cat /dev/zero? I don't get it XD Anyway the program seems to do what it should doDiaphanous
@AndreaMazzocchi - yep, the program will be counting the number of zeroes. You'll have to ask him why :) Your program is ok.Bever

© 2022 - 2024 — McMap. All rights reserved.