您的位置:首页 > 运维架构 > Linux

linux/unix 环境编程-信号总结

2014-03-19 16:03 183 查看
一些疑问:

信号,信号,如果学习信号,我想按照一个人的思维来问自己这么几个问题。

信号是什么,信号怎么产生的,有哪些信号,啥用?

怎么发送信号?

谁来接收信号,接收什么信号?

收到信号了该做些什么?

我的理解:

在unix环境高级编程中指出信号是一种软中断。软中断与硬中断相对应。硬中断是指硬件对CPU的中断,软中断是指中断处理程序中 下半部对内核的中断。根据中断类型的不同就会产生不同的信号(没有查证,暂时这么理解)。内核会将信号发送给当前正在运行的进程。 信号是一种异步事件处理机制。

信号的种类大约40种,不同的系统不一样。都已SIG开头,其中SIGUSR1和SIGUSR2是用户定义信号,可用于应用程序。

信号是由进程发往进程的,包括内核进程,用户进程。发送信号可以使用下面的函数:

kill(pit_t pid, int sig); //kill向进程号为pid的进程发送sig信号

raise(int sig);进程向自己发送sig信号。

另外值得一提的是alarm()函数和pause(void)函数,alarm函数是定时器,比如alarm(5);那么在5秒后会向进程自己发送一个SIGALRM函数。pause(void)函数则是 使进程挂起,直到进程调用了一个信号处理函数,这是pause会返回-1,并置errno为EINTR。pause函数有个缺点。设想如果之前屏蔽掉了部分信号,接着解除,然后调用pause函数。可能在解除之后,就捕捉到了很多信号,这时调用pause可能就再也捕捉不到信号了。所以进程就会一直被挂起。

要解决这种问题就需要将解除屏蔽和进程休眠合为一个原子操作。int sigsuspend(const sigset_t *sigmask);函数就是用来干这种事的。返回-1,并将errno置为EINTR。

一个进程怎么捕获想要处理的信号呢?

首先了解一下信号集这个数据结构。sigset_t 是一个位数多于信号种类个数的整数,这样每一位都可以对应一个信号。 我们可以用sigset_t来表示我们想捕获或不想收到的信号集。

操作sigset_t有4个函数

int sigemptyset(sigset_t  *set); //将set各位置0

int sigfillset(sigset_t *set);   //将set各位置1

int sigaddset(sigset_t *set, int sig); //将sig位置1

int sigdelset(sigset_t *set , int sig); //将sig位置0

前四个返回值是: 成功返回0,出错返回-1

int sigismember(sigset_t *set, int sig); //判断sig是否在set中。

返回值 1  表示在,0表示不在,-1 表示错误。 

有了信号集,我们就要告诉系统是对这个集合感兴趣(捕获),还是不感兴趣(不捕获),可以使用下面的函数。

int sigprocmask(int how, sigset_t *set, sigset_t *old_set);

how 有三个值:

SIG_BLOCK: 该进程最新的信号屏蔽字是 set和原来的屏蔽字的并集

SIG_UNBLOCK:set包含了我们需要 解除阻塞的信号,新的屏蔽字为 原来的与set的补集的交集。

SIG_SETMASK:进程最新的屏蔽字就是set。

上面的屏蔽和阻塞的意思是说进程将不在处理这些信号。

old_set用来保存原先的屏蔽字。

若set为空,则how值无意义。

调用sigprocmask函数之后,在进程的生命中 能捕获的信号就确定了,在重新设置之前。

可以使用sigpending()函数来查看当前进程中有哪些信号被阻塞没有处理。

int  sigpending(sigset_t *set);返回当前屏蔽字。

捕获了信号那么我们应该怎么处理呢?

捕捉到信号后,需要调用信号处理例程。我们可以定义自己的信号处理例程,并与相关信号绑定。这样收到该信号后就能调用该例程。实现 信号和例程的绑定,知道的有2种方法。

第一种使用signal函数,原型如下:

void (*signal(int sig, void (*sigfun)(int))) (int);

看上去很难理解,仔细推敲一下。  先只看  signal(int sig, void(*sigfun(int)));这样我们就知道了signal函数的2个参数,一个是要绑定的信号,另外一个是 一个返回值是void,参数只有一个int的函数指针。 这样 我们就明白了 void(*signal())(int),是说signal的返回值也是一个参数是int 无返回值的函数指针,是改变前的信号处理例程。

使用signal函数绑定自定义处理例程是不可靠的,因为信号可能丢失。即如果在处理例程的时候又来了一个信号,那么这个信号就捕获不到了,也就是使用signal不能在信号发生的时候记住它等进程做好准备后再通知。

第二种方法:使用sigaction函数,其功能是检查或修改与指定信号相关联的处理动作。原型如下:

int sigaction(int signo,struct sigaction *act, struct sigaction *oact);

成功返回0,失败返回-1;

struct sigaction的定义如下:

struct sigaction

{

void (*signal_handler)(int); //当flag的值不为 SA_SIGINFO时,将这个函数作为信号的动作。 

sigset_t sa_mask; //设置进程的信号屏蔽字,当处理例程运行完毕会将进程信号屏蔽字还原。

int flag; //有多种值,常用有 SA_INTERRUPT,表示被信号中断的系统调用不重启,SA_RESTART表示会重启。

void (*sa_sigaction)(int, siginfo_t, void*); //这个相当于另外一种动作,当flag为SA_SIGINFO时使用,和signal_handler二选一。

};

sa_mask的作用是将暂时设置信号屏蔽字,这样当我们处理完毕被屏蔽的信号会被再次发送。这样就不会产生使用signal函数所带来的信号丢失问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: