操作系统 — 浅析信号

浅析信号







首先每个信号都有一个名字,这些名字都以3个字符SIG开头.例如,SIGABRT是夭折信号,当进程调用abort函数时产生这种信 号.SIGALRM是闹钟信

号, 由alarm函数设置的定时器超时后将产生这种信号. 信号的种类是非常庞大的. 在头文件<signal.h>中,信号名都被定义为正整数常量(信号编

号). 实际上,实现将各信号定义在另一个头文件中,但是该头文件又包括在<signal.h>中.内核包括对用户级应用程序有意义的头文件,这被认为

一种 不好的形式,so如若应用程序和内核两者都需使用同一定义,那么就将有关信息放置在内核头文件当中,然后用户级文件再包括该内核头 件.



实际上,不包括编号为0的信号,kill函数对编号0有特殊的应用. 当然在生活中产生信号的条件非常之多:1.比如当用户按某些终端键时,引发终

的信号. 2.硬件异常产生信号  3.进程调用Kill 用户可用kill(1)命令将信号发送给其他进程. 4.当检测到某种软件条件已经发生,并应将

其通知有关 程时 也产生信号.
.


信号分为可靠信号和不可靠信号



不可靠信号:又叫非实时信号,linux的信号继承unix,所以会有一点问题. 1.在unix信号中,一旦信号抵达了,信号执行了自己的信号处理函数之

后,信号在内核 中将自己的信号处理方式就会恢复至默认,不过我们的Linux对这种情况进行了优化已经解决了问题. 2.在unix系统下会导致信号丢

,也就是瞬 间来到了许许多多个信号,最多只能处理一个信号(信号丢失),这种情况下linux没有进行优化,其实是拥有解决方法:比如对信号进

行排队等等 但是呢! 因为linux现在已经用了这么多年了,很多基于避免信号丢失的代码已经写成了,所以对于Linux来说,就算你现在有更好的解

决方法, 但是你不能够更改 1-31号 信号的结构. 通俗的来说,就是你在盖楼,当你盖到50层的时候,发现第一层有一点小小的问题. 而这49层又基于

第一 层,你不可能因为这个小问题而让49层楼重新盖吧?


可靠信号:又叫实时信号,也就是说1.信号在执行自己的信号处理函数之后,信号在内核中的处理方式不会恢复回默认! 2.不会出现信号的丢失.

其实我们64个信号当中,34-64号信号全都是可靠的信号,但是这些函数用户不能使用. 那些都是系统用来处理线程当中的问题的.


处理信号



在某个信号出现时,可以告诉内核按下列3种方式之一进行处理,我们称之为信号的处理或与信号相关的动作.

1.忽略此信号. 大多数信号都可使用这种方式处理,但是有两种信号绝对不会被忽略. 它们是SIGKILL和SIGSTOP. 这两种信号不能被忽略的原因是:

们向内核和超级用户提供了使进程终止或者停止的可靠方法. 另外,如果忽略某些由硬件异常产生的信号,则此进程的运行行为是未定义的.

2.捕捉信号. 为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数. 在用户函数中,可执行用户希望对这种事件进行的处理. 例如

,若正在编写一个命令解释器,它将用户的输入解释为命令并执行之,当用户用键盘产生中断信号时,很可能希望该命令解释器返回到主循环,终

止正在为该用户执行的命令. 如果捕捉到SIGCHLD信号,则表示一个子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进

程ID以及它的终止状态. 所以我们总结一点! 不能捕捉SIGKILL和SIGSTOP这两个信号

3.执行系统的默认动作. 下图就是一些信号的说明和系统默认动作! 我们看到大多数信号的系统默认动作是终止该进程.



绝大部分信号的默认动作无外乎就是五种!

core dumped:将你的内存相关退出信息,存储到core文件中,以便于你以后查看.

IGN:告诉父进程,子进程已经执行结束等待回收

term:什么也不做直接死亡

cont:程序进行执行

stop:让你的程序暂停.

在系统默认动作列,"终止+core"表示在进程当前工作目录的core文件中复制了该进程的内存映像(该文件名为core,由此可以看出这种功能很久之

前就是UNIX的一部分). 大多数UNIX系统调试程序都使用core文件检查进程终止时的状态.  在下列条件下不产生core文件:

 (a)进程是设置用户ID的,而且当前用户并非程序文件的所有者;

 (b)进程是设置组ID的,而且当前用户并非该程序文件的组所有者;

 (c)文件太大.  core文件的权限通常是用户读写.


我们的信号其实也叫做"软中断". 何为中断? 硬件的中断:正在运行的硬件,突然被打断去做另外一个事情,然后完成后又回来继续执行刚刚没

做完的事情. 所以呢信号就是在软件上的对中断的模拟. 提到信号大家绝壁会想到一个指令! kill指令. 我们平时使用Kill来发送信号,所以

对他就很眼熟,其实呢kill指令的底层是使用kill函 数构成的! 所以呢我们来认识一下这个函数:


kill函数


kill命令就是通过调用kill函数生成的,kill函数的作用可大了. kill函数将信号发送给内核,然后呢内核根据信号的对应操作对指定的进程进行

执行.比如你向一个进程发送SIGKILL信号,那么内核就会终止掉这个进程! 我们继续来了解它的情况:

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

 int kill(pid_t pid, int sig);
pid参数就是你信号发送至的进程ID,后面的sig就是发送那个信号. 对于pid参数有好几种情况分别为:

pid > 0  : 给指定的pid发送信号                                 pid = 0  :本进程组的任何一个进程发送消息.

pid = -1 : 给任何有权发送信号的进程发送信号                    pid < -1 :|pid|的进程组发送信号.


信号的阻塞



实际执行信号的处理动作称为信号递达,信号从产生到递达之间的状态称之为信号未决,进程可以选择性的阻塞某个信号,被阻塞的信号产生时将

保持在未决状态,知道进程解除对此信号的阻塞,才执行 实际执行信号的处理动作称为信号递达,信号从产生到递达之间的状态,称为信号未决.进

程可以选择阻塞某个信号。 被阻塞的信 号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作. 阻塞和忽略是不相同的,

只要信号被阻塞就不会递 达,而忽略是在递达之后可选的一种处理动作. 达的动作,阻塞和忽略是不相同的,只要信号被阻塞就不会递达,而忽略

是在递达之后 可以选择的一种处理动作!


在PCB当中,分别使用了pending,block,handler三个表项来分别记录 信号是否递达,应该让那些信号阻塞,每个信号对应的处理函数.

每个信号都有两个标志位分为表示阻塞和未决,还有一个函数指针表处理动作.信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号

递达才消除该标志.

如果在进程在阻塞某信号时,该信号产生过多次,Linux是这样实现的:常规信号在抵达之前产生多次只计一次,而实时信号在抵达之前产生多个信

号可以依次放到一个队列中,每个信号只能有一个bit的未决标志,非0既1,不记录该信号产生了多少次,阻塞标志也是这样表示的. 因此呢,未决

和阻塞标志可以用想用的数据类型sigset_t来存储,sigset_t为信号集,这个类型可以表示每个信号的"有效"或"无效"状态,在阻塞信号集中"有

效"和"无效"的含义是该信号是否被阻塞,而在未决信号集中类似. 阻塞信号集也叫做当前进程的信号屏蔽字,屏蔽这样理解"是阻塞 不是忽略".

常见信号处理函数


//sigset_t类型对于每种信号用一个bit表示 "有效"或者"无效" 接下来我们来认识一下信号集操作函数
#Include<signal.h>

int sigemptyset(sigset_t *set);
//初始化set所指向的信号集,使其中所有信号的对应的bit清零,表示该信号集不包含任何有效信号.

int sigfillset(sigset_t *set);
//初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号机的有效信号包括系统支持的所有信号.

int sigaddset(sigset_t *set,int signo);
//在该信号集中添加某种有效信号.

int sigdelset(sigset_t *set,int signo);
//在该信号集中删除某种有效信号

int sigismemeber(const sigset_t *set,int signo);
//是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1

int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
//读取或更改进程的信号屏蔽字(阻塞信号集)如果成功返回0 失败返回-1

int sigpending(sigset_t *set);
//读取当前进程的未决信号集,通过set参数传出,调用成功则返回0,出错则返回-1.
在这里我们着重介绍一下sigset_t的结构,我们来看看他究竟是一个什么东西:

typedef struct
{
    unsigned long int __val[_SISSET_NWORDS];
}__sigset_t;
当你定义了一个sigset_t变量的时候,一定要要记住!! 使用sigemptyset()函数对变量初始化,否则信号集可能会出现随机值!!

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

how: 
SIG_BLOCK: mask = mask = mask | set;
SIG_UNBLOCK: mask = mask & ~set;
SIG_SETMASK: mask = set;
//往内核当中设置,存储阻塞信号的状态.
sigprocmask的返回值如果失败返回-1 成功返回为0. 接下来我实现一个关于信号阻塞的实例,用来打印出block表当中的内容. 也就是说打印出被
阻塞的信号. 那么话不多说我们开始写代码:

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

void printsigset(sigset_t *set)
{
	int i = 0;
	for(;i<32;i++){
		if(sigismember(set,i))
			putchar('1');
		else
			putchar('0');
	}
	puts("");
}

int main()
{
	sigset_t s,p;
	sigemptyset(&s);
	sigaddset(&s,SIGINT);
	sigprocmask(SIG_BLOCK,&s,NULL);
	while(1)
	{
		sigpending(&p);
		printsigset(&p);
		sleep(1);
	}
	return 0;
}
接下来带上代码注释:


这个程序的大概意思就是 我们阻塞一个信号集,让它一直处于未决状态,并把它里面的信号编号显示出来,比如中途我们加入了一个ctrl+c. 后面
信号集里面就会出现这个信号,然后他们还是一直处于未决状态. 我们来看看结果:

特别提醒:如果一个信号被进程阻塞,它就不会传递给进程,但会停留在待处理状态,当进程解除对待理信号的阻塞时,待处理信号就会立刻被处理. 如果掌握了这些之后,我觉得我们就可以继续学习信号! 信号的捕捉. <-----博客传送门

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值