信号 信号是UNIX中所使用的进程通信的一种最古老的方法,它是在软件层次上对中断机制的模拟,是一种异步通信方式。信号可以直接进行用户空间进程和内核空间进程之间的交互,内核进程也可以利用它来通知用户进程发生了哪些系统事件。它可以在任何时候发给某一进程而无需知道该进程的状态
一个完整的信号生命周期可分为三个重要阶段,三个重要阶段由四个重要事件刻画,如下图所示:
相邻的两个事件的间隔就是一个重要阶段
一个不可靠的信号处理过程是这样的:如果发现该信号已经注册,那么忽略该信号。因此,若前一个信号还未注销又产生了相同的信号就会产生信号丢失。而当可靠信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此信号不会丢失
用户进程对信号的相应有三种方式:
忽略信号:即对信号不做任何处理,有两个信号不能被忽略,SIGKILL和SIGSTOP
捕捉信号:定义信号处理函数,当信号发生时,执行相应的自定义处理函数
执行默认操作:Linux对每种信号都规定了默认操作
常见的信号及其默认操作如下:
信号
含义
默认操作
SIGHUP
该信号在用户终端连接结束时发出,通常是在中端的控制进程结束时,通知同一会话内的各个作业与控制中断不再关联
终止
SIGINT
该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程
终止
SIGQUT
此信号与SIGINT类似,由QUIT字符(通常是Ctrl-\)来控制
终止
SIGILL
该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出
终止
SIGFPE
该信号在发生致命的运算错误时发出,这里不仅包括浮点运算错误,还包括溢出及除数为0等其他所有的算数错误
终止
SIGKILL
该信号用来立即结束程序的运行,并且不能被阻塞、处理或忽略
终止
SIGALRM
该信号当一个定时器到时的时候发出
终止
SIGSTOP
该信号用于暂停一个进程,并且不能被阻塞、处理或忽略
暂停进程
SIGTSTP
该信号用于交互停止进程,用户键入SUSP字符时(通常是Ctrl-Z)发出
停止进程
SIGCHLD
子进程改变状态时,父进程会收到这个信号
忽略
SIGABORT
进程异常终止时发出
信号发送和捕捉 kill()和raise() kill()函数可以发送信号给进程或进程组,raise函数向自身发送信号
1 2 3 4 5 #include <signal.h> #include <sys/types.h> int kill (pid_t pid, int sig) ;int raise (int sig) ;
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> int main () { pid_t pid; int ret; if ((pid = fork()) < 0 ) { printf ("Fork error!\n" ); exit (1 ); } if (pid == 0 ) { printf ("Child (pid:%d) is waiting for any signal\n" , getpid()); raise(SIGSTOP); printf ("exit\n" ); exit (0 ); } else { sleep(1 ); if ((waitpid (pid, NULL , WNOHANG)) == 0 ) { if ((ret = kill (pid, SIGKILL)) == 0 ) { printf ("Parent kill %d\n" , pid); } } waitpid(pid, NULL , 0 ); exit (0 ); } }
alarm()和pause() alarm()也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,他就向进程发送SIGALARM信号
pause()函数是用于将调用程序挂起直到捕捉到信号为止
1 2 3 #include <unistd.h> unsigned int alarm (unsigned int seconds) ;int pause (void ) ;
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main () { int ret = alarm (5 ); pause() ; printf ("I have been waken up \n" ); }
信号的处理 信号处理的主要方法由两种,一种是使用简单的signal()函数,另一种是使用信号集函数组
信号处理函数 使用signal()函数时,只需指出要处理的信号和处理函数即可。主要用于前32中非实时信号的处理,不支持信号传递信息
Linux还支持一个更强壮、更新的信号处理函数sigaction(),推荐使用
1 2 3 #include <sigal.h> void (*signal (int signum, void (*handler)(int )))(int );int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact)
主要介绍一下sigaction()函数:
signum:信号代码,可以为除了SIGKILL及SIGSTOP外的任何一个特定有效的信号
act:指向结构sigaction的一个实例的指针,指定对特定信号的处理
oldact:保存原来对应信号的处理
sigaction的定义如下:
1 2 3 4 5 6 7 struct sigaction { void (*sa_handler) (int signo); sigset_t sa_mask; int sa_flags; void (*sa_restore) (void ); }
sa_handler是一个函数指针,指定信号处理函数,这里除了可以是用户自定义的处理函数外,还可以是SIG_DFL或SIG_IGN。它的处理函数只有一个参数,即信号值
sa_mask是一个信号集,它可以指定在信号处理程序执行过程中哪些信号应当被频闭,在调用信号捕获函数之前,该信号集要加入到信号的信号屏蔽字中
sa_flag中包含了许多标志位,是对信号进行处理的各个选项。常见可选值如下:
选项
含义
SA_NODEFER\SA_NOMASK
当捕捉到此信号时,在执行其信号捕捉函数时,系统不会自动屏蔽此信号
SA_NOCLDSTOP
进程忽略子进程产生的任何SIGSTOP、SIGTSTP、SIGTTIN和SIGTTOU信号
SA_RESTART
令重启的系统调用起作用
SA_ONESHOT\SA_RESTHAND
自定义信号只执行一次,在执行完毕后恢复信号的系统默认动作
例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> void my_func (int sign_no) { if (sign_no == SIGINT) { printf ("I have get SIGINT\n" ); } else if (sign_no == SIGQUIT) { printf ("I have get SIGQUIT\n" ); } }void my_signal () { signal (SIGQUIT, my_func); signal (SIGINT, my_func); }void my_sigaction () { struct sigaction action ; action.sa_handler = my_func; sigemptyset (&action.sa_mask); action.sa_flags = 0 ; sigaction (SIGINT, &action, 0 ); sigaction (SIGQUIT, &action, 0 ); }int main () { printf ("Waiting for signal SIGINT or SIGQUIT...\n" ); my_sigaction(); pause(); exit (0 ); }
信号集函数组 使用信号集函数组处理信号时涉及一系列的函数,这些函数按照调用的先后次序可分为以下几大功能模块:创建信号集合、注册信号处理函数以及检测信号。
其中,创建信号集合主要用于处理用户感兴趣的一些信号,其函数包括以下几个。
sigemptyset(): 将信号集合初始化为空
sigfillset(): 将信号集和初始化为包含所有已定义的信号的集合
sigaddset(): 将指定信号加入到信号集合中
sigdelset(): 将指定信号从信号集合中删除
sigismember(): 查询指定信号是否在信号集合中
1 2 3 4 5 6 #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 (sigset_t *set , int signum) ;
注册信号处理函数主要用于决定进程如何处理信号,只有当信号的状态处于非阻塞状态时才会真正起作用。因此首先使用sigprocmask()函数检测并更改屏蔽字,然后使用sigaction()函数来定义进程接收到特定信号之后的行为
1 2 #include <signal.h> int sigprocmask (int how, const sigset_t *set , sigset_t oset)
how决定函数的操作方式:
SIG_BLOCK:增加一个信号集合到当前进程的阻塞集合之外
SIG_UNBLOCK:从当前的阻塞集合之中删除一个信号集合
SIG_SETMASK:将当前信号集合设置为信号阻塞集合
set:指定信号集
oset:信号屏蔽字
若set是一个非空指针,则how表示函数的操作方式;若how为空,则表示忽略此操作
因为被阻塞的信号不会传递给进程,所以这些信号就处于“未处理”信号。sigpending()函数允许检测到“未处理”信号,并进一步决定对他们作何处理
1 2 #include <signal.h> int sigpending (sigset_t *set ) ;
例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> void my_func (int signum) { printf ("If you want to quit, please try SIGQUIT!\n" ); }int main () { sigset_t set , penset; struct sigaction action1 , action2 ; if (sigemptyset (&set ) < 0 ) { perror ("sigemptyset" ); exit (1 ); } if (sigaddset (&set , SIGQUIT) < 0 ) { perror ("sigemptyset" ); exit (1 ); } if (sigaddset (&set , SIGINT) < 0 ) { perror ("sigemptyset" ); exit (1 ); } if (sigismember (&set , SIGINT)) { sigemptyset (&action1.sa_mask); action1.sa_handler = my_func; action1.sa_flags = 0 ; sigaction (SIGINT, &action1, NULL ); } if (sigismember (&set , SIGQUIT)) { sigemptyset (&action2.sa_mask); action2.sa_handler = SIG_DFL; action2.sa_flags = 0 ; sigaction (SIGQUIT, &action2, NULL ); } if (sigprocmask (SIG_BLOCK, &set , NULL ) < 0 ) { perror ("sigprocmask" ); exit (1 ); } else { printf ("Signal set was blocked , Press any key!\n" ); getchar(); } if (sigprocmask (SIG_UNBLOCK, &set , NULL ) < 0 ) { perror ("sigprocmask" ); exit (1 ); } else { printf ("Signal set is unblock state\n" ); } while (1 ); return 0 ; }