使用 c 安排警报

Posted

技术标签:

【中文标题】使用 c 安排警报【英文标题】:Schedule alarm using c 【发布时间】:2020-03-18 13:08:51 【问题描述】:

我们的任务是开发一个创建子进程的应用程序。然后父进程等待 3 秒(我们不允许在父进程中使用sleep)并发送一个SIGUSR2 信号,然后是一个SIGUSR1 信号。之后,父母会定期(3 秒)向孩子发送SIGUSR1 信号,直到它终止。在子终止时,它会打印 Parent done 并退出。我们应该使用alarm()pause() 来实现这种行为。

子进程应该阻塞信号SIGUSR2 13 秒。收到SIGUSR1 后,它应该打印Received SIGUSR1。收到SIGUSR2 后,它会打印Child done 并退出。我的实现方法如下:

请注意SIGUSR2 信号只发送一次,直到现在我还没有实现,每次都与SIGUSR1 信号一起发送。

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

static pid_t chld;

void init_signal(struct sigaction* action, void (*handler)(int));
void init_sigset(sigset_t* set, int signum);
void bind_signal(struct sigaction* action, int n, ...);
void par_signal(int signum);
void chld_signal(int signum);

int main()
    chld = fork();
    struct sigaction action;

    if (chld == -1)
        fprintf(stderr, "Failed to create child process.\n");
        return EXIT_FAILURE;
     else if (chld == 0)
        init_signal(&action, &chld_signal);
        bind_signal(&action, 3, SIGUSR1, SIGUSR2, SIGALRM);        

        sigset_t sig;
        init_sigset(&sig, SIGUSR2);

        sigprocmask(SIG_BLOCK, &sig, NULL);
        alarm(13);
        pause();
    
    init_signal(&action, &par_signal);
    bind_signal(&action, 2, SIGALRM, SIGCHLD);

    alarm(3);
    pause();


void init_signal(struct sigaction* action, void (*handler)(int))
    action->sa_flags = 0;
    action->sa_handler = handler;

    sigemptyset(&action->sa_mask);


void init_sigset(sigset_t* set, int signum)
    sigemptyset(set);
    sigaddset(set, signum);


void bind_signal(struct sigaction* action, int n, ...)
    va_list args;
    va_start(args, n);

    for (int i = 0; i < n; i++)
        int signum = va_arg(args, int);
        sigaction(signum, action, NULL);
    
    va_end(args);


void par_signal(int signum)
    if (signum == SIGALRM)
        kill(chld, SIGUSR2);
        kill(chld, SIGUSR1);
        alarm(3);
        pause();
     else if (signum == SIGCHLD)
        printf("Parent done\n");
        exit(EXIT_SUCCESS);
    


void chld_signal(int signum)
    if (signum == SIGALRM)
        sigset_t sig;
        init_sigset(&sig, SIGUSR2);

        sigprocmask(SIG_UNBLOCK, &sig, NULL);
        pause();
     else if (signum == SIGUSR1)
        printf("Received SIGUSR1\n");
        pause();
     else if (signum == SIGUSR2)
        printf("Child done\n");
        exit(EXIT_SUCCESS);
    

它的工作原理是,只有父进程的第一个信号被子进程接收。我认为这与我对pausealarm 的使用有关。你知道我做错了什么吗?

可以找到练习任务表的链接here(任务 3)。

【问题讨论】:

我很好奇,你是说:“之后,父母会定期(3 秒)向孩子发送 SIGUSR1 信号,直到它终止” - 然后你说:“收到 SIGUSR2 后,[子进程] 打印 Child done 并退出。” - 如果父进程在开始时只发送一次 SIGUSR2,然后继续发送 SIGUSR1,这怎么可能?父母是否应该在任意秒数后的某个时间发送 SIGUSR2? 根据我们的任务表,SIGUSR2 信号只能在 3 秒后发送一次。孩子阻止了它,因此它不会立即收到,而是在 13 秒后才收到。我暂时没有正确实现这一点,因为父母还不断向孩子发送SIGUSR2(这应该没有任何区别,因为它被阻止了)。 @ThomasThaler 您应该edit 您的问题并在那里添加所有说明或要求的信息,而不是编写 cmets。我建议信号处理程序不应该做的不仅仅是设置volatile sig_atomic_t 类型的变量。在任何情况下,信号处理程序都不得调用任何非异步信号安全的函数。 printf 不是。见man signal-safety。从信号处理程序调用pause 可能会有问题。触发信号处理程序的信号通常会被阻塞,直到您从信号处理程序返回。 @Bodo 我添加了练习任务表的链接以及我在 cmets 中提供的信息 【参考方案1】:

在信号处理程序中调用pause 是有问题的,因为在这种情况下,刚刚触发信号处理程序的信号会被阻塞,直到您从信号处理程序返回。这意味着该过程将不再对此信号作出反应。信号处理器只能被其他信号触发。

请参阅下面的系统调用跟踪及其解释。为了清楚起见,我用@PARENT@ 替换了父母的PID,用#CHILD## 替换了孩子的PID。

$ strace -f -r -e trace=signal,process ./proc
     0.000000 execve("./proc", ["./proc"], 0x7ffc146ba360 /* 74 vars */) = 0
     0.001428 arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd3c958fc0) = -1 EINVAL (Invalid argument)
     0.002302 arch_prctl(ARCH_SET_FS, 0x7f264e40a540) = 0
# clone is fork()
     0.001086 clone(strace: Process #CHILD## attached
child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f264e40a810) = #CHILD##
# parent establishes SIGALRM handler
[pid @PARENT@]      0.000791 rt_sigaction(SIGALRM, sa_handler=0x564709a5859c, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470,  <unfinished ...>
# child establishes SIGUSR1 handler
[pid #CHILD##]      0.000087 rt_sigaction(SIGUSR1, sa_handler=0x564709a58605, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470,  <unfinished ...>
[pid @PARENT@]      0.000132 <... rt_sigaction resumed> NULL, 8) = 0
[pid #CHILD##]      0.000056 <... rt_sigaction resumed> NULL, 8) = 0
# child establishes SIGUSR2 handler
[pid #CHILD##]      0.000072 rt_sigaction(SIGUSR2, sa_handler=0x564709a58605, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470, NULL, 8) = 0
# child establishes SIGALRM handler
[pid #CHILD##]      0.000141 rt_sigaction(SIGALRM, sa_handler=0x564709a58605, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470, NULL, 8) = 0
# parent establishes SIGCHLD handler
[pid @PARENT@]      0.000389 rt_sigaction(SIGCHLD, sa_handler=0x564709a5859c, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f264e25e470,  <unfinished ...>
# child blocks SIGUSR2
[pid #CHILD##]      0.000132 rt_sigprocmask(SIG_BLOCK, [USR2], NULL, 8) = 0
# child waits for signal
[pid #CHILD##]      0.000204 pause( <unfinished ...>
[pid @PARENT@]      0.000837 <... rt_sigaction resumed> NULL, 8) = 0
# parent waits for signal
[pid @PARENT@]      0.000133 pause()       = ? ERESTARTNOHAND (To be restarted if no handler)
# parent receives SIGALRM
[pid @PARENT@]      3.000317 --- SIGALRM si_signo=SIGALRM, si_code=SI_KERNEL ---
# parent sends SIGUSR2 and SIGUSR1 to child
[pid @PARENT@]      0.000106 kill(#CHILD##, SIGUSR2) = 0
[pid @PARENT@]      0.000116 kill(#CHILD##, SIGUSR1) = 0
[pid #CHILD##]      0.000144 <... pause resumed> ) = ? ERESTARTNOHAND (To be restarted if no handler)
# child receives SIGUSR1 (SIGUSR2 is blocked)
[pid #CHILD##]      0.000166 --- SIGUSR1 si_signo=SIGUSR1, si_code=SI_USER, si_pid=@PARENT@, si_uid=1000 ---
# parent waits for signal again. As this "pause" was called from a signal handler triggered by SIGALRM, this signal is now blocked.
[pid @PARENT@]      0.000227 pause( <unfinished ...>
# child waits for signal. As this "pause" was called from a signal handler triggered by SIGUSR1, this signal is now blocked.
[pid #CHILD##]      0.000566 pause()       = ? ERESTARTNOHAND (To be restarted if no handler)
# child receives SIGALRM
[pid #CHILD##]      9.997742 --- SIGALRM si_signo=SIGALRM, si_code=SI_KERNEL ---
# child unblocks SIGUSR2
[pid #CHILD##]      0.000102 rt_sigprocmask(SIG_UNBLOCK, [USR2], NULL, 8) = 0
# child now receives SIGUSR2 which was already sent by the parent
[pid #CHILD##]      0.000122 --- SIGUSR2 si_signo=SIGUSR2, si_code=SI_USER, si_pid=@PARENT@, si_uid=1000 ---
# child exits
[pid #CHILD##]      0.000183 exit_group(0) = ?
[pid #CHILD##]      0.000204 +++ exited with 0 +++
     0.000081 <... pause resumed> )     = ? ERESTARTNOHAND (To be restarted if no handler)
# parent receives SIGCHLD from child
     0.000056 --- SIGCHLD si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=#CHILD##, si_uid=1000, si_status=0, si_utime=0, si_stime=0 ---
Parent done
# parent exits
     0.000563 exit_group(0)             = ?
     0.000301 +++ exited with 0 +++

我建议从信号处理程序中设置标志并在主函数中进行决策和等待。像这样的

static volatile sig_atomic_t childSigUsr1 = 0;
static volatile sig_atomic_t childSigUsr2 = 0;
static volatile sig_atomic_t childSigAlrm = 0;

void chld_signal(int signum) 
    if (signum == SIGALRM) 
        childSigAlrm = 1;
     else if (signum == SIGUSR1) 
        childSigUsr1 = 1;
     else if (signum == SIGUSR2) 
        childSigUsr2 = 1;
    


int main() 
    chld = fork();
    struct sigaction action;

    if (chld == -1) 
        fprintf(stderr, "Failed to create child process.\n");
        return EXIT_FAILURE;
     else if (chld == 0) 
        init_signal(&action, &chld_signal);
        bind_signal(&action, 3, SIGUSR1, SIGUSR2, SIGALRM);        

        sigset_t sig;
        init_sigset(&sig, SIGUSR2);

        sigprocmask(SIG_BLOCK, &sig, NULL);
        alarm(13);
        while(1) 
            pause();
            if(childSigUsr1) 
                childSigUsr1 = 0;
                printf("Received SIGUSR1\n");
            
            if(childSigUsr2) 
                childSigUsr2 = 0;
                printf("Child done\n");
                exit(EXIT_SUCCESS);
            
            if(childSigAlrm) 
                sigset_t sig;
                init_sigset(&sig, SIGUSR2);

                sigprocmask(SIG_UNBLOCK, &sig, NULL);
            
        
    
    init_signal(&action, &par_signal);
    bind_signal(&action, 2, SIGALRM, SIGCHLD);

    while(1) 
        alarm(3);
        pause();
        /* handle and reset signal flags similar to child */
    

【讨论】:

以上是关于使用 c 安排警报的主要内容,如果未能解决你的问题,请参考以下文章

Javascript,.NET:显示和使用 OK/Cancel 警报

警报管理器未正确安排警报

如何安排无需用户干预即可唤醒 iOS 应用程序的警报

尽管声明它们是目标 c ios 8,但不允许显示警报

无法在 Android 上使用 AlarmManager 安排通知(使用 Qt)

打开和关闭警报 ios