我眼中的“信号”

生活中,我们会遇到很多种类的信号,例如交通灯、闹钟、还有的就是门铃等等;但这只是你眼中的信号,在我看来信号有了新的解释;

1、信号的概念

所谓的信号,我们看到了一种现象,并且会对这种现象做出某种反应,这就是信号;

从操作系统角度下,信号的理解就是,外界给一个进程发送一个信号,该进程对于此类信号作出识别并且进行某种操作 。

我们都知道,操作系统中每一个进程都有一个PCB,一个PCB中都包含一个信号字段,所谓的进程收到一个信号,就是操作系统对进程PCB信号字段,做出的更改。

2、信号在进程PCB中的存储形式

要知道信号在PCB中的存储形式,就要知道一个东西------信号到底有多少种类??
在Linux下我们可以使用命令来查看所有的信号
kill  -l  使用该命令可以显示Linux系统下的所有的信号
下面是Linux下的信号种类 :

看到上面的这副图,大家来告诉我,这幅图上显示了多少个信号????
要是谁说是   64 个信号的 ,自己先  自我谴责一下啊,怎么这么的不小心,没看到没有 32  号、33号信号 吗?真是太失败了!!吐舌头
所以说,在Linux下,总共 只有  62(不是64)个信号。。。。。。不知道小朋友要好好的记住啊
其中   1-31表示的是 普通信号 ;;;;;34 -64表示的是 实时信号  。。。在这里只需要研究  普通信号就好了。
总共有31个信号,表示的时候,我们只需要表示有没有信号产生。那么大家觉得用我们学过的哪种数据结构可以更好的表示信号呢??
大家也不用来猜了,,我直接来说吧!!!!我们可以使用位图Bitmap来表示一个信号到底有没有产生。
总结来说的话,就是在Linux下我们使用的是 位图来表示的一个进程中的信号字段。
所以在进程PCB中产生了三张表   pending、block、handler表;
这三张表总览了进程中的信号从 产生到处理完成的一系列操作 ;

pending表

在这来介绍一下----------------【pending】表;
想要知道这个,在此之前还要知道一些概念:
1、信号递达:操作对于信号的执行处理过程,我们把这个过程叫做是信号递达;
2、信号未决:我们把信号从产生到信号递达过程的信号未决;
在这里我们所说的pending表----指的就是表示信号的未决状态;也就是 pending表 就是表示 信号未决状态 的一个 位图 ; 

block表

【block】表——指的是一个信号有没有被屏蔽;block表指的是信号屏蔽状态的一个位图  ;
当位图的    2 号信号位   显示的是   1;也就是 说,信号2被屏蔽了;
一个信号如果被屏蔽的话,那么这个信号永远否不会被递达;

handler表

这个表和上面的两个表有点不一样,上面 的  pending表 还有block表 都是    使用位图来表示的。但handler表示不是这样的。
【handler】表——指的是一个信号的执行的抵达方式   ;它的表示方式是 :一个函数指针表。。。
对于一个信号的递达方式主要有下面的三种 :
1、忽略信号 ;SIGIGN
2、执行默认操作 ;SIGDFL
3、自定义的信号捕捉;
下面来看看这幅图就大致可以知道了这三张表了:

要看这张表的话,需要横着看;
将上面的三张表来解释一下:
我们对于一号信号    block ,,当前产生了一号信号,并且处于未决状态 ,信号的递达动作是  默认动作 ;
       二号信号   未block,当前也没有产生二号信号,信号的递达动作是 忽略它;
       三号信号   block,,当前未产生三号信号 ,信号的递达动作是   自定义捕捉 ;

3、信号的产生方式

在Linux下,信号又是怎么产生的呢??、
Linux下的信号的产生方式  :
(1)、通过键盘输入产生信号
例如:
ctr+c产生   2号信号;
ctr+z产生  20号信号;
这类信号一般只对前台进程产生效果 ;
(2)、软硬件条件产生信号
简单举个例子就是 :
当进程运行出现某个错误的话,产生错误的硬件会向操作系统发信息,然后操作系统会发送8号信号给进程。。。
如果说是我们要是代码中出现的是除零错误的时候,系统的运算单元,系统会发送信号11号信号给进程。。。
(3)、使用kill 命令可以产生命令
在LInux下使用命令  kill 可以产生信号 
kill  -[信号]  进程pid
(4)、系统调用接口产生信号  
在Linux下,操作系统为我们提供了几个函数可以产生信号 传给进程:
kill函数——可以给任意进程发送信号  
       #include <sys/types.h>
       #include <signal.h>

       int kill(pid_t pid, int sig);//使用该函数 可以给  pid进程发送信号sig
raisse函数 ——可以给当前的进程发送任意的信号 
       #include <signal.h>

       int raise(int sig);/?使用此函数可以当前进程发送信号 sig
abort函数 ——可以当前的进程发送唯一的信号
       #include <stdlib.h>

       void abort(void);//给当前的进程发送6号SIGABRT信号
(5)、软件条件产生信号 
在Linux下,有的函数会产生一些信号  给进程  
例如  :
函数 alarm函数 
      #include <unistd.h>

       unsigned int alarm(unsigned int seconds);//函数会在senconds之后给当前进程发送一个14号(SIGALRM)信号   
						//函数的返回值表示的是 之前调用此函数 距离发送信号还有多少秒,要是没有信号的话,返会0

4、信号的自定义捕捉 

信号产生之后,操作系统会对操作系统做出递达操作 ,但是我们想要一个信号执行一个  我们定义的动作,要怎么做 呢??、
这个时候就要用到我们说到自定义信号 的捕捉了。
在Linux下,系统为我们提供了一个接口来实现自定义的捕捉 :
       #include <signal.h>

       typedef void (*sighandler_t)(int);//重命名一个函数指针

       sighandler_t signal(int signum, sighandler_t handler);//函数的意思就是signum信号执行handler函数的操作
下面我们来试验一下


这份代码中我们对  1-31号信号都进行了自定义的信号捕捉 ,
执行之后 

看来我们是成功了,但是我们要怎么才能关掉这个进程呢??我们把所有的信号都进行了自定义捕捉要怎么办呢 ?、
大家不要怕!!!!
我们可以使用   9 号信号,9号信号是永远不会改变的。。。。。
使用   kill  -9  进程pid
尽可以挂掉这个进程了。。。
5、信号是在什么时候被处理的
我们经常说信号是在合适的时候被处理的,但是什么时候是  合适的时候呢??、
今天我就来告诉大家这个合适的时候是什么时候吧!!!!
操作系统是 在进程运行的时,,,从内核态   切换到   用户态的时候   处理的。。。。。
但什么是内核态,什么又是用户态呢???
所谓的用户态,指的就是进程执行用户写的代码时候;
而内核态指的又是,进程在执行函数时,有的函数 底层是要调用到系统接口的,而此时的用户权限是不够的,所以要内核来执行。
下面有一副很形象的图就可以解释信号被递达的时机:

6、实现对一个信号屏蔽操作 

在Linux下我们需要对于一个信号实现屏蔽 ,系统为我们提供下面的一些接口来实现:

(1)、信号集的设置

我们都知道在PCB中信号的存储是 通过位图来实现的, 但是系统不允许我们直接使用移位来实现信号集的设定 ,他为我们实现了一些接口来设置它:


(2)、block表设定

要想对block表进行设定,系统为我们提供了下面的系统调用函数:

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

(3)、pending表获取

要想的到一个pending表的话,系统也为我们提供了接口来调用:
 #include <signal.h>

       int sigpending(sigset_t *set); //set为输出性参数 ,得到当前的pending集 

(4)、handler表设定 

对于一个信号的捕捉动作,我们是可以对它进行修改的。系统调用接口是 :
 #include <signal.h>

       int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);//signum表示修改的信号;
           struct sigaction {
               void     (*sa_handler)(int);//定义的捕捉动作
               void     (*sa_sigaction)(int, siginfo_t *, void *);//先不用了解这个是针对于实时信号
               sigset_t   sa_mask;//在捕捉当前信号的时候 对 sa_mask信号进行屏蔽,默认当前信号进行屏蔽
               int        sa_flags;//捕捉的方式 ,此处填0
               void     (*sa_restorer)(void);
           };
	oldact输出型参数,得到老的信号捕捉方式 
有些同学,可能会问为甚么,,,有些函数总是想要得到老的信号集 或者捕捉动作。。。。。
那是因为我们,有点时候需要对当前的信号集或者是 捕捉动作做出   恢复。

(5)、代码实现信号屏蔽

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

//验证sigprocmask函数
//还有法人就是  sigpending
//sigaction函数

//实现一个2信号的屏蔽  
void handler(int sig)
{
	printf("get  a signal :%d \n",sig);
}
//打印penging表
void Printsigset(sigset_t * set)
{
	int i = 0 ;
	for(i =1 ;i <32;++i)
	{
		if(sigismember(set,i))
		{
			printf("1");
		}
		else
		{
			printf("0");
		}
	}
	printf("\n");
}
int main()
{
	//假设要为二号信号添加屏蔽
	sigset_t  set,oset;
	sigemptyset(&set);
	sigemptyset(&set);
	sigaddset(&set,2);
	//修改2号信号的捕捉动作 
	struct sigaction act,oact;
	act.sa_handler =   handler;
	act.sa_flags = 0 ;
	sigemptyset(&act.sa_mask);
	sigaction(2,&act,&oact);
	//为2号信号添加屏蔽 
	sigprocmask(SIG_SETMASK,&set,&oset);
	int count  = 0 ;
	//10秒后 为2号解屏蔽
	while(count++ < 10)
	{
		sigset_t p;
		sigemptyset(&p);
		sigpending(&p);
		Printsigset(&p);
		sleep(1);
	}
	//解开2号信号的屏蔽  
	sigprocmask(SIG_SETMASK,&oset,NULL);
	sleep(2);
	//修改2号信号的捕捉动作 
	//sigaction(2,&oact,NULL);


	return  0;

}
执行结果 :

7、实现一个自己的sleep函数 

看到了这个题目 ,大家都会在想,要怎么实现呢???
发动脑筋想想.......
没思路!!!!没思路!!!!!还是没思路 。。。。。。。生气
你当然没思路了,要是你有了思路 ,我还讲什么呢??、
要想实现实现sleep函数 我们就需要知道一个   和信号有关的函数  -----pause函数 

(1)、pause函数 

函数原型 :
       #include <unistd.h>

       int pause(void);
这个函数 有什么用呢?它为什么可以实现sleep函数呢???
大家不要急   !!!容我细细说来;
pause函数的作用是 :使调用进程挂起直到有信号递达。
pause进程是没有正确的返回值的,为什么这么说呢???大家往下看
如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;
如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;
如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR, 所以pause只有出错的返回值(想想以前还学过什么函数只有出错返回值?)。错误码EINTR表 示“被信号中断”。
当然这样就想实现一个sleep函数的话,那是不可能,我们还需要调用之前学过的alarm函数     
下⾯面我们⽤用alarm和pause实现sleep(3)函数,称为mysleep。

(2)、简单实现mysleep函数 

实现代码:


上面实现的一个sleep函数 ,大家觉得这个函数正确吗???
可能大家都看到我上面写的代码中:
alarm函数之后写了一个存在bug;;;;大家觉得次出有什么bug呢?是不能实现sleep,还是函数运行会出错呢???
看大家想的这么辛苦我就直接来告诉大家吧!!!!
要这个函数在多线程、多进程的情况下运行的话 。。。。。

如果要是发生了上面的错误之后 ,那要该怎么办呢????

(3)、sigsuspend函数调用

根据上面的错误,我们就会想是,要是下面这样执行步骤是不是就可以完成sleep函数了。。。
1. 屏蔽SIGALRM信号;
2. alarm(nsecs);
3. 解除对SIGALRM信号的屏蔽;
4. pause();
这样就可以了,正常的接受到alarm函数定时产生的SIGALRM信号,但是有的同学马上就提出了疑问,?
要是在 第三步刚执行完之后,进程的执行刚好有被切出去了,返回是 刚好是 内核态转到用户态,直接将  信号递达了,但是pause函数还是没有执行,我们要怎么办???
这还是真是一个问题,有的小朋友就在想,要是可以将第三步,第四步合到一块不就可以了吗??、
但有这样的操作吗???
怎么说还真是有  ,sigsuspend函数就实现了这两步操作,并且这个操作还是原子的。。。
函数原型:
       #include <signal.h>

       int sigsuspend(const sigset_t *mask);//mask表示的是要解除某个信号的信号集

(4)、sleep函数的正确实现

8、关于SIGCHLD信号

其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自 定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。
下面我们来写段代码验证一下:
#include<stdio.h>
#include<signal.h>
//验证SIGCHLD信号
void myhandler(int sig)
{
	printf("get  a signal :%d \n",sig);
}
int main()
{
	//先对信号SIGCHLD进行自定义捕捉 
	signal(SIGCHLD,myhandler);
	//fork
	pid_t  pid ;
	if(pid = fork() == 0)
	{
		//child
		printf("I am  child  :%d \n",getpid());
		sleep(2);
		exit(1);
	}
	else
	{
		//parent
		printf("I  am parent :%d \n",getpid());
		waitpid(pid,NULL,0);
	}
	return  0;
}

实现结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值