信号signal

中断

中断是系统对于异步事件的响应

中断信号

中断源

现场信息

中断处理程序

中断向量表

中断分类

硬件中断(外部中断)

外部中断是指由外部设备通过硬件请求的方式产生的中断,也称为硬件中断。

软件中断(内部中断)

内部中断是有CPU运行·程序错误或执行内部程序调用引起的一种中断,也称为软件中断。

信号

信号是Unix系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。

信号时因为某些错误条件而产生的,比如内存段冲突。浮点处理器或者非法指令等

信号时在软件层次上对中断的一次模拟,所有通常把它称为软中断。

信号与中断

信号与中断的相似点:

(1)采用相同的异步通信方式;

(2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;

(3)都在处理完毕后返回原来的断点;

(4)对信号或中断都可以进行屏蔽。

信号与中断的区别:

(1)中断有优先级,而信号没有优先级,所有的信号都是平等的;

(2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;

(3)中断响应是及时的,而信号响应通常都有较大的时间延迟。

信号名称

信号的名称是在头文件signal.h里定义的(一共64个举例说明)

信号名称描述
SIGABRT进程停止运行
SIGALRM警告钟
SIGFPE算术运算例外
SIGHUP系统挂断
SIGILL非法指令
SIGINT终端中断
SIGKILL停止进程(此信号不能被忽略或捕捉)
SIGPIPE向没有读者的管道写入数据
SIGEGV无效内存段访问
kill -l

可以查看所有的信号

 

进程对信号的三种响应

忽略信号

不采取任何操作、有两个信号不能被忽略也不能被捕获:SIGKILL(9号信号) 和 SIGSTOP(19号信号)。

捕捉并处理信号:

内核中断正在执行的代码,转去执行先前注册过的处理程序。

执行默认操作:

默认操作通常是终止进程,这取决于被发送的信号。

 

signal

typedef void(*__sighandler_t)(int);

#define SIG_ERR((__sighandler_t)-1)

#define SIG_DFL((__sighandler_t)0)

#define SIG_IGN((__sighandler)1)

函数原型:

__sighandler_t signal(int signum,__sighandler_t handler);

参数:

signal是一个带signum和handler两个参数的函数,准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数有handler给出。

handler这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void

handler也可以是下面两个特殊值:

SIG_IGN        屏蔽该信号

SIG_DFL        恢复默认行为

返回值:成功返回上次一处理的信号 失败返回-1.

实现一个最简单的信号代码

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_EXIT(m)				\
	do						     \
	{						     \
		perror(m);				\
		exit(EXIT_FAILURE);		\
	}while(0)

void handler(int sig);
int main()
{
	signal(SIGINT,handler);
	for(;;);
	return 0;	
}
void handler(int sig)
{
	printf("recv a sig= %d\n",sig);
}

 

执行结果

解释:

signal 注册信号

按下Ctrl+c会产生一个2号的信号,signal函数捕捉到了这个信号。因为Ctrl+c就是SIGINT信号,捕捉到信号然后把这个信号放到中断函数handle中执行。所以输出了recv a sig = 2.然后主函数循环。再按下Ctrl+C又执行signal函数,再次循环。退出按下Ctrl+\(SIGQUIT)就好了。

 

信号的分类

可靠信号

不可靠信号

实时信号

非实时信号

  

信号发送

kill 

kill(pid,sig);kill -9 pid (意思是向pid发送9号命令)

如果pid > 0,信号sig发送给进程号=pid的进程

如果pid=0,信号将发送给调用者所在组中的每一个进程

如果pid=-1,信号sig将被发送给调用者进程有权发送的每一个进程除了1号进程与自身之外

如果pid<-1,信号sig将被发送给进程组=-pid中的每一个进程

 

 

raise

给自己发送信号。raise(sig)等价于kill(getpid(),sig);

killpg

给进程组发送信号。killpg(pgrp,sig)等价于kill(-pgrp,sig);

sigqueue()信号发送函数

给进程发送信号,支持排队,可以附带信息。

sigaction()信号安装函数

和signal()函数相似。

pause

功能:让进程暂停直到信号出现

将进程置为可中断睡眠状态。然后它调用schedule(),使linux进程调度器找到另一个进程来运行。

pause使调用者进程挂起,直到一个信号被捕获

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_EXIT(m)				\
	do						     \
	{						     \
		perror(m);				\
		exit(EXIT_FAILURE);		\
	}while(0)

void handler(int sig);
int main()
{
	if(signal(SIGUSR1,handler) == SIG_ERR)
	{
		ERR_EXIT("signal error");
	}
	pid_t pid = fork();
	if(pid == 0)
	{
		kill(getppid(),SIGUSR1);
		exit(EXIT_SUCCESS);
	}

	int n = 5;
	do
	{
		n = sleep(n);	
	}while(n > 0) ;
	return 0;	
}
void handler(int sig)
{
	printf("recv a sig= %d\n",sig);
}

强行解释一波:

主函数下第一行代码

if(signal(SIGUSR1,handler) == SIG_ERR)

这行代码是信号的安装以及判断信号是否正常。

然后fork一个子进程。pid==0的时候进子进程中。给父进程发送SIGUSR1的信号,之前安装的信号就是SIGUSR1的信号。然后退出。

父进程先睡眠5秒,那为什么不直接sleep(5);而是使用了一个循环那?

这是因为信号会打断睡眠的,然后会直接执行睡眠的下一行代码。所以根本执行不了sleep 5秒的,说以要强行睡眠5秒,必须这么做。

因为sleep函数假如打断了会返回剩下的时间的。所以就这样做的。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_EXIT(m)				\
	do						     \
	{						     \
		perror(m);				\
		exit(EXIT_FAILURE);		\
	}while(0)

void handler(int sig);
int main()
{
	signal(SIGINT,handler);
	for(;;)
	{
		pause();
		printf("pause return\n");
	}
	return 0;	
}
void handler(int sig)
{
	printf("recv a sig= %d\n",sig);
	sleep(1);
}

更多信号发送函数:

alarm函数  setitimer函数    abort函数

alarm函数只能发送SIGALRM信号

setitimer函数可以发送SIGALRM信号,SIGVTALRM信号 SIGPROF信号

abort SIGABRT信号

 

实例alarm函数

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_EXIT(m)				\
	do						     \
	{						     \
		perror(m);				\
		exit(EXIT_FAILURE);		\
	}while(0)

void handler(int sig);
int main()
{
	if(signal(SIGALRM,handler) == SIG_ERR)
	{
		ERR_EXIT("signal error");
	}
	alarm(1);
	for(;;)
	{
		pause();
	}
	return 0;	
}
void handler(int sig)
{
	printf("recv a sig= %d\n",sig);
}

这个代码的作用是隔一秒发送一次

但是只发送一次可以手工kill 进程就可以再发送一次了。

如何办到每个一秒钟发送一次?

只需要修改handler函数就行 。

void handler(int sig)
{
	printf("recv a sig= %d\n",sig);
	alarm(1);
}

实现了一次递归。

先安装信号,第一次alarm函数发送一次信号。执行handler函数。当printf完之后又发送一次信号。再次执行handler函数,这样就可以实现每隔一秒钟打印一次了。

可重入函数:

为了增强程序得稳定性,在信号处理函数中应用使用可重入函数。

所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重人函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据。这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可载入函数在信号处理函数中被视为不安全函数。

满足下列条件的函数多数是不可再入函数:

(1)如getlogin()gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等。

(2)函数实现时,调用了malloc()函数或者free函数

(3)实现时使用了标准I/O函数的

 

信号在内核中的表示

执行信号的处理动作称为信号递达,信号从产生到递达之间的状态称为信号未决。进程可以选择阻塞某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看做是这样的:

 

信号集操作函数

#include <signal.h>

int sigemptyset(sigset_t *set)    //把64位清零

int sigfillset(sigset_t *set)        //把64位都置一

int sigaddset(sigset_t *set,int signo)   //把对应的位置一

int sigdelset(sigset_t *set,int signo)     //把对应的位置零

int sigismember(const sigset_t *set,int signo)     //检测当前的位是零还是一

sigprocmask函数

#include <signal.h>

int sigprocmask(int how,const sigset_t *set,sigset_t *oset)

功能:

读取或更改进程的信号屏蔽字。

返回值:

若成功则为0,若出错则为-1.

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号的屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how的可选值。

how参数的含义

SIG_BLOCKset包含了我们希望添加到当前屏蔽字的信号,相当于mask=mask|set
SIG_UNBLOCKset包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
SIG_SETMASK设置当前信号屏蔽字为set所指向的值,相当于mask=set

sigaction(新的安装信号的函数)

包含头文件<signal.h>

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

原型:

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

参数:

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

第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理。

第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。

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

sigaction结构体

第二个参数最为重要,其中包含了对指定信号的处理。信号所传递的信息。信号处理函数执行过程中应屏蔽掉那些函数等等

struct sigaction{

    void (*sa_handler)(int);   //对不可靠信号和可靠信号

    void (*sa_sigaction)(int,siginfo_t *,void *);    //对可靠信号

    sigset_t sa_mask;      //指定信号屏蔽字,可以屏蔽这个信号

    int sa_flags;

    void (*sa_restorer)(void);      //废弃了

};

一个最简单是栗子:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_EXIT(m)				\
	do						     \
	{						     \
		perror(m);				\
		exit(EXIT_FAILURE);		\
	}while(0)

void handler(int sig);
int main()
{
	struct sigaction act;
	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	if(sigaction(SIGINT,&act,NULL) < 0)
	{
		ERR_EXIT("sigaction error\n");
	}
	for(;;)
	{
		pause();
	}
	return 0;	
}
void handler(int sig)
{
	printf("recv a sig= %d\n",sig);
	
}

结果:

 

关于sa_mask的使用。

sa_mask 的作用,阻塞信号,当之前的信号递达正在执行信号处理函数,在来一个信号A会阻塞信号A的信号。前提是sa_mask设置的是信号A.

举个栗子:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_EXIT(m)				\
	do						     \
	{						     \
		perror(m);				\
		exit(EXIT_FAILURE);		\
	}while(0)

void handler(int sig);
int main()
{
	struct sigaction act;
	act.sa_handler = handler;
	sigaddset(&act.sa_mask,SIGQUIT);
	act.sa_flags = 0;
	if(sigaction(SIGINT,&act,NULL) < 0)
	{
		ERR_EXIT("sigaction error\n");
	}
	for(;;)
	{
		pause();
	}
	return 0;	
}
void handler(int sig)
{
	printf("recv a sig= %d\n",sig);
	sleep(5);
}

  执行结果:

当执行Ctrl+c的时候,是可以执行的,第二个Ctrl+c的时候信号执行函数sleep5秒还没有执行完。Ctrl + C的信号被阻塞了。所以没有立即执行。

 

 

 

sigqueue函数

功能:新的发送信号系统调用,主要是针对实施信号提出的支持信号带有参数,与函数sigaction()配合使用。

原型:

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

参数

sigqueue的第一个参数是指定接收信号的进程id,第二个参数确定即将发送的信号,第三个参数联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值、

返回值:

成功返回0,失败返回-1.

 

sigval联合体

sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给进程组。

typedef union sigval

{

    int sival_int;

    void *sival_ptr;

}sigval_t;

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

波雅_汉库克

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值