信号的阻塞、未达:

  linux中进程1向进程2发送信号,要经过内核,内核会维护一个进程对某个信号的状态,如下图所示:

当进程1向进程2发送信号时,信号的传递过程在内核中是有状态的,内核首先要检查这个信号是不是处于阻塞状态,然后检查这个信号是不是处于未决状态,最后检查是不是忽略该信号。

更详细的信号传递过程如下:

一个信号送到进程2时,先检查这个进程的信号屏蔽字block,如果该信号对应位是1,表示进程把这个信号是屏蔽(阻塞)了,然后内核就将pending状态字的相应位置为1,表示信号未抵达,当我们在进程2中调用一个函数将block中的相应位置为0时,pending中的对应位就会被置为0,这时候刚才未达的信号就可以继续往后走了,然后检查进程2对这个信号是不是忽略,如果不是忽略就调用相应的信号处理函数。

  下面介绍几个操作信号集的函数:

int sigemptyset(sigset_t  *set)   把信号集(64bit)全部清零

int sigfillset(sigset_t *set)     把信号集全部置为1

int sigaddset(sigset_t *set, int signo) 根据signo,把信号集中的相应位置为1

int sigdelset(sigset_t *set, int signo) 根据signo,把信号集中相应的位清0

int sigismember(const sigset_t *set,  int signo) 判断signo是否在信号集中

  获取block状态字状态的函数:

int sigprocmask(int how, const sigset_t *set, sigset_t *oset)  读取或者更改进程的信号屏蔽状态字(block)

若成功则返回0,若出错则返回-1

如果oset是非空指针,则读取进程的当前信号屏蔽状态字通过oset传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的状态字备份到oset里,然后根据set和how参数更改信号屏蔽字,假设当前的信号屏蔽字为mask,下表说明了how的可选值。

int sigpending(sigset_t *set)  获取进程没有抵达的状态字

信号阻塞、未达示例程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
if(SIGINT == num)
{
printf("recv signal SIGINT\n");
}
else if(SIGQUIT == num)
{
sigset_t uset;
sigemptyset(&uset);
sigaddset(&uset, SIGINT);
sigprocmask(SIG_UNBLOCK, &uset, NULL);
printf("recv signal num = %d\n", num);
} } void printsigset(sigset_t *set)
{
int i = ;
for(i = ; i < ; i++)
{
if(sigismember(set, i))
{
putchar('');
}
else
{
putchar('');
}
} printf("\n");
} int main()
{
sigset_t bset;
sigset_t pset; sigemptyset(&bset);
sigaddset(&bset, SIGINT); if(signal(SIGINT, my_handler) == SIG_ERR)
{
perror("signal error");
exit();
} if(signal(SIGQUIT, my_handler) == SIG_ERR)
{
perror("signal error");
exit();
} sigprocmask(SIG_BLOCK, &bset, NULL); for(;;)
{
sigpending(&pset);
printf("bset : \n");
printsigset(&bset);
printf("pset : \n");
printsigset(&pset); sleep();
} return ;
}

在主函数中,我们将SIGINT设置为阻塞,执行程序时,当没有按下ctrl+c(产生SIGINT)时,pending状态字为全0,说明没有未达信号,当按下ctrl+c时,由于block中将SIGINT设置为了阻塞,所以当产生SIGINT时,pending中的第2位(SIGINT对应的位,信号从第1位开始算起)被置为了1。执行程序,结果如下:

ctrl+c产生SIGINT, ctrl+\产生SIGQUIT。按下ctrl+\触发信号处理函数,产生SIGQUIT信号,解除对SIGINT的阻塞,而刚刚那个未达的SIGINT被送达,再一次触发信号处理函数,打印出recv signal SIGINT。SIGINT被送达后,相应的pending位被清0了。我们在信号处理函数中将block中的阻塞位清除,但是并没有起作用(原因未知,在信号处理函数中设置block,只是临时起作用,例如:现在有一个未达信号SIGINT,我们产生SIGQUIT进入信号处理函数,临时将block中的阻塞位解除,然后处理这个未达信号,处理完之后,将阻塞位恢复原样,然后继续执行。 从执行结果也可以看出,是先打印出recv signal SIGINT,又打印出recv signal num = 3)。

  block中设置SIGINT屏蔽字,按下ctrl+c,pending相应位置为1,按下ctrl+\,SIGQUIT被送达一次,信号处理函数被调用,SIGINT又被送达一次(因为刚才处于pending),SIGQUIT被送达的那次,在信号处理函数中将block中SIGINT位清0,并恢复SIGINT的处理函数为默认。再次按下ctrl+c,应该退出程序,但是没有退出,而是pending中又被置为1,再次按下ctrl+\,则信号处理函数中SIGQUIT相关的打印被输出,然后程序退出。

sigaction注册信号处理函数:

  sigaction也用来做信号的安装,该函数功能比signal更强大。函数原型如下:

int sigaction(int signum, const struct sigaction *act, const struct sigaction *old)

  该函数的第一个参数为信号的值,可以为除SIGKILL和SIGSTOP外的任何一个有效信号值(为这两个信号定义自己的处理函数,将导致安装错误)。

  第二个参数为指向sigaction的一个实例的指针,在结构sigaction中指定了对特定信号的处理,可以为空,进程会以缺省方式对信号进行处理。

  第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定为NULL。

第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等。

struct sigaction

{

  void (*sa_handler)(int)  //信号处理程序,不接受额外数据,老的处理函数

  void (*sa_sigaction)(int, siginfo_t *, void *) // 信号处理程序,能接受额外数据,和sigqueue配合使用

  sigset_t sa_mask;

  int sa_flags;  //影响信号的行为,SA_SIGINFO表示能接受数据,如果进程想要接收额外数据,则应设置该位

  void (*sa_restorer)(void)  //废弃

}

sa_handler和sa_sigaction不能同时存在,两个都赋值的话,优先调用sa_sigaction。

示例程序:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
printf("recv signal num = %d\n", num);
} int main()
{
struct sigaction act = {}; act.sa_handler = my_handler; sigaction(SIGINT, &act, NULL); for(;;)
{
sleep();
} return ;
}

执行结果如下:

赋值sa_sigaction的示例程序:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
printf("recv signal num = %d\n", num);
} void my_sa_sigaction(int num, siginfo_t *info, void *p)
{
printf("recv sig : %d\n", num);
} int main()
{
struct sigaction act = {}; act.sa_handler = my_handler;
act.sa_sigaction = my_sa_sigaction; sigaction(SIGINT, &act, NULL); for(;;)
{
sleep();
} return ;
}

执行结果如下:

sigqueue函数:

  新的发送信号的系统调用,主要是针对实时信号提出的信号带有参数,与函数sigaction()配合使用。比kill函数强大,函数原型如下:

int sigqueue(pid_t pid, int sig, const union sigval value)

  第一个参数指定接收信号的进程id,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

sigval联合体如下:

  typedef union sigval

  {

    int sival_int;

    void *sival_ptr;

  }sigval_t;

下面我们编写带有额外数据的信号发送处理函数,先给出信号处理函数中第二个参数siginfo_t的具体定义:

siginfo_t {
  int si_signo; /* Signal number */
  int si_errno; /* An errno value */
  int si_code; /* Signal code */
  int si_trapno; /* Trap number that caused
  hardware-generated signal
  (unused on most architectures) */
  pid_t si_pid; /* Sending process ID */
  uid_t si_uid; /* Real user ID of sending process */
  int si_status; /* Exit value or signal */
  clock_t si_utime; /* User time consumed */
  clock_t si_stime; /* System time consumed */
  sigval_t si_value; /* Signal value */
  int si_int; /* POSIX.1b signal */
  void *si_ptr; /* POSIX.1b signal */
  int si_overrun; /* Timer overrun count; POSIX.1b timers */
  int si_timerid; /* Timer ID; POSIX.1b timers */
  void *si_addr; /* Memory location which caused fault */
  int si_band; /* Band event */
  int si_fd; /* File descriptor */
}

程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_handler(int num)
{
printf("recv signal num = %d\n", num);
} void my_sa_sigaction(int num, siginfo_t *info, void *p)
{
int myintnum = ;
myintnum = info->si_value.sival_int;
printf("%d %d \n", myintnum, info->si_int);
} int main()
{
pid_t pid;
struct sigaction act = {}; act.sa_sigaction = my_sa_sigaction;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO; if (sigaction(SIGINT, &act, NULL) < )
{
perror("sigaction error");
exit();
} pid = fork(); if(pid == -)
{
perror("fork error");
} if(pid == )
{
union sigval usig;
usig.sival_int = ;
int n = ;
while(n > )
{
sigqueue(getppid(), SIGINT, usig);
n--;
sleep();
}
exit();
} for(;;)
{
sleep();
} return ;
}

执行结果如下:

  如果子进程不睡眠,而是一直发信号,则可能造成信号丢失,因为SIGINT是不可靠信号。

实时信号与非实时信号示例程序如下:

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> void my_sigaction(int signum, siginfo_t *info, void *p)
{
if(SIGINT == signum)
{
printf("recv SIGINT, num = %d\n", signum);
}
else if(SIGRTMIN == signum)
{
printf("recv SIGRTMIN num = %d\n", signum);
}
else if(SIGUSR1 == signum)
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGRTMIN);
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
else
{
printf("recv else\n");
}
} int main()
{
pid_t pid;
int ret = ;
struct sigaction act = {};
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = my_sigaction; if (sigaction(SIGINT, &act, NULL) < )
{
perror("sigaction error");
exit();
} if(sigaction(SIGRTMIN, &act, NULL) < )
{
perror("sigaction error");
exit();
} if(sigaction(SIGUSR1, &act, NULL) < )
{
perror("sigaction error");
exit();
} sigset_t bset; sigemptyset(&bset);
sigaddset(&bset, SIGINT);
sigaddset(&bset, SIGRTMIN); sigprocmask(SIG_BLOCK, &bset, NULL); pid = fork(); if(pid == -)
{
perror("fork error");
exit();
} if(pid == )
{
int i = ;
union sigval v;
v.sival_int = ; for(i = ; i < ; i++)
{
ret = sigqueue(getppid(), SIGINT, v);
if(ret != )
{
printf("sent SIGINT failed\n");
}
else
{
printf("sent SIGINT success\n");
} } v.sival_int = ;
for(i = ; i < ; i++)
{
ret = sigqueue(getppid(), SIGRTMIN, v);
if(ret != )
{
printf("sent SIGRTMIN failed\n");
}
else
{
printf("sent SIGRTMIN success\n");
}
} v.sival_int = ;
kill(getppid(), SIGUSR1); exit();
} while()
{
sleep();
} printf("end main ...\n"); return ;
}

我们注册了非实时信号SIGINT和实时信号SIGRTMIN,还有一个用户自定义信号SIGUSR1,在本例中负责发送解除命令。它们为同一个处理程序,只是有不同的分支,在子进程中发送了3次SIGINT和三次SIGRTMIN,一开始,这两个信号都是阻塞的,因此发送完成后它们都处于未决状态,当发送SIGUSR1后,临时解除阻塞,未决信号重新被发送,但是非实时信号只被发送了一次,属于不可靠信号。而实时信号发送原来的次数,属于可靠信号。执行结果如下所示:

实时的信号会存储在进程的结构中,所以不会丢失,会缓存到内核中,但是存储的数量也是有上限的,超过缓存上限后,再到来的信号会直接扔掉而不会将之前的冲掉,具体上限可以使用ulimit -a查看,pending signals如下所示:

而非实时信号,linux内核是不缓存的,发送多少也无所谓,最终只缓存一条。

2.3 linux中的信号分析 阻塞、未达的更多相关文章

  1. 2.2 linux中的信号分析

    信号: 信号是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动. 信号是因为某些错误条件而产生的,比如内存段冲突.浮点处理器错误或者非法指令等. 信号是在软件层次上对中断的一种 ...

  2. 25 Linux中的信号

    Linux中的信号 信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件).每个信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD.SIGINT等,它们在系统头文件中定义,也可以通 ...

  3. Linux中多线程信号的处理

    1. 博文:Linux多线程中使用信号-1  http://blog.csdn.net/qq276592716/article/details/7325250 2. 博文:Linux多线程信号总结  ...

  4. linux中的信号简介和trap命令

    1.信号 linux通过信号来在运行在系统上的进程之间通信,也可以通过信号来控制shell脚本的运行 主要有一下信号 1 ##进程重新加载配置 2 ##删除进程在内存中的数据 3 ##删除鼠标在内存中 ...

  5. Linux中的日志分析及管理

    日志文件对于诊断和解决系统中的问题很有帮助,因为在Linux系统中运行的程序通常会把系统消息和错误消息写入相应的日志文件,这样系统一旦出现问题就会“有据可查”.此外,当主机遭受攻击时,日志文件还可以帮 ...

  6. linux中的信号机制

    概述 Linux信号机制是在应用软件层次上对中断机制的一种模拟,信号提供了一种处理异步事件的方法,例如,终端用户输入中断键(ctrl+c),则会通过信号机制停止一个程序[1]. 这其实就是向那个程序( ...

  7. 转:linux中select()函数分析

    源地址:http://blog.csdn.net/zi_jin/article/details/4214359 Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱 ...

  8. [转帖]Linux教程(8)-Linux中的进程和日志㐇、

    Linux教程(8)-Linux中的进程和日志 2018-08-20 23:42:23 钱婷婷 阅读数 3554更多 分类专栏: Linux教程与操作 Linux教程与使用   版权声明:本文为博主原 ...

  9. Java基础(一):I/O多路复用模型及Linux中的应用

    IO多路复用模型广泛的应用于各种高并发的中间件中,那么区别于其他模式他的优势是什么.其核心设计思想又是什么.其在Linux中是如何实现的? I/O模型 I/O模型主要有以下五种: 同步阻塞I/O:I/ ...

随机推荐

  1. python获取文件扩展名的方法

    主要介绍了python获取文件扩展名的方法,涉及Python针对文件路径的相关操作技巧 import os.path def file_extension(path): ] print file_ex ...

  2. Phpstorm配置scss不生成缓存

    --no-cache 加上这个,就不会生成 .sass-cache 文件夹了.

  3. Python爬虫Urllib库的基本使用

    Python爬虫Urllib库的基本使用 深入理解urllib.urllib2及requests  请访问: http://www.mamicode.com/info-detail-1224080.h ...

  4. 一个纯净的webpack4+angular5脚手架

    该篇主要是结合刚发布不久的webpack4,搭建一个非cli的angular5的脚手架demo,主要分为以下几个方面阐述下脚手架结构: # 脚手架基础架构(根据angular5的新规范) /** * ...

  5. 动态规划-最长可互除子序列 Largest Divisible Subset

    2018-08-28 17:51:04 问题描述: 问题求解: 本题是一个求最优解的问题,很自然的会想到动态规划来进行解决.但是刚开始还是陷入了僵局,直到看到了hint:LIS,才有了进一步的思路.下 ...

  6. Oracle数据库system用户忘记了密码怎么办

    1.在运行里面输入cmd调出dos窗口,然后在dos窗口中输入sqlplus /nolog 如:D:\oracle\ora92\bin>sqlplus /nolog 2.输入连接命令 如:SQL ...

  7. SpringBoot读取war包jar包Resource资源文件解决办法

    SpringBoot读取war包jar包Resource资源文件解决办法 场景描述 在开发过程中我们经常会碰到要在代码中获取资源文件的情况,而我在最近在SpringBoot项目中时碰到一个问题,就是在 ...

  8. 用MVVM模式开发中遇到的零散问题总结(1)

    https://www.cnblogs.com/tong-tong/archive/2011/11/28/2250948.html

  9. 用Omniauth来Login with Facebook(Go-rails课程)

    https://gorails.com/episodes/login-with-facebook?autoplay=1 大概看了一遍,留了视频的截图. https://gorails.com/epis ...

  10. Confluence 6 为 Active Directory 配置一个 SSL 连接预要求

    希望生成证书,你需要安装下面的组件在你希望连接的 Windows Domain Controller 上. Internet Information Services (IIS) 在你安装 Windo ...