linux进程间通信—信号(linux查看进程之间的通信指令i)
gudong366 2025-07-23 15:02 4 浏览
Linux环境进程间通信(二)信号(下)
一、信号生命周期
从信号发送到信号处理函数的执行完毕
对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:信号诞生;信号在进程中注册完毕;信号在进程中的注销完毕;信号处理函数执行完毕。相邻两个事件的时间间隔构成信号生命周期的一个阶段。
下面阐述四个事件的实际意义:
信号"诞生"。信号的诞生指的是触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等)。
信号在目标进程中"注册";进程的task_struct结构中有关于本进程中未决信号的数据成员:
struct sigpending pending:
struct sigpending{
struct sigqueue *head, **tail;
sigset_t signal;
};
第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
信号在进程中注册指的就是信号值加入到进程的未决信号集中(sigpending结构的第二个成员sigset_t signal),并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。 只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
注:
当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册);
当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构(一个非实时信号诞生后,(1)、如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,相当于不知道本次信号发生,信号丢失;(2)、如果进程的未决信号中没有相同信号,则在进程中注册自己)。
信号在进程中的注销。在目标进程执行过程中,会检测是否有信号等待处理(每次从系统空间返回到用户空间时都做这样的检查)。如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集中删除对于实时与非实时信号是不同的。对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则应该把信号在进程的未决信号集中删除(信号注销完毕)。否则,不应该在进程的未决信号集中删除该信号(信号注销完毕)。
进程在执行信号相应处理函数之前,首先要把信号在进程中注销。
信号生命终止。进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的本次发送对进程的影响彻底结束。
注:
1)信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。
2)在信号被注销到相应的信号处理函数执行完毕这段时间内,如果进程又收到同一信号多次,则对实时信号来说,每一次都会在进程中注册;而对于非实时信号来说,无论收到多少次信号,都会视为只收到一个信号,只在进程中注册一次。
二、信号编程注意事项
防止不该丢失的信号丢失。如果对八中所提到的信号生命周期理解深刻的话,很容易知道信号会不会丢失,以及在哪里丢失。
程序的可移植性
考虑到程序的可移植性,应该尽量采用POSIX信号函数,POSIX信号函数主要分为两类:
POSIX 1003.1信号函数: Kill()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、sigpending()、sigprocmask()、sigsuspend()。
POSIX 1003.1b信号函数。POSIX 1003.1b在信号的实时性方面对POSIX 1003.1做了扩展,包括以下三个函数: sigqueue()、sigtimedwait()、sigwaitinfo()。 其中,sigqueue主要针对信号发送,而sigtimedwait及sigwaitinfo()主要用于取代sigsuspend()函数,后面有相应实例。
#include <signal.h>
int sigwaitinfo(sigset_t *set, siginfo_t *info).
该函数与sigsuspend()类似,阻塞一个进程直到特定信号发生,但信号到来时不执行信号处理函数,而是返回信号值。因此为了避免执行相应的信号处理函数,必须在调用该函数前,使进程屏蔽掉set指向的信号,因此调用该函数的典型代码是:
sigset_t newmask;
int rcvd_sig;
siginfo_t info;
sigemptyset(&newmask);
sigaddset(&newmask, SIGRTMIN);
sigprocmask(SIG_BLOCK, &newmask, NULL);
rcvd_sig = sigwaitinfo(&newmask, &info)
if (rcvd_sig == -1) {
..
}
调用成功返回信号值,否则返回-1。sigtimedwait()功能相似,只不过增加了一个进程等待的时间。
程序的稳定性。
为了增强程序的稳定性,在信号处理函数中应使用可重入函数。
信号处理程序中应当使用可再入(可重入)函数(注:所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错)。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数。
满足下列条件的函数多数是不可再入的:(1)使用静态的数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函数实现时,调用了malloc()或者free()函数;(3)实现时使用了标准I/O函数的。The Open Group视下列函数为可再入的:
_exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown() 、close()、creat()、dup()、dup2()、execle()、execve()、fcntl()、fork()、fpathconf()、fstat()、fsync()、getegid()、 geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、kill()、link()、lseek()、mkdir()、mkfifo()、 open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、setgid()、setpgid()、setsid()、setuid()、 sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、 umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。
即使信号处理函数使用的都是"安全函数",同样要注意进入处理函数时,首先要保存errno的值,结束时,再恢复原值。因为,信号处理过程中,errno值随时可能被改变。另外,longjmp()以及siglongjmp()没有被列为可再入函数,因为不能保证紧接着两个函数的其它调用是安全的。
三、深入浅出:信号应用实例
linux下的信号应用并没有想象的那么恐怖,程序员所要做的最多只有三件事情:
安装信号(推荐使用sigaction());
实现三参数信号处理函数,handler(int signal,struct siginfo *info, void *);
发送信号,推荐使用sigqueue()。
实际上,对有些信号来说,只要安装信号就足够了(信号处理方式采用缺省或忽略)。其他可能要做的无非是与信号集相关的几种操作。
实例一:信号发送及处理
实现一个信号接收程序sigreceive(其中信号安装由sigaction())。
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=new_op;
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d", signum);
sleep(5);
}
说明,命令行参数为信号值,后台运行sigreceive signo &,可获得该进程的ID,假设为pid,然后再另一终端上运行kill -s signo pid验证信号的发送接收及处理。同时,可验证信号的排队问题。
注:可以用sigqueue实现一个命令行信号发送程序sigqueuesend,见 附录1。
实例二:信号传递附加信息
主要包括两个实例:
向进程本身发送信号,并传递指针参数;
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
union sigval mysigval;
int i;
int sig;
pid_t pid;
char data[10];
memset(data,0,sizeof(data));
for(i=0;i < 5;i++)
data[i]='2';
mysigval.sival_ptr=data;
sig=atoi(argv[1]);
pid=getpid();
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;//三参数信号处理函数
act.sa_flags=SA_SIGINFO;//信息传递开关
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
sigqueue(pid,sig,mysigval);//向本进程发送信号,并传递附加信息
}
}
void new_op(int signum,siginfo_t *info,void *myact)//三参数信号处理函数的实现
{
int i;
for(i=0;i<10;i++)
{
printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
}
printf("handle signal %d over;",signum);
}
这个例子中,信号实现了附加信息的传递,信号究竟如何对这些信息进行处理则取决于具体的应用。
2、 不同进程间传递整型参数:把1中的信号发送和接收放在两个程序中,并且在发送过程中传递整型参数。
信号接收程序:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
pid_t pid;
pid=getpid();
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_sigaction=new_op;
act.sa_flags=SA_SIGINFO;
if(sigaction(sig,&act,NULL)<0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("the int value is %d \n",info->si_int);
}
信号发送程序:命令行第二个参数为信号值,第三个参数为接收进程ID。
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
main(int argc,char**argv)
{
pid_t pid;
int signum;
union sigval mysigval;
signum=atoi(argv[1]);
pid=(pid_t)atoi(argv[2]);
mysigval.sival_int=8;//不代表具体含义,只用于说明问题
if(sigqueue(pid,signum,mysigval)==-1)
printf("send error\n");
sleep(2);
}
注:实例2的两个例子侧重点在于用信号来传递信息,目前关于在linux下通过信号传递信息的实例非常少,倒是Unix下有一些,但传递的基本上都是关于传递一个整数,传递指针的我还没看到。我一直没有实现不同进程间的指针传递(实际上更有意义),也许在实现方法上存在问题吧,请实现者email我。
实例三:信号阻塞及信号集操作
#include "signal.h"
#include "unistd.h"
static void my_op(int);
main()
{
sigset_t new_mask,old_mask,pending_mask;
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=(void*)my_op;
if(sigaction(SIGRTMIN+10,&act,NULL))
printf("install signal SIGRTMIN+10 error\n");
sigemptyset(&new_mask);
sigaddset(&new_mask,SIGRTMIN+10);
if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
printf("block signal SIGRTMIN+10 error\n");
sleep(10);
printf("now begin to get pending mask and unblock SIGRTMIN+10\n");
if(sigpending(&pending_mask)<0)
printf("get pending mask error\n");
if(sigismember(&pending_mask,SIGRTMIN+10))
printf("signal SIGRTMIN+10 is pending\n");
if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
printf("unblock signal error\n");
printf("signal unblocked\n");
sleep(10);
}
static void my_op(int signum)
{
printf("receive signal %d \n",signum);
}
编译该程序,并以后台方式运行。在另一终端向该进程发送信号(运行kill -s 42 pid,SIGRTMIN+10为42),查看结果可以看出几个关键函数的运行机制,信号集相关操作比较简单。
注:在上面几个实例中,使用了printf()函数,只是作为诊断工具,pringf()函数是不可重入的,不应在信号处理函数中使用。
结束语:
系统地对linux信号机制进行分析、总结使我受益匪浅!感谢王小乐等网友的支持!
Comments and suggestions are greatly welcome!
附录1:
用sigqueue实现的命令行信号发送程序sigqueuesend,命令行第二个参数是发送的信号值,第三个参数是接收该信号的进程ID,可以配合实例一使用:
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char**argv)
{
pid_t pid;
int sig;
sig=atoi(argv[1]);
pid=atoi(argv[2]);
sigqueue(pid,sig,NULL);
sleep(2);
}
相关推荐
- linux进程间的通信方式有哪些(linux怎么看这个进程在哪个路径下)
-
管道通信(PIPE)两个进程利用管道进行通信时.发送信息的进程称为写进程.接收信息的进程称为读进程。管道通信方式的中间介质就是文件.通常称这种文件为管道文件.它就像管道一样将一个写进程和一个读进程连接...
- Linux守护进程 daemonize 命令(linux 守护进城)
-
daemonize:作为守护进程运行命令的工具daemonize是一个命令行实用程序,它作为Unix/Linux守护进程运行命令。daemonize工具是用C语言编写的。大多数程序被设计为作为守护...
- linux进程间通信—信号(linux查看进程之间的通信指令i)
-
Linux环境进程间通信(二)信号(下)一、信号生命周期从信号发送到信号处理函数的执行完毕对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个...
- Linux 上以树状查看文件和进程(linux如何查看进程树)
-
提供了一些方便的命令,用于以树状分支形式查看文件和进程,从而易于查看它们之间的关系。在本文中,我们将介绍、和命令以及它们提供的一些选项,这些选项可帮助你将注意力集中在要查看的内容上。ps我们用...
- 文件搜不到?进程理不清?掌握这3个Linux技巧,效率直接翻倍!
-
哎家人们,今天必须跟你们唠唠Linux里那些“用了就回不去”的效率神器!我猜屏幕前肯定有不少朋友跟我之前一样——明明装着Linux当开发机/服务器,结果每天被命令行折腾得够呛:找个文件翻遍目录,看进程...
- 如何在 Linux 中使用 PID 号查找进程名称?
-
在Linux的复杂世界中,进程是系统运行的核心,每个进程都由一个唯一的「进程ID」(PID)标识。无论是系统管理员在排查失控进程,还是开发者在调试应用程序,知道如何将PID映射到对应的进程名称都是一项...
- Linux进程深度解析(1):进程结构、内存布局与资源管理
-
0.简介进程是系统执行的核心单元,理解进程是我们诊断性能问题、开发高并发程序的基础。Linux进程深度解析系列将会从原理角度对进程进行深入分析,帮助读者系统的理解其设计的思路,让读者在理解原理的基础...
- Linux 下如何查看进程的资源限制信息?
-
简介Linux上的cat/proc/$pid/limits命令提供有关特定进程的资源限制的信息,其中$pid是相关进程的进程ID(pid)。该文件是`/proc文件系统的一部分,该...
- Linux 8种进程状态及状态转换(linux进程状态切换)
-
Linux8种进程状态通过psaux可以看到进程的状态。O:进程正在处理器运行,这个状态从来没有见过.S:休眠状态(sleeping)R:等待运行(runable)RRunningorrun...
- 如何查看linux后台运行的进程?如何查看后台运行进程的状态?
-
linux系统中挂在后台的进程,如何能够被运维人员知道呢?以及挂在后台的进程当前状态是怎样的?是否可以继续执行?如何杀死一个在后台挂起的进程?后台进程可以通过‘&’符号或nohup命令,让程序进入...
- 最新保姆级、超详细介绍VMware的使用(一)软件的安装
-
一、VMware简介VMWare虚拟机软件是一个“虚拟PC”软件,它使你可以在一台机器上同时运行二个或更多Windows、DOS、LINUX系统。与“多启动”系统相比,VMware采用了完全不同的概...
- Shell脚本怎么写?(0)(shell脚本怎么写变量)
-
各位不好意思,最近加班+感冒,有三四天没更新了,python自动化脚本系列总共六期,基本已经囊括了编写python自动化脚本的所有基础函数和语法了。大家如果没看过这个系列的话,可以参考下面的链接:...
- 用Python从头开发一个自己的Shell(下)
-
编程派微信号:codingpy平常工作中经常用到shell吧?好不好奇shell的具体执行方式?今天推送的这两篇文章,将利用Python实现一些简单的shell功能。本文原作者为Su...
- Linux编程Shell之入门——Shell函数返回值
-
在Shell中,函数可以通过返回值将结果传递给调用者。返回值可以是一个整数、字符串或其他类型的数据。以下是关于Shell函数返回值的详细介绍:使用return语句在Shell函数中,使用return...
- Linux shell编程中易混淆的符号(linux中shell编程详解)
-
符号主要功能执行环境是否解析变量典型用例()子Shell执行命令子进程是隔离环境操作{}代码块或扩展当前Shell是(代码块内)组合命令或生成序列""弱引用字符串-是含变量...
- 一周热门
- 最近发表
-
- linux进程间的通信方式有哪些(linux怎么看这个进程在哪个路径下)
- Linux守护进程 daemonize 命令(linux 守护进城)
- linux进程间通信—信号(linux查看进程之间的通信指令i)
- Linux 上以树状查看文件和进程(linux如何查看进程树)
- 文件搜不到?进程理不清?掌握这3个Linux技巧,效率直接翻倍!
- 如何在 Linux 中使用 PID 号查找进程名称?
- Linux进程深度解析(1):进程结构、内存布局与资源管理
- Linux 下如何查看进程的资源限制信息?
- Linux 8种进程状态及状态转换(linux进程状态切换)
- 如何查看linux后台运行的进程?如何查看后台运行进程的状态?
- 标签列表
-
- linux一键安装 (31)
- linux运行java (33)
- ln linux (27)
- linux 磁盘管理 (31)
- linux 内核升级 (30)
- linux 运行python (28)
- linux 备份文件 (30)
- linux 网络测试 (30)
- linux 网关配置 (31)
- linux jre (32)
- linux 杀毒软件 (32)
- linux语法 (33)
- linux博客 (33)
- linux 压缩目录 (37)
- linux 查看任务 (32)
- 制作linux启动u盘 (35)
- linux 查看存储 (29)
- linux乌班图 (31)
- linux挂载镜像 (31)
- linux 软件源 (28)
- linux题目 (30)
- linux 定时脚本 (30)
- linux 网站搭建 (28)
- linux 远程控制 (34)
- linux bind (31)