信号

一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件(是相对于进程来说的)。Linux系统中共有62个信号,1—31(普通信号),34—64(实时信号)。

信号的产生

1、终端组合键
Ctrl-C产生SIGINT信号(终止进程),Ctrl-\产生SIGQUIT信号(终止进程并且Core Dump),Ctrl-Z产生SIGTSTP信号(来自终端的停止信号)。
**Core Dump(核心转储):**当⼀个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core.进程id,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存在PCB中)。默认是不允许产生core⽂件的,因为core文件中可能包含用户密码等敏感信息,不安全且占用资源。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。
查看核心文件:ulimit -a
修改core文件的大小:ulimit -c(默认为0即不允许产生core文件)
事后调试:core file core.进程id

2、硬件异常
当前进程执行了除0指令,内核将这个异常解释为SIGFPE信号(浮点异常)发送给进程。
当前进程访问了非法的内存,内核将这个异常解释为SIGSEGV信号(无效的存储器引用)发送给进程。
3、系统函数的调用(raise函数、kill函数、abort函数)
int raise(int signo)//给当前进程发送指定的信号
int kill(pid_t pid, int signo)//给一个指定的进程发送指定的信号成功返回0,错误返回-1
void abort(void) //给当前进程发送的信号SIGABRT信号
代码示例:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
int count = 0;
int main()
{
   while(1)
  {
     printf("hello world.%d\n",count);
	 count++;
	 if(count ==10)
	 {
		printf("i will be killed\n");
		//kill(getpid(),2);
     	//raise(2);
		abort();
	 }
  }
}
//void handler(int sig)
//{
//	printf("catch a signal.%d\n",sig);
//}
//int main()
//{
//	signal(2,handler);
//  while(1)
//	{
//		printf("ljh\n");
//		sleep(1);
//	}
//	return 0;
//}

4、软件条件(alarm函数)
unsigned int alarm(unsigned int seconds)//告诉内核在seconds秒后给进程发送SIGALRM信号(终止进程)
代码示例:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
    alarm(5);
	while(1)
	{
		printf("my pid is %d\n",getpid());
		sleep(1);
	}
	return 0;
}

信号的处理:
1、忽略
2、执行默认的处理动作(大部分为终止进程)
3、捕捉信号(提供一个信号处理函数,要求内核在处理该信号时切回用户态去执行这个自定义的处理函数)下面再对这部分进行详谈
信号的几种状态:
信号递达(Delivery):执行信号的处理动作
信号未决(Pending):信号产生到信号被递达之间的状态
信号阻塞(Block):进程可阻塞某个信号(被阻塞的信号就不会被递达,除非先解除阻塞才有可能被递达)

信号集:
sigset_t :对每一种信号用一个bit位表示有效或无效
sigset_t pending:未决信号集(使用位图来存储)
sigset_t block:阻塞信号集也叫信号屏蔽字(使用位图来存储)

信号在进程PCB中的存储结构:
这里写图片描述
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。

信号集操作函数:
int sigemptyset(sigset_t *set)//初始化set所指向的信号集,即所有的bit位置0,表示无效信号
int sigfillset(sigset_t *set)//初始化set所指向的信号集,即所有的bit位置1,表示有效信号
int sigaddset(sigset_t *set, int signo)//在set所指向的信号集增加signo信号
int sigdelset(sigset_t *set, int signo)//在set所指向的信号集删除signo信号
int sigismember(const sigset_t *set, int signo)//判断该信号是否在set所指向的信号集中(返回值类型bool型)
注:在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。
读取/更改当前进程的信号屏蔽字:
int sigprocmask(int how, const sigset_t *set, sigset_t *oset)
set : 信号屏蔽字
oset : 备份原来的信号屏蔽字,然后根据set和how参数更改信号屏蔽字
how : 指示如何更改信号屏蔽字
SIG_BLOCK: set包含希望添加到当前信号屏蔽字的信号
SIG_UNBLOCK: set包含希望从当前信号屏蔽字中解除的信号
SIG_SETMASK: 设置当前信号屏蔽字为set所指向的信号屏蔽字
读取当前进程的未决信号集:
int sigpending(sigset_t *set);
示例代码:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
void show_pending(sigset_t *pending)
{	int i = 1;
	for(;i<32;i++)
	{
		if(sigismember(pending,i))
			printf("1");
		else
			printf("0");
	}
	printf("\n");
}
void handler(int signo)
{
	printf("i will dill this signal-->%d\n",signo);
}
int main()
{	
	signal(2,handler);
	sigset_t block,oblock,pending;
	sigemptyset(&block);
	sigemptyset(&oblock);
	sigemptyset(&pending);
	sigaddset(&block,2);
	sigprocmask(SIG_SETMASK,&block,&oblock);
	int count = 0;
	while(1)
	{
		sleep(1);
		sigpending(&pending);
		show_pending(&pending);
		if(count>10)
		{
			sigprocmask(SIG_SETMASK,&oblock,NULL);
		}
		else
			count++;
	}
	return 0;
}

这里写图片描述


现在来详谈一下捕捉信号
先来分析一下信号捕捉的整个过程:
1、执行主控制流程序,此时发生中断、异常或系统调用由用户态–>内核态
2、内核处理完异常,在切回用户态之前先处理进程中的可抵达信号
3、由内核态–>用户态去执行用户自定义的信号处理函数
4、信号处理函数返回时执行特殊系统调用sigreturn由用户态–>内核态
5、没有可抵达的信号,则由内核态–>用户态回到主控制流程被中断的地方继续执行

读取/修改与指定信号相关联的处理动作:
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact)
signo:信号编号
act:根据act来修改信号的处理动作
oact:备份该信号原来的处理动作

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<signal.h>
#include<stdlib.h>
#include<unistd.h>
void show_block(sigset_t *block)
{	int i = 1;
	for(;i<32;i++)
	{
		if(sigismember(block,i))
			printf("1");
		else
			printf("0");
	}
	printf("\n");
}
void handler(int signo)
{
	printf("i will dill this signal--->%d\n",signo);
}
int main()
{
	struct sigaction act,oact;
	act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
	sigaddset(&act.sa_mask,3);
	act.sa_flags = 0;
	sigaction(2,&act,&oact);
	while(1)
	{
		show_block(&act.sa_mask);
		sleep(1);
	}
	return 0;
}

挂起调用进程直到有信号递达:
int pause(void);
若信号的处理动作是终止进程,pause函数没有机会返回;
若信号的处理动作是忽略,则pause不返回,进程继续处于挂起状态;
若信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR(被信号中断),即pause只有出错返回。


利用pause()alarm()模拟实现sleep函数:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
void handler(int signo)
{
	;
}
int mysleep(int seconds)
{
	struct sigaction act,oact;
	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGALRM,&act,&oact);
	alarm(seconds);
	pause();
	int _time=alarm(0);
	sigaction(SIGALRM,&oact,NULL);
	return _time;
}
int main()
{
	while(1)
	{
		printf("i am running\n");
		mysleep(3);
	}
	return 0;
}

我们都知道:系统运行的时序(Timing)并不像我们写程序时所设想的那样。在上面的mysleep函数中,虽然alarm(seconds)紧接着的下⼀行就是pause(),但是无法保证pause()一定会在调用alarm(seconds)之 后的seconds秒之内被调用。像这种因为时序问题而导致程序出现bug称作竞态条件。为了避免mysleep出现错误,需要对其进行优化,首先我们先来了解一下sigsuspend函数。
int sigsuspend(const sigset_t *sigmask)//解除对某个信号的屏蔽并挂起调用进程直到有信号递达,与pause函数一样,只有出错返回。
sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题
实现代码:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
void handler(int signo)
{
	;
}
int mysleep(int seconds)
{
	sigset_t block,oblock;
	struct sigaction act,oact;
	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGALRM,&act,&oact);
	sigemptyset(&block);
	sigemptyset(&oblock);
	sigaddset(&block,SIGALRM);
	sigprocmask(SIG_SETMASK,&block,&oblock);
	alarm(seconds);
	sigdelset(&block,SIGALRM);
	sigsuspend(&block);//unblock and pause
	int _time=alarm(0);//cancel alarm
	sigaction(SIGALRM,&oact,NULL);
	sigprocmask(SIG_SETMASK,&oblock,NULL);
	return _time;
}
int main()
{
	while(1)
	{
		printf("i am running\n");
		mysleep(2);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值