信号处理程序不处理信号

时间:2018-11-12 10:48:55

标签: c linux process signals fork

我有这份作业,应该编写一个生成孩子的C程序。父级打开文件,并通过读取每一行,打印行号和行内容然后倒带该文件来永久循环。

子进程以随机间隔将SIGUSR1信号发送给父进程。为了创建这些间隔,我选择让孩子入睡。

当收到第一个信号时,父母会跳过打印语句。收到另一个SIGUSR1信号时,它将再次开始打印。这样一直进行到发送了随机数的信号为止。然后,它将忽略五个信号。忽略五个信号后,父进程将打印接收到的信号数,并在接收到另一个信号后退出。

我的问题是从子进程发送信号时程序崩溃。父进程具有信号处理程序,但从未使用过。我犯了什么错误?

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void checkArguments(int nbrOfArg);
static void sig_exit(int signo);
static void sig_handler(int signo);

int sigcount, n;

int main(int argc, char *argv[]){
    int interval, i, linecount = 1;
    char buffer[1024];
    FILE *f;
    sigcount = 0;
    signal(SIGUSR1, sig_handler);
    srand(time(NULL)); // generate random numbers   
    n = rand() % 20 + 10;
    if(fork() != 0){ //parent process
        printf("%d\n", getpid()); //debugging
        checkArguments(argc);
        f = fopen(argv[1], "r"); //open file
        if(f == NULL){
            printf("Error opening file\n");
            exit(EXIT_FAILURE);
        }
        if(sigcount < n){ //n is random number of signals sent that starts/stops printing
            signal(SIGUSR1, sig_handler); //set signal handler
            do {
                while(!feof(f)){// infinite printing loop
                    fgets(buffer, 1024, f);
                    if(sigcount % 2 == 0){ //stop printing at odd number of signals received
                        printf("%d %s", linecount, buffer);
                    }   
                    linecount++;
                }
                rewind(f);
                linecount = 1;  
            } while(linecount == 1); //infinite loop
        } else if(sigcount < (n + 6)) {
            signal(SIGUSR1, SIG_IGN);   // ignore signal
        } else {
            printf("%d signals received", sigcount);
            signal(SIGUSR1, sig_exit); // terminate parent process
        }   
    } else {
        for(i = 0; i < n; i++){
            interval = rand() % 10 + 1; //send signals at random interval
            sigcount++; //number of signals sent
            printf("Sending signal %d from %d to %d\n", sigcount, getpid(), getppid()); //debugging
            kill(getppid(), SIGUSR1); //send signal
            printf("Child sleeping for %d seconds\n", interval); //debugging
            sleep(interval); //let child sleep after sending signal
        }
    }
}

/** Checks so command line argument is valid **/
void checkArguments(int nbrOfArg){
    int k;
    if(nbrOfArg != 2){
        printf("Wrong number of arguments");
        exit(-1);
    }
}

void sig_handler(int signo){
    if(signo == SIGUSR1){
        printf("Receivied SIGUSR1 signal\n");   
    } else printf("Error: received undefined signal\n");

}

void sig_exit(int signo){
    if(signo == SIGUSR1){
        printf("Received SIGUSR1 signal\n");
        exit(SIGUSR1);  
    } else printf("Error: received undefined signal\n");
}

2 个答案:

答案 0 :(得分:2)

除了注释中指出的要点外,您还应该查看设置信号处理程序的位置,它是在调用fork()并启动子进程之后,因此您所拥有的是父级和子级之间的竞争条件。孩子:

         fork()
      /         \
 Parent          Child
 CheckArguments  Send signal     
 Open file
 Create Handler

因此,在父级注册其处理程序之前,孩子启动并发送信号。您可以通过在孩子发送第一个信号之前为其添加睡眠来检查这是否是问题。

此外,信号的行为在Unix版本之间也有所不同,当我在计算机上尝试您的代码时,捕获到第一个信号,但此后没有一个。这是因为在我的机器上,信号处理程序在运行后会被卸载,因此您需要将其作为信号处理程序的一部分重新启用。

void sig_handler(int signo){
    if(signo == SIGUSR1)
    {
        printf("Receivied SIGUSR1 signal\n");
    } 
    else {
        printf("Error: received undefined signal\n");
    }
    signal(SIGUSR1, sig_handler);
}

我还将考虑更改为使用sigaction(而不是signal(),它可以使您更好地控制信号处理。

答案 1 :(得分:2)

使用signal(),不同的系统表现不同。在运行macOS的Mac上(10.14.1 Mojave,但它也适用于其他版本),使用signal()的原始代码可以正常工作-有很多陷阱,但是信号处理有效。在运行Ubuntu 18.04 LTS(在同一Mac上托管)的VM中,使用signal()的代码无法正常工作,因为(如comments中所述),在以下情况下,信号处理将重置为默认值:在进入信号处理程序之前捕获信号。这就建立了比赛条件。这两种不同的行为都符合标准C — macOS提供了“可靠的信号”,而Linux没有。

但是,一切并没有丢失; Linux和macOS都具有sigaction(),可以实现很好的控制—可以用来模拟signal()的任何一种行为。另请参见What is the difference between sigaction() and signal()?

还有一些其他问题需要解决。您应该在分叉之前验证参数并(请检查是否可以)打开文件;您应该报告错误。子代应记录其原始父代的PID,以便如果父代去世,则可以尝试向其发送信号并收到通知,告知其失败。当原始父对象去世时,父PID会切换到PID 1,init过程,基本上会忽略信号。

我解决了feof()的问题-没有理由在循环的控制条件下使用feof()(请参见while (!feof(file)) is always wrong!)。而是测试基本的I / O功能。 (Ubuntu上的C库标记了fgets()函数,以便必须使用返回值。请注意编译器的警告。)

下面的代码减慢了主打印循环的速度,因此它每秒处理4行,而不是全倾斜运行。 macOS和Linux都具有nanosleep(); Linux没有使usleep()可用,尽管它具有更简单(但功能不那么强大)的界面。

以下代码将父级使用的sigcount与子级使用的i分开,以对接收和发送的信号进行计数。我还假定了C99或更高版本的支持,并将变量声明移到了它们使用的地方。

按照编写的方式,代码永远不会进入忽略SIGUSR1的状态,更不用说触发错误的状态了。子级(现在)发送了足够的信号(n *= 2;发送了父级“期望”的两倍的信号),但是父级仍然停留在if (sigcount < n)的原始代码中。请注意,如果忽略这些信号,则无法计数。代码的那部分需要认真的修改。收到适当数量的信号后,您应该退出文件读取循环,然后简单地计算接下来的五个信号(不要忽略它们),然后设置sig_exit处理程序。以下代码未实现此功能。

我没有尝试通过在信号处理程序中调用printf()来解决此问题。它似乎并没有在代码中引起麻烦(我也不希望这样),但是总的来说这很危险。有关更多详细信息,请参见How to avoid using printf() in signal handlers?

#define _XOPEN_SOURCE 700允许定义POSIX函数等,即使编译选项需要-std=c11(禁用大多数扩展名)也是如此。程序源位于sig41.c中,因此程序为sig41。该代码可以使用(GCC 8.2.0)干净地编译:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
>     sig41.c -o sig41
$

伴随其他一些小的更改(例如,stderr上的报告错误),此代码可在Ubuntu和macOS上使用:

#define _XOPEN_SOURCE 700
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

static void sig_exit(int signo);
static void sig_handler(int signo);

static int sigcount, n;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s filename\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    FILE *f = fopen(argv[1], "r");
    if (f == NULL)
    {
        fprintf(stderr, "Error opening file %s for reading\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    struct sigaction sa = { 0 };
    sa.sa_handler = sig_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGUSR1, &sa, 0);

    srand(time(NULL));
    n = rand() % 20 + 10;
    int pid = fork();
    if (pid < 0)
    {
        fprintf(stderr, "failed to fork!\n");
        exit(EXIT_FAILURE);
    }
    else if (pid != 0)
    {
        printf("%d\n", getpid());
        if (sigcount < n)
        {
            int linecount = 1;
            while (linecount == 1)
            {
                char buffer[1024];
                while (fgets(buffer, 1024, f))
                {
                    if (sigcount % 2 == 0)
                    {
                        printf("%d %s", linecount, buffer);
                    }
                    linecount++;
                    // nap time = quarter second
                    struct timespec nap = { .tv_sec = 0, .tv_nsec = 250000000 };
                    nanosleep(&nap, NULL);
                }
                rewind(f);
                linecount = 1;
            }
        }
        else if (sigcount < (n + 6))
        {
            printf("Going into SIG_IGN mode\n");
            sa.sa_handler = SIG_IGN;
            sigaction(SIGUSR1, &sa, 0);
        }
        else
        {
            printf("%d of %d signals received - sig_exit mode\n", sigcount, n);
            sa.sa_handler = sig_exit;
            sigaction(SIGUSR1, &sa, 0);
        }
    }
    else
    {
        fclose(f);
        int pid = getpid();
        int ppid = getppid();
        n *= 2;                     // Child needs to send more signals
        for (int i = 0; i < n; i++)
        {
            int interval = rand() % 10 + 1;
            printf("Sending signal %d of %d from %d to %d\n", i + 1, n, pid, ppid);
            if (kill(ppid, SIGUSR1) != 0)
            {
                fprintf(stderr, "Child failed to signal parent - exiting\n");
                exit(1);
            }
            printf("Child sleeping for %d seconds\n", interval);
            sleep(interval);
        }
    }
}

static void sig_handler(int signo)
{
    sigcount++;
    if (signo == SIGUSR1)
        printf("Received SIGUSR1 signal %d of %d\n", sigcount, n);
    else
        printf("Error: received undefined signal\n");
}

static void sig_exit(int signo)
{
    if (signo == SIGUSR1)
    {
        fprintf(stderr, "Received SIGUSR1 signal\n");
        exit(SIGUSR1);
    }
    else
        printf("Error: received undefined signal\n");
}

要做好这项工作,要做好一点困难。我最终中断了程序以停止它。

$ ./sig41 sig41.c
3247
1 #define _XOPEN_SOURCE 700
Sending signal 1 of 30 from 3248 to 3247
Child sleeping for 7 seconds
Received SIGUSR1 signal 1 of 15
Sending signal 2 of 30 from 3248 to 3247
Child sleeping for 7 seconds
Received SIGUSR1 signal 2 of 15
30     sa.sa_flags = 0;
31     sigemptyset(&sa.sa_mask);
…
56                     }
57                     linecount++;
Sending signal 3 of 30 from 3248 to 3247
Child sleeping for 1 seconds
Received SIGUSR1 signal 3 of 15
Sending signal 4 of 30 from 3248 to 3247
Child sleeping for 4 seconds
Received SIGUSR1 signal 4 of 15
62                 rewind(f);
63                 linecount = 1;
…
76             sigaction(SIGUSR1, &sa, 0);
77         }
Sending signal 5 of 30 from 3248 to 3247
Child sleeping for 2 seconds
Received SIGUSR1 signal 5 of 15
Sending signal 6 of 30 from 3248 to 3247
Child sleeping for 3 seconds
Received SIGUSR1 signal 6 of 15
86         {
87             int interval = rand() % 10 + 1;
…
96         }
97     }
Sending signal 7 of 30 from 3248 to 3247
Child sleeping for 7 seconds
Received SIGUSR1 signal 7 of 15
Sending signal 8 of 30 from 3248 to 3247
Child sleeping for 10 seconds
Received SIGUSR1 signal 8 of 15
8 static void sig_exit(int signo);
9 static void sig_handler(int signo);
…
46         {
47             int linecount = 1;
Sending signal 9 of 30 from 3248 to 3247
Child sleeping for 5 seconds
Received SIGUSR1 signal 9 of 15
Sending signal 10 of 30 from 3248 to 3247
Child sleeping for 8 seconds
Received SIGUSR1 signal 10 of 15
68             printf("Going into SIG_IGN mode\n");
69             sa.sa_handler = SIG_IGN;
…
98 }
99 
Sending signal 11 of 30 from 3248 to 3247
Child sleeping for 9 seconds
Received SIGUSR1 signal 11 of 15
Sending signal 12 of 30 from 3248 to 3247
Child sleeping for 4 seconds
Received SIGUSR1 signal 12 of 15
18         exit(EXIT_FAILURE);
19     }
…
32     sigaction(SIGUSR1, &sa, 0);
33 
Sending signal 13 of 30 from 3248 to 3247
Child sleeping for 6 seconds
Received SIGUSR1 signal 13 of 15
Sending signal 14 of 30 from 3248 to 3247
Child sleeping for 6 seconds
Received SIGUSR1 signal 14 of 15
58                     // nap time = quarter second
59                     struct timespec nap = { .tv_sec = 0, .tv_nsec = 250000000 };
…
80     {
81         fclose(f);
Sending signal 15 of 30 from 3248 to 3247
Child sleeping for 7 seconds
Received SIGUSR1 signal 15 of 15
Sending signal 16 of 30 from 3248 to 3247
Child sleeping for 8 seconds
Received SIGUSR1 signal 16 of 15
110 {
111     if (signo == SIGUSR1)
…
22     if (f == NULL)
23     {
Sending signal 17 of 30 from 3248 to 3247
Child sleeping for 1 seconds
Received SIGUSR1 signal 17 of 15
Sending signal 18 of 30 from 3248 to 3247
Child sleeping for 6 seconds
Received SIGUSR1 signal 18 of 15
28     struct sigaction sa = { 0 };
29     sa.sa_handler = sig_handler;
…
^C
$

如果您处理输出,则可以看到输出暂停了给定的行数-如果孩子睡眠1秒钟,则省略4条输出行。