linux信号基本概念

linux信号基本概念

1.信号和中断比较

中断相关概念:
中断是系统对于异步事件的响应(进程执行代码的过程中可以随时被打断,然后去执行异常处理程序,中断信号,中断源,现场信息,中断处理程序,中断向量表。

计算机中的中断场景:
中断源发出中断信号,cpu判断中断是否屏蔽,保护现场,cpu执行中断处理程序,cpu恢复现场,继续原来任务。

中断其他概念:
中断向量表保存了中断处理程序的入口地址
中断个数固定,操作系统启动时初始化中断向量表
中断有优先级
中断可以屏蔽

信号概念:
1.信号是系统响应一些状况而产生的事件,进程在接收到信号时会采取相应的行动
2.信号是因为某些错误条件而产生的,比如内存段冲突,非法指令等等
3.信号是在软件层次上对中断的一种模拟,也称为软中断。

信号和中断的异同:
相似点:
1.都是异步通信方式。
2.当检测出有信号或中断,都是暂停正在执行的程序而去执行相应的处理程序。
3.都在处理完毕后回到原来的断点。
4.对中断和信号都可以进行屏蔽。
区别:
1.中断有优先级,而信号没有优先级,所有信号都是平等的。
2.信号处理程序是在用户态下运行的,而中断处理程序则是在核心态下运行的。
3.中断响应是及时的,而信号响应通常有较大的时间延迟。

2.信号的生命周期

2.1.信号产生
信号事件的发生有两个来源:硬件来源(比如我们按下键盘或者其他硬件故障);软件来源(系统函数kill,raise,alarm,sigqueue等等函数,此外还有一些非法运算操作)。
2.1.1.通过终端按键产生信号
ctrl+z 产生SIGTSTP , ctrl+c产生SIGINT,ctrl+\产生SIGQUIT
2.1.2通过调用系统函数向进程发送信号
kill -x函数,比如9(SIGKILL)就是杀死一个进程的信号。
2.1.3通过软件异常产生信号
在socket通信中,使用管道,如果服务器关闭进入time–wait,客户端继续往(管道破裂)管道写,就会产生SIGPIPE信号,默认动作关闭客户端程序。
2.1.4通过硬件异常产生信号
当运算中以0为除数,则cpu的运算会检测到除0异常,并发送SIGFPE信号

在这里插入图片描述

2.2.信号在进程中注册
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。
进程的task_struct结构中有关于本进程中未决信号的数据成员:

struct sigpending{

    struct sigqueue *head, *tail;

    sigset_t signal;

};
第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:

struct sigqueue{

    struct sigqueue *next;

    siginfo_t info;

}

信号在进程中注册:
是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
可靠信号的注册:
当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册)。
不可靠信号的注册:
当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册(通过sigset_t signal指示),则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。
2.3.信号在进程中注销
对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕)。否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号。
内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返*回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。
2.4.信号处理函数执行
因为信号是操作系统发给进程来通知某个事件的到来,所以对信号的处理也就是对事件的处理。
信号的处理方式:
1.默认处理方式:就是操作系统为每一种信号准备的对应的处理方式
2.忽略:什么的不做,(SIGKLL 和SIGSTOP)不能忽略
3.自定义:自己写一个回调函数
在这里插入图片描述

信号的捕捉流程:
1.在主函数时因为异常或者中断或者系统调用进入内核态。
2.处理完异常后开始处理信号
3.调用用户自定义的回调函数,返回用户态
4.回调函数执行完毕,通过系统调用sigreturn返回内核态
5.信号处理完毕,调用sys_sigreturn()返回到一开始主函数被中断的地方,继续执行下面的语句

3.信号分类

不可靠性信号和可靠信号
linux下的不可靠信号问题主要指的是信号可能丢失。
可靠信号支持排队,不会丢失。
实时信号和非实时信号
前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。也是非实时信号。后面信加的是实时信号。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
在这里插入图片描述

4.信号的安装函数

signal函数:

    #include <signal.h>

   typedef void (*sighandler_t)(int);

   sighandler_t signal(int signum, sighandler_t handler);

准备捕捉或屏蔽的信号由参数signum指出,接收到指定信号时将要调用的函数由handler给出。handler也可以是下面的特殊值:
SIG_IGN 屏蔽该信号
SIG_DFL 恢复默认行为

#include <stdio.h>
#include <signal.h>

void func(int sig)
{
	printf("recv sig:%d\n",sig);
	if(sig==SIGINT)
	{
	     printf("ctrl + c\n");
	}
}
int main()
{
	 signal(SIGINT,func);
	int n = 10;
	printf("main ....begin\n");
    do 
	{
		n = sleep(n);  //sleep是可中断睡眠,让进程睡够 
		printf("sleep==================\n");
	} while(n > 0);
	printf("sleep ....end\n");
	char temp;
	while((temp=getchar())!='a')
	{
	      pause();
	}
	signal(SIGINT,SIG_DFL);//是ctrl+c 会产生2号信号  默认中断应用程序
	while(1)
	{
	    pause();
	}
	printf("main...end\n");//没有执行机会
    return 0;
}

运行结果:
在这里插入图片描述

sigaction函数:

   #include <signal.h>

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

signum为信号的值,第二个参数可以为空(进程会以缺省方式对信号处理)
第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。函数成功返回0,失败返回-1。
struct sigaction:

 The sigaction structure is defined as something like:

       struct sigaction {
           void     (*sa_handler)(int); //信号处理函数,不接受额外数据
           void     (*sa_sigaction)(int, siginfo_t *, void *);//可以接受数据,配合sigqueue使用
           sigset_t   sa_mask;  //信号屏蔽集
           int        sa_flags;    //影响信号的行为,SA_SIGINFO表示能接受数据
           void     (*sa_restorer)(void);
       };
#include <stdio.h>
#include <signal.h>

void func1(int sig)
{
	printf("func1 recv a sig:%d\n",sig);
	if(sig==SIGINT)
	   printf("void (*sa_handler)(int)\n");
}
void func2(int sig,siginfo_t *pt,void *p)
{
	 printf("func2 recv a sig:%d\n",sig);
}
int main()
{
	struct sigaction act;
	// act.sa_handler=func1;
	act.sa_sigaction=func2;
	sigaction(SIGINT,&act,NULL);
	printf("start........\n");
	while(1)
	{
	    pause();
	}
    return 0;
}

测试sa_mask的作用:

#include <stdio.h>
#include <signal.h>

void func(int sig,siginfo_t *pt,void *p)
{
	 printf("recv a sig:%d\n",sig);
}
int main()
{

	struct sigaction act;
	act.sa_sigaction=func;
	sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask,SIGINT);
	act.sa_flags=0;
	sigprocmask(SIG_BLOCK,&act.sa_mask,NULL);//信号的屏蔽与放开
	if( sigaction(SIGINT,&act,NULL)<0)
	{
	      printf("error\n");
		  exit(0);
	}
	printf("hello world...........\n");
	sleep(10);
	sigprocmask(SIG_UNBLOCK,&act.sa_mask,NULL);//十秒之前发送很多SIGINT信号只执行一次处理函数
	while(1)
	{
	    sleep(1);	//进入while就正常了
	}
    return 0;
}

运行结果:
在这里插入图片描述

5.信号的发送函数

5.1kill函数

kill既可以向自身发送信号,也可以向其他进程发送信号;

   #include <sys/types.h>
   #include <signal.h>
   int kill(pid_t pid, int sig);

pid>0 ,将信号sig发给pid进程;
pid=0, 将信号sig发给同组进程;
pid=-1, 将信号sig发送给所有进程,调用者进程有权限发送的每一个进程(除了1号进程之外,还有它自身);
pid<-1, 将信号sig发送给进程组是pid(绝对值)的每一个进程;

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>

void func(int sig)
{
    printf("recv a sig:%d\n",sig);
}

int main()
{
	signal(SIGINT,func);
	printf("start......\n");
	while(1)
	{
	    sleep(1);
		//kill(getpid(),SIGINT);
	    raise(SIGINT);
	}
    return 0;
}

5.2raise函数

int raise(int signo);
给自己发送信号,等同于kill(getpid(),sig);

5.3alarm函数

设置一个闹钟延迟发送信号;
告诉linux内核n秒中以后,发送SIGALRM信号;

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

typedef struct
{
    int age;
	int numid;
}Teacher;

Teacher g_t;

void printfT() //不可重入函数  (打印的值不确定)
{
	printf("age:%d\t numid:%d\n",g_t.age,g_t.numid);
}

void func(int sig)
{
    printf("recv a sig:%d\n",sig);
	printfT();
	alarm(1);
}
int main()
{
	Teacher t1,t2;
	t1.age=18;
	t1.numid=1;
	t2.age=21;
	t2.numid=2;

	if(signal(SIGALRM,func)<0)
	{
	     perror("func signal err\n");
		 return 0;
	}
    alarm(1);
	while(1)
	{
	    g_t=t1;
		g_t=t2;
	}
    return 0;
}

运行结果:
在这里插入图片描述

5.4sigqueue函数

   #include <signal.h>

   int sigqueue(pid_t pid, int sig, const union sigval value);

pid是指定接收信号的进程id;
sig是发送的信号;
value指定了信号传递的参数;

#include <stdio.h>
#include <signal.h>
#include <string.h>

void func(int sig,siginfo_t *pt,void *p)
{
	printf("recv a sig:%d\n",sig);
	printf("%d\t%d\n",pt->si_value.sival_int, pt->si_int );
}
int main()
{
	 int ret;
	 pid_t pid;
	 struct sigaction act;
	 act.sa_sigaction=func;
	 act.sa_flags=SA_SIGINFO;
	 if(sigaction(SIGUSR1,&act,NULL)<0)
	 {
	      perror("func sigaction err\n");
		  return 0;
	 }
	 pid=fork();
	 if(pid==-1)
	 {
	      printf("fork error\n");
		  return 0;
	 }
	 if(pid==0)
	 {
		  union sigval  mysigval;
		  mysigval.sival_int=77;
		  for(int i=0;i<5;i++)
		  {
		       ret=sigqueue(getppid(),SIGUSR1,mysigval);
			   if(ret!=0)
			   {
			        printf("sigqueue .....\n");
				    exit(0);
			   }
			   else
			   {
			         printf("sigqueue...successs\n");
				     sleep(1);
			   }
		  }   
	 }
	 else if(pid>0)
	 {
	      while(1)
			 pause();
	 }
     return 0;
}

运行结果:在这里插入图片描述

#include <stdio.h>
#include <signal.h>
#include <string.h>

void func(int sig,siginfo_t *pt,void *p)
{
	printf("recv a sig:%d\n",sig);
	char *pmsg=(char *)pt->si_value.sival_ptr;
	printf("recv  msg: %s\n",pmsg);
}
int main()
{
	 int ret;
	 pid_t pid;
//	 char pmsg[64]="this this this !";
	 struct sigaction act;
	 act.sa_sigaction=func;
	 act.sa_flags=SA_SIGINFO;
	 if(sigaction(SIGUSR1,&act,NULL)<0)
	 {
	      perror("func sigaction err\n");
		  return 0;
	 }
	 pid=fork();
	 if(pid==-1)
	 {
	      printf("fork error\n");
		  return 0;
	 }
	 if(pid==0)
	 {
		  union sigval  mysigval;
		  char pmsg[64];
		  strcpy(pmsg,"this is a test!");
	      mysigval.sival_ptr=pmsg;
		  printf("pmsg:%s\n",pmsg);
		  for(int i=0;i<5;i++)
		  {
		       ret=sigqueue(getppid(),SIGUSR1,mysigval);
			   if(ret!=0)
			   {
			        printf("sigqueue .....\n");
				    exit(0);
			   }
			   else
			   {
			         printf("sigqueue...successs\n");
				     sleep(1);
			   }
		  }   
	 }
	 else if(pid>0)
	 {
	      while(1)
			 pause();
	 }
     return 0;
}

运行结果:在这里插入图片描述

#include <stdio.h>
#include <signal.h>
#include <string.h>

void func(int sig,siginfo_t *pt,void *p)
{
	printf("recv a sig:%d\n",sig);
	char *pmsg=(char *)pt->si_value.sival_ptr;
	printf("recv  msg: %s\n",pmsg);
}
int main()
{
	 int ret;
	 pid_t pid;
	 char pmsg[64]="this this this !";
	 struct sigaction act;
	 act.sa_sigaction=func;
	 act.sa_flags=SA_SIGINFO;
	 if(sigaction(SIGUSR1,&act,NULL)<0)
	 {
	      perror("func sigaction err\n");
		  return 0;
	 }
	 pid=fork();
	 if(pid==-1)
	 {
	      printf("fork error\n");
		  return 0;
	 }
	 if(pid==0)
	 {
		  union sigval  mysigval;
		  strcpy(pmsg,"this is a test!");
	      mysigval.sival_ptr=pmsg;
		  printf("pmsg:%s\n",pmsg);
		  for(int i=0;i<5;i++)
		  {
		       ret=sigqueue(getppid(),SIGUSR1,mysigval);
			   if(ret!=0)
			   {
			        printf("sigqueue .....\n");
				    exit(0);
			   }
			   else
			   {
			         printf("sigqueue...successs\n");
				     sleep(1);
			   }
		  }   
	 }
	 else if(pid>0)
	 {
	      while(1)
			 pause();
	 }
     return 0;
}

运行结果:
在这里插入图片描述
总结:(自己分析的)
根据上面三个实验可以知道使用 union sigval联合的sival_int可以正确传递信息;
但是第二个实验想使用 union sigval联合的sival_ptr传递一个指针,但是结果是随机的,是乱码;分析(子进程把pmsg的地址传递给父进程,但是父进程可能没有那个地址,也有可以有,但是肯定不是子进程的字符串。)实验三分析(在fork之前定义了一个pmsg,那么两个进程的pmsg偏移量是一样的,这也是为什么能传过去,因为父进程中也有相同的偏移量地址,但是子进程修改各父进程没有关系,也传不过去“this is a test”);那么?为什么sival_int可以传递?
我认为是siginfo_t结构体为sival_int分配了内存空间,是int类型,二sival_ptr是一个指针,所指向的地址没有分配,所有传递不了,而sival_int可以。

6.信号的阻塞和未达

执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:
在这里插入图片描述
说明:
6.1 pcb进程控制块中函数有信号屏蔽状态字,信号未决状态字,和是否忽略标志
6.2 信号屏蔽状态字,1代表阻塞;信号未决状态字,1代表未决,0代表可抵达;
6.3 向进程发送SIGINT,内核首先判断信号屏蔽状态字是否阻塞,信号未决状态字(pending相应位制成1;若阻塞解除,信号未决状态字(pending相应位制成0;表示信号可以抵达了。
6.4 block状态字、pending状态字 64bit(62种信号);
6.5 block状态字用户可以读写,pending状态字用户只能读;

信号集操作函数(状态字表示)

    #include <signal.h>

   int sigemptyset(sigset_t *set);//    清0

   int sigfillset(sigset_t *set);//    清1

   int sigaddset(sigset_t *set, int signum);//根据signum,把信号集中的对应为置成1

   int sigdelset(sigset_t *set, int signum);//根据signum,把信号集中的对应为置成0

   int sigismember(const sigset_t *set, int signum);//判断signum是否在信号集中

sigprocmask函数
读取或更改进程的信号屏蔽状态字(block)

   #include <signal.h>
       
   /* Prototype for the glibc wrapper function */
   int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

   /* Prototype for the underlying system call */
   int rt_sigprocmask(int how, const kernel_sigset_t *set,
                      kernel_sigset_t *oldset, size_t sigsetsize);

   /* Prototype for the legacy system call (deprecated) */
   int sigprocmask(int how, const old_kernel_sigset_t *set,
                   old_kernel_sigset_t *oldset);

how含义:
在这里插入图片描述
sigpending函数
获取信号未决状态字(pending)信息 ;

int sigpending(sigset_t *set);

#include <stdio.h>
#include <signal.h>

void func(int sig)
{
     if(sig==SIGINT)
	 {
	     printf("recv a sig=%d\n",sig);
	 }
	/* if(sig==SIGRTMIN+15)
	 {
	      printf("recv a sig=%d\n",sig);
	 }*/
	 if(sig==SIGQUIT)
	 {
	     printf("recv a sig=%d\n",sig);

		/*以下三句话是不能实现解除阻塞功能的!
          进入信号处理函数时会将本信号加入全局信号屏蔽字,
          退出时还原,所以在信号处理函数里面对信号屏蔽字所做的操作只在该函数有效!
           一旦返回到main函数或者其他函数,系统将会恢复全局屏蔽字
           也就是保留之前的屏蔽操作
           意味着在信号处理函数里调用sigprocmask是不明智的选择,
            因为退出信号处理函数以后屏蔽字恢复成之前的状态(全局信号屏蔽字)
         */
		 sigset_t uset;
		 sigemptyset(&uset);
		 sigaddset(&uset,SIGINT);
		 sigprocmask(SIG_UNBLOCK,&uset,NULL);
	 }
}
void printSigset(sigset_t *set)
{
     int i;
	 for(i=1;i<NSIG;i++)
	 {
	     if(sigismember(set,i))
			 putchar('1');
		 else
			 putchar('0');
	 }
	 printf("\n");
}

//测试显示 信号未达状态 关键字
//int main()
//{
//	sigset_t pset;	
//	for(;;)
//	{
//		//获取信号未决  sigset_t字
//		sigpending(&pset); 
//		//打印信号未决  sigset_t字
//		//信号没有被阻塞,信号没有未决,
//		printSigset(&pset);
//		sleep(2);
//	}
//	return 0;
//}

测试显示 信号未达状态 关键字 + 注册SIGINT信号
//int main(int argc, char *argv[])
//{
//	sigset_t pset;
//	
//	sigset_t bset;
//	sigemptyset(&bset);
//	sigaddset(&bset, SIGINT);
//
//	if (signal(SIGQUIT, func) == SIG_ERR)
//	{
//	       perror("func signal\n");
//		   return 0;
//	}
//    sigprocmask(SIG_BLOCK, &bset, NULL);
//	for (;;)
//	{
//		sigpending(&pset); 
//		printSigset(&pset);
//		sleep(2);
//	}
//	return 0;
//}

//连续的按ctrl+c键盘,虽然发送了多个SIGINT信号,但是因为信号是不稳定的,只保留了一个。
//不支持排队
int main(int argc, char *argv[])
{
	sigset_t pset; //用来打印的信号集
	sigset_t bset; //用来设置阻塞的信号集
	sigemptyset(&bset);
	sigaddset(&bset, SIGINT);
	
	if (signal(SIGINT, func) == SIG_ERR)
	{
       	 perror("func signal\n");
		 return 0;
	}
	/*if (signal(SIGRTMIN+15, func) == SIG_ERR)
	{
       	 perror("func signal\n");
		 return 0;
	}*/
	if (signal(SIGQUIT, func ) == SIG_ERR)
	{
       	 perror("func signal\n");
		 return 0;
	}
	//sigprocmask用来阻塞ctrl+c信号
	//ctrl+c信号被设置成阻塞,即使用户按下ctl+c键盘,也不会抵达
	sigprocmask(SIG_BLOCK, &bset, NULL);
	printf("hello world!\n");
	/* for(int i=0;i<5;i++)
	   raise(SIGRTMIN+15);*/
	for (;;)
	{
		sigpending(&pset);
		printSigset(&pset);
		sleep(1);
	}
	return 0;
}

运行结果:
main01
在这里插入图片描述
main02
在这里插入图片描述
在这里插入图片描述
如果发送的是实时信号,是可以执行多次的,比如注释的SIGRTMIN+15信号;

7.参考文献

https://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html
https://blog.csdn.net/qq_35423154/article/details/105530173
https://blog.csdn.net/lzjsqn/article/details/53544043

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值