信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件)。信号是硬件中断的软件模拟(软中断)。每个信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD( 子进程结束向父进程发送的一个信号 )、SIGINT(Ctrl+c)等,它们在系统头文件<signal.h>中定义,也可以通过在shell下键入kill –l查看信号列表,或者键入man 7 signal查看更详细的说明。
信号的生成来自内核,让内核生成信号的请求来自3个地方:
l 用户:用户能够通过输入CTRL+c(2号信号)、Ctrl+\ ( 产生的是三号信号,SIGQUIT ),或者是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号;
l 内核:当进程执行出错时,内核会给进程发送一个信号,例如非法段存取(内存访问违规)、浮点数溢出等;
由进程的某个操作产生的信号称为同步信号(synchronous signals),例如除0;由像用户击键这样的进程外部事件产生的信号叫做异步信号(asynchronous signals)。
进程接收到信号以后,可以有如下3种选择进行处理:
l 接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如连接到终端的进程,用户按下CTRL+c,将导致内核向进程发送一个SIGINT的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号,即终止进程的执行; signal(SIGINT,SIG_DFL);
l 忽略信号:进程可以通过代码,显示地忽略某个信号的处理,例如:signal(SIGINT,SIG_IGN);但是某被些信号是不能忽略的,例如9号信号;
l 捕捉信号并处理:进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并且处理信号。
可以用函数signal注册一个信号捕捉函数。原型为:
#include <signal.h>
typedef void (*sighandler_t)(int); //函数指针(钩子,也就是回调函数)
sighandler_t signal(int signum, sighandler_t handler);
signal的第1个参数signum表示要捕捉的信号,第2个参数是个函数指针,表示要对该信号进行捕捉的函数,该参数也可以是SIG_DFL(表示交由系统缺省处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任何处理)。
sighandler_t 是信号捕捉函数,由 signal 函数注册,注册以后,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号捕捉函数。该函数只有一个整型参数,表示信号值。signal.c | signal_sleep.c |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_func(int sig)
{
printf("sig num=%d\n",sig);
}
int main()
{
if( signal(SIGINT,sig_func)==SIG_ERR )
{
perror("signal");
return -1;
}
while(1);
return 0;
}
|
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_func(int sig)
{
printf("before sleep ,sig=%d is coming\n",sig);
sleep(3);
printf("after sleep ,sig=%d is coming\n",sig);
}
int main()
{
if( signal(SIGINT,sig_func)==SIG_ERR )
{
perror("signal");
return -1;
}
if( signal(SIGQUIT,sig_func)==SIG_ERR )
{
perror("signal");
return -1;
}
while(1);
return 0;
}
|
signal_ign.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_func(int sig)
{
printf("sig=%d is coming\n",sig);
}
int main()
{
if(signal(SIGINT,SIG_IGN)==SIG_ERR)
{
perror("signal");
return -1;
}
if(signal(SIGQUIT,sig_func)==SIG_ERR)
{
perror("signal");
return -1;
}
while(1);
return 0;
}
|
在signal处理机制下,还有许多特殊情况需要考虑:
1、 注册一个信号处理函数,并且处理完毕一个信号之后,是否需要重新注册,才能够捕捉下一个信号;(不需要)
2、 如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个同类型的信号,这时该怎么处理;(接着执行),后续相同信号忽略(会多执行一次)。
3、 如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个不同类型的信号,这时该怎么处理;(跳转去执行另一个信号,之后再执行剩下的没有处理完的信号)
4、 如果程序阻塞在一个系统调用 ( 如read(...) ) 时,发生了一个信号,这时是让系统调用返回错误再接着进入信号处理函数,还是先跳转到信号处理函数,等信号处理完毕后,系统调用再返回。(后者)
signal_read.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void sig_func(int sig)
{
printf("before sleep ,sig=%d is coming\n",sig);
sleep(3);
printf("after sleep ,sig=%d is coming\n",sig);
}
int main()
{
if( signal(SIGINT,sig_func )==SIG_ERR )
{
perror("signal");
return -1;
}
if( signal(SIGQUIT,sig_func)==SIG_ERR )
{
perror("signal");
return -1;
}
char buf[128]={0};
read(0,buf,sizeof(buf));
printf("buf=%s\n",buf);
return 0;
}
|
函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); //函数调用成功,将返回0,否则返回-1
sigaction也用于注册一个信号处理函数
参数signum为需要捕捉的信号
参数act是一个结构体,里面包含信号处理函数地址、处理方式等信息
参数oldact是一个传出参数,sigaction函数调用成功后,oldact里面包含以前对signum的处理方式的信息,通常为NULL
如果函数调用成功,将返回0,否则返回-1
结构体 struct sigaction (注意名称与函数sigaction相同)的原型为:
struct sigaction
{
void (*sa_handler)(int); //老类型的信号处理函数指针,不用,我们用最新的
void (*sa_sigaction)(int, siginfo_t *, void *); //新类型的信号处理函数指针
sigset_t sa_mask; //将要被阻塞的信号集合(sigset_t)
int sa_flags; //信号处理方式掩码 ( SA_SIGINFO )
void (*sa_restorer)(void); //保留,不要使用
};
|
该结构体的各字段含义及使用方式:
1、字段sa_handler是一个函数指针,用于指向原型为void handler(int)的信号处理函数地址,即老类型的信号处理函数(如果用这个再将sa_flags = 0,就等同于signal()函数)
2、字段sa_sigaction也是一个函数指针,用于指向原型为:
void handler(int iSignNum, siginfo_t *pSignInfo, void *pReserved); 的信号处理函数,即新类型的信号处理函数
该函数的三个参数含义为:
iSignNum :传入的信号
pSignInfo:与该信号相关的一些信息,它是个结构体
pReserved:保留,现没用,通常为NULL
3、字段 sa_handler 和 sa_sigaction 只应该有一个生效,如果想采用老的信号处理机制,就应该让 sa_handler 指向正确的信号处理函数,并且让字段 sa_flags 为0;否则应该让 sa_sigaction 指向正确的信号处理函数,并且让字段sa_flags包含SA_SIGINFO选项
4、字段sa_mask是一个包含信号集合的结构体,该结构体内的信号表示在进行信号处理时,将要被阻塞的信号。针对 sigset_t 结构体,有一组专门的函数对它进行处理,它们是:
sigset_t sa_mask; //将要被阻塞的信号集合
#include <signal.h>
int sigemptyset(sigset_t *set); //清空信号集合set
int sigfillset(sigset_t *set); //将所有信号填充进set中
int sigaddset(sigset_t *set, int signum); //往set中添加信号signum
int sigdelset(sigset_t *set, int signum); //从set中移除信号signum
int sigismember(const sigset_t *set, int signum); //判断signum是否包含在set中(是:返回1,否:0)
int sigpending(sigset_t *set); //将被阻塞的信号集合由参数set指针返回(挂起信号) //成功返回 0 , 失败返回 -1 .
****** 其中,对于函数sigismember而言,如果signum在set集中,则返回1;不在,则返回0;出错时返回-1。其他的函数都是成功返回0,失败返回-1.
例如,如果打算在处理信号SIGINT时,只阻塞对SIGQUIT信号的处理,可以用如下方法:
struct sigaction act;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = newHandler; //newHandler参数指定
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGQUIT);
sigaction(SIGINT,&act,NULL);
|
通过 man sigaction 查看 siginfo_t 的结构体
5、 字段sa_flags是一组掩码的合成值,指示信号处理时所应该采取的一些行为,各掩码的含义为:
掩码 | 描述 |
SA_RESETHAND | 处理完毕要捕捉的信号后,将自动撤消信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号。该选项不符合一般的信号处理流程,现已经被废弃。 |
SA_NODEFER | 在处理信号时,如果又发生了其它的信号,则立即进入其它信号的处理,等其它信号处理完毕后,再继续处理当前的信号,即递规地处理。(不常用)不断重入,次数不丢失 |
SA_RESTART | 如果在发生信号时,程序正阻塞在某个系统调用,例如调用read()函数,则在处理完毕信号后,接着从阻塞的系统返回。如果不指定该参数,中断处理完毕之后,read函数读取失败。 |
SA_SIGINFO | 指示结构体的信号处理函数指针是哪个有效,如果sa_flags包含该掩码,则 sa_sigaction 指针有效,否则是 sa_handler 指针有效。(常用) |
sigaction.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void sig(int signum,siginfo_t* p,void* p1)
{
printf("signum=%d is coming\n",signum);
}
int main()
{
struct sigaction act;
bzero(&act,sizeof(act));
act.sa_sigaction=sig;
act.sa_flags=SA_SIGINFO; //act.sa_flags=SA_SIGINFO|SA_RESTART;
int ret=sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
return -1;
}
while(1); // char buf[128]={0}; read(0,buf,sizeof(buf)); printf("buf=%s\n",buf);
return 0;
}
|
//不设置 act.sa_flags=SA_SIGINFO|SA_RESTART; //阻塞等待输入,如果在输入之前有信号中断,中断处理完就直接退出了 //设置 |
sigaction_mask.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
void sig(int signum,siginfo_t* p,void* p1)
{
printf("before signum=%d is coming\n",signum);
sleep(3);
printf("after signum=%d is coming\n",signum);
}
int main()
{
struct sigaction act;
bzero(&act,sizeof(act));
act.sa_sigaction=sig;
act.sa_flags=SA_SIGINFO|SA_RESTART;
int ret=sigaddset(&act.sa_mask,SIGQUIT); //sigaction执行中屏蔽SIGQUIT信号,不受影响,执行完后再执行过程中发生的SIGQUIT信号操作
if(-1==ret)
{
perror("sigaddset");
return -1;
}
ret=sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
return -1;
}
while(1);
return 0;
}
|
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 Linux 2.6.32) */
void *si_call_addr; /* Address of system call instruction (since Linux 3.5) */
int si_syscall; /* Number of attempted system call (since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call (since Linux 3.5) */
}
|
sigaction_siginfo.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void sig(int signum,siginfo_t* p,void* p1)
{
printf("signum=%d is coming\n",signum);
printf("sending signal process id=%d\n",p->si_pid);
printf("sending signal user id=%d\n",p->si_uid);
}
int main()
{
struct sigaction act;
bzero(&act,sizeof(act));
act.sa_sigaction=sig;
act.sa_flags=SA_SIGINFO;
int ret=sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
return -1;
}
while(1);
return 0;
}
|
//本窗口: id都为0,在其他窗口 kil -2 进程号id(表示向该进程发送2号信号(SIGINT)) 进程的id就是它自己,为用户id为当前登录用户 |
3.3. sigprocmask信号阻塞
函数sigaction中设置的被阻塞信号集合只是针对于要处理的信号,例如
struct sigaction act;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
sigaction(SIGINT,&act,NULL);
|
函数sigprocmask是全程阻塞,在sigprocmask中设置了阻塞集合后,被阻塞的信号将不能再被信号处理函数捕捉,直到重新设置阻塞信号集合。
原型为:
参数how 的值为如下3者之一:
a:SIG_BLOCK ,将参数2的信号集合添加到进程原有的阻塞信号集合中
b:SIG_UNBLOCK ,从进程原有的阻塞信号集合移除参数2中包含的信号
c:SIG_SETMASK,重新设置进程的阻塞信号集为参数2的信号集
参数set 为阻塞信号集
参数oldset 是传出参数,存放进程原有的信号集,通常为NULL
sigprocmask.c | sigpro_pending.c |
#include<stdio.h>
#include<signal.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
void sigFunc(int sig,siginfo_t *p,void *p1)
{
printf("before sleep , signum=%d\n",sig);
sleep(3);
printf("after sleep , signum=%d\n",sig);
}
int main(void)
{
sigset_t set;
memset(&set,0,sizeof(set));
sigaddset(&set,SIGINT);
int ret = sigprocmask(SIG_BLOCK,&set,NULL);
if(-1==ret)
{
perror("sigprocmask");
exit(-1);
}
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_sigaction = sigFunc;
act.sa_flags = SA_SIGINFO | SA_RESTART;
sigemptyset(&act.sa_mask);
ret = sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
exit(-1);
}
//while(1);
sleep(5);
printf("\nafter sleep , unblock SIG_INT\n");
ret=sigismember(&set,SIGINT); //查看信号SIGINT在不在指定集合中
if(ret !=1)
{
return -1;
}
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1);
return 0;
}
|
//将被阻塞的信号集合由参数set指针返回(挂起信号)
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
int ret ;
ret=sigprocmask(SIG_BLOCK,&set,NULL);
if(-1==ret)
{
perror("sigprocmask");
return -1;
}
sleep(5);
sigemptyset(&set); //把集合set清空
ret =sigpending(&set); //把阻塞的信号拿出来放到set中,有阻塞信号返回1,没有返回0
//这个阻塞不是前面设定好,就会找到,是设置好阻塞,在执行过程中触发了这个信号,但是没有处理,内核记录了这个信号发生。俗称挂起。
ret = sigismember(&set,SIGINT);
if(1==ret)
{
printf("SIGINT is in the set\n");
}
if(0==ret)
{
printf("SIGINT is not in the set\n");
}
return 0;
}
|
sigaction_mask_pending.c | |
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
void sig(int signum,siginfo_t* p,void* p1)
{
printf("before signum=%d\n",signum);
sleep(3);
sigset_t set;
sigemptyset(&set);
sigpending(&set);
if( sigismember(&set,SIGQUIT) )
{
printf("SIGQUIT is come , but pending\n");
}
else
{
printf("SIGQUIT is not come\n");
}
printf("after signum=%d\n",signum);
}
|
int main()
{
struct sigaction act;
bzero(&act,sizeof(act));
act.sa_sigaction=sig;
act.sa_flags=SA_SIGINFO|SA_RESTART;
int ret;
ret=sigaddset(&act.sa_mask,SIGQUIT); //sigaction执行中屏蔽SIGQUIT信号,不受影响,执行完后在来执行相应操作
if(-1==ret)
{
perror("sigaddset");
return -1;
}
ret=sigaction(SIGINT,&act,NULL);
if(-1==ret)
{
perror("sigaction");
return -1;
}
while(1);
return 0;
}
|
4. 用程序发送信号
kill.c | kill0.c |
#include<sys/types.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char* argv[])
{
if(argc!=2)
{
printf("error args\n");
return -1;
}
pid_t pid;
pid=atoi(argv[1]); //传递一个进程id号
int ret =kill(pid,SIGINT);
if(-1==ret)
{
perror("kill");
return -1;
}
return 0;
}
|
#include<sys/types.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
if(!fork())
{
printf("I am child\n");
while(1);
}
else
{
printf("I am parent\n");
sleep(10);
int ret=kill(0,SIGINT);
if(-1==ret)
{
perror("kill");
return -1;
}
wait(NULL);
printf("wait is execute\n");
}
return 0;
}
|
5.1.睡眠函数
alarm.c | pause.c |
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
//设定SIGALRM信号的处理行为,alarm(3),while(1)
void sigFunc(int sig)
{
printf("I am sig %d\n",sig);
}
int main(void)
{
signal(SIGALRM,sigFunc);
sleep(3); //等待3秒
printf("sleep over\n");
alarm(1);
printf("alarm over\n");
while(1); //3秒时候才产生一个SIGALRM信号,所以一定要等,不然程序结束了,看不到对应输出了
return 0;
}
|
#include<unistd.h>
int main()
{
pause();
return 0;
}
|
Linux为每个进程维护3个计时器,分别是真实计时器、虚拟计时器和实用计时器。
l 真实计时器计算的是程序运行的实际时间;---直接
l 虚拟计时器计算的是程序运行在用户态时所消耗的时间( 可认为是实际时间减掉(系统调用和程序睡眠所消耗)的时间 );---需要了解内核
l 实用计时器计算的是程序处于用户态和处于内核态所消耗的时间之和。(睡觉就不算)---常用
例如:有一程序运行,在用户态运行了5秒,在内核态运行了6秒,还睡眠了7秒,则真实计算器计算的结果是18秒,虚拟计时器计算的是5秒,实用计时器计算的是11秒。
用指定的初始间隔和重复间隔时间为进程设定好一个计时器后,该计时器就会定时地向进程发送时钟信号。3个计时器发送的时钟信号分别为:SIGALRM , SIGVTALRM 和 SIGPROF 。
用到的函数与数据结构:
#include <sys/time.h>
1. int getitimer(int which, struct itimerval *value); //获取计时器的设置;成功返回 0 , 失败返回 -1 ;
参数which 指定哪个计时器,可选项为ITIMER_REAL(真实计时器)、ITIMER_VIRTUAL(虚拟计时器、ITIMER_PROF(实用计时器))
参数value 为一结构体的传出参数,用于传出该计时器的初始间隔时间和重复间隔时间
返回值:如果成功,返回0,否则-1
2.int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue); //设置计时器 ; 成功返回 0,失败返回 -1 ;
参数which 指定哪个计时器,可选项为ITIMER_REAL(真实计时器)、ITIMER_VIRTUAL(虚拟计时器、ITIMER_PROF(实用计时器))
参数value 为一结构体的传入参数,指定该计时器的初始间隔时间和重复间隔时间
参数ovalue 为一结构体传出参数,用于传出以前的计时器时间设置(NULL)。
返回值:如果成功,返回0,否则-1
struct itimerval
{
struct timeval it_interval; /* next value */ //重复间隔
struct timeval it_value; /* current value */ //初始间隔
};
//初始间隔就是隔多少秒,发送SIGALRM信号;重复间隔就是以后每隔多少时间再发送SIGALRM信号
|
struct timeval
{
long tv_sec; /* seconds */ //时间的秒数部分
long tv_usec; /* microseconds */ //时间的微秒部分
};
|
setitimer.c //启用真实计时器的进行时钟处理(获得当前系统时间,并一秒更新一次) |
#include<sys/time.h>
#include<signal.h>
#include<stdio.h>
#include<time.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
void sig_handle(int sig)
{
time_t t;
time(&t); //取当前秒数;
struct tm* p=gmtime(&t); //ctime();很简单,这里就不用了;用的格林威治时间;
printf("%2d:%2d:%d\n",p->tm_hour,p->tm_min,p->tm_sec);
}
int main()
{
signal(SIGALRM,sig_handle);
kill(0,SIGALRM); //为了设定timer之前,就发一个SIGALRM信号打印时间,看看后面是不是间隔5秒发送SIGALRM信号,我们可以用kill来发送,当前运行程序的窗口id就是0;
//sig_handle(0); //参数随便传
struct itimerval rt;
memset(&rt,0,sizeof(rt)); //这里全部赋0,后面就不用设置微妙0了;
rt.it_value.tv_sec=5;
rt.it_interval.tv_sec=2; //设置初始间隔和重复间隔,微秒我们也感应不出来,就不设置了;也可以设置为0 ;
int ret=setitimer(ITIMER_REAL,&rt,NULL);
if(-1==ret)
{
perror("setitimer");
return -1;
}
while(1);
return 0;
}
|