POSIX 信号并不总是得到处理

POSIX Signals does not get always get handled

我正在尝试通过以下方式设计和实施模拟考试 POSIX 进程和进程间通信技术。模拟 工作方式如下:

  1. 名为 Teacher 的进程将从 从头到尾。

  2. 教师进程使用 fork/exec 系统调用 generate/load 10 个学生 进程。

  3. 每个学生进程:

    一个。等待老师的信号开始考试。

    b。一旦学生收到信号,它将开始考试。

    c。学生进程将调用睡眠系统 API 随机睡眠 在 [10 – 30] 秒之间选择的数字,以模拟 不同进程完成考试的时间不同

    d。一旦该进程的睡眠结束,它将触发一个信号,表明它 已完成考试

  4. 教师进程将继续跟踪学生进程的顺序 已经完成了。

  5. 所有Student进程结束后,Teacher进程会宣布 考试成绩如下,终止。

考试结束 -> 结果是:

ID=4128的同学第一名

ID=4122 学生第二名

ID=4126 的学生获得第三名 ...

我在 c:

中制作了这段代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <math.h> 
#include <time.h>
#include <string.h>
#include <sys/wait.h>
#include<signal.h>

static int child_recieved_sig = 0;
static int parent_recieved_sig = 0;
static int pids[10];
static int pidIndex = 0;

int random_time() {
    // generate random time from 10-30 sec
    srand(time(NULL) + getpid());
    int rand_time = (rand() % 21) + 10; //form [0-20]+10
    return rand_time;
}

//signal sent from the parent to the child to start the exam
void start_signal(int sig) {
    if (sig == SIGUSR1) {
        child_recieved_sig = 1;
    }
}

//signal sent from the child to the parent indicating that the exam is over
void finish_signal(int sig, siginfo_t* info, void* context) {
    if (sig == SIGUSR2) {
        parent_recieved_sig++;
        pids[pidIndex] = info->si_pid;
        pidIndex++;
    }
}

int main() {
    struct sigaction ss = { 0 }; // fs = start signal
    ss.sa_sigaction = start_signal;
    ss.sa_flags = SA_SIGINFO;
    sigaction(SIGUSR1, &ss, NULL);

    struct sigaction fs = { 0 }; // fs = finish signal
    fs.sa_sigaction = finish_signal;
    fs.sa_flags = SA_SIGINFO;
    sigaction(SIGUSR2, &fs, NULL);

    for (int i = 0;i < 10;i++) { //generate 10 children
        pid_t pid = fork();

        if (pid < 0)
            perror("Forking has Failed!");
        else if (pid == 0) { //child process
            while (child_recieved_sig != 1); // wait for signal
            printf("My ID is %d and I started my exam\n", getpid());
            int time_interval = random_time();
            printf("SleepTime: %d<-----------------------\n", time_interval);
            sleep(time_interval);
            kill(getppid(), SIGUSR2);
            printf("Student %d finished exam\n", getpid());
            exit(0);
        } else {
            kill(pid, SIGUSR1);
        }
    }

    while (parent_recieved_sig < 10) usleep(50000);

    printf("parent_received_sig: %d\n", parent_recieved_sig);
    printf("The Exam is Over ==> Results are:\n");
    printf("Student ID=%d Finished First\n", pids[0]);
    printf("Student ID=%d Finished Second\n", pids[1]);
    printf("Student ID=%d Finished Third\n", pids[2]);
    printf("Student ID=%d Finished Forth\n", pids[3]);
    printf("Student ID=%d Finished Fifth\n", pids[4]);
    printf("Student ID=%d Finished Sixth\n", pids[5]);
    printf("Student ID=%d Finished Seventh\n", pids[6]);
    printf("Student ID=%d Finished Eighth\n", pids[7]);
    printf("Student ID=%d Finished Nineth\n", pids[8]);
    printf("Student ID=%d Finished Tenth\n", pids[9]);
}

问题是有时代码可以正常工作并按预期完成工作,而其他时候没有对代码进行任何修改它就会卡在 while look (while (parent_recieved_sig < 10) usleep(50000);) 中,我一直在努力弄清楚原因,但我不能得出结论,我试过打印 parent_recieved_sig 的值,有时它没有达到 10,意味着某些信号没有被处理?

只有实时信号排队。其他的可以合并。有可能发送了两个而只收到了一个。

如另一个答案中所述,非实时信号不会排队,因此如果它们同时发送,您可能会错过其中一些信号(来自OS 观点).

下面是您的示例,为了用 SIGCHLD 的处理替换 SIGUSR2 的处理,稍作修改。 当子进程终止时,此信号会自动发送到其父进程。

有趣的部分是信号处理程序,只要成功,它就会以非阻塞方式 (WNOHANG) 为任何子进程 (-1) 在 waitpid() 上循环。 处理一个 SIGCHLD 可能导致检测到多个子进程同时终止。

EDIT1 如评论中所建议,更直接的解决方案是摆脱 SIGCHLD 的处理并修改 [=19 中的等待循环=] 像这样的功能

    while (parent_recieved_sig < 10) {
        pid_t r=wait(NULL);
        if(r>0) {
            parent_recieved_sig++;
            pids[pidIndex] = r;
            pidIndex++;
        } else {
            perror("wait()");
            exit(1);
        }
    }

EDIT2 正如评论中所建议的,全局 ints 应该是 volatile sig_atomic_ts 以便被检测到(优化器不认为未更改) 在被信号处理程序修改时由主线程执行。

/**
  gcc -std=c99 -o prog_c prog_c.c \
      -pedantic -Wall -Wextra -Wconversion \
      -Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
      -g -O0 -UNDEBUG -fsanitize=address,undefined
**/

#undef __STRICT_ANSI__ 
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <math.h> 
#include <time.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>

// ensure modifications in the signal handlers are detected
static volatile sig_atomic_t child_recieved_sig = 0;
static volatile sig_atomic_t parent_recieved_sig = 0;
static volatile sig_atomic_t pids[10];
static volatile sig_atomic_t pidIndex = 0;

int random_time(void) {
    // generate random time from 10-30 sec
    srand((unsigned int)time(NULL) + (unsigned int)getpid());
    int rand_time = (rand() % 5) + 2; //form [0-20]+10
    return rand_time;
}

//signal sent from the parent to the child to start the exam
void start_signal(int sig, siginfo_t* info, void* context) {
    (void)info;
    (void)context;
    if (sig == SIGUSR1) {
        child_recieved_sig = 1;
    }
}

//signal sent from the child to the parent indicating that the exam is over
void finish_signal(int sig, siginfo_t* info, void* context) {
    (void)info;
    (void)context;
    if (sig == SIGCHLD) {
        for(;;) {
            pid_t r=waitpid(-1, NULL, WNOHANG);
            if(r<=0) break;
            parent_recieved_sig++;
            pids[pidIndex] = r;
            pidIndex++;
        }
    }
}

int main(void) {
    struct sigaction ss = { 0 }; // fs = start signal
    ss.sa_sigaction = start_signal;
    ss.sa_flags = SA_SIGINFO;
    sigaction(SIGUSR1, &ss, NULL);

    struct sigaction fs = { 0 }; // fs = finish signal
    fs.sa_sigaction = finish_signal;
    fs.sa_flags = SA_SIGINFO;
    sigaction(SIGCHLD, &fs, NULL);

    for (int i = 0;i < 10;i++) { //generate 10 children
        pid_t pid = fork();

        if (pid < 0)
            perror("Forking has Failed!");
        else if (pid == 0) { //child process
            while (child_recieved_sig != 1); // wait for signal
            printf("My ID is %d and I started my exam\n", getpid());
            int time_interval = random_time();
            printf("SleepTime: %d<-----------------------\n", time_interval);
            sleep((unsigned int)time_interval);
            // kill(getppid(), SIGUSR2);
            printf("Student %d finished exam\n", getpid());
            exit(0);
        } else {
            kill(pid, SIGUSR1);
        }
    }

    while (parent_recieved_sig < 10) usleep(50000);

    printf("parent_received_sig: %d\n", parent_recieved_sig);
    printf("The Exam is Over ==> Results are:\n");
    printf("Student ID=%d Finished First\n", pids[0]);
    printf("Student ID=%d Finished Second\n", pids[1]);
    printf("Student ID=%d Finished Third\n", pids[2]);
    printf("Student ID=%d Finished Forth\n", pids[3]);
    printf("Student ID=%d Finished Fifth\n", pids[4]);
    printf("Student ID=%d Finished Sixth\n", pids[5]);
    printf("Student ID=%d Finished Seventh\n", pids[6]);
    printf("Student ID=%d Finished Eighth\n", pids[7]);
    printf("Student ID=%d Finished Nineth\n", pids[8]);
    printf("Student ID=%d Finished Tenth\n", pids[9]);
}

正如@ZanLynx 在 中观察到的那样,常规信号不会排队,因此如果您依赖于从 children 接收信号来识别它们已经完成,那么您就有丢失信号的风险靠得很近。

此外,由于您是从 10 个不同进程的 21 种可能延迟中进行选择,因此您面临信号靠在一起到达的风险相当高,因此实际上很可能存在具有完全相同延迟的对(尽管这本身并不能保证信号丢失)。您可以通过使用更精细的延迟粒度来降低这种可能性,您可以使用 nanosleep() 而不是 sleep() 来实现。这不能确保您不会错过信号,但会降低这种可能性。

然而,更好的方法是使用更可靠的机制来识别 children 何时结束。正如评论中所建议的,最自然的替代方法是 wait() 函数。