信号的种类 常用信号信号是系统响应某些条件所产生的一个事件,接收到该信号的进程会进行相应的操作
信号也可以由一个进程发送给另一个进程
| SIGALRM | 由 alarm 函数设置的定时器产生 |
| SIGHUP | 有一个处于非连接状态的终端发送给控制进程,或者由控制进程在自身结束时发送给每个前台进程 |
| SIGINT | 一般由终端敲入 Ctrl + C 组合键或预先设置好的中断字符产生 |
| SIGKILL | 该信号不能被捕获或忽略,一般在 shell 中用来终止异常进程 |
| SIGPIPE | 如果向管道写数据时没有与之对应的读进程,就会产生该信号 |
| SIGTERM | 作为一个请求被发送,要求进程结束运行。 UNIX 在关机时用该信号要求系统服务停止运行。它是 kill 命令默认发送的信号 |
| SIGUSR1, SIGUSR2 | 进程之间可以用这个信号进行通信,例如报告状态信息等 |
| SIGABORT | 进程异常终止 |
| SIGQUIT | 终端退出 |
| SIGSEGV | 无效内存段访问 |
| SIGILL | 非法指令 |
如果进程接收到上面的信号,而事先有没有安排捕获,那么进程会终止
信号的处理 signal 函数#includetypedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
示例:
#include#include #include void fun(int sig) { printf("nGot signal %dn", sig); (void) signal(SIGINT, SIG_DFL); // 中断处理函数中再次修改 SIGINT 为默认处理行为(结束进程) } int main() { (void) signal(SIGINT, fun); // 修改默认 SIGINT 处理行为,调用中断处理函数 while(1) { printf("Hellon"); sleep(1); } return 0; }
增强版 sigaction 函数
#includeint 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_restorer)(void); };
对于信号的关联,用到了两个同样的结构体变量:act,oldact 分别用于设置指定信号的动作,以及保存原先对该信号的动作(如果 oldact 不为空)
结构体提供了更细致的控制,常用的栏位:
void (*sa_handler)(int):信号处理函数
sigset_t sa_mask:信号集,在调用信号处理函数之前,该信号集将被加入到进程的信号屏蔽字中,相当于这组信号不被当前进程所接收
int sa_flags:信号处理方式
SA_NOCLDSTOP 子进程停止时不产生 SIGCHILD 信号 SA_RESETHAND 将对此信号的处理方式在信号处理函数入口处重置为 SIG_DFL SA_RESTART 重启可中断的函数而不是给出 EINTR 错误 SA_NODEFER 捕获到信号时不将它添加到信号屏蔽字中
示例:
信号函数集#include#include #include void fun(int sig) { printf("nGot signal %dn", sig); } int main() { struct sigaction act; act.sa_handler = fun; sigemptyset(&act.sa_mask); // 不屏蔽任何信号 act.sa_flags = SA_RESETHAND; // 重置为默认行为, 将在信号处理函数入口处进行 sigaction(SIGINT, &act, 0); while(1) { printf("Hellon"); sleep(1); } return 0; } 默认情况下,sigaction 是不被重置的,如果要想重置,则 sa_flags = SA_RESETHAND
sigaction 的 sa_mask 信号集使用下面一组函数进行设定
- int sigemptyset(sigset_t *set);
将信号集初始化为空- int sigfillset(sigset_t *set);
将信号集初始化为包含所有已定义的信号- int sigaddset(sigset_t *set, int signum);
将信号 signum 添加到信号集 set 中- int sigdelset(sigset_t *set, int signum);
将信号 signum 从信号集 set 中删除- int sigismember(const sigset_t *set, int signum);
判断给定的信号 signum 是否是信号集 set 中的一个成员- int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
set : 新的屏蔽字,非空 how 才有意义
how:
SIG_BLOCK 把参数 set 中的信号添加到信号屏蔽字中 SIG_UNBLOCK 从信号屏蔽字中删除参数 set 中的信号 SIG_SETMASK 把信号屏蔽字设置为 set 中的信号
oldset:原来的屏蔽字,如果非空保存当前信号屏蔽字
注意:调用这个函数才能改变进程的屏蔽字,其余函数只是改变变量的值而已
- int sigpending(sigset_t *set);
将阻塞的信号中停留在待处理状态的一组信号写到 set 指向的信号集中
- int sigsuspend(const sigset_t *mask);
将进程的屏蔽字替换为由参数 mask 给出的信号集,然后挂起进程的执行
注意:先替换再挂起,程序将在信号处理函数执行完成后再继续执行,如果收到信号终止了程序,那么该函数不会返回
如果一个信号被进程阻塞,它就不会传递给进程,但是会停留在待处理的状态。
当进程解除对待处理信号的阻塞时,待处理信号就会立刻被处理
示例:
#include#include #include void handler(int sig) { printf("Handle the signal %dn", sig); } int main(int argc, char **argv) { sigset_t sigset; // 用于记录屏蔽字 sigset_t ign; // 用于记录被阻塞(屏蔽)的信号集 struct sigaction act; // 清空信号集 sigemptyset(&sigset); sigemptyset(&ign); // 向信号集中添加 SIGINT sigaddset(&sigset, SIGINT); // 设置处理函数 和 信号集 act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT, &act, 0); printf("Wait the signal SIGNAL...n"); pause(); // 设置进程屏蔽字, 在本例中为屏蔽 SIGINT sigprocmask(SIG_SETMASK, &sigset, 0); printf("Please press Ctrl + C in 10 seconds...n"); sleep(10); // 测试 SIGINT 是否被屏蔽 sigpending(&ign); // 将阻塞的信号中停留在待处理状态的一组信号写到 set 指向的信号集中 if (sigismember(&ign, SIGINT)) { printf("The SIGINT signal has ignoredn"); } // 从信号集中删除信号 SIGINT sigdelset(&sigset, SIGINT); printf("Wait the signal SIGINT...n"); // 将进程的屏蔽字重新设置, 即取消对 SIGINT 的屏蔽 // 并挂起进程 sigsuspend(&sigset); printf("The app will exit in 5 secondes!n"); sleep(5); return 0; }
问题
信号的发送 kill 函数Q:在使用 signal 或者 sigaction 来指定信号处理函数时,如果在建立这个关联前就接收到了要处理的信号,进程会有怎样的反应?
A:不会调用信号处理函数
sam_mask 制定了一个信号集,在调用信号处理函数前,该信号集就被加入到进程的信号屏蔽字中,设置信号屏蔽字可以防止信号在它的信号处理函数还未运行结束时就被接收到的情况
#include#include int kill(pid_t pid, int sig);
alarm 函数失败原因:
- EINVAL An invalid signal was specified. 无效信号
- EPERM The process does not have permission to send the signal to any of the target processes. 发送权限不够
- ESRCH The pid or process group does not exist. Note that an existing process might be a zombie, a process which already committed termination, but has not yet been wait(2)ed for. 目标进程不存在
常用的定时器,时间到了发送 SIGARM 信号
#includeunsigned int alarm(unsigned int seconds);
示例:
#include#include #include #include #include static int isalarm = 0; void fun(int sig) { isalarm = 1; } int main() { pid_t pid; pid = fork(); switch(pid) { case -1: perror("fork failedn"); exit(1); case 0: // 子进程 sleep(5); // 向父进程发送信号 kill(getppid(), SIGALRM); exit(0); default:; } // 设置信号处理函数 signal(SIGALRM, fun); while(!isalarm) { printf("Hellon"); sleep(1); } if(isalarm) printf("nGot a signal %dn", SIGALRM); exit(0); } 子进程休息 5s 后向父进程发送 alarm 信号,期间父进程完成了信号处理函数的关联,并持续打印,直到收到信号进入处理函数
为了避免父进程的持续等待信号,可以使用 pause 函数替代while 打印部分
pause 函数用于挂起进程,直到接收到信号
信号处理函数的可重入性
信号处理函数可以在其执行期间被中断并被再次调用
这就要求我们的信号处理函数是可重入的,那么内部调用的函数也必须是可重入的
特别容易出错的是,printf 来进行打印,print 函数是不可重入的
可重入函数表:
access fpathconf rename sysconf aio_return fstat rmdir tcdrain aio_suspend fsync sem_post tcflow alarm getegid setgid tcflush cfgetispeed geteuid setpgid tcgetattr cfgetospeed getgid setsid tcgetpgrp cfsetispeed getgroups setuid tcsendbreak cfsetospeed getpgrp sigaction tcsetattr chidir getpid sigaddset tcsetpgrp chmod getppid sigdelset time chown getuid sigemptyset timer_getoverrun clock_gettime kill sigfillset timer_gettime close link sigismember timer_settime create lseek signal times dup mkdir sigpause umask dup2 mkfifo sigpending uname execle open sigprocmask unlink execve pathconf sigqueue utime _exit pause sigset wait fcntl pipe sigsuspend waitpid fdatasync raise sleep write fork read stat



