这一章的标题是 “信号” ,所以本文将对信号的各个方面进行介绍,由于Linux中的信号机制远比想象的要复杂,所以,本文不会讲的很全面。。。
信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断。从它的命名可以看出,它的实质和使用很象中断。所以,信号可以说是进程控制的一部分。
一、信号的基本概念
本节先介绍信号的一些基本概念,然后给出一些基本的信号类型和信号对应的事件。基本概念对于理解和使用信号,对于理解信号机制都特别重要。下面就来看看什么是信号。
1、基本概念
软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。
收 到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处 理。第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信 号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。由此可以看出,进程对不同的信号可以同时保留,但对于同一个信号,进程并不知道在处理之前来过多少个。
2、信号的类型
发出信号的原因很多,这里按发出信号的原因简单分类,以了解各种信号:
(1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。
(2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。
(3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。
(4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。
(5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。
(6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。
(7) 跟踪进程执行的信号。
Linux支持的信号列表如下。很多信号是与机器的体系结构相关的
SIGHUP 1 A 终端挂起或者控制进程终止 SIGINT 2 A 键盘中断(如break键被按下) SIGQUIT 3 C 键盘的退出键被按下 SIGILL 4 C 非法指令 SIGABRT 6 C 由abort(3)发出的退出指令 SIGFPE 8 C 浮点异常 SIGKILL 9 AEF Kill信号 SIGSEGV 11 C 无效的内存引用 SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道 SIGALRM 14 A 由alarm(2)发出的信号 SIGTERM 15 A 终止信号 SIGUSR1 30,10,16 A 用户自定义信号1 SIGUSR2 31,12,17 A 用户自定义信号2 SIGCHLD 20,17,18 B 子进程结束信号 SIGCONT 19,18,25 进程继续(曾被停止的进程) SIGSTOP 17,19,23 DEF 终止进程 SIGTSTP 18,20,24 D 控制终端(tty)上按下停止键 SIGTTIN 21,21,26 D 后台进程企图从控制终端读 SIGTTOU 22,22,27 D 后台进程企图从控制终端写 SIGIOT 6 C IO捕获指令,与SIGABRT同义 SIGEMT 7,-,7 SIGSTKFLT -,16,- A 协处理器堆栈错误 SIGIO 23,29,22 A 某I/O操作现在可以进行了(4.2 BSD) SIGCLD -,-,18 A 与SIGCHLD同义 SIGPWR 29,30,19 A 电源故障(System V) SIGINFO 29,-,- A 与SIGPWR同义 SIGLOST -,-,- A 文件锁丢失 SIGWINCH 28,28,20 B 窗口大小改变(4.3 BSD, Sun) SIGUNUSED -,31,- A 未使用的信号(will be SIGSYS)
处理动作一项中的字母含义如下 A 缺省的动作是终止进程 B 缺省的动作是忽略此信号 C 缺省的动作是终止进程并进行内核映像转储(dump core) D 缺省的动作是停止进程 E 信号不能被捕获 F 信号不能被忽略
三、有关信号的系统调用
前面两节已经介绍了有关信号的大部分知 识。这一节我们来了解一下这些系统调用。其中,系统调用signal是进程用来设定某个信号的处理方法,系统调用kill是用来发送信号给指定进程的。这 两个调用可以形成信号的基本操作。后两个调用pause和alarm是通过信号实现的进程暂停和定时器,调用alarm是通过信号通知进程定时器到时。所 以在这里,我们还要介绍这两个调用。
1、signal 系统调用
系统调用signal用来设定某个信号的处理方法。该调用声明的格式如下:
void (*signal(int signum, void (*handler)(int)))(int); 在使用该调用的进程中加入以下头文件: #include <signal.h>
typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
#include <signal.h> #include <unistd.h> #include <stdio.h> void sigroutine(int dunno) { /* 信号处理例程,其中dunno将会得到信号的值 */ switch (dunno) { case 1: printf("Get a signal -- SIGHUP "); break; case 2: printf("Get a signal -- SIGINT "); break; case 3: printf("Get a signal -- SIGQUIT "); break; } return; } int main() { printf("process id is %d ",getpid()); signal(SIGHUP, sigroutine); //* 下面设置三个信号的处理方法 signal(SIGINT, sigroutine); signal(SIGQUIT, sigroutine); for (;;) ; }
2、kill 系统调用
系统调用kill用来向进程发送一个信号。该调用声明的格式如下:
nt kill(pid_t pid, int sig); 在使用该调用的进程中加入以下头文件: #include <sys/types.h> #include <signal.h>
3、pause系统调用
系统调用pause的作用是等待一个信号。该调用的声明格式如下:
int pause(void); 在使用该调用的进程中加入以下头文件: #include <unistd.h>
#include <unistd.h> #include <stdio.h> #include <signal.h> void sigroutine(int unused) { printf("Catch a signal SIGINT "); } int main() { signal(SIGINT, sigroutine); pause(); printf("receive a signal "); }
raise() #include <signal.h> int raise(int signo)
以PAYLOAD 送出信号
sigqueue() #include <sys/types.h> #include <signal.h> int sigqueue(pid_t pid, int sig, const union sigval val)
调用成功返回 0;否则,返回 -1。
sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。
sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
typedef union sigval { int sival_int; void *sival_ptr; }sigval_t;
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。
在调用sigqueue时,sigval_t指定的信息会拷贝到3参数信号处理函数(3参数信号处理函数指的是信号处理函数由sigaction安装,并设定了sa_sigaction指针,稍后将阐述)的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。
注:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。
四.信号集操作函数
信号集被定义为一种数据类型:
typedef struct { unsigned long sig[_NSIG_WORDS]; } sigset_t
#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(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指向的信号集中。
五.子进程对信号的继承
#include <signal.h> #include <sys/types.h> #include <unistd.h> void new_op(int,siginfo_t*,void*); int main(int argc,char**argv) { struct sigaction act; int sig; pid_t pid; pid=getpid(); sig=atoi(argv[1]); sigemptyset(&act.sa_mask); act.sa_sigaction=new_op; act.sa_flags=SA_SIGINFO; if(sigaction(sig,&act,NULL)<0)//安装信号 { printf("install sigal error\n"); } if(fork()==0)//创建子进程 { union sigval sv; sv.sival_int = 66; printf("child pid is %d\n", getpid()); while(1) { sleep(1); sigqueue(getpid(), sig, sv);//子进程向自己发信号 } } while(1) { sleep(2); printf("wait for the signal\n"); } } void new_op(int signum,siginfo_t *info,void *myact)//信号处理函数 { printf("pid is %d the int value is %d \n", getpid(), info->si_int); }
child pid is 21309 pid is 21309 the int value is 66 wait for the signal pid is 21309 the int value is 66 pid is 21309 the int value is 66 wait for the signal pid is 21309 the int value is 66 pid is 21309 the int value is 66 wait for the signal pid is 21309 the int value is 66 pid is 21309 the int value is 66 信号处理函数中打印的进程号和子进程是一致的,子进程继承了父进程对信号的处理。
sigwait
sigwait()
提供了一种等待信号的到来,以串行的方式从信号队列中取出信号进行处理的机制。sigwait(
)只等待函数参数中指定的信号集,即如果新产生的信号不在指定的信号集内,则 sigwait()
继续等待。对于一个稳定可靠的程序,我们一般会有一些疑问:
#include <signal.h> #include <errno.h> #include <pthread.h> #include <unistd.h> #include <sys/types.h> void sig_handler(int signum) { printf("Receive signal. %d\n", signum); } void* sigmgr_thread() { sigset_t waitset, oset; int sig; int rc; pthread_t ppid = pthread_self(); pthread_detach(ppid); sigemptyset(&waitset); sigaddset(&waitset, SIGRTMIN); sigaddset(&waitset, SIGRTMIN+2); sigaddset(&waitset, SIGRTMAX); sigaddset(&waitset, SIGUSR1); sigaddset(&waitset, SIGUSR2); while (1) { rc = sigwait(&waitset, &sig); if (rc != -1) { sig_handler(sig); } else { printf("sigwaitinfo() returned err: %d; %s\n", errno, strerror(errno)); } } } int main() { sigset_t bset, oset; int i; pid_t pid = getpid(); pthread_t ppid; sigemptyset(&bset); sigaddset(&bset, SIGRTMIN); sigaddset(&bset, SIGRTMIN+2); sigaddset(&bset, SIGRTMAX); sigaddset(&bset, SIGUSR1); sigaddset(&bset, SIGUSR2); if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0) printf("!! Set pthread mask failed\n"); kill(pid, SIGRTMAX); kill(pid, SIGRTMAX); kill(pid, SIGRTMIN+2); kill(pid, SIGRTMIN); kill(pid, SIGRTMIN+2); kill(pid, SIGRTMIN); kill(pid, SIGUSR2); kill(pid, SIGUSR2); kill(pid, SIGUSR1); kill(pid, SIGUSR1); // Create the dedicated thread sigmgr_thread() which will handle signals synchronously pthread_create(&ppid, NULL, sigmgr_thread, NULL); sleep(10); exit (0); }
程序编译运行在 Ubuntu 的结果如下:
从以上测试程序发现以下规则:
六.信号未决与信号阻塞
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。下面是与信号阻塞相关的几个函数:
#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));
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。
七.信号使用实例
实例一:信号发送及处理
实现一个信号接收程序sigreceive(其中信号安装由sigaction())。
#include <signal.h> #include <sys/types.h> #include <unistd.h> void new_op(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=new_op; if(sigaction(sig,&act,NULL) < 0) { printf("install sigal error\n"); } while(1) { sleep(2); printf("wait for the signal\n"); } } void new_op(int signum,siginfo_t *info,void *myact) { printf("receive signal %d", signum); sleep(5); }
实例二:信号传递附加信息
主要包括两个实例:
#include <signal.h> #include <sys/types.h> #include <unistd.h> void new_op(int,siginfo_t*,void*); int main(int argc,char**argv) { struct sigaction act; union sigval mysigval; int i; int sig; pid_t pid; char data[10]; memset(data,0,sizeof(data)); for(i=0;i < 5;i++) data[i]='2'; mysigval.sival_ptr=data; sig=atoi(argv[1]); pid=getpid(); sigemptyset(&act.sa_mask); act.sa_sigaction=new_op;//三参数信号处理函数 act.sa_flags=SA_SIGINFO;//信息传递开关 if(sigaction(sig,&act,NULL) < 0) { printf("install sigal error\n"); } while(1) { sleep(2); printf("wait for the signal\n"); sigqueue(pid,sig,mysigval);//向本进程发送信号,并传递附加信息 } } void new_op(int signum,siginfo_t *info,void *myact)//三参数信号处理函数的实现 { int i; for(i=0;i<10;i++) { printf("%c\n ",(*( (char*)((*info).si_ptr)+i))); } printf("handle signal %d over;",signum); }
这个例子中,信号实现了附加信息的传递,信号究竟如何对这些信息进行处理则取决于具体的应用。
#include <signal.h> #include <sys/types.h> #include <unistd.h> void new_op(int,siginfo_t*,void*); int main(int argc,char**argv) { struct sigaction act; int sig; pid_t pid; pid=getpid(); sig=atoi(argv[1]); sigemptyset(&act.sa_mask); act.sa_sigaction=new_op; act.sa_flags=SA_SIGINFO; if(sigaction(sig,&act,NULL)<0) { printf("install sigal error\n"); } while(1) { sleep(2); printf("wait for the signal\n"); } } void new_op(int signum,siginfo_t *info,void *myact) { printf("the int value is %d \n",info->si_int); }
#include <signal.h> #include <sys/time.h> #include <unistd.h> #include <sys/types.h> main(int argc,char**argv) { pid_t pid; int signum; union sigval mysigval; signum=atoi(argv[1]); pid=(pid_t)atoi(argv[2]); mysigval.sival_int=8;//不代表具体含义,只用于说明问题 if(sigqueue(pid,signum,mysigval)==-1) printf("send error\n"); sleep(2); }
实例三:信号阻塞及信号集操作
#include "signal.h" #include "unistd.h" static void my_op(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*)my_op; 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(10); printf("now begin to get pending mask and unblock SIGRTMIN+10\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\n"); sleep(10); } static void my_op(int signum) { printf("receive signal %d \n",signum); }
编译该程序,并以后台方式运行。在另一终端向该进程发送信号(运行kill -s 42 pid,SIGRTMIN+10为42),查看结果可以看出几个关键函数的运行机制,信号集相关操作比较简单。
注:在上面几个实例中,使用了printf()函数,只是作为诊断工具,pringf()函数是不可重入的,不应在信号处理函数中使用。
用sigqueue实现的命令行信号发送程序sigqueuesend,命令行第二个参数是发送的信号值,第三个参数是接收该信号的进程ID,可以配合实例一使用:
#include <signal.h> #include <sys/types.h> #include <unistd.h> int main(int argc,char**argv) { pid_t pid; int sig; sig=atoi(argv[1]); pid=atoi(argv[2]); sigqueue(pid,sig,NULL); sleep(2); }