Linux-信号

信号

一、信号本质及信号来源

1.1-信号本质
信号可以说是在软件层次上,对中断机制的一种模拟,在原理上,一个进程收到的一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作等待信号到达,事实上进程也不知道进程什么时候可以到达。

1.2信号来源

信号的发生可以有两个来源

  1. 硬件来源:比如我们按下键盘或是其他的硬件故障
  2. 软件来源:最常用的发送信号的系统函数是killraisealarmsetitimer以及sigqueue函数,软件来源还包括一些非法的运算符操作

二、信号分类

  • 信号分为可靠信号和不可靠信号

    • 不可靠信号的缺点 ①:处理完信号,需要重新再注册信号;②信号可能丢失。
    • Linux已经对缺点①做了优化,现在的不可靠问题主要指的是信号可能丢失
  • 信号还可以分为实时信号和非实时信号

    • 一般不可靠信号指的是前32种信号,这32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作
    • 可靠信号指的后32种信号
    • 非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

可以通过 kill -l 命令来查看所有信号:
Linux-信号

三、信号机制

3.1-信号在目标进程中注册

  • 在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的。·
  • 信号在进程中注册指的就是信号值加入到进程的未决信号集 sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞
  • Linux-信号
    Linux-信号

四、进程对信号的响应

4.1-进程可以通过三种方式来响应一个信号:

  • 忽略信号:即对信号不做任何处理,其中 SIGKILLSIGSTOP不能忽略
  • 捕捉信号:定义信号处理函数,当信号发生时执行对应的处理函数,详情见4.3
  • 执行默认操作:Linux每种信号都对应了默认操作,详情见4.2

4.2-信号的默认处理动作

  • Term表示终止当前进程.
  • Core表示终止当前进程并且Core Dump(Core Dump 用于gdb调试)
  • Ign表示忽略该信号,其中 SIGKILL 和 SIGSTOP不能忽略
  • Stop表示停止当前进程.
  • Cont表示继续执行先前停止的进程.

Linux-信号
4.3-信号的安装——设置信号关联动作

  • 如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作
  • linux主要有两个函数实现信号的安装:signal()sigaction()
  • 其中·signal()·只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue()系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数

4.3.1 - signal()函数

  • 如果想让程序能够处理信号,可以使用signal库函数,要引入头文件<signal.h>

  • 函数定义:typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);

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

  • 举例:
    Linux-信号

4.3.2 - sigaction()函数

  • 包含头文件<signal.h>

  • 功能:sigaction函数用于改变进程接收到特定信号后的行为。

  • 函数原型:

    • int sigaction (int signum , const struct sigaction *act , const struct sigaction *old);
  • 参数

    • 第一个参数signum为信号的值,可以为除sigkillsigstop外的任何一
      个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)

    • 第二个参数act是指向结构体sigaction的一个实例的指针,在结构
      sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理.
      第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些信号等等。

    • 第三个参数oldact指向的对象用来保存原来对相应信号 的处理,如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性

  • 返回值:函数成功返回0,失败返回-1

五、发射信号的常用函数

5.1 - kill()函数

  • 函数原型及头文件
    #include <sys/type.h>
    #include <signal.h>
    int kill (pid_t pid,int signo)
  • 参数说明:
    • 该系统调用可以用来向任何进程或进程组发送任何信号。
    • 参数pid的值为信号的接收进程:
      pid>0进程ID为pid的进程
      pid=0同一个进程组的进程
      pid<0 pid!=-1 进程组ID为 -pid的所有进程
      pid=-1 除发送进程自身外,所有进程ID大于1的进程
    • Signo是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,因此,可用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)
  • 举例应用:
#include<stdio.h>

#include<unistd.h>

#include<stdlib.h>

#include<signal.h>

//创建5个子进程,父杀死第三子进程 

#define N 5



int main(void)

{

	pid_t pid,p;

	int i=0; 

	for(i=0;i<N;i++)

	{

		pid=fork();

		if(pid==0)//如果是子进程则退出循环 

			break;

		if(i==2)//获取第三个进程的id=p 

			p=pid;

	}

	

	if(i<5)//子进程执行 

	{

		while(1){

			printf("I'am child %d,getpid = %u\n",i,getpid());

			sleep(2); 

		}

		

		 

	}

	else//父进程执行 

	{

		printf("kill\n");

		kill(p,SIGKILL);

		while(1);

	}

	return 0;



}

5.2 - alarm()函数

  • 函数原型及头文件:

    #include <unistd.h>

    unsigned int alarm(unsigend int seconds)

  • 系统调用alarm安排内核为调用进程在指定的seconds秒后发出一个SIGALRM的信号。如果指定的参数seconds为0,则不再发送 SIGALRM信号。后一次设定将取消前一次的设定。该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用而返回0。

  • 注意:在使用时,alarm只设定为发送一次信号,如果要多次发送,就要多次使用alarm调用。

5.2 - abort()函数

  • 函数原型及头文件:

    #include <stdlib.h>

    void abrot(void);

  • 向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数

  • 即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。

5.2 - raise()函数

  • 函数原型及头文件:

    #include <signal.h>

    int raise(int signo);

  • 向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。

5.2 - setitimer()函数

  • 包含头文件<sys/time.h>

  • 功能setitimer()比alarm功能强大,支持3种类型的定时器

  • 原型

    int setitimer(int which , const struct itimerval *value , struct itimerval *ovalue));

  • 参数

    • 第一个参数which指定定时器类型

    • 第二个参数是结构itimerval的一个实例,结构itimerval形式

    • 第三个参数可不做处理。

  • 三种定时器类型

    • itimer_real: 设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程

    • itimer_virtual : 设定程序执行时间,经过指定的时间后,内核将发送SIGVTALRM信号给本进程

    • itimer_prof : 设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送SIGPROF信号给本进程

  • struct itimerval结构体
    Linux-信号

  • 应用示例:

#include<stdio.h>

#include<sys/time.h>

#include<signal.h>

//拦截定时器信号,打印helloworld 

void myfun(int signo)

{

	printf("hello world\n");

}

int main(void)

{

	struct itimerval it,oldit;

	//拦截定时器信号 并打印helloworld 

	signal(SIGALRM,myfun);//调用signal 捕捉注册 利用回调机制 

	

	it.it_value.tv_sec = 5;//定时的时长 

	it.it_value.tv_usec = 0;

	 

	it.it_interval.tvsec = 3;//间隔,两次定时任务之间间隔的时间 

	it.it_interval.tvusec = 0; 

	

	if(setitimer(ITIMER_REAL,&it,&oldit)==-1)

	{

		perror("timer error\n");

		return -1;

	}

	

	while(1);

	return 0;

}

六、信号集及信号集操作函数

  • 信号集被定义为一种数据类型:
    Linux-信号
  • 信号集用来描述信号的集合,每个信号占用一位。Linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:
    Linux-信号
    Linux-信号

#include<stdio.h>

#include<signal.h>

//看未决信号集,看几个信号被屏蔽,在对应的位置显示1; 

void printfped(sigset_t *ped)

{

	int i;

	for(i=1;i<32;i++)

	{

		if(sigismember(ped,i)==1)

		{

			putchar('1');

		}

		else

		{

			putchar('0');

		}

	}

	printf("\n");

}



int main(void)

{

	sigset_t myset,oldset,ped;

	//定义覆盖

	sigemptyset(&myset);

	sigaddset(&myset,SIGQUIT);

	sigaddset(&myset,SIGINT);

	sigprocmask(SIG_BLOCK,&myset,&oldset); 

	while(1)

	{

		

		sigpending(&ped);

		printfped(&ped);

		sleep(1);

	}

	return 0;

 }