我眼中的“信号”
Posted 巴山雨夜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我眼中的“信号”相关的知识,希望对你有一定的参考价值。
生活中,我们会遇到很多种类的信号,例如交通灯、闹钟、还有的就是门铃等等;但这只是你眼中的信号,在我看来信号有了新的解释;
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;
实现结果:
以上是关于我眼中的“信号”的主要内容,如果未能解决你的问题,请参考以下文章
为啥 semget() 在 *创建* 信号量时会导致 EACCES 错误?