APUE——信号与线程

APUE原文
Linux 多线程应用中如何编写安全的信号处理函数
LinuxThread与NPTL
多线程下慎用sigwait
linux中使用信号–sigwait()和pthread_sigmask()

1. 信号与线程三个函数

利用sigwait,sigwaitinfo,pthread_sigmask函数可以解决如下问题

  1. 以线程同步的方式,处理异步信号
  2. 在指定的线程中处理信号
    信号与线程主要涉及如下三个函数:
#include <signal.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);
返回值:若成功则返回0,否则返回错误编号

set参数指出了线程等待的信号集,signop指向的整数将作为返回值,表明信号编号。

#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
返回值:若成功则返回0,否则返回错误编号
  1. Linux 多线程应用中,每个线程可以通过调用 pthread_sigmask() 设置本线程的信号掩码。一般情况下,被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错如SIGSEGV;另外不能被忽略处理的信号 SIGKILL 和 SIGSTOP 也无法被阻塞。
  2. 利用线程信号屏蔽集的继承关系,新建的线程会继承主线程的信号屏蔽集!当一个线程调用 pthread_create() 创建新的线程时,此线程的信号掩码会被新创建的线程继承。
#include <signal.h>
int pthread_kill(pthread_t thread, int signo);
返回值:若成功则返回0,否则返回错误编号

2. sigwait函数分析

在这里插入图片描述

  1. sigwait函数是将队列中处于pending状态的信号(已经产生,并未被处理的信号,可能被阻塞)移除,但是并不解除原来信号的屏蔽状态
  2. 由于进程中,如果接收到一个信号,如果运行的线程没有屏蔽该信号,如果signal或者sigaction已经设置了信号处理函数,那么该线程会执行信号处理函数,那么调用sigwait函数的线程并不能获得pending状态的信号,所以在调用sigwait前需要所有线程均屏蔽该信号
  3. 在一个进程中,signal和sigaction是所有线程共享的信号处理函数,所以如果在一个线程中使用signal,那么会改变所有线程的信号处理,所以多线程中尽量使用sigwait等
  4. sigpromask用在单线程的场景,如果是多线程则必须使用pthread_sigmask
  5. 正常情况下sigwait不会被信号打断,如果已经将该信号加入信号屏蔽集,但是也有例外,比如SIGSEGV,SIGKILL 和 SIGSTOP等!

3. linux多线程模型分析

多线程下信号处理函数建立流程;

  1. 主线程设置信号掩码,阻碍希望同步处理的信号;主线程的信号掩码会被其创建的线程继承;
  2. 主线程创建信号处理线程;信号处理线程将希望同步处理的信号集设为 sigwait()的第一个参数。
  3. 主线程创建工作线程。
    在这里插入图片描述

3.1 例子1

主线程设置信号掩码阻碍 SIGUSR1 和 SIGRTMIN 两个信号,然后创建信号处理线程sigmgr_thread()和五个工作线程 worker_thread()。主线程每隔10秒调用 kill() 对本进程发送 SIGUSR1 和 SIGTRMIN 信号。信号处理线程 sigmgr_thread()在接收到信号时会调用信号处理函数 sig_handler()。

#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
  
void sig_handler(int signum)
{
    static int j = 0;
    static int k = 0;
    pthread_t  sig_ppid = pthread_self(); 
    // used to show which thread the signal is handled in.
    
    if (signum == SIGUSR1) {
        printf("thread %d, receive SIGUSR1 No. %d\n", sig_ppid, j);
        j++;
    //SIGRTMIN should not be considered constants from userland, 
    //there is compile error when use switch case
    } else if (signum == SIGRTMIN) {
        printf("thread %d, receive SIGRTMIN No. %d\n", sig_ppid, k);
        k++;
    }
}
 
void* worker_thread()
{
    pthread_t  ppid = pthread_self();
    pthread_detach(ppid);
    while (1) {
        printf("I'm thread %d, I'm alive\n", ppid);
        sleep(10);
    }
}
 
void* sigmgr_thread()
{
    sigset_t   waitset, oset;
    siginfo_t  info;
    int        rc;
    pthread_t  ppid = pthread_self();
 
    pthread_detach(ppid);
 
    sigemptyset(&waitset);
    sigaddset(&waitset, SIGRTMIN);
    sigaddset(&waitset, SIGUSR1);
 
    while (1)  {
        rc = sigwaitinfo(&waitset, &info);
        if (rc != -1) {
            printf("sigwaitinfo() fetch the signal - %d\n", rc);
            sig_handler(info.si_signo);
        } else {
            printf("sigwaitinfo() returned err: %d; %s\n", errno, strerror(errno));
        }
    }
}
 
 
int main()
{
    sigset_t bset, oset;
    int             i;
    pid_t           pid = getpid();
    pthread_t       ppid;
     
 
    // Block SIGRTMIN and SIGUSR1 which will be handled in 
    //dedicated thread sigmgr_thread()
    // Newly created threads will inherit the pthread mask from its creator 
    sigemptyset(&bset);
    sigaddset(&bset, SIGRTMIN);
    sigaddset(&bset, SIGUSR1);
    if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)
        printf("!! Set pthread mask failed\n");
     
    // Create the dedicated thread sigmgr_thread() which will handle 
    // SIGUSR1 and SIGRTMIN synchronously
    pthread_create(&ppid, NULL, sigmgr_thread, NULL);
   
    // Create 5 worker threads, which will inherit the thread mask of
    // the creator main thread
    for (i = 0; i < 5; i++) {
        pthread_create(&ppid, NULL, worker_thread, NULL);
    }
 
    // send out 50 SIGUSR1 and SIGRTMIN signals
    for (i = 0; i < 50; i++) {
        kill(pid, SIGUSR1);
        printf("main thread, send SIGUSR1 No. %d\n", i);
        kill(pid, SIGRTMIN);
        printf("main thread, send SIGRTMIN No. %d\n", i);
        sleep(10);
    }
    exit (0);
}

3.2 例子2


    #include<stdio.h>
    #include<pthread.h>
    #include<signal.h>
 
    static void sig_alrm(int signo);
    static void sig_init(int signo);
    int
    main()
    {
        sigset_t set;
        int sig;
        sigemptyset(&set);
        sigaddset(&set, SIGALRM);
        pthread_sigmask(SIG_SETMASK, &set, NULL);//阻塞SIGALRM信号
        
        signal(SIGALRM, sig_alrm);
        signal(SIGINT, sig_init);
        sigwait(&set, &sig);//sigwait只是从未决队列中删除该信号,并不改变信号掩码。也就是,当sigwait函数返回,它监听的信号依旧被阻塞。
        switch(sig){
        case 14:
            printf("sigwait, receive signal SIGALRM\n");
            /*do the job when catch the sigwait*/
            break;
        default:
            break;
        }
        sigdelset(&set, SIGALRM);
        pthread_sigmask(SIG_SETMASK, &set, NULL);
 
        for(;;)
        {}
        return 0;
    }
 
    static void
    sig_alrm(int signo)
    {
        printf("after sigwait, catch SIGALRM\n");
        fflush(stdout);
        return ;
    }
 
    static void
    sig_init(int signo)
    {
        printf("catch SIGINT\n");
        return ;
    }

注意,sigdelset(&set, SIGALRM);
pthread_sigmask(SIG_SETMASK, &set, NULL);需要解除屏蔽,因为sigwait不解除屏蔽!

3.3 例子3

#include "apue.h"
#include <pthread.h>

int        quitflag;    /* set nonzero by thread */
sigset_t    mask;

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t    wait = PTHREAD_COND_INITIALIZER;

void *
thr_fn(void *arg)
{
    int err, signo;
    
    for(; ;)
    {
        err = sigwait(&mask, &signo);
        if(err != 0)
            err_exit(err, "sigwait failed");
        switch(signo)
        {
            case SIGINT:
                printf("\ninterrupt\n");
                break;
        
            case SIGQUIT:
                pthread_mutex_lock(&lock);
                quitflag = 1;
                pthread_mutex_unlock(&lock);
                pthread_cond_signal(&wait);
                return(0);

            default:
                printf("unexpected signal %d\n", signo);
                exit(1);        
        }
    }
}

int 
main(void)
{
    int        err;
    sigset_t    oldmask;
    pthread_t    tid;

    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);
    
    if((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
        err_exit(err, "SIG_BLOCK error");
    
    err = pthread_create(&tid, NULL, thr_fn, 0);
    if(err != 0)
        err_exit(err, "can't create thread");

    pthread_mutex_lock(&lock);
    while(quitflag == 0)
        pthread_cond_wait(&wait, &lock);
    pthread_mutex_unlock(&lock);

    /* SIGQUIT has been caught and is now blocked; do whatever */
    quitflag = 0;
    
    /* reset signal mask which unblocks SIGQUIT */
    if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");
    exit(0);
}
interrupt        键入中断字符
interrupt        再次键入中断字符
interrupt        再一次
                    用结束字符终止

这里并不让信号处理程序中断主控线程,而是由专门的独立控制线程进行信号处理。改动quitflag的值是在互斥量的保护下进行的,这样主控线程不会在调用pthread_cond_signal时错失唤醒调用。在主控线程中使用相同的互斥量来检查标志的值,并且原子地释放互斥量,等待条件的发生。

注意在主线程开始时阻塞SIGINT和SIGQUIT。当创建线程进行信号处理时,新建线程继承了现有的信号屏蔽字。因为sigwait会解除信号的阻塞状态,所以只有一个线程可以用于信号的接收。这使得对主线程进行编码时不必担心来自这些信号的中断。

4.总结

在基于 Linux 的多线程应用中,对于因为程序逻辑需要而产生的信号,可考虑使用同步模型进行处理而对会导致程序运行终止的信号如 SIGSEGV 等,必须按照传统的异步方式使用 signal()、 sigaction()注册信号处理函数进行处理。这两种信号处理模型可根据所处理的信号的不同同时存在一个 Linux 应用中:

  1. 不要在线程的信号掩码中阻塞不能被忽略处理的两个信号 SIGSTOP 和 SIGKILL。
  2. 不要在线程的信号掩码中阻塞 SIGFPE、SIGILL、SIGSEGV、SIGBUS。
  3. 确保 sigwait() 等待的信号集已经被进程中所有的线程阻塞。
  4. 在主线程或其它工作线程产生信号时,必须调用 kill() 将信号发给整个进程,而不能使用 pthread_kill() 发送某个特定的工作线程,否则信号处理线程无法接收到此信号。
  5. 因为 sigwait()使用了串行的方式处理信号的到来,为避免信号的处理存在滞后,或是非实时信号被丢失的情况,处理每个信号的代码应尽量简洁、快速,避免调用会产生阻塞的库函数。

猜你喜欢

转载自blog.csdn.net/weixin_44537992/article/details/106240022