曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 6932|回复: 3
打印 上一主题 下一主题

[信号] 信号集操作函数

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-7-6 13:49:34 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一、信号集
sigset_t 类型其实是个结构体,定义在 /usr/include/bits/sigset.h 头文件中:
[C++] 纯文本查看 复制代码
typedef __sigset_t  sigset_t
 typedef struct {
         unsigned long int __val[ _SIGSET_NWORDS ];
 }sigset_t;

而 _SIGSET_NWORDS 定义为:
[Plain Text] 纯文本查看 复制代码
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))

因此,_SIGSET_NWORS 的值为 32 。

POSIX 还定义了一系列函数用来操作信号集。在 shell 下输入 man sigsetops 可查看她们的函数原型如下:
#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);
各个函数的含义为:
      sigemptyset : 用来初始化一个信号集,使其不包括任何信号。
      sigfillset : 用来初始化一个信号集,使其保留所有信号。
      sigaddset : 用来向 set 指定的信号集中添加由 signum 指定的信号。
      sigdelset : 用来从 set 指定的信号集中删除由 signum 指定的信号。
      sigismember : 用来测试信号 signum 是否包括在 set 指定的信号集中。
sigemptyset(), sigfillset(), sigaddset(), sigdelset() 这几个函数在执行成功时返回 0 ,失败返回 -1。函数 sigismember() 返回 1 表示测试的信号在信号集中,返回 0 表示测试的信号不在信号集中,出错返回 -1 。

注意所有应用程序在使用信号集前,要对该信号集调用一次 sigemptyset() 或 sigfillset() 以初始化信号集。这是因为 C 语言编译器将不赋初值的外部和静态量都初始化为 0 。

二、信号屏蔽
信号屏蔽又成为信号阻塞,在 shell 下输入 man sigprocmask 可获得信号阻塞的一系列函数的说明:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
int sigsuspend(const sigset_t *mask);
( 1 )sigprocmask() 函数
每个进程都有一个信号屏蔽码,它规定了当前阻塞而不能递送给该进程的信号集。sigprocmask() 可以检测或更改进程的信号屏蔽码。如果 oldset 是非空指针,则该进程之前的信号屏蔽码通过 oldset 返回(相当于一个备份);如果 set 为非空指针,则函数根据参数 how 来修改信号当前的屏蔽码,how 的取值为:
      SIG_BLOCK : 将进程新的信号屏蔽码设置为当前信号屏蔽码和 set 指向信号集的并集。
      SIG_UNBLOCK : 将进程的信号屏蔽码设置为当前信号屏蔽码中删除了 set 所指向信号集,即 set 包含了我们希望解除阻塞的信号。即使对当前信号屏蔽码中不存在的信号使用 SIG_UNBLOCK 也是合法操作。
      SIG_SETMASK : 将进程新的信号屏蔽码设置为 set 所指向的值。
函数执行成功返回 0,有错误时返回 -1,错误代码存入 errno 中。

( 2 )sigpending() 函数
函数 sigpending() 用来获取调用进程因被阻塞而不能递送和当前未决的信号集。该信号通过参数 set 返回。函数执行成功返回 0 ,有错误返回 -1,错误代码存入 errno 中。

测试代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

void my_err(const char *err_string, int line)
{
     fprintf(stderr, "line:%d", line);
     perror(err_string);
     exit(1);
}


void hander_sigint(int signo)
{
     printf("recv SIGINT\n");
}


int main()
{
     sigset_t newmask, oldmask, pendmask;    //定义信号集

     /*安装信号处理函数*/
     if (signal(SIGINT, hander_sigint) == SIG_ERR) {
         my_err("signal", __LINE__);  /*__LINE__ 表示当前__LINE__ 所在的行数*/
     }
     printf("进入睡眠10秒\n");
     sleep(10);
     printf("睡眠醒来\n");

     /*初始化信号集 newmask 并将 SIGINT 添加进去*/
     sigemptyset(&newmask);
     sigaddset(&newmask, SIGINT);

     /*屏蔽信号 SIGINT*/
     if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
         my_err("sigprocmask", __LINE__);
     } else {
         printf("SIGINT blocked\n");
     }

     sleep(10);

     /*获取未决信号队列*/
     if (sigpending(&pendmask) < 0) {
         my_err("sigpending", __LINE__);
     }

     /*检查未决信号队列里是否有 SIGINT*/
     switch (sigismember(&pendmask, SIGINT)) {
         case 0:
         printf("SIGINT is not in pending queue\n");
         case 1:
         printf("SIGINT is in pending queue\n");
         break;
         case -1:
         my_err("sigismember", __LINE__);
         break;
         default:
         break;
     }

     /*解除对 SIGINT 的屏蔽*/
     if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
         my_err("sigprocmask", __LINE__);
     } else {
         printf("SIGINT unblocked\n");
     }

     while(1)
     ;

     return 0;
}
运行及输出
beyes@linux-beyes:~/C> ./sig_mask.exe
进入睡眠10秒
^Crecv SIGINT
睡眠醒来
SIGINT blocked
^C^C^C^C^C^C^C^C^CSIGINT is in pending queue
recv SIGINT
SIGINT unblocked
^\退出
说明
在输出结果中,^C 表示按下了一次 Ctrl + C 的组合键。由输出结果可以分析得:
在程序进入睡眠时(10s),只要接收到信号,它就会马上醒过来,然后进行接收到信号处理,如上面的输出:recv SIGINT。注意到,“睡眠醒来“ 醒来这句话是在 “recv SIGINT” 之后的,这是因为程序醒来后,第一件事是要去处理信号,而并不着急要输出睡眠函数的下面的语句。接着,我们阻塞了 SIGINT 信号,然后又进入 10 秒的睡眠。这时,连续多次输入 ctrl+c 后程序并不会就立马响应这些信号。所以,当用 sigismember() 函数来测试 SIGINT 是否在未决信号队列中,由输出 SIGINT blocked 可见,SIGINT 信号确实已经被阻塞。再接着,我们解除了 SIGINT 信号的屏蔽,用的是 sigprocmask(SIG_SETMASK, &oldmask, NULL),这里 oldmask 是原来屏蔽的信号集(相当于在设置新的信号集前我们对之前的信号集做了一个备份)。当 SIGINT 信号的屏蔽被解除后,程序马上去处理这个信号。再注意到,SIGINT unblocked 提示后于 recv SIGINT 输出,也是因为一旦解除了被屏蔽的信号且未决信号队列中有这个信号,那么程序会立即处理信号函数,而不着急输出提示。为什么只有一次 recv SIGINT  输出呢?这是因为 SIGINT 信号是不可靠信号,当有多个这样的信号时,信号处理函数往往只会被调用一次。

下帖会用一个可靠的实时信号了做一个测试。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2009-7-6 14:20:30 | 只看该作者
实时可靠信号测试
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

void my_err(const char *err_string, int line)
{
        fprintf(stderr, "line:%d", line);
        perror(err_string);
        exit(1);
}


void hander_sigint(int signo)
{
        printf("recv SIGRTMIN\\n");
}


int main()
{
        sigset_t newmask, oldmask, pendmask;    //定义信号集

        /*安装信号处理函数*/
        if (signal(SIGRTMIN, hander_sigint) == SIG_ERR) {
                my_err("signal", __LINE__);  /*__LINE__ 表示当前__LINE__ 所在的行数*/
        }
        printf("进入睡眠30秒\\n");
        sleep(30);
        printf("睡眠醒来\\n");

        /*初始化信号集 newmask 并将 SIGINT 添加进去*/
        sigemptyset(&newmask);
        sigaddset(&newmask, SIGRTMIN);

        /*屏蔽信号 SIGINT*/
        if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
                my_err("sigprocmask", __LINE__);
        } else {
                printf("SIGRTMIN blocked\\n");
        }
        printf("设置了屏蔽实时信号 SIGRTMIN 后再睡眠10秒\\n");
        sleep(10);
        printf("醒来后要查看悬而未决的SIGRTMIN 信号情况\\n");
        /*获取未决信号队列*/
        if (sigpending(&pendmask) < 0) {
                my_err("sigpending", __LINE__);
        }
        /*检查未决信号队列里是否有 SIGINT*/
        switch (sigismember(&pendmask, SIGRTMIN)) {
                case 0:
                printf("SIGRTMIN is not in pending queue\\n");
                case 1:
                printf("SIGRTMIN is in pending queue\\n");
                break;
                case -1:
                my_err("sigismember", __LINE__);
                break;
                default:
                break;
        }

        /*解除对 SIGINT 的屏蔽*/
        if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
                my_err("sigprocmask", __LINE__);
        } else {
                printf("SIGRTMIN unblocked\\n");
        }

        while(1)
        ;

        return 0;
}
在测试这个程序时,先在另一个 shell 窗口里用 ps aux 找出这个程序的进程 ID,然后用另外一个程序去测试这个程序,下面的程序的作用主要是一次性发送 5 个可靠的实时信号:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
        int i;
        int signum = SIGRTMIN;
        pid_t pid;

        if (argc == 2)
                pid = atoi(argv[1]);
        else return 0;

        for(i=0; i<5; i++)
        {
                if (kill(pid, signum) < 0) {
                        perror("kill");
                        exit(1);
                }
        }

        return 0;
}

先运行第一个程序
beyes@linux-beyes:~/C> ./sig_mask2.exe
进入睡眠30秒
在 30 秒中内,我们及时的找出 sig_mask2.exe 这个程序的进程 ID ,然后结合这个 ID 用第二个程序给第一个程序发送信号:
beyes@linux-beyes:~/C> ./my_kill.exe 10546
一次性发送了 5 个 SIGRTMIN 信号,第一个程序收到信号后马上醒来响应:
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
睡眠醒来
SIGRTMIN blocked
设置了屏蔽实时信号 SIGRTMIN 后再睡眠10秒
可见,可靠信号是每个都处理的。接着再在这 10秒 内再发送一次 5 个 SIGRTMIN 信号:
beyes@linux-beyes:~/C> ./my_kill.exe 10546
这时,继续观察第一个程序的输出:
醒来后要查看悬而未决的SIGRTMIN 信号情况
SIGRTMIN is in pending queue
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
SIGRTMIN unblocked
^\\退出
由输出可见,对于实时的可靠信号,在解除阻塞后,每个信号都得到了处理。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
板凳
 楼主| 发表于 2009-7-6 14:49:11 | 只看该作者

小结

在第一个帖子中,程序设置 SIGINT 阻塞时,先保存了进程的信号屏蔽字在 oldmask 中,这是为了方便以后解除 SIGINT 的阻塞。

在解除 SIGINT 的阻塞时,重新设置进程的信号屏蔽字 (SIG_SETMASK) 为 oldmask。或者也可以使用 SIG_UNBLOCK 使信号不被阻塞,但是这样会产生一个问题,当一个大的程序中其他地方可能也阻塞此信号时,使用 SIG_UNBLOCK 就会把起他地方的设置也改掉( 因为用 SIG_UNBLOCK 方法,则该进程新的信号屏蔽字是当前信号屏蔽字和 set 所指向信号集的“交集”<SIG_BLOCKS 是并集>)。因此,建议使用 SIG_SETMASK 恢复进程的信号屏蔽字而不是使用 SIG_UNBLOCK 解除特定信号的阻塞。()


程序的结果再次证明了不可靠信号不支持排队,有可能丢失信号。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
地板
 楼主| 发表于 2009-7-6 20:00:50 | 只看该作者

sigset_t 类型

在 sigaction 结构体中有 sa_mask 这个数据成员,它是 sigset_t 类型数据,而 sigset_t 类型如下定义:

typedef __sigset_t  sigset_t
typedef struct {
        unsigned long int __val[ _SIGSET_NWORDS ];
}sigset_t;

在 __val 这个数组中,记录了要阻塞的信号。设置这个数组使用 sigaddset()函数,例如要阻塞 SIGINT信号,则:
sigaddset(&newmask, SIGINT);
其中,newmask 就是 sigset_t 类型数据。

sigset_t 具体的设置方法为,如果要屏蔽第 1 号信号 SIGHUP,就将 __val[0] 这个数的第 0 位置位;如果要将 SIGINT 信号阻塞,就会设置 __val[0] 的第 1 位;依次类推下去。

可靠信号从 SIGRTMIN 开始。它们的置位从 __val[1] 这个数开始,如要阻塞 SIGRTMIN 这个信号,就将 __val[1] 的第 0 位置位。

这样的设置,可以通过以下代码片段测试得知:
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGHUP);
for (i=0; i<_SIGSET_NWORDS; i++)
    if (newmask.__val[i] != 0)
        printf("%d:%d\\n", i,newmask.__val[i]);
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-3 23:21 , Processed in 0.070360 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表