【问题标题】:Cancellation points in signal handlers?信号处理程序中的取消点?
【发布时间】:2011-05-13 11:35:21
【问题描述】:

如果程序从信号处理程序调用作为取消点的函数会发生什么? POSIX 将许多函数指定为异步信号安全点和取消点。如果信号处理程序调用了这样的函数并执行了取消操作,则结果与线程启用异步取消时会发生的情况非常相似 - 实际上更糟,因为所有取消清理处理程序,可能不是异步信号 -安全,将从信号处理程序上下文中调用。

在这种情况下,POSIX 实际指定了什么,实现实际做了什么?我在 POSIX 中找不到任何禁止对信号处理程序中的取消点采取行动的语言,在 glibc/nptl 源代码中也找不到任何此类保护。

【问题讨论】:

    标签: c pthreads posix signals cancellation


    【解决方案1】:

    我不知道POSIX竟然敢提这个话题,但我没有做过详尽的搜索。

    对 gcc/nptl 系统的一些简短实验表明,正如我所怀疑的那样,我想你也这样做了,NPTL 中没有这种保护 - 确实从信号处理程序上下文中调用了取消处理程序。

    下面的程序(为hackiness等道歉)显示以下输出:

    Signal handler called
    Sent cancellation
    Cleanup called
    In sighandler
    

    ...表示:

    • 信号处理程序被调用
    • 另一个线程然后调用pthread_cancel()
    • 然后调用取消处理程序,而信号处理程序没有完成

    这是程序:

    #include <stdio.h>
    #include <pthread.h>
    #include <signal.h>
    #include <string.h>
    #include <unistd.h>
    #include <assert.h>
    
    pthread_t mainthread;
    
    int in_sighandler = 0;
    
    void
    cleanup (void *arg)
    {
        write(1, "Cleanup called\n", strlen("Cleanup called\n"));
        if (in_sighandler) {
            write(1, "In sighandler\n", strlen("In sighandler\n"));
        } else {
            write(1, "Not in sighandler\n", strlen("In sighandler\n"));
        }
    }
    
    
    void
    sighandler (int sig, siginfo_t *siginfo, void *arg)
    {
        in_sighandler = 1;
        write(1,"Signal handler called\n", strlen("Signal handler called\n"));  // write() is a CP
        usleep(3000000); // usleep() is a CP; not strictly async-signal-safe but happens to be so in Linux
        write(1, "Signal handler exit\n", strlen("Signal handler exit\n"));
        in_sighandler = 0;
    }
    
    void *
    thread (void *arg)
    {
        sleep(1);
        pthread_kill(mainthread, SIGUSR1);
        usleep(500000);
        pthread_cancel(mainthread);
        printf("Sent cancellation\n");
        return (NULL);
    }
    
    int
    main (int argc, char **argv)
    {
        int rc;
        struct sigaction sa;
        pthread_t threadid;
    
        mainthread = pthread_self();
    
        // Set up a signal handler to test its cancellation properties
        sa.sa_sigaction = &sighandler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_SIGINFO;
        rc = sigaction(SIGUSR1, &sa, NULL);
        assert(rc == 0);
    
        // Set up a thread to send us signals and cancel us
        rc = pthread_create(&threadid, NULL, &thread, NULL);
        assert(rc == 0);
    
        // Set up cleanup handlers and loop forever
        pthread_cleanup_push(&cleanup, NULL);
        while (1) {
            sleep(60);
        }
        pthread_cleanup_pop(0);
        return (0);
    }
    

    【讨论】:

    • 短期内没有更好的答案,我可能会接受你的。据我所知,这基本上只是意味着您不能在程序中使用取消,除非您的信号处理程序自己禁用取消或避免调用任何可能是取消点的函数。不幸的是,这反过来又意味着在调用程序没有意识到/协作的情况下使用线程的库代码根本不能使用取消(因为调用程序可能具有设置信号处理程序);任何使用都可能导致信号处理程序被取消的竞争条件。
    • 是的,没错——虽然严格来说信号处理程序不能禁用取消(pthread 函数不是异步信号安全的)。库可以在它创建的任何线程中阻塞 all 信号,但这并不理想(特别是因为在 Linux 上阻塞 SIGRTMIN 会禁用取消......)。我一直认为取消是危险的——你不能从可取消线程调用任何库函数,除非你确定库的设计考虑到了取消(否则它可能会分配资源,调用取消点函数,然后永远不会释放这些资源...)
    • 对于它的价值,我刚刚在运行 Solaris 10 的机器上尝试了相同的程序,结果相同......我想这意味着,即使结果确实有一些东西使这个安全的标准,它是无用的,因为最常见的实现不支持它:-/
    • 我认为您关于阻止所有信号的评论解决了问题。不允许通过pthread_sigmask 阻止信号阻止取消。如果取消是通过信号实现的,那必须对应用程序透明,并且在实践中(至少在 glibc/nptl 上),pthread_sigmask 库函数(和 sigprocmask 函数在线程应用程序)默默地拒绝屏蔽 SIGCANCELSIGSETXID(均由实现在内部使用)。
    • 因此,如果一个库希望在内部使用线程和取消,而无需调用应用程序的合作,只需在调用pthread_create之前使用pthread_sigmask阻塞所有信号,并且必须恢复原始信号掩码从那时到返回给调用者之间的某个时间。如果使用取消,它还应该在调用可能无法感知取消的外部库代码时显式禁用取消。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-21
    • 2013-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-23
    相关资源
    最近更新 更多