一、sigqueue函数

功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用

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

参数:

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

在调用sigqueue时,sigval指定的信息会拷贝到3参数信号处理函数(3参数信号处理函数指的是信号处理函数由sigaction安装,并设定了sa_sigaction指针)的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。

信号参数的传递过程可图示如下:

返回值:成功返回0,失败返回-1

typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;

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

注意:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。

写两个小程序测试一下:

首先是接收信号:

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h> #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) void handler(int, siginfo_t *, void *); int main(int argc, char *argv[])
{
struct sigaction act;
act.sa_sigaction = handler; //sa_sigaction与sa_handler只能取其一
//sa_sigaction多用于实时信号,可以保存信息
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO; // 设置标志位后可以接收其他进程
// 发送的数据,保存在siginfo_t结构体中 if (sigaction(SIGINT, &act, NULL) < 0)
ERR_EXIT("sigaction error"); for (; ;)
pause(); return 0; } void handler(int sig, siginfo_t *info, void *ctx)
{
printf("recv a sig=%d data=%d data=%d\n",
sig, info->si_value.sival_int, info->si_int); }

在前面的《UNIX环境高级编程——信号(API)》中说过,sa_sigaction与SA_SIGINFO要配合使用,如上所示,siginfo_t 结构体也可以参见这篇文章。

然后是信号发送:

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h> #define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0) int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage %s pid\n", argv[0]);
exit(EXIT_FAILURE);
} pid_t pid = atoi(argv[1]); //字符串转换为整数
union sigval val;
val.sival_int = 100;
sigqueue(pid, SIGINT, val); // 只可以发信号给某个进程,而不能是进程组 return 0; }

测试如下:

先运行recv程序:

huangcheng@ubuntu:~$ ./sigqueue_recv

再ps出recv进程的pid,然后运行send程序:

huangcheng@ubuntu:~$ ./sigqueue_send 2222
huangcheng@ubuntu:~$ ./sigqueue_send 2222
huangcheng@ubuntu:~$ ./sigqueue_send 2222
huangcheng@ubuntu:~$ ./sigqueue_send 2222

则recv进程会输出一条recv语句,当然我们也可以ctrl+c 给自己发送信号,如下所示,结果是一样的。

huangcheng@ubuntu:~$ ./sigqueue_recv
recv a sig=2 data=100 data=100
recv a sig=2 data=100 data=100
recv a sig=2 data=100 data=100
recv a sig=2 data=100 data=100
^Crecv a sig=2 data=100 data=100
^Crecv a sig=2 data=100 data=100
^Crecv a sig=2 data=100 data=100
^Crecv a sig=2 data=100 data=100
............................................................

需要提醒一下的是siginfo_t 结构体的两个参数
(int      si_int;      /* POSIX.1b signal */      
  void    *si_ptr;      /* POSIX.1b signal */)的值也会与si_value 一致,取决于发送的是sival_int 还是 sival_ptr。

二、利用pause和alarm函数实现sleep函数

#include <unistd.h>
int pause(void);

pause函数使调用进程挂起直到信号递达如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR,所以pause只有出错的返回值。错误码EINTR表示“被信号中断”。

下面使用pause和alarm实现sleep(3)函数,称为mysleep:

#include<stdio.h>
#include<signal.h>
#include<unistd.h> void sig_alrm(int signo)
{
/* nothing to do */
} unsigned int mysleep(unsigned int nsecs)
{
struct sigaction newact, oldact;
unsigned int unslept; newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact); alarm(nsecs);
pause(); unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL); return unslept;
} int main(void)
{
while (1)
{
mysleep(2);
printf("Two seconds passed\n");
}
return 0;
}

1. main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
2. 调用alarm(nsecs)设定闹钟。
3. 调用pause等待,内核切换到别的进程运行。
4. nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。
5. 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm。
6. 切换到用户态执行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽,从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
7. pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。

需要注意的是虽然sig_alrm函数什么都没干,但还是得注册作为SIGALRM的处理函数,因为SIGALRM信号的默认处理是终止进程,这也是在mysleep函数返回时要恢复SIGALRM信号原来的sigaction的原因。此外,mysleep函数的返回值表示“未睡到”的时间,即unslept,当尚未计时到nsecs而pause函数先被其他信号处理函数所中断返回,在外界看来就是在sleep期间被其他信号处理函数中断了,则mysleep返回非0值,即unslept。如sleep(3)的man 手册写的返回值:

RETURN VALUE: Zero if the requested time has elapsed, or the number of seconds left to sleep, if the call was interrupted  by  a signal handler.

当然如果是被SIGALRM handler所中断,则表示睡眠时间到,mysleep返回值为0。

三、竞态条件与sigsuspend函数

现在重新审视上面的mysleep函数,设想这样的时序:

1. 注册SIGALRM信号的处理函数。
2. 调用alarm(nsecs)设定闹钟。
3. 内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个都要执行很长时间
4. nsecs秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。
5. 优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处理函数sig_alrm之后再次进入内核。
6. 返回这个进程的主控制流程,alarm(nsecs)返回,调用pause()挂起等待。
7. 可是SIGALRM信号已经处理完了,还等待什么呢?
      出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。虽然alarm(nsecs)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(nsecs)之后的nsecs秒之内被调用。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件(Race Condition)。

如何解决上述问题呢?我们可能会想到,在调用pause之前屏蔽SIGALRM信号使它不能提前递达就可以了。看看以下方法可行吗?
1. 屏蔽SIGALRM信号;
2. alarm(nsecs);
3. 解除对SIGALRM信号的屏蔽;
4. pause();

从解除信号屏蔽到调用pause之间存在间隙,SIGALRM仍有可能在这个间隙递达。要消除这个间隙,我们把解除屏蔽移到pause后面可以吗?
1. 屏蔽SIGALRM信号;
2. alarm(nsecs);
3. pause();
4. 解除对SIGALRM信号的屏蔽;

这样更不行了,还没有解除屏蔽就调用pause,pause根本不可能等到SIGALRM信号。要是“解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作就好了,这正是sigsuspend函数的功能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。

#include <signal.h>

int sigsuspend(const sigset_t *sigmask);

和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。
调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

以下用sigsuspend重新实现mysleep函数:

#include<stdio.h>
#include<signal.h>
#include<unistd.h> void sig_alrm(int signo)
{
/* nothing to do */
} unsigned int mysleep(unsigned int nsecs)
{
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept; /* set our handler, save previous information */
newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact); /* block SIGALRM and save current signal mask */
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask); alarm(nsecs); suspmask = oldmask;
sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't block */ sigsuspend(&suspmask); /* wait for any signal to be caught */ /* some signal has been caught. SIGALRM is now blocked */
unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL); /* reset previous action */ /* reset signal mask, which unblocks SIGALRM */
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return(unslept);
} int main(void)
{
while (1)
{
mysleep(2);
printf("Two seconds passed\n");
}
return 0;
}

如果在调用mysleep函数时SIGALRM信号没有屏蔽:
1. 调用sigprocmask(SIG_BLOCK, &newmask, &oldmask);时屏蔽SIGALRM。
2. 调用sigsuspend(&suspmask);时解除对SIGALRM的屏蔽,然后挂起等待待。
3. SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。
4. 调用sigprocmask(SIG_SETMASK, &oldmask, NULL);时再次解除对SIGALRM的屏蔽。

最新文章

  1. Eclipse自动补全设置
  2. 用Runtime.getRuntime().exec()需要注意的地方
  3. Redis系列(4)_持久化方式-RDB
  4. python 跨语言数据交互、json、pickle(序列化)、urllib、requests(爬虫模块)、XML。
  5. docker RESTful API
  6. ubuntu 安装AMP环境的笔记 Prefork方式与fast-cgi方法
  7. kav卡巴斯基2014 优化设置
  8. MediaInfo源代码分析 2:API函数
  9. Fantageek翻译系列之《使用Autolayout显示变化高度的UITableViewCell》
  10. Linux经常使用命令(十一) - more
  11. 华为C8812E 手机logcat不出日志解决方案
  12. Git在tortoiseGit以及eclipse的使用方法
  13. svn up出现类似svn: Error converting entry in directory &#39;.&#39; to UTF-8问题解决
  14. 转:sql server锁知识及锁应用
  15. burpsuite拦截https数据包(Firefox)
  16. KindEditor的简单使用,以及上传图片预览图片,用户删除图片后的数据处理(重点),以及 BeautifulSoup,shutil两模块了解
  17. WCF中的REST是什么
  18. KVM虚拟机安装报错 KVM is not available
  19. OpenACC 简单的直方图
  20. 009--EXPLAIN用法和结果分析

热门文章

  1. Mysql锁机制--索引失效导致行锁变表锁
  2. 在移动端画出真正的1px边框
  3. WPF ListBox 获取listBoxItem
  4. 吴恩达深度学习第1课第3周编程作业记录(2分类1隐层nn)
  5. UI相关
  6. python序列化pickle/cPickle
  7. ACM pie
  8. Java第7次实验提纲(多线程)
  9. GDAL C#版本 &quot;安全透明方法&quot;问题解决方案
  10. Lucene总结