信号(signal):
信号的基本概念:
信号是事件发生时对进程的通知机制,有时也叫软件中断
,信号可以让一个正运行进程被另一个运行进程异步进程中断,转而处理某突发事件。
注意:信号的产生和处理都是由内核完成
简单说一下使内核为进程产生信号的事件有:
1 终端发特殊字符,如CTRL+C 对前台进程发送中断信号
2 硬件发生异常,如被0除等异常机器语言指令。
3 系统状态变化:如alarm定时器到期引起SIGALRM信号,进程某个子进程退出
4 运行kill 命令/函数。
信号使用的目的:
1.让进程知道发生了一件特定的事情。
2.强迫进程执行自己的信号处理程序。
特点:
简单,但不能携带大量的信息,满足特定条件才发送信号,其优先级比较高。
命令:
在linux下用kill -l
查看信号列表 前31为常规信号,后面的是实时信号。
信号列表:
linux下的信号专用名、定义值和默认反映
信号的默认动作:
Term
:终止
lgn
:忽略
Core
:终止并生成一个Core文件(为了调试错误)
Stop
:暂停当前进程
Cont
:继续执行当前被暂停的进程
信号的几种状态:
产生,未处理(未决),递达(被处理)
信号的处理方法:
信号有三种处理方法,分别是:忽略、捕捉和默认动作
忽略信号:
大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL
和SIGSTOP
)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
捕捉信号:
需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
系统默认动作:
对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
温馨提示:
SIGKILL
和SIGSTOP
信号不能被捕捉,阻塞或忽略,只能执行默认动作
上面唠叨了那么多,那么信号到底是怎么来使用的呢
1.信号处理函数的注册(接收)
信号处理函数分为入门版和高级版
1.入门版:
signal
2.高级版:sigaction
函数原型:
入门版:signal
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
高级版:sigaction
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
2.信号处理发送函数
信号处理发送函数同样分为入门版和高级版
1.入门版:
kill
2.高级版:sigqueue
函数原型:
入门版:kill
int kill(pid_t pid, int sig);
高级版:sigqueue
int sigqueue(pid_t pid, int sig, const union sigval value);
3.信号处理函数的注册demo
信号注册函数入门版—signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
根据函数原型可以看出由两部分组成,一个是真实处理信号的函数,另一个是注册函数了。 对于sighandler_t signal(int
signum, sighandler_t handler);函数来说,signum 显然是信号的编号,handler 是中断函数的指针。
同样,typedef void (*sighandler_t)(int);中断函数的原型中,有一个参数是 int
类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。
signaldemo12
.c
#include <stdio.h>
#include <signal.h>
// typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
switch(signum)
{
case 1:
printf("signum is :%d\n",signum);
break;
case 3:
printf("signum is :%d\n",signum);
break;
case 2:
printf("signum is :%d\n",signum);
break;
}
printf("never quit!\n");
}
int main()
{
printf("the pid is:%d\n",getpid());
signal(SIGINT,handler);
signal(SIGQUIT,handler);
signal(SIGHUP,handler);
while(1);
return 0;
}
signaldemo14
.c
#include <stdio.h>
#include <signal.h>
// typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
switch(signum)
{
case 1:
printf("signum is :%d\n",signum);
break;
case 3:
printf("signum is :%d\n",signum);
break;
case 2:
printf("signum is :%d\n",signum);
break;
}
printf("never quit!\n");
}
int main()
{
printf("the pid is:%d\n",getpid());
signal(SIGINT,SIG_IGN);
signal(SIGQUIT,handler);
signal(SIGHUP,handler);
signal(SIGKILL,SIG_IGN);
while(1);
return 0;
}
发信号: ps -aux|grep a.out
例如:kill -2 进程id
简单的总结一下,我们通过 signal 函数注册一个信号处理函数,分别注册了两个信号(SIGIO 和
SIGUSER1);随后主程序就一直“长眠”了。 通过 kill 命令发送信号之前,我们需要先查看到接收者,通过 ps
命令查看了之前所写的程序的 PID,通过 kill 函数来发送。 对于已注册的信号,使用 kill
发送都可以正常接收到,但是如果发送了未注册的信号,则会使得应用程序终止进程。那么,已经可以设置信号处理函数了,信号的处理还有两种状态,分别是默认处理和忽略,这两种设置很简单,只需要将 handler 设置为SIG_IGN(忽略信号)或 SIG_DFL(默认动作)即可。
在此还有两个问题需要说明一下:
1.当执行一个程序时,所有信号的状态都是系统默认或者忽略状态的。除非是 调用exec进程忽略了某些信号。exec 函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不会改变 。
2.当一个进程调动了 fork 函数,那么子进程会继承父进程的信号处理方式。
我们看到以上有三个demo,signaldemo12
.c 是用signal来捕获信号SIGINT
,SIGQUIT
,SIGHUP
并做相应的处理,记住这是接收信号函数,发送的还是kill
,我们可以在终端命令行用kill
指令向demo12
.c发送信号,signaldemo14
.c 是执行忽略信号的处理,但记住上面说过的SIGKILL
和SIGSTOP
是不能被忽略,捕捉的,只能执行默认动作
那么入门版的信号注册函数看完了,那么现在我们来看一下入门版的信号的发送函数:
signaldemo13
.c
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
// int kill(pid_t pid, int sig);
int main(int argc,char **argv)
{
int pid;
int signum;
char cmd[128];
printf("the argc is:%d\n",argc);
pid = atoi(argv[1]);
signum = atoi(argv[2]);
printf("pid = %d,signum = %d\n",pid,signum);
sprintf(cmd,"kill -%d %d",pid,signum);
// kill(pid,signum);
system(cmd);
printf("kill ok!\n");
return 0;
}
从代码中可以看出,向一个进程发送信号是用kill
函数来发送,或者是用system来对进程发送信号
信号注册函数高级版----sigaction
函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
};
参数说明:
void(*sa_handler)(int);
信号处理函数,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作,如果 sa_flags 中
存在SA_SIGINFO 标志,那么 sa_sigaction 将作为 signum 信号的处理函数,否则用 sa_handler
void(*sa_sigaction)(int, siginfo_t *, void *);
信号处理函数,能够接受额外数据和 sigqueue 配合使用
sigset_t sa_mask;
阻塞关键字的信号集,可以在调用捕捉函数之前把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值
int sa_flags;
影响信号的行为,SA_SIGINFO 表示能够接受数据
sigaction
函数是一个系统调用,根据这个函数原型,我们不难看出,在函数原型中,第一个参数 signum
应该就是注册的信号的编号;第二个参数 act
如果不为空说明需要对该信号有新的配置;第三个参数 oldact
如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。
struct sigaction
结构体中的 sa_mask
成员,设置在其的信号集中的信号,会在捕捉函数调用前设置为阻塞,并在捕捉函数返回时恢复默认原有设置。这样的目的是,在调用信号处理函数时,就可以阻塞默写信号了。在信号处理函数被调用时,操作系统会建立新的信号阻塞字,包括正在被递送的信号。因此,可以保证在处理一个给定信号时,如果这个种信号再次发生,那么他会被阻塞到对之前一个信号的处理结束为止。
sigaction
函数的时效性:当对某一个信号设置了指定的动作的时候,那么,直到再次显式调用sigaction
函数并改变动作之前都会一直有效。
void(*sa_sigaction)(int, siginfo_t *, void *);
处理函数中,第一个参数 int
是信号;第三个参数 void *
是接收到信号所携带的额外数据,不为空则说明有额外数据;而第二个参数 struct siginfo_t
这个结构体主要适用于记录接收信号的一些相关信息
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since kernel 2.6.32) */
}
我们可以通过这个结构体获取到信号相关的信息,si_signo
和 si_code
是必须实现的两个成员。
关于发送过来的数据存在两个地方:si_value
保存了发送过来的信息,同时,在 si_int
或者 si_ptr
成员中也保存了对应的数据
好,以上就是对sigaction
的相关信息介绍完了,那么现在我们就来看一下demo
sigactiondemo15
.c
#include <stdio.h>
#include <signal.h>
//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
void handler(int signum, siginfo_t *info, void *context)
{
printf("the signum is:%d\n",signum);
if(context != NULL)
{
printf("get the data is:%d\n",info->si_int);
printf("get the data is:%d\n",info->si_value.sival_int);
printf("from the:%d\n",info->si_pid);
}
}
int main()
{
struct sigaction act;
printf("the pid is:%d\n",getpid());
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
信号发送函数高级版——sigqueue
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
温馨提示:
1.使用sigqueue
函数前,sigaction
函数需要制定 SA_SIGINFO
标志
2.sigaction
结构体中的 sa_sigaction
成员提供了信号捕捉函数。如果实现的是 sa_handler
成员,那么将无法获取额外携带的数据
3.sigqueue
函数只能把信号发送给单个进程,可以使用 value
参数向信号处理函数传递整数值或者指针值
4.sigqueue
函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了POSIX
.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue
发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次;但是,信号不能无限的排队,信号排队的最大值受到 SIGQUEUE_MAX
的限制,达到最大限制后,sigqueue
会失败,errno
会被设置为 EAGAIN
那么现在我们直接来看一下demo
sigqueuedemo16
.c
#include <stdio.h>
#include <signal.h>
int main(int argc,char **argv)
{
// int sigqueue(pid_t pid, int sig, const union sigval value);
int pid;
int signum;
pid = atoi(argv[1]);
signum = atoi(argv[2]);
union sigval value;
value.sival_int = 1234536;
sigqueue(pid,signum,value);
printf("the pid is:%d\n",getpid());
printf("ok the send is done!\n");
return 0;
}
这个demo
的意思就是向某个进程发送数字为1234536的内容。
那么现在我们就来看一下结果:
接收端(即sigactiondemo15
.c )
CLC@Embed_Learn:~/SECOND/IPC$ ./r
the pid is:22930
the signum is:10
get the data is:1234536
get the data is:1234536
from the:22932
发送端(即sigqueuedemo16.c):
CLC@Embed_Learn:~/SECOND/IPC$ ./s 22930 10
the pid is:22932
ok the send is done!
以上就是对进程间通信之信号的学习笔记,仅供参考