信号处理程序中的取消点?

8
如果一个程序从信号处理器中调用一个被定义为异步信号安全和可取消点的函数会发生什么?POSIX规定了许多函数既是异步信号安全的又是可取消点的。如果信号处理器调用这样的函数并且取消被执行,结果与线程启用异步取消相似,但实际上更糟糕,因为所有取消清理处理程序都将从信号处理器上下文中调用,这些处理程序可能不是异步信号安全的。
在这种情况下,POSIX实际上规定了什么?实现会做什么?我在POSIX中找不到任何禁止在信号处理器中使用取消点的语言,也没有在glibc/nptl源代码中找到相关保护。
1个回答

4

我不知道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);
}

2
除非有更好的答案很快出现,否则我可能会接受你的答案。据我所知,这基本上意味着,除非您的信号处理程序禁用取消或避免调用可能是取消点的任何函数,否则您无法在程序中使用取消。不幸的是,这反过来意味着,在没有调用程序的意识/合作的情况下利用线程的库代码根本不能使用取消(因为调用程序可能已经设置了信号处理程序);任何使用都可能导致竞争条件,其中信号处理程序被取消。 - R.. GitHub STOP HELPING ICE
3
没错 - 严格来说,信号处理程序无法禁用取消操作(pthread函数不是异步信号安全的)。库可以在创建任何线程时阻止所有信号,但这并不理想(特别是在Linux上阻止SIGRTMIN会禁用取消操作...)。我一直认为取消操作很危险 - 除非你确定库是设计用于响应取消操作的,否则不能从可取消的线程调用任何库函数(否则它可能会分配资源、调用取消点函数,然后永远不释放这些资源...) - psmears
1
就我所知,我刚刚在运行Solaris 10的机器上尝试了同样的程序,并得到了相同的结果...我猜这意味着,即使标准中确实有一些安全措施,由于大多数常见的实现不支持它,所以它是无用的 :-/ - psmears
1
你关于阻塞所有信号的评论解决了问题,我认为。通过 pthread_sigmask 阻塞信号不允许阻塞取消操作。如果取消操作是通过信号实现的,则必须对应用程序透明,并且在实践中(至少在 glibc/nptl 上),pthread_sigmask 库函数(以及 sigprocmask 函数在线程化应用程序中根本不需要安全)会默默地拒绝掩码 SIGCANCELSIGSETXID(两者都由实现内部使用)。 - R.. GitHub STOP HELPING ICE
1
因此,希望在没有调用应用程序的合作下内部使用线程和取消的库只需要使用 pthread_sigmask 来阻止所有信号,在调用 pthread_create 之前,并且必须在那时和返回给调用者之间的某个时间恢复原始信号掩码。如果使用取消,还应该在调用可能不支持取消的外部库代码时显式禁用取消。 - R.. GitHub STOP HELPING ICE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接