信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,用来通知进程发生了异步事件。比如,终端用户输入了
ctrl+c
来中断程序,会通过信号机制停止一个程序。
内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件,并不给该进程传递任何数据。
阻塞信号:信号被进程解除阻塞之前不会把信号传递出去,被阻塞的信号也不会影响进程的行为,暂时被阻止传递。
忽略信号:信号会被传递出去但进程会将信号丢弃。
信号处理器程序(也称为信号捕捉器):当指定信号传递给进程时将会调用的一个函数。
进程对信号的响应
当信号发生时,用户可以要求进程以下列3种方式之一对信号做出响应。
- 捕捉信号:对于要捕捉的信号,可以为其指定信号处理函数,信号发生时该函数自动被调用,在该函数内部实现对该信号的处理。
- 忽略信号:大多数信号都可使用这种方式进行处理,但是SIGKILL和SIGSTOP这两个信号不能被忽略,同时这两个信号也不能被捕获和阻塞。此外,如果忽略某某些由硬件异常产生的信号(如非法存储访问或除以0),则进程的行为是不可预测的。
- 按系统默认方式处理。大部分信号的默认操作是终止进程,且所有的实时信号的默认动作都是终止进程。
信号处理函数
信号安装
signal
系统调用:为指定的信号安装一个新的信号处理函数,信号只出现并处理一次
sigactionl
系统调用:希望能用相同方式处理信号的多次出现
void (*signal(int sig, void (*handler)(int)))(int);
typedef void (*sighandler_t)(int);
/*
signal可以改写为:sighandler_t signal(int sig, sighandler_t handler)
功能:设置一个函数来处理信号
参数 sig:准备捕捉或屏蔽的信号
handle 新的信号处理句柄
SIG_DFL 采用系统默认方式处理信号。适用于将之前signal调用所改变的信号处置还原
SIG_IGN 忽略该信号。如果信号专为此进程而生,那么内核会默默将其丢弃
返回值 成功: 返回以前的信号处理句柄。失败: SIG_ERR
*/
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
/*
功能:查询或设置信号处理方式
返回值 成功0,失败-1
参数 signum:指定要捕获的信号类型。可以指定SIGKILL和SIGSTOP以外的所有信号。
act:指定新的信号处理方式
oldact:输出先前信号的处理方式,不关心就指为NULL
struct sigaction {
void (*sa_handler)(int); //普通信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); //带附加信息的信号处理函数
sigset_t sa_mask; //信号处理函数的屏蔽集
int sa_flags; //信号处理选项
void (*sa_restorer)(void); //备选函数的接口
}
*/
信号处理函数可以采用void (*sa_handler)(int) 或 void (*sa_sigaction)(int, siginfo_t *, void *)
1.默认情况下采用void (*sa_handler)(int),此时只能向处理函数发送信号的数值。
2.sa_flags中设置了SA_SIGINFO位就采用void (*sa_sigaction)(int, siginfo_t *, void *),此时可以向处理函数发送附加信息;
sa_handler:与signal()的参数handler相同,代表新的信号处理函数
sa_mask :用来设置在处理该信号时暂时将sa_mask指定的信号集搁置。sigaddset函数添加需要被捕捉的信号
sa_flags :用来设置信号处理的其他选项操作,下列的数值可用。
SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
SA_NODEFER :一般情况下当信号处理函数运行时,内核将阻塞该给定信号。
如果设置了 SA_NODEFER标记, 在该信号处理函数运行时,内核将不会阻塞该信号。
sigaction函数的功能是检查或修改与指定信号相关联的处理动作。
//SIGINT: ctrl+C产生信号 SIGQUIT: ctrl+\产生信号
//sigaddset: 添加信号屏蔽集, SIGINT处理函数执行时,先屏蔽词信号,处理完之后再执行
//act.sa_handler 普通信号处理函数的入口处 act.sa_sigaction 带附加信息的信号处理函数
#include <stdio.h>
#include <string.h>
#include <signal.h>
#if 0 //sa_handler
void show_handler(int sig){
printf("I got signal %d\n", sig);
int i = 0;
for(i = 0; i < 5; i++){
printf("i = %d\n", i);
sleep(1);
}
}
int main(void){
int i = 0;
struct sigaction act, oldact;
memset(&act, 0, sizeof(act));
act.sa_handler = show_handler;
sigemptyset(&act.sa_mask);//将信号集初始化为空
sigaddset(&act.sa_mask, SIGQUIT);//将SIGQUIT信号添加信号屏蔽集
sigaction(SIGINT, &act, &oldact);
while(1) {
sleep(1);
printf("sleeping %d\n", i);
i++;
}
}
#else //sa_sigaction
void sig_int(int signo, siginfo_t *info, void *conetxt){
printf("%d, %d, %d\n", info->si_signo, info->si_pid, info->si_uid);
}
int main(void){
struct sigaction act;
act.sa_sigaction = sig_int;
act.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &act, NULL);
while(1)
pause();
return 0;
}
#endif
发送信号函数
1.int raise(int signo); 对当前进程发送指定信号
2.int kill(pid_t pid,int signo); 通过进程编号发送信号
3.void abort(void) 使当前进程收到SIGABRT信号而异常终止
4.unsigned int alarm(unsigned int seconds);
alarm也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。如果忽略或者不捕获此信号,则其默认动作是终止调用该alarm函数的进程。
参数:seconds:指定秒数
返回值:成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。出错:-1
5.int setitimer(int which, const struct itimerval *value, struct itimerval *oldvalue);
可定时发送信号
which可指定三种信号类型 :SIGALRM、SIGVTALRM 和 SIGPROF;
struct itimerval 的成员 it_interval定义间隔时间,it_value 为0时,使计时器失效;
6.int pause(void); 将进程挂起等待信号
信号集及信号集操作
1.int sigfillset(sigset_t *set); 设置所有的信号到set信号集中(将所有信号对应的bit设置为1)
2.int sigemptyset(sigset_t *set); 从set信号集中清空所有信号(将所有信号对应的bit设置为0)
3.int sigaddset(sigset_t *set,int signo);在set信号集中加入signo信号;
4.int sigdelset(sigset_t *set,int signo);在set信号集中删除signo信号;
5.int sigismember(const sigset_t *set, int signo)判断一个信号集中是否包含某种信号
屏蔽信号相关函数
1.int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
根据how值,设置或改变屏蔽信号集
SIG_BLOCK 表示对某种信号进行屏蔽,由set决定
SIG_UNBLOCK 解除被屏蔽的信号
SIG_SETMASK 新的屏蔽集代替旧的
2.int sigpending(sigset_t *set); 获取在阻塞中的所有信号
3.int sigsuspend(const sigset_t *set); 类似于 pause()函数
timer定时器就相当于系统每隔一段时间给进程发一个定时信号,我们所要做的是定义一个信号处理函数
//SIGALRM 定时器超时信号
//alarm 超时产生SIGALRM信号,只有一次
//itimerval 超时产生SIGALRM信号,一直循环下去
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
int flag = 0;
void my_handler1(int signo){
flag = 1;
}
void my_handler2(int signo){
printf("get alarm...\n");
}
int main(void){
int i;
struct sigaction act;
memset(&act, 0, sizeof(act));
#if 1
act.sa_handler = my_handler1;
sigaction(SIGALRM, &act, NULL);
alarm(1);
for(i = 0; !flag; i++)
printf("i : %d\n", i);
#else
struct itimerval it;
act.sa_handler = my_handler2;
sigaction(SIGALRM, &act, NULL);
it.it_value.tv_sec = 1;
it.it_value.tv_usec = 0;
it.it_interval = it.it_value;
setitimer(ITIMER_REAL, &it, NULL);
while(1);
#endif
return 0;
}
//在多线程环境下,产生的信号是传递给整个进程的,所有线程都有机会收到这个信号,进程在收到信号的的线程上下文执行信号处理函数,具体是哪个线程执行的难以获知。信号会随机发个该进程的一个线程
//多线程中,给指定线程处理信号
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#define NUM 5
sigset_t set;
void sig_int(int signo){
printf("%u catch sigint...\n", (unsigned)pthread_self());
}
void *tfn(void *arg){
int i = 0;
i = (int)arg;
if(i != 2)
pthread_sigmask(SIG_BLOCK, &set, NULL); //函数来屏蔽某个线程对某些信号的响应处理,仅留下需要处理该信号的线程来处理指定的信号
while(1)
pause();
return NULL;
}
int main(void){
struct sigaction act;
pthread_t tid[NUM]; int i;
sigemptyset(&set);
sigaddset(&set, SIGINT);
memset(&act, 0, sizeof(act));
act.sa_handler = sig_int;
sigaction(SIGINT, &act, NULL);
for(i = 0; i < NUM; i++){
pthread_create(&tid[i], NULL, tfn, (void *)i);
printf("%dth %u create...\n", i + 1, (unsigned)tid[i]);
}
printf("the main %u\n", (unsigned)pthread_self());
pthread_sigmask(SIG_BLOCK, &set, NULL);
pthread_kill(tid[2], SIGINT);//向tid[2]线程传递SIGINT信号
for(i = 0; i < NUM; i++)
pthread_join(tid[i], NULL);//以阻塞的方式等待线程结束。当函数返回时被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回
return 0;
}
//进程正在执行某个系统调用,在该系统调用返回前信号是不会被递送的。
//但慢速系统调用除外,如读写终端、网络、磁盘,以及wait和pause。
//当系统调用被中断时,可以选择使用循环再次调用,或者设置重新启动该系统调用(SA_RESTART)
//给信号设置了SA_RESTART,当执行某个阻塞系统调用时,收到该信号时进程不会返回,而是重新执行该系统调用
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
void myhandler(int singo){
printf("catch sigint...\n");
}
int main(void){
int n; char buf[1024];
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = myhandler;
act.sa_flags = SA_RESTART;
sigaction(SIGINT, &act, NULL);
n = read(STDIN_FILENO, buf, 1024);
if(n == -1){//act.sa_flags=SA_RESTART如果信号中断了进程的某个系统调用,则系统自动启动该系统调用,调用信号处理函数
if(errno == EINTR)
printf("sys_call interrupted by signal...\n");
else
perror("error");
}else
write(STDOUT_FILENO, buf, n);
return 0;
}
SA_RESTART
//进程正在执行某个系统调用,在该系统调用返回前信号是不会被递送的。
//但慢速系统调用除外,如读写终端、网络、磁盘,以及wait和pause。
//当系统调用被中断时,可以选择使用循环再次调用,或者设置重新启动该系统调用(SA_RESTART)
//给信号设置了SA_RESTART,当执行某个阻塞系统调用时,收到该信号时进程不会返回,而是重新执行该系统调用
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
void myhandler(int singo){
printf("catch sigint...\n");
}
int main(void){
int n; char buf[1024];
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = myhandler;
act.sa_flags = SA_RESTART;
sigaction(SIGINT, &act, NULL);
n = read(STDIN_FILENO, buf, 1024);
if(n == -1){//act.sa_flags=SA_RESTART如果信号中断了进程的某个系统调用,则系统自动启动该系统调用,调用信号处理函数
if(errno == EINTR)
printf("sys_call interrupted by signal...\n");
else
perror("error");
}else
write(STDOUT_FILENO, buf, n);
return 0;
}
sigqueue
//进程1阻塞等待信号sigint,进程2给进程1发送信号sigint
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
void my_handler(int signo, siginfo_t *info, void *context){
int *p;
p = (int *)info->si_value.sival_ptr;
printf("value : %d\n", *p);
}
int main(void){
struct sigaction act;
int *p; pid_t pid;
sigset_t set, old;
sigemptyset(&set);
sigaddset(&set, SIGINT);//将SIGINT信号添加信号屏蔽集
//用于改变进程的当前阻塞信号集
//一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集
//1.how = SIG_BLOCK:将newset所指向的信号集中所包含的信号加到当前的信号掩码中,作为新的信号屏蔽字
//2.how = SIG_UNBLOCK:将参数newset所指向的信号集中的信号从当前的信号掩码中移除
//3.how = SIG_SETMASK:设置当前信号掩码为参数newset所指向的信号集中所包含的信号
//set: 指新设的信号集.如果仅想读取现在的屏蔽值置为NULL
//oldset: 存放原来的信号集
sigprocmask(SIG_BLOCK, &set, &old);
sigdelset(&old, SIGINT);//将信号SIGINT从信号集里删除
memset(&act, 0, sizeof(act));
act.sa_sigaction = my_handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &act, NULL);
p = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
pid = fork();
if(pid == 0)
//在接收到某个信号之前,临时用mask替换进程的信号屏蔽集,并暂停进程执行,直到收到信号为止
sigsuspend(&old);
else{
sigval_t sigval;
*p = 100;
sigval.sival_ptr = (void *)p;
//sigqueue向一个进程发送信号,同时也可以带数据
sigqueue(pid, SIGINT, sigval);
wait(NULL);//等待直到一个进程标识终止
}
return 0;
}
inotify
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/inotify.h>
#include <signal.h>
#include <dirent.h>
#include <sys/mman.h>
#include "err.h"
pid_t *sp;
int disp_dir(const char *dir_name){
int i; DIR *dp;
struct dirent *p;
dp = opendir(dir_name);
if(dp == NULL)
return -1;
i = 0;
while(p = readdir(dp)){
if(p->d_name[0] != '.'){
printf("%s", p->d_name);
if(i == 4){
printf("\n");
i = 0;
}else{
printf(" ");
i++;
}
}
}
printf("\n");
closedir(dp);
return 0;
}
void sig_usr1(int signo){
/* do nothing... */
}
int watch_dir(const char *dir_name){
int ifd; int wd, n;
char buf[1024]; int size;
struct inotify_event *p;
ifd = inotify_init();
wd = inotify_add_watch(ifd, dir_name, IN_CREATE);
while(1){
n = read(ifd, buf, 1024);
size = 0; p = (struct inotify_event *)buf;
while(size < n){
if(p->mask & IN_CREATE){
kill(sp[0], SIGUSR1);
}
size += sizeof(struct inotify_event) + p->len;
p = (struct inotify_event *)&buf[size];
}
}
close(ifd);
return 0;
}
int main(void){
int i; pid_t pid;
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = sig_usr1;
sigaction(SIGUSR1, &act, NULL);
sp = (pid_t *)mmap(NULL, sizeof(pid_t) * 2, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
for(i = 0; i < 2; i++){
if((pid = fork()) == 0)
break;
sp[i] = pid;
}
if(i == 0){//1st child 显示当前目录,等待信号到达,更新显示目录
while(1){
disp_dir(".");//显示当前目录的文件
pause();//使调用进程挂起直至捕捉到一个信号,pause才返回
}
}else if(i == 1){//2nd child 监控当前目录 如有新文件生成,给子进程1发信号
watch_dir(".");
}else{ //parent
for(i = 0; i < 2; i++)
wait(NULL);
}
return 0;
}
信号种类
kill -l 查看所有信号
- SIGHUP:当用户退出Shell时,由该Shell启动的所有进程都接收到这个信号(默认动作为终止进程)
- SIGINT:从键盘按下ctrl+\,用户端时向正在运行中的由该终端启动的程序发出此信号(默认动作为终止进程)
- SIGQUIT:从键盘按下quit键(即ctrl+),用户终端向正在运行中的由该终端启动的程序发出此信号(默认动作为终止进程并产生core文件)
- SIGILL :CPU检测到某进程执行了非法指令(默认动作为终止进程并产生core文件)
- SIGTRAP:该信号由断点指令或其他trap指令产生(默认动作为终止进程并产生core文件)
- SIGABRT:调用abort函数时产生该信号(默认动作为终止进程并产生core文件)
- SIGBUS:非法访问内存地址,包括内存地址对齐(alignment)出错(默认动作为终止进程并产生core文件)
- SIGFPE:在发生致命的算术错误时产生。不仅包括浮点运行错误,还包括溢出及除数为0等所有的算术错误。默认动作为终止进程并产生core文件
- SIGKILL:无条件终止进程。本信号不能被忽略、处理和阻塞。默认动作为终止进程。它向系统管理员提供了一种可以杀死任何进程的方法
- SIGUSR1:用户定义的信号,即程序可以在程序中定义并使用该信号(默认动作为终止进程)
- SIGSEGV:指示进程进行了无效的内存访问。默认动作为终止进程并使用该信号(默认动作为终止进程)
- SIGUSR2:这是另外一个用户定义信号,程序员可以在程序中定义并使用该信号(默认动作为终止进程)
- SIGPIPE:Broken pipe:向一个没有读端的管道写数据(默认动作为终止进程)
- SIGALRM:定时器超时,超时的时间由系统调用alarm设置(默认动作为终止进程)
- SIGTERM:程序结束(terminate)信号,与SIGKILL不同的是,该信号可以被阻塞和处理。通常用来要求程序正常退出。执行Shell命令kill时,缺少产生这个信号。默认动作为终止进程
- SIGCHLD:子程序结束时,父进程会收到这个信号(默认动作为忽略该信号)
- SIGCONT:让一个暂停的进程继续执行
- SIGSTOP:停止(stopped)进程的执行。注意它和SIGTERM以及SIGINT的区别:该进程还未结束,只是暂停执行。本信号不能被忽略、处理和阻塞。默认作为暂停进程
- SIGTSTP:停止进程的动作,但该信号可以被处理和忽略。按下组合键时发出该信号(默认动作为暂停进程)
- SIGTTIN:当后台进程要从用户终端读数据时,该终端中的所有进程会收到SIGTTIN信号(默认动作为暂停进程)
- SIGTTOU:该信号类似于SIGTIN,在后台进程要向终端输出数据时产生(默认动作为暂停进程)
- SIGURG:套接字(socket)上有紧急数据时,向当前正在运行的进程发出此信号,报告有紧急数据到达(默认动作为忽略该信号)
- SIGXCPU:进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程(默认动作为终止进程)
- SIGXFSZ:超过文件最大长度的限制(默认动作为终止进程并产生core文件)
- SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是它只计算该进程占有用的CPU时间(默认动作为终止进程)
- SIGPROF:类似于SIGVTALRM,它不仅包括该进程占用的CPU时间还抱括执行系统调用的时间(默认动作为终止进程)
- SIGWINCH:窗口大小改变时发出(默认动作为忽略该信号)
- SIGIO:此信号向进程指示发出一个异步IO事件(默认动作为忽略)
- SIGPWR:关机(默认动作为终止进程)
- SIGRTMIN~SIGRTMAX:Linux的实时信号,它没有固定的含义(或者说可以由用户自由使用)。注意,Linux线程机制使用了前3个实时信号。所有的实时信号的默认动作都是终止进程