linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)

信号(signal)是一种软件中断,它提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式。在Linux系统中,根据POSIX标准扩展以后的信号机制,不仅可以用来通知某种程序发生了什么事件,还可以给进程传递数据。

一、信号的来源

信号的来源可以有很多种试,按照产生条件的不同可以分为硬件和软件两种。

1、  硬件方式

当用户在终端上按下某键时,将产生信号。如按下组合键后将产生一个SIGINT信号。

硬件异常产生信号:除数据、无效的存储访问等。这些事件通常由硬件(:CPU)检测到,并将其通知给Linux操作系统内核,然后内核生成相应的信号,并把信号发送给该事件发生时正在进行的程序。

2、  软件方式

用户在终端下调用kill命令向进程发送任务信号。进程调用killsigqueue函数发送信号。当检测到某种软件条件已经具备时发出信号,如由alarmsettimer设置的定时器超时时将生成SIGALRM信号。

二、信号的种类

Shell下输入kill –l 可显示Linux 系统支持的全部依赖,信号列表如下:

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第1张图片

信号的值定义在signal.h中,在Linux中没有1632这两个信号。上面信号的含义如下:

(1) SIGHUP:当用户退出Shell时,由该Shell启的发所有进程都退接收到这个信号,默认动作为终止进程。

(2) SIGINT:用户按下组合键时,用户端时向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。

(3) SIGQUIT:当用户按下组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程并产生core文件。

(4) SIGILL CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件。

(5) SIGTRAP:该信号由断点指令或其他trap指令产生。默认动作为终止进程并产生core文件。

(6) SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。

(7) SIGBUS:非法访问内存地址,包括内存地址对齐(alignment)出错,默认动作为终止进程并产生core文件。

(8) SIGFPE:在发生致命的算术错误时产生。不仅包括浮点运行错误,还包括溢出及除数为0等所有的算术错误。默认动作为终止进程并产生core文件。

(9) SIGKILL:无条件终止进程。本信号不能被忽略、处理和阻塞。默认动作为终止进程。它向系统管理员提供了一种可以杀死任何进程的方法。

(10) SIGUSR1:用户定义的信号,即程序可以在程序中定义并使用该信号。默认动作为终止进程。

(11) SIGSEGV:指示进程进行了无效的内存访问。默认动作为终止进程并使用该信号。默认动作为终止进程。

(12) SIGUSR2:这是另外一个用户定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。

(13) SIGPIPEBroken pipe:向一个没有读端的管道写数据。默认动作为终止进程。

(14) SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。

(15) SIGTERM:程序结束(terminate)信号,与SIGKILL不同的是,该信号可以被阻塞和处理。通常用来要求程序正常退出。执行Shell命令kill时,缺少产生这个信号。默认动作为终止进程。

(16) SIGCHLD:子程序结束时,父进程会收到这个信号。默认动作为忽略该信号。

(17) SIGCONT:让一个暂停的进程继续执行。

(18) SIGSTOP:停止(stopped)进程的执行。注意它和SIGTERM以及SIGINT的区别:该进程还未结束,只是暂停执行。本信号不能被忽略、处理和阻塞。默认作为暂停进程。

(19) SIGTSTP:停止进程的动作,但该信号可以被处理和忽略。按下组合键时发出该信号。默认动作为暂停进程。

(20) SIGTTIN:当后台进程要从用户终端读数据时,该终端中的所有进程会收到SIGTTIN信号。默认动作为暂停进程。

(21) SIGTTOU:该信号类似于SIGTIN,在后台进程要向终端输出数据时产生。默认动作为暂停进程。

(22) SIGURG:套接字(socket)上有紧急数据时,向当前正在运行的进程发出此信号,报告有紧急数据到达。默认动作为忽略该信号。

(23) SIGXCPU:进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程。默认动作为终止进程。

(24) SIGXFSZ:超过文件最大长度的限制。默认动作为yl终止进程并产生core文件。

(25) SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是它只计算该进程占有用的CPU时间。默认动作为终止进程。

(26) SIGPROF:类似于SIGVTALRM,它不仅包括该进程占用的CPU时间还抱括执行系统调用的时间。默认动作为终止进程。

(27) SIGWINCH:窗口大小改变时发出。默认动作为忽略该信号。

(28) SIGIO:此信号向进程指示发出一个异步IO事件。默认动作为忽略。

(29) SIGPWR:关机。默认动作为终止进程。

(30) SIGRTMIN~SIGRTMAXLinux的实时信号,它没有固定的含义(或者说可以由用户自由使用)。注意,Linux线程机制使用了前3个实时信号。所有的实时信号的默认动作都是终止进程。

1、可靠信号与不可靠信号

Linux系统中,信号的可靠性是指信号是否会丢失,或者说该信号是否支持排除。SIGHUP( 1 ) ~SIGSYS( 31 )之间的信号都是继承自UNIX系统是不可靠信号。Linux系统根据POSIX标准定义了SIGRTMIN(33) ~SIGRTMAX(64)之间的信号,它们都是可靠信号,也称为实时信号。

当导致产生信号的事件发生时,内核就产生一个信号。信号产生后,内核通常会在进程表中设置某种形的标志。当内核设置了这个标志,我们就说内核向一个进程递送了一个信号。信号产生(generate)和递送(delivery)之间的时间间隔,称主信号未决(pending)

进程可以调用sigpending将信号设为阻塞,如果为进程产生一个阻塞信号,而对信号的动作是捕捉该信号(即不忽略信号),则内核将为该进程的此信号保持为未决状态,直到该进程对此信号解除阻塞或者对此信号的响应更改为忽略。如果在进程解除对某个信号的阻塞之前,这种信号发生了多次,那么如果信号被递送多次(即信号在未决信号队列里面排队),则称之为可靠信号;只被递送一次的信号称为不可靠信号。

2、信号的优先级

信号实质上是软中断,中断有优先级,信号也有优先级。如果一个进程有多个未决信号,则对于同一个未决的实时信号,内核将按照发送的顺序来递送信号。如果存在多个未决信号,则值(或者说编号)越小的越先被递送。如果即存在不可靠信号,又存在可靠信号(实时信号),虽然POSIX对这一情况没有明确规定,但Linux系统和大多数遵循POSIX标准的操作系统一样,将优先递送不可靠信号。

三、进程对信号的响应

当信号发生时,用户可以要求进程以下列3种方式之一对信号做出响应。

1  捕捉信号:对于要捕捉的信号,可以为其指定信号处理函数,信号发生时该函数自动被调用,在该函数内部实现对该信号的处理。

2  忽略信号:大多数信号都可使用这种方式进行处理,但是SIGKILLSIGSTOP这两个信号不能被忽略,同时这两个信号也不能被捕获和阻塞。此外,如果忽略某某些由硬件异常产生的信号(如非法存储访问或除以0),则进程的行为是不可预测的。

3  按照系统默认方式处理。大部分信号的默认操作是终止进程,且所有的实时信号的默认动作都是终止进程。

四、各种信号的默认处理情况

程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP

不能恢复至默认动作的信号有:SIGILL,SIGTRAP

默认会导致进程流产的信号有:SIGABRTSIGBUSSIGFPESIGILLSIGIOTSIGQUITSIGSEGVSIGTRAPSIGXCPUSIGXFSZ

默认会导致进程退出的信号有:SIGALRMSIGHUPSIGINTSIGKILLSIGPIPESIGPOLLSIGPROFSIGSYSSIGTERMSIGUSR1SIGUSR2SIGVTALRM

默认会导致进程停止的信号有:SIGSTOPSIGTSTPSIGTTINSIGTTOU

默认进程忽略的信号有:SIGCHLDSIGPWRSIGURGSIGWINCH

五.信号函数说明

1、信号安装

(1)signal()
#include
void (*signal(int signum, void (*handler))(int)))(int);
如果该函数原型不容易理解的话,可以参考下面的分解方式来理解:
typedef void (*sighandler_t)(int)

sighandler_t signal(int signum, sighandler_t handler));
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)
如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR

直接上代码

#include 
#include 
#include 
void handle(int s)
{
    staticint count_2=0;
    staticint count_34=0;  
    if(s==2)
    {
       count_2++;
       printf("signalid %d count %d\n",s,count_2);  
    }
    elseif(s==34)
    {
       count_34++;
       printf("signalid %d count %d\n",s,count_34);
    }
    else
    {
       printf("signalid %d count\n",s);
    }  
}
int main()
{
    printf("pid%d\n",getpid());
    signal(2,handle);
    signal(11,handle);
    signal(34,handle);  
    while(1)
    {
       //printf("processruning %d\n",getpid());
       sleep(1);
    }
}

本例注册了三个信号,由系统默认转变为用户处理

此程序为了说明一下几点

->信号安装,信号发送

->可靠信号和不可靠信号的差异

运行下,从另外一个终端发送11信号,发现进入我们自己注册的信号处理函数

验证可靠性就不能用终端敲了,直接用一个程序验证

#include 
#include 
int main()
{
    int index=0;
    for(index=0;index<20;index++)
    {
       kill(21975,2);
    }
    for(index=0;index<20;index++)
    {
       kill(21975,34);
    }
}

此程序分别向pid 21975,就是我们上面运行的程序,分别发送20次信号2(不可靠)和信号34(可靠),观察下打印结果

结果如下

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第2张图片

不可靠信号只出现了一次,可靠信号出现了20

(2)sigaction()
#include
int sigaction(int signum,const struct sigaction *act,struct sigaction*oldact));

sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILLSIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldactNULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

Sigaction的结构体为

struct sigaction
{
       void(*sa_handle)(int);
       void(*sa_sigaction)(int,siginfo_t*,void*);
       sigset_t*mask;//屏蔽信号
       intflags;//SA_SIGINFO
       void**//保留成员.
 }

源码

#include 
#include 
#include 
void hanle1(int s,siginfo_t *info,void*d)
{
    printf("handles %d\n",info->si_int);
    sleep(5);
    printf("handlee\n");
}
int main()
{
    structsigaction act={0};
   
    printf("pid%d\n",getpid());
    //act.sa_handler=handle;
    act.sa_sigaction= hanle1;
    sigemptyset(&act.sa_mask);
    act.sa_flags=SA_SIGINFO;
    sigaction(SIGUSR1,&act,0);
 
    while(1);
}

此程序可以通过sigqueue(后面说明)发送带值得信号 

 2、发送信号函数

1 int raise(int sig); 

    这个比较容易理解,就是对当前进程发送指定信号

2 int pause(void);  将进程挂起等待信号

    直接上源码

#include
#include 
void deal(int s)
{
    printf("signal%d\n",s);
}
int main()
{
    signal(11,deal);
    printf("pid%d\n",getpid());
    printf("beforepasue\n");
    pause();
    printf("afterpasue\n");    
}

执行结果

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第3张图片

总结:如果用了pause,注册了相关的信号处理,那么就会唤醒,继续执行,从打印after pause就能看出,如果你发送了没有注册的信号,那么就会按照系统默认的动作去执行,可能会直接退出程序,这是我们不愿意看到的

3 int kill(pid_t pid,int sig); 通过进程编号发送信号

    上面测试signal的例子已经有用到kill,可供参考

    另外,补充killpid参数

    >0:发送信号到指定进程

    =0:发送信号到该进程所在进程组的所有进程

    -1:发送给所有进程,init

    <0:发送给指定的进程组(ID=绝对值)

4 unsigned int alarm(unsigned int seconds);指定时间(秒)发送(由内核发送给进程)SIGALRM信号。seconds 0 时取消所有已设置的alarm请求;

源码

#include 
#include 
void deal(int s)
{
    printf("getup\n");
    alarm(5);
}
int main()
{
    signal(SIGALRM,deal);
    alarm(5);
    while(1);   
}

此程序是注册下SIGALRM的信号,然后5s做一次信号中断

执行结果为5s打印一次,和我们预期的一样

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第4张图片

5int sigqueue(pid_t pid,int sig,const unionsigval val);类似于kill函数,多了附带共用体union sigval形数,将共用体中的成员 int sival_int  void *sival_ptr 的值传递给 信号处理函数中的定义类型 siginfo_t 中的 int si_int void *si_ptr

程序源码

#include 
#include 
#include 
int main()
{
    union sigvalval;
    val.sival_int=9999; 
    sigqueue(18963,SIGUSR1,val);
}

此程序的意思是像pid 18963的进程发送一个SIGUSR1,并且带有9999 value的信号

6int setitimer(int which,const structitimerval *value,struct itimerval *oldvalue); 此函数也是由kernel发送SIGLARM的信号,但是是每隔多久发送,一直发送;

int setitimer(int which,//计时方式//ITIMER_REAL / ITIMER_VIRTUAL /ITIMER_PROF

const struct itimerval *val,//定时器的时间参数

struct itimer *oldval);//返回原来设置的定时器//如果=NULL,则不返回

其中使用的结构体为

struct itimerval
{
    struct timevalit_interval;//间隔时间
    struct timeval it_value;//延时时间
}
struct timeval
{
    long tv_sec;
    long tv_usec;
}

源码:

#include 
#include 
#include 
void deal(int s)
{
    printf("getup\n");
}
int main()
{
    struct itimervalv={0};
    signal(SIGALRM,deal);
    v.it_value.tv_sec=10;
    v.it_interval.tv_sec=1;
    setitimer(ITIMER_REAL,&v,0);
    while(1);  
}

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第5张图片

7 void abort(void) 

 Abort函数不管进程有没有捕捉处理函数,都会退出当前进程,但是让进程步骤信号的用途是:让进程做必须的清理动作,如果信号处理函数不退出进程,那么当处理函数返回时,改进程也会退出

源码证明这点

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第6张图片

结果

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第7张图片

3、信号集及信号集操作

此部分为信号屏蔽做铺垫

#include

int sigemptyset(sigset_t *set); set信号集中清空所有信号;

int sigfillset(sigset_t *set); 设置所有的信号到set信号集中;

int sigaddset(sigset_t *set, int signum); set信号集中加入sig信号;

int sigdelset(sigset_t *set, int signum); set信号集中删除sig信号;

int sigismember(const sigset_t *set, int signum);判断一个信号是否在信号集内

4、阻塞信号相关函数

int sigprocmask(int how,const sigset_t *set,sigset_t*set);  根据how值,设置阻塞信号集,或释放阻塞的信号集

直接上程序

#include 
#include 
int main()
{
    int sum=0;
    int i;
    sigset_t sigs;
    sigemptyset(&sigs);
    sigaddset(&sigs,SIGINT);
    sigprocmask(SIG_BLOCK,&sigs,0);
    for(i=1;i<=10;i++)
    {
       sum+=i;
       sleep(1);
    } 
    printf("sum%d\n",sum);
    sigprocmask(SIG_UNBLOCK,&sigs,0);
    printf("over\n");
    while(1); 
}

执行结果

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第8张图片

int sigpending(sigset_t *set); 获取在阻塞中的所有信号;

在上面的例子中直接修改

#include 
#include 
int main()
{
    int sum=0;
    int i;
    sigset_t sigs;
    sigset_t sigp;
    sigemptyset(&sigs);
    sigemptyset(&sigp);
    sigaddset(&sigs,SIGINT);
    sigprocmask(SIG_BLOCK,&sigs,0);
    for(i=1;i<=10;i++)
    {
       sum+=i;
       sigpending(&sigp);
       if(sigismember(&sigp,SIGINT))
           printf("HaveSIGINT pending\n");
       sleep(1);
    }
    printf("sum%d\n",sum);
    sigprocmask(SIG_UNBLOCK,&sigs,0);
    printf("over\n");
    while(1); 
}

执行结果为

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第9张图片

int sigsuspend(const sigset_t *set);

此函数有点绕,比较难以理解,有以下三个作用

1)屏蔽新的信号,原来的信号失效.

2sigsuspend是阻塞函数.对参数信号屏蔽.

3)对参数没有指定的信号不屏蔽,如果指定的信号有user自己定义的动作,此函数才会返回

4)当sigsuspend返回的时候,恢复旧的屏蔽信号.

程序源码

#include 
#include 
#include 
#include 
void handle(int s)
{
    printf("USR2signal\n");
}
main()
{
    sigset_tsigs;
    sigemptyset(&sigs);
    sigaddset(&sigs,2);
    sigaddset(&sigs,10);
    signal(SIGUSR2,handle);
    sigsuspend(&sigs);   
    printf("over!\n");
}

此函数屏蔽了信号2,信号10,如果发送SIGUSR2发现程序返回

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第10张图片

此部分发送2信号无效,然后发送SIGUSR2才返回,但是此时恢复了信号2的处理,所以没有打印over

在来个比较高深的例子

#include 
#include 
void h(int s)
{
    printf("抽空处理int信号\n");
}
main()
{
    intsum=0;
    inti;
    //1.
    signal(SIGINT,h);
    sigset_tsigs,sigp,sigq;
    //2.
    sigemptyset(&sigs);
    sigemptyset(&sigp);
    sigemptyset(&sigq);
    
    sigaddset(&sigs,SIGINT);
    //3.
    sigprocmask(SIG_BLOCK,&sigs,0);
    for(i=1;i<=10;i++)
    {
       sum+=i;
       sigpending(&sigp);
       if(sigismember(&sigp,SIGINT))
       {
           printf("SIGINT在排队!\n");
           sigsuspend(&sigq);
           //使原来屏蔽信号无效,开放原来信号
           //使新的信号屏蔽,
           //当某个信号处理函数处理完毕
           //sigsuspend恢复原来屏蔽信号,返回 
       }
       sleep(1);
    }
    printf("sum=%d\n",sum);
    sigprocmask(SIG_UNBLOCK,&sigs,0);
    printf("Over!\n");
}

执行结果,在处理的时候此时我们按下ctrl+c就会打印此部分内容

linux学习---信号(signal,sigaction,kill,sigqueue,sigprocmask,sigpending,sigsuspend)_第11张图片

程序分析:

先注册屏蔽SIGINT2的信号,如果检测到你按下ctrl+c,就会使用sigsuspend是原有信号失效,然后去调用信号处理函数,然后此时sigsuspend返回,回复原有的人信号屏蔽,这部分好绕

部分理论知识内容参考:http://blog.chinaunix.net/uid-25002135-id-3300821.html

 

 

你可能感兴趣的:(linux开发)