PCB的信号集及信号的三种状态

2019-07-14 11:25发布

信号的三种处理方式: 
1.忽略此信号。 
2.执行该信号的默认处理动作(终止该信号)。 
3.提供信号处理程序(自定义动作),要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。 这三种处理方式的信号状态都为信号递达。 

1.信号的三种状态

1. 信号递达:实际执行信号的处理动作。 
2. 信号未决:信号从产生到递达之间的状态。 
3. 进程可以选择阻塞(block)某个信号。一旦该信号被阻塞就不会被抵达,只有解除阻塞才可被递达。 
  • - 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。 
  • 阻塞与忽略是不同的,只要信号阻塞就不会被递达,忽略是在递达之后可选的一种处理动作。

2.信号在内核中的表示(三张表)信号在进程pcb中的表示方法

每一个进程都有一个PCB(进程控制块),在PCB中有两个信号集,分别是未决信号集阻塞信号集其中未决信号集是记录有没有信号来临,一旦有信号来临则该信号位置1,如下图中有2号信号来临,则将2号未决信号集置1然后未决信号集再将该信号向阻塞信号集传递,如果对应2号信号集的位是1,则表示阻塞,该信号被阻塞在了阻塞信号集上无法到达句柄,该信号处于未决态。若阻塞信号集的2号信号位是0,则2号信号可以抵达句柄,进行动作。该信号此时处于递达态。
  • 一旦信号处于递达态,则不再是未决信号,所以未决信号集对应信号位翻转为0.
  • 未决信号集用户只有读权限,没有修改的权限。这是系统自动设定的。但是阻塞信号集用户是可以自己设定的。
  • pending表中的数据是判断信号是否存在的唯一依据。
结合上图就可以知道:
  • SIGHUP信号未阻塞也未产生过,但当它递达时就会执行默认处理动作。
  • SIGINT信号产生过,但已被阻塞。所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产生过,一旦产生将被阻塞,它的处理动作是用户自定义的捕捉函数handler。
那么可以将信号的三张表总结成这样:
  • 如果一个信号被block,若收到信号,则该信号必定被Pending.
  • 一个进程收到信号时不会立即递达,在此过程中一直被Pending.
 信号集对于前32个信号来说是不支持排队的,意思就是说,你发送了一个信号过来给未决信号集,未决信号集对应信号位置1,你在发送几个相同的信号,未决信号集及记录一个。但是要是在之前那个信号变为递达态的时候在发送过来就可以被记录。但是对后32 个信号是支持排队的。就像你看电视频就可以下一集,对按几次就会跳多集,道理是一样的。Linux是这样规定的:常规信号在递达之前产生多次只记一次,而实时信号在递达之前产生多从可以依次放在一个队列中。
使用kill -l命令查看系统定义的信号列表:  由于没有32、33信号,所以信号共有62个。其中编号34以上为实时信号,我们先来讨论34以下的信号。 

3.信号集操作函数:

信号集sigset_t: 
  • 在上图中,未决和阻塞标志都可以用相同的数据结构(位图)存储。所以当然可以用同一数据类型来表示,这就是sigset_t. 
  • sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。在阻塞信号集其含义是该信号是否被阻塞;在未决信号集中就代表该信号是否处于未决状态。
  • 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

信号集操作函数: #include 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 sigismember(const sigset_t *set,int signo);//是一个布尔函数,用于判断一个信号集的有效信号中是否 包含某种信号,若包含则返回1,不包含返回0,出错返回-1. 除了sigismember,其他四种函数都是成功返回0,出错返回-1。 Sigprocmask函数: 
调用函数Sigprocmask可以获取或更改进程的信号屏蔽字(阻塞信号集)。 #include int sigprocmask(int how,const sigset_t *set,sigset_t *oset); 返回值:成功返回0,出错返回-1. 参数: how参数的可选值 oset:原来的信号屏蔽字。 
如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset中,然后根据set和how参数更改信号屏蔽字。 注:如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。 Sigpending函数:读取当前进程的未决信号集 #include int Sigpending(sigset_t *set); 返回值:成功返回0,出错返回-1. 下面来进行代码测验:阻塞SIGINT信号,按Ctrl-c将SIGINT信号处于未决状态。比特位变为1. // 阻塞SIGINT信号,按Ctrl - c将SIGINT信号处于未决状态。比特位变为1. #include #include #include #include void handler(int signo) { printf("get a %d signo ", signo); exit(1); } void show_pending(sigset_t *pending) { int i = 1; for (; i < 32; i++) { if (sigismember(pending, i)) { printf("1"); } else { printf("0"); } } printf(" "); } int main() { sigset_t set, oset, pending; sigemptyset(&set); // 初始化 清零 sigaddset(&set, SIGINT); // 添加SIGINT信号 signal(SIGINT, handler); sigprocmask(SIG_SETMASK, &set, &oset); // 设置阻塞信号屏蔽字 int count = 0; while (1) { sigpending(&pending); // 获取当前未决信号 show_pending(&pending); sleep(1); } return 0; } 结果: 按下ctrl+c,2号位置1,SIG_INT处于未决态。 解除SIG_INT信号的阻塞状态,使其抵达。捕捉到该信号后,信号集数据又从1变0,变为以前的状态。再次crtl+c后,就不会发生1中的变化了。 #include #include #include #include void handler(int signo) { printf("get a %d signo ", signo); //exit(1); } void show_pending(sigset_t *pending) { int i = 1; for (; i < 32; i++) { if (sigismember(pending, i)) { printf("1"); } else { printf("0"); } } printf(" "); } int main() { sigset_t set, oset, pending; sigemptyset(&set); // 初始化 清零 sigaddset(&set, SIGINT); // 添加SIGINT信号 signal(SIGINT, handler); sigprocmask(SIG_SETMASK, &set, &oset); // 设置阻塞信号屏蔽字 int count = 0; while (1) { sigpending(&pending); // 获取当前未决信号 show_pending(&pending); sleep(1); count++; if (count == 15) { sigprocmask(SIG_SETMASK, &oset, NULL); // 解除了屏蔽 } } return 0; } 结果: 信号阻塞时,按下ctrl+c,2号位置1,SIG_INT处于未决态。解除阻塞状态后,该信号首先被递送到捕捉函数,一旦信号处于递达态,则不再是未决信号,所以未决信号集对应信号位翻转为0。