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

【Linux多进程通信】信号

2016-12-07 21:42 344 查看
该文章参考自:Linux进程间通信——使用信号(ljianhui的专栏)

注意区分信号与信号量之间的区别。

一、什么是信号

用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中的进程捕获到这个信号然后作出一定的操作并最终被终止。

信号是UNIX/Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动。通常信号是由一个错误产生的。但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程。一个信号的产生叫生成,接收到一个信号叫捕获。

二、信号的种类

信号的名称是在头文件signal.h中定义的,信号都以SIG开头,常用的信号并不多,常用的信号如下:



更多的信号类型可查看附录表。

三、信号的处理——signal函数

程序可用使用signal函数来处理指定的信号,主要通过忽略和恢复其默认行为来工作。signal函数的原型如下:
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);

这是一个相当复杂的声明,耐心点看可以知道signal是一个带有sig和func两个参数的函数,func是一个类型为void (*)(int)的函数指针。signal函数的返回值是一个与func相同类型的函数指针,指向先前指定信号处理函数的函数指针。准备捕获的信号的参数由sig给出,接收到的指定信号后要调用的函数由参数func给出。

在 Linux Programmer's Manual 上,有更清楚的解释:

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

其实这个函数的使用是相当简单的,通过下面的例子就可以知道。注意信号处理函数的原型必须为void func(int)(里面的 int 是接收到的信号值,说明一个信号处理函数可以处理多个信号,只需依据传进来的信号值加以判断再寄予不同的处理即可)或者是下面的特殊值:

    SIG_IGN:忽略信号

    SIG_DFL:恢复信号的默认行为

下面用一个例子来说明:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void ouch(int sig)
{
printf("\nOUCH! - I got signal %d\n", sig);
//恢复终端中断信号SIGINT的默认行为
(void) signal(SIGINT, SIG_DFL);
}

int main()
{
//改变终端中断信号SIGINT的默认行为,使之执行ouch函数
(void) signal(SIGINT, ouch);
while(1)
{
printf("Hello World!\n");
sleep(1);
}
return 0;
}


输出如下:

Hello World!
Hello World!
Hello World!
Hello World!
^COUCH, I got signal 2
Hello World!
Hello World!
Hello World!
^C


第一次的 ctrl+c 被捕捉到后执行了 ouch 函数,第二次的 ctrl+c 结束了进程。如我们所愿。

四、信号处理——sigaction函数

前面我们看到了signal函数对信号的处理,但是一般情况下我们可以使用一个更加健壮的信号接口——sigaction函数。

The behavior of signal() varies across UNIX versions, and has also
varied historically across different versions of Linux.  Avoid its
use: use sigaction(2) instead.


它的原型为:
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);


该函数与signal函数一样,用于设置与信号sig关联的动作。oldact如果不是空指针的话,就用它来保存原先对该信号的动作的位置,act不为空时则用于设置指定信号的动作。

sigaction结构体定义在signal.h中:

The sigaction structure is defined as something like:

struct sigaction {
void     (*sa_handler)(int);
void     (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t   sa_mask;
int        sa_flags;
void     (*sa_restorer)(void);
};

On some architectures a union is involved: do not assign to both
sa_handler and sa_sigaction.

The sa_restorer field is not intended for application use.  (POSIX
does not specify a sa_restorer field.)  Some further details of
purpose of this field can be found in sigreturn(2).

sa_handler specifies the action to be associated with signum and may
be SIG_DFL for the default action, SIG_IGN to ignore this signal, or
a pointer to a signal handling function.  This function receives the
signal number as its only argument.

If SA_SIGINFO is specified in sa_flags, then sa_sigaction (instead of
sa_handler) specifies the signal-handling function for signum.  This
function receives the signal number as its first argument, a pointer
to a siginfo_t as its second argument and a pointer to a ucontext_t
(cast to void *) as its third argument.  (Commonly, the handler
function doesn't make any use of the third argument.  See
getcontext(3) for further information about ucontext_t.)

sa_mask specifies a mask of signals which should be blocked (i.e.,
added to the signal mask of the thread in which the signal handler is
invoked) during execution of the signal handler.  In addition, the
signal which triggered the handler will be blocked, unless the
SA_NODEFER flag is used.

sa_flags specifies a set of flags which modify the behavior of the
signal. It is formed by the bitwise OR of zero or more other flags.


sa_flags,除了 SA_SIGINFO,通常可以取以下的值:



下面给出使用 sigaction() 的例子:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>

void ouch(int sig)
{
printf("OUCH, I got signal %d\n", sig);
}

int main()
{
//改变终端中断信号SIGINT的默认行为,使之执行ouch函数
struct sigaction act;
act.sa_handler = ouch;
//创建空的信号屏蔽字,即不屏蔽任何信号
sigemptyset(&act.sa_mask);
//恢复终端中断信号SIGINT的默认行为
act.sa_flags = SA_RESETHAND;
sigaction(SIGINT, &act, 0);
while(1)
{
printf("Hello World!\n");
sleep(1);
}
}
输出结果与之前的一样。
注意sigaction函数在默认情况下是不被重置的,如果要想它重置,则sa_flags就要为SA_RESETHAND。

下面再举个例子说明 sa_mask 的作用:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>

void ouch(int sig)
{
printf("OUCH, I got signal %d\n", sig);
sleep(5);
}

int main()
{

//改变终端中断信号SIGINT的默认行为,使之执行ouch函数
struct sigaction act;
act.sa_handler = ouch;//创建空的信号屏蔽字,即不屏蔽任何信号
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGQUIT); //在信号处理函数执行阶段将会屏蔽掉SIGQUIT信号(ctrl+\),完毕后处理
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
while(1){printf("Hello World!\n");sleep(1);}
}


输出如下:
Hello World!
Hello World!
Hello World!
^COUCH, I got signal 2
^\Quit (core dumped)
先按下ctrl+c ,然后马上ctrl+\,程序是不会马上终止的,即等到handler处理完毕才会处理 SIGQUIT 信号。

关于sigaction函数,还有更多细节,这里不做过多的分析。

五、发送信号

上面说到的函数都是一些进程接收到一个信号之后怎么对这个信号作出反应,即信号的处理的问题,有没有什么函数可以向一个进程主动地发出一个信号呢?我们可以通过两个函数kill和alarm来发送一个信号。

1、kill函数

先来看看kill函数,进程可以通过kill函数向包括它本身在内的其他进程发送一个信号,如果程序没有发送这个信号的权限,对kill函数的调用就将失败,而失败的常见原因是目标进程由另一个用户所拥有。想一想也是容易明白的,你总不能控制别人的程序吧,当然超级用户root,这种上帝般的存在就除外了。

kill函数的原型为:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);


它的作用把信号sig发送给进程号为pid的进程,成功时返回0。

kill调用失败返回-1,调用失败通常有三大原因:

1、给定的信号无效(errno = EINVAL)

2、发送权限不够( errno = EPERM )

3、目标进程不存在( errno = ESRCH )

2、alarm函数

这个函数跟它的名字一样,给我们提供了一个闹钟的功能,进程可以调用alarm函数在经过预定时间后向自己发送一个SIGALRM信号。

alarm函数的型如下:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);


alarm函数用来在seconds秒之后安排发送一个SIGALRM信号,如果seconds为0,将取消所有已设置的闹钟请求。

下面实践一下:

#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

static bool flag = true;

void handler(int sig)
{
if(SIGALRM == sig)
{
printf("alarm, I got signal %d\n", sig);
}
else
{
printf("quit, I got signal %d\n", sig);
flag = false;
}
}

int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
printf("fork() error\n");
}
else if(pid > 0)
{
sleep(5);
kill(pid, SIGQUIT);
exit(0);
}

struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGALRM, &act, 0);
sigaction(SIGQUIT, &act, 0);

while(flag)
{
alarm(1);
pause();
}

printf("Over\n");

return 0;
}
以上程序先 fork(),子进程每隔1秒收到一次 SIGALRM 信号,5秒后父进程向子进程发送 SIGQUIT 信号,flag 被修改,子进程退出了 while 循环,然后结束。

PS:这里用了 handler() 处理了两个信号。

关于 pause() :pause() causes the calling process (or thread) to sleep until a signal is delivered that either terminates the process or causes the invocation of a signal-catching function.

六、附录——信号表



如果进程接收到上面这些信号中的一个,而事先又没有安排捕获它,进程就会终止。

此外,还有其他的一些信号,如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: