Linux signal 函数的常用功能分析

先来看一批比较老的signal function:

#include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t sigset(int sig, sighandler_t disp);

       int sighold(int sig);

       int sigrelse(int sig);

       int sigignore(int sig);
他们的具体描述请参见linux manual: http://www.kernel.org/doc/man-pages/online/pages/man3/sigset.3.html

他们的作用很简单的可以从他们的名字中看出来,这里就不加叙述了。

现在的一些新的程序已经不在使用上面这些函数,取而代之的是我们接下来要看的这几个函数:

#include <signal.h>

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
sigaction这个函数功能取代了上面的sigset(),用来设置在收到特定信号时所采取的动作。

参数介绍:

    第一个参数 int signum 用来说明这次设定是针对那一个信号。这里要注意的是,不能用这个函数对 SIGKILLSIGSTOP进行设置。

    后两个参数使用相同的类型,从名字上可以看出,第二个参数 const struct sigaction *act 是调用这个函数时要设置的行为描述,而第三个参数 struct sigaction *oldact 则用来返回在调用这个函数之前原先对于这个信号的行为描述。

当第二参数为NULL时,sigaction可用来查询旧的action设置。当第二个和第三个参数都为NULL时,则此函数用来检查当前设备是否支持signum这个信号。

下面就来分析一下这个结构体的成员:

struct sigaction {
//指定新的信号处理函数,可以是SIG_DEF(表示使用默认处理函数),或是SIG_IGN(用来表示忽略次信号)
void (*sa_handler)(int);
//当sa_flags被设置为SA_SIGINFO时,信号处理函数将不再使用sa_handler,而是使用此项sa_sigaction。
//里面储存的是这次信号的详细信息,包括发送信号的pid,用户id等。
    void     (*sa_sigaction)(int, siginfo_t *, void *);
//指定在执行信号处理函数时将被阻塞的信号,触发此动作的信号默认被阻塞,除非在sa_flags中设置SA_NODEFER
sigset_t sa_mask;
//指定此次信号动作的flag,除了上面提到的SA_SIGINFO和SA_NODEFER外其他详见linux manual
int sa_flags;
//已废弃,不再使用
    void     (*sa_restorer)(void);
}; 值得注意的是通过fork()产生的子进程将会继承父进程的信号处理行为,而在调用execve后,信号处理行为将被重置为默认。对于忽略掉的信号处理则不做改变。如果SIGFPE(算术错误),SIGILL(错误指令),SIGSEGV(内存访问错误)被忽视,该程序的执行将变的不可知。


说完sigaction,我们来看下一个取代最上面被废弃了的后三个函数的函数:

 #include <signal.h>
       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
此函数用来查看或者设置信号集传递被当前线程阻塞的情况。

参数how有三个可选值:SIG_BLOCK设置阻塞的信号集为原先的和参数set中的并集,SIG_UNBLOCK从原先的阻塞信号集中移除参数set中的信号,SIG_SETMASK废弃原先的阻塞集改为参数set中的信号集。

对于后面两个参数,起意义和sigaction的后面两参数类似。而对他们的操作则有另一些函数来完成:

 #include <signal.h>
    int sigemptyset(sigset_t *set); //初始化信号集为空
    int sigfillset(sigset_t *set);  //初始化信号集为所有信号
    int sigaddset(sigset_t *set, int signum);  //向信号集中添加信号
    int sigdelset(sigset_t *set, int signum);  //从信号集中删除信号
    int sigismember(const sigset_t *set, int signum);  //检查信号是否在信号集中

需要注意的是此函数不适用于多线程程序,若要在多线程程序中使用则请使用:

       #include <signal.h>

       int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

       Compile and link with -pthread.
此函数和sigprocmask基本相同,而它则适用于多线程的程序。


除了设置signal handler之外,我们还看是一个线程挂起来等待一个信号的到来:

       #include <signal.h>

        int sigwait(const sigset_t *set, int *sig);
当调用这个函数时,当前线程会被挂起,知道set信号集中的一个信号到来时才会再次唤醒这个线程,然后将唤醒线程的信号写入sig中。


上面是信号接收端的一些设置函数,下面介绍的是信号发出端的常用的一些函数

首先这个是对这整个进程而言的一个信号发出函数:

       #include <sys/types.h>
      #include <signal.h>

       int kill(pid_t pid, int sig);
相对这个函数,可能在命令行下的kill命令大家会更熟悉一些,但命令行下的kill默认是杀死一个进程即发送一个TERM信号给pid所代表的线程。当然也可以在kill指定要发送的信号,那么其功能也就等同于这个函数了。

有几种特殊情况简单介绍一下:

pid为0,则是发送sig给所有和调用kill的进程在同一个进程组里的进程。

pid为-1,则发送sig给所有调用kill的进程有权限发送信号的出1号进程(init)之外的其他进程。

pid < -1,则发送sig给进程号为绝对值pid的进程。

sig = 0,此时,这个函数用来检查pid表示的进程或者进程组是否存在。

上面这个是发送信号给其他进程,而下面这个则是发送信号给自己:

       #include <signal.h>

       int raise(int sig);
具体用法同kill就不加叙述。

下面接触线程间的信号传送:

       #include <signal.h>

       int pthread_kill(pthread_t thread, int sig);

       Compile and link with -pthread.
这个函数的用法和kill相似,只是将进程号变为了线程号。另外,把sig设成0同样可以利用该函数的错误检查机制来作为线程是否存在的检查工具。


特别注意:

1. 不要在signal handler中使用printf函数,因为printf不属于asyc-signal-safe function,在signal handler中使用的函数有限制,不然会出现无法预料的情况,详细可使用的函数请见linux manual:http://www.kernel.org/doc/man-pages/online/pages/man7/signal.7.html

2. 如果对一个线程使用pthread_kill,而这个线程又设定了这个signal的handler,并且对于这个signal的处理是“stop”,“continue”,或者“terminate”,那么,这个处理将会影响到整个进程。


上面是我对linux signal的理解,如果有错误的地方欢迎指正。

另外,我在学习过程中有几个疑问却一直无法想到很好的解释,希望各位能提提自己的看法。先谢谢。我的主要问题如下:

1. 当一个线程mask了一些signal之后,在给这个signal设置handler,再发送signal给这个线程,它的handler是否还会被触发?(后面的实验证明会)

2. 当一个线程mask了一些signal之后,让这个线程sigwait已经被mask了的signal,这个sigwait是否会捕捉到发给这个线程的signal?(同样被证明可以被捕捉)

然后的疑问是这里都会触发响应动作是因为设置handler或者sigwait的同时会修改该线程被block的信号集吗??

3. 再有就是同一个信号即设置了handler又同时被该线程sigwait,谁会被触发?(似乎和信号发送的快慢时机有关)

我的例程:()

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <string.h>

#define THREAD_NUM 5
int hdler_cnt1 = 0;
int hdler_cnt2 = 0;

static void signal_handler1(int sig) {
//    printf("handler1: get sig(%d)\n", sig);
    hdler_cnt1++;
}

static void signal_handler2(int sig) {
//    printf("handler2: get sig(%d)\n", sig);
    hdler_cnt2++;
}


static void *thread_fn1(void *arg) {
    struct sigaction act;
    sigset_t set;
    int s, sig;

    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGUSR2);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    memset(&act, 0, sizeof(act));
    act.sa_handler = signal_handler1;
    s = sigaction(SIGUSR1, &act, NULL);
    if (0 != s)
        printf("thread1: sigaction failed!\n");

    s = sigwait(&set, &sig);
    if (0 != s)
        printf("thread1: sigwait failed!\n");

    printf("thread1: after sigwait, get sig=%d.\n", sig);
    while(1);
}

static void *thread_fn2 (void *arg) {
    struct sigaction act;
    sigset_t *set = (sigset_t *)arg;
    int s, sig;

    s = pthread_sigmask(SIG_UNBLOCK, set, NULL);
    if (0 != s)
        printf("thread2: sigmask failed!\n");

    memset(&act, 0, sizeof(act));
    act.sa_handler = signal_handler2;
    s = sigaction(SIGUSR2, &act, NULL);
    if (0 != s)
        printf("thread2: sigaction failed!\n");

    sig = 0;
    while (sig != SIGUSR2) {
        s = sigwait(set, &sig);
        if (0 != s)
            printf("thread2: sigwait failed!\n");
        printf("thread2: after sigwait, get sig=%d.\n", sig);
    }
    printf("thread2: after while sig.\n");
    while(1);
}

int main(int argc, char *argv[]) {
    pthread_t thread1, thread2;
    sigset_t set;
    int s;
    char input = 0;

    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGUSR2);
    s = pthread_sigmask(SIG_BLOCK, &set, NULL);
    if (0 != s) {
        printf("main: sigmask failed!\n");
    }

    s = pthread_create(&thread1, NULL, &thread_fn1, (void *)&set);
    if (0 != s)
        printf("main: pthread create failed!\n");

    s = pthread_create(&thread2, NULL, &thread_fn2, (void *)&set);
    if (0 != s)
        printf("main: pthread create failed!\n");

    input = 0;
    sleep(3);   //wait the thread to be ready
    while (4 > input) {
        s = input % 2;
        if (0 == s) {
            printf("main: SIGUSR1\n");
            pthread_kill(thread1, SIGUSR1);
            pthread_kill(thread2, SIGUSR1);
        }
        else if (1 == s) {
            printf("main: SIGUSR2\n");
            pthread_kill(thread1, SIGUSR2);
            pthread_kill(thread2, SIGUSR2);
        }
        input++;
        sleep(1);   //if here do not sleep, some signal will be lost, may because sigal when the thread in the handler.
    }
    printf("cnt1=%d, cnt2=%d\n", hdler_cnt1, hdler_cnt2);

    getchar();
    exit(0);
}
按上面的代码编译执行的结果:

$ ./a.out
main: SIGUSR1                                          //main线程向两个子线程发送SIGUSR1信号
thread1: after sigwait, get sig=10.          //子线程1的sigwait捕捉到结束wait,进入while(1)
thread2: after sigwait, get sig=10.          //子线程2的sigwait捕捉到结束wait,但因为等到的不是所要的sig所以进入下一次sigwait
main: SIGUSR2                                         //mainthread向两个线程发送SIGUSR2信号,这时子线程1已经不在等待信号而且没有设置SIGUSR2的handler。所以子线程1没有动作
thread2: after sigwait, get sig=12.          //子线程2等到了SIGUSR2再次结束sigwait,并且不再sigwait进入while1
thread2: after while sig.
main: SIGUSR1                                          //之后main线程连续两个signal分别被子线程1和2的handler不活并且计数。所以两个cnt都是1.
main: SIGUSR2
cnt1=1, cnt2=1

从上面的结果来看sigwait会先于handler得到signal,而且根据后面的分析似乎没有什么问题。

当注释掉上面程序红色的sleep再次编译执行的结果如下:

$ ./a.out
main: SIGUSR1
main: SIGUSR2
thread1: after sigwait, get sig=10.
main: SIGUSR1
main: SIGUSR2
cnt1=1, cnt2=1
thread2: after sigwait, get sig=10.

从结果上看子线程1的处理似乎没有影响,但是子线程2似乎漏掉了一次SIGUSR2等捕获,按照上一次运行结果的分析,如果sigwait真的优先于handler,那么handler处理过程mask掉一个SIGUSR2这样的解释好像不太合理。而且多次执行的结果都是一样,子线程而遗漏一个SIGUSR2。

这里请教各位,对于这个运行结果有没有什么好的解释。多谢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章