Chinaunix首页 | 论坛 | 博客
  • 博客访问: 217652
  • 博文数量: 72
  • 博客积分: 3890
  • 博客等级: 中校
  • 技术积分: 810
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-05 20:00
文章分类

全部博文(72)

文章存档

2010年(20)

2009年(52)

我的朋友

分类: LINUX

2010-08-17 20:21:09

siginfo_t结构中的联合数据成员确保该结构适应所有的信号,比如对于实时信号来说,则实际采用下面的结构形式:

typedef struct {
int si_signo;
int si_errno;
int si_code;
union sigval si_value;
} siginfo_t;

结构的第四个域同样为一个联合数据结构:
union sigval {
int sival_int;
void *sival_ptr; }

采用联合数据结构,说明siginfo_t结构中的si_value要么持有一个4字节的整数值,要么持有一个指针,这就构成了与信号相关的数据。

在信号的处理函数中,包含这样的信号相关数据指针,但没有规定具体如何对这些数据进行操作,操作方法应该由程序开发人员根据具体任务事先约定。

前面在讨论系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将

拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。信号可以传递信息对程序开发是非常有意义的。

c,sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。

注:请注意sa_mask指定的信号阻塞的前提条件,是在由sigaction()安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。

d,sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGINFO,当设定了该标志

位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为 sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置 SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)。

注:很多文献在阐述该标志位时都认为,如果设置了该标志位,就必须定义三参数信号处理函数。实际不是这样的,验证方法很简单:自己实现一个单一参数 信号处理函数,并在程序中设置该标志位,可以察看程序的运行结果。实际上,可以把该标志位看成信号是否传递参数的开关,如果设置该位,则传递参数;否则, 不传递参数。

C:信号集及信号集操作函数:

信号集被定义为一种数据类型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t

信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:

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
sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的所有信号被清空;
sigfillset(sigset_t *set)调用该函数后,set指向的信号集中将包含linux支持的64种信号;
sigaddset(sigset_t *set, int signum)在set指向的信号集中加入signum信号;
sigdelset(sigset_t *set, int signum)在set指向的信号集中删除signum信号;
sigismember(const sigset_t *set, int signum)判定信号signum是否在set指向的信号集中。

D:信号阻塞与信号未决:

每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。下面是与信号阻塞相关的几个函数:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask));

头文件:
#include

sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种:

参数how 进程当前信号集
SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号
SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞
SIG_SETMASK 更新进程阻塞信号集为set指向的信号集

sigpending(sigset_t *set))获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。

sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。

sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。

三,示例程序

实例一:信号发送及处理,看看函数sigaction是怎么使用的。

CODE

#include
#include
#include
void user_func(int,siginfo_t*,void*);

int main(int argc,char**argv)
{struct sigaction act;
int sig;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void * )user_func;
if(sigaction(sig,&act,NULL) < 0)
{printf("install sigal error\n");
}

while(1)
{sleep(2);
printf("wait for the signal\n");}
}
void user_func(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d\n\n\n", signum);
sleep(5);}

在一终端执行cc -o act act.c
$./act 8&
[1] 992
$ wait for the signal
$

在另一终端执行

#kill -s 8 992

看看。。

$receive signal 8

实例二:信号阻塞及信号集操作

CODE

#include
#include
void user_func(int);
main()
{
sigset_t new_mask,old_mask,pending_mask;
struct sigaction act;

sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void*)user_func;

if(sigaction(SIGRTMIN+10,&act,NULL))
printf("install signal SIGRTMIN+10 error\n");

sigemptyset(&new_mask);
sigaddset(&new_mask,SIGRTMIN+10);

if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
printf("block signal SIGRTMIN+10 error\n");

sleep(20);

printf("\n\nNow begin to get pending mask and unblock SIGRTMIN+10\n\n");
if(sigpending(&pending_mask)<0)
printf("get pending mask error\n");
if(sigismember(&pending_mask,SIGRTMIN+10))
printf("signal SIGRTMIN+10 is pending\n");

if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
printf("unblock signal error\n");

printf("signal unblocked ,ok ... ...\n\n\n");
}
void user_func(int signum)
{printf("receive signal %d \n",signum);}

$cc -o sus sus.c
$./sus&
[1] 997

another console
#kill -s 42 pid

看看...

$
Now begin to get pending mask and unblock SIGRTMIN+10

signal SIGRTMIN+10 is pending
receive signal 42
signal unblocked ,ok ... ...

[1]+ Exit 31 ./d
$

实例三:signal例子

CODE

#include
#include
#include
#define damo
void user_func(int no)
{       switch (no)
        {
                case 1:
                        printf("Get SIGHUP.\n");
                        break;
                case 2:
                        printf("Get SIGINT\n");
                        break;
                case 3:
                        printf("Get SIGQUIT \n");
                        break;
                default:
                        printf("What wan yi a \n\n");
                        break;
        }

}
int main()
{
        printf("Process id is %d\n ",getpid());

#ifdef damo
        signal(SIGHUP, user_func);
        signal(SIGINT, user_func);
        signal(SIGQUIT, user_func);
        signal(SIGBUS, user_func);
#else
        signal(SIGHUP, SIG_IGN);
        signal(SIGINT, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
        signal(SIGBUS, SIG_DFL);
#endif

        while(1)
               ;
}


这个就是signal的用法集中展示,也是我经常用的一个方法。说实话,sco太古老了。。俺只能用这个函数了。
测试时你可以把#define damo这个注释和不注释看看。深刻体会signal的用法。

BTW:signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);

等等忽略了这些信号后,可以让一个进程终端无关,即使你退出这个tty.当然kill信号还是不能屏蔽的。
这种方式多在守护进程中采用。

$ cc -o si si.c
$./si

Process id is 1501

在另一个tty上
#ps -ef
bank 1501 1465 51 04:07 pts/0 00:00:13 ./si
bank 1502 800 0 04:08 pts/1 00:00:00 ps -ef
#
注意观察这时候是1456
你把./si这个tty关掉。
这时候你再
#ps -ef看看
bank 1501 1 50 04:07 ? 00:00:59 ./si
bank 1503 800 0 04:09 pts/1 00:00:00 ps -ef
注意这个1和?,知道这个进程成啥了吧?成精拉。哈哈~~


实例四:pause函数

CODE

#include
#include
#include
void user_func()
{
        printf("\n\nCatch a signal SIGINT \n");
}

int main()
{
        printf ("pid = %d \n\n ",getpid());
        signal(SIGINT, user_func);
        pause();
        printf("receive a signal \n\n");
}


在这个例子中,程序开始执行,就象进入了死循环一样,这是因为进程正在等待信号,
当我们按下Ctrl-C时,信号被捕捉,并且使得pause退出等待状态。


实例五:下面是关于setitimer调用的一个简单例子。

CODE

#include
#include
#include
#include

void user_func(int sig)
{
if ( sig ==   SIGALRM)
    printf("Catch a signal   SIGALRM \n");
else if ( sig == SIGVTALRM)
    printf("Catch a signal   SIGVTALRM\n");
}

int main()
{
        struct itimerval value,ovalue,value2;

        printf("Process id is   =   %d \n",getpid());

        signal(SIGALRM, user_func);
        signal(SIGVTALRM, user_func);

        value.it_value.tv_sec = 1;
        value.it_value.tv_usec = 0;
        value.it_interval.tv_sec = 1;
        value.it_interval.tv_usec = 0;

        setitimer(ITIMER_REAL, &value, &ovalue);

        value2.it_value.tv_sec = 1;
        value2.it_value.tv_usec = 500000;
        value2.it_interval.tv_sec = 1;
        value2.it_interval.tv_usec = 500000;

        setitimer(ITIMER_VIRTUAL, &value2, &ovalue);

        while(1);
}


在该例子中,每隔1秒发出一个SIGALRM,每隔1.5秒发出一个SIGVTALRM信号:

结果如下
$ ./ti
Process id is = 1734
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM
Catch a signal SIGALRM
Catch a signal SIGALRM
Catch a signal SIGVTALRM

ctrl+c中断。....
开始喜欢setitimer函数了。。。


四,补充

不得不介绍一下setjmp和longjmp的作用。

在用信号的时候,我们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。
这种进程突然改变其上下文的情况,就是使用setjmp和longjmp的结果。setjmp将保存的上下文存入用户区,并继续在旧的
上下文中执行。这就是说,进程执行一个系统调用,当因为资源或其他原因要去睡眠时,内核为进程作了一次setjmp,如果
在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操作是内核为进程将原先setjmp调用保存在进程
用户区的上下文恢复成现在的上下文,这样就使得进程可以恢复等待资源前的状态,而且内核为setjmp返回1,使得进程知道
该次系统调用失败。这就是它们的作用。

有时间再man 一下waitpid吧。。都是很有用的。

五,后记

信号的处理从以前的使用到最终文档的成形,每次都感觉挺难,其实是挺偏的。毕竟,信号我们一般接触的少,用的也少。其实大家不要谈信号色变,仔细研 究一下,发现很有意思。我接触的东西没有直接用信号处理一大堆东西的,都是简单的对信号的使用,所有例子比较少,很多例子都是参考网络资料的。很多资料都 是参考了网友的资料而成,尤其是郑彦兴网友。

阅读(1048) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~