进程通信——信号
文章目录
- 1.1 信号来源
- 1.2 信号分类
- 1.3 信号的传递和响应
- 1.4 信号处理函数
- 1.4.1 未决信号集
- 1.4.2 信号屏蔽字
- 1.4.3 信号捕获
- 1.4.3.1 signal()函数
- 1.4.3.2 sigaction()函数
- 1.4.3.3 示例
- 1.5 发送信号的函数
https://www.cnblogs.com/BroccoliFighter/p/18042922
信号(signal)机制是Linux系统中最为古老的进程之间的通信机制。Linux信号也可以称为软中断,是在软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号
与处理器收到一个中断请求
可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,进程不需要通过任何操作等待信号到达。
1.1 信号来源
- 硬件来源:硬件触发,硬件异常,如除零运算,内存非法访问等。
- 软件来源:系统函数 kill() raise() alarm() 和 setitimer() 等函数,ctl+c 发出 SIGINT 等
1.2 信号分类
linux
系统中一共定义了64种信号,分为实时信号和非实时信号。
- 非实时信号:编号范围从1-31,这些标准信号是由
操作系统内核
发出,用于通知进程发生了某种事件或错误。 - 实时信号:编号范围从32-64。有如下特点:
实时信号可以排队传递,有多个相同类型的实时信号同时到达时,系统会将其排队传递给进程,直到进程处理所有信号。
实时信号的优先级高于标准信号,进程在接收实时信号时会暂时把非实时信号
放入挂起状态。
实时信号支持使用sigqueue()
函数向目标进程发送带有额外数据的信号。
可以使用kill -l
命令查看所有的信号。
前31种信号含义如下:
- SIGHUP(1):当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作是终止进程。
- SIGINT(2):当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作是终止进程。
- SIGQUIT(3):当用户按下<Ctrl+>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作是终止进程。
- SIGILL(4):非法指令信号,当一个进程执行了一条非法的、不支持的或者无效的指令时,操作系统会向该进程发送SIGILL信号。默认动作是终止进程并产生core文件。
- SIGTRAP(5):该信号由断点指令或其他trap指令产生,当一个进程执行了一个与调试器相关的操作(打断点)时,操作系统会向该进程发送SIGTRAP信号。默认动作为终止进程并产生core文件。
- SIGABRT(6):调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
- SIGBUS(7):非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
- SIGFPE(8):在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件.
- SIGKILL(9):无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
- SIGUSE1(10):用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程.
- SIGSEGV(11):指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
- SIGUSR2(12):这是另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
- SIGPIPE(13):管道破裂信号,向一个没有读端的管道写数据。默认动作为终止进程。
- SIGALRM(14):定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
- SIGTERM(15):程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和处理。通常用来告示程序正常退出。执行 kill 命令时,缺省产生这个信号。默认动作为终止进程。
- SIGSTKFLT(16):段错误信号,当一个进程访问了无效的内存地址或者试图对不可写的内存进行写操作时,会产生该信号。默认动作为终止进程并产生core文件。
- SIGCHLD(17):子进程状态(终止、暂停、继续)发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。
- SIGCONT(18):用于通知一个被停止(暂停)的进程继续执行。
- SIGSTOP(19):用于暂停(停止)一个正在执行的进程,本信号不能被忽略,处理和阻塞。
- SIGTSTP(20):当用户按下<Ctrl+Z>组合键时产生该信号,用于将一个进程暂停(停止)执行并放入后台。
- SIGTTIN(21):当一个进程在后台运行,并尝试从终端读取输入时,如果该终端处于非控制状态(即不是当前正在与之交互的终端),那么该进程将会被操作系统发送SIGTTIN信号,以提示它正在尝试从非控制终端读取输入。默认动作为暂停进程。
- SIGTTOU(22):类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
- SIGURG(23):套接字上有紧急数据时,向当前正在运行的进程发送该信号,报告有紧急数据到达。默认动作为忽略该信号。
- SIGXCPU(24):表示进程已经超过了它被允许的CPU时间限制。当一个进程超过了被操作系统设定的CPU时间限制时,操作系统会向该进程发送SIGXCPU信号。这通常发生在进程占用了太多的CPU时间或者执行了一个长时间运行的计算任务。默认动作为终止进程。
- SIGXFSZ(25):超过文件的最大长度设置。默认动作为终止进程。
- SIGVTALRM(26):按照进程在用户态占用的CPU时间定时,默认动作为终止进程。
- SIGPROF(27):按照进程在用户态和内核态占用的CPU时间定时,默认动作为终止进程。
- SIGWINCH(28):表示窗口尺寸已更改,当用户调整终端窗口的大小时,会触发此信号。默认动作为忽略该信号。
- SIGIO(29):此信号向进程指示发出了一个异步IO事件。默认动作为忽略。
- SIGPWR(30):关机。默认动作为终止进程。
- SIGSYS(31):无效的系统调用。默认动作为终止进程并产生core文件。
1.3 信号的传递和响应
信号传递流程如下:
- 信号产生:信号可以由内核、操作系统或者应用程序生成。常见的信号产生方式包括用户在终端按下键盘快捷键产生的信号,以及系统调用执行过程中出现的异常情况。
- 信号传递:一旦信号被产生,内核会把信号传递给目标进程。目标进程可以是正在等待信号的进程,或者正在执行的进程。如果信号没有被阻塞并且未被忽略,内核将会将信号传递给进程。
- 信号处理:一旦进程接收到信号,内核会检查信号的处理方式。根据注册的信号处理方式,可以有
以下几种处理方式
:
处理方式 | 解释 |
---|---|
忽略信号 | 即对信号不做任何处理,但是两个信号不能忽略: SIGKILL 以及 SIGSTOP |
捕捉信号 | 当信号发生时,执行用户定义的信号处理函数。处理函数可以完成一系列操作,比如修改全局变量、执行清理操作等。在信号处理函数执行期间,进程可能会被阻塞在信号处理函数中。 |
执行默认操作 | Linux对每种信号都规定了默认操作,man 7 signal查看 |
信号返回:一旦信号处理函数执行完毕,程序将会从信号处理函数中返回,程序恢复执行之前的代码。
**注:**信号传递不支持排队,当在很短的时间内传递多个相同信号,该信号只会被处理一次。
1.4 信号处理函数
1.4.1 未决信号集
指的是进程当前已经接收到但尚未被处理的信号的集合。当进程接收到一个信号时,该信号会被添加到进程的未决信号集中,直到进程处理完该信号或将其使用信号屏蔽字
阻塞。
可以使用系统调用sigpending()函数获取当前进程的未决信号集,该函数定义如下:
#include <signal.h>
int sigpending(sigset_t *set);
参数说明
set:要安装的信号值。
返回值
调用成功则返回0,出错则返回‐1。
1.4.2 信号屏蔽字
信号屏蔽字是一个掩码,用于指定在信号处理期间需要被阻塞的信号集合。当某个信号被加入到信号屏蔽字中时,该信号在信号处理期间将被阻塞,即不会被传递给进程。
可以通过系统调用sigprocmask()
函数来查看或更改进程的信号屏蔽字。该函数定义如下:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/*
参数说明:
how:指定了函数的操作,有如下取值:
SIG_BLOCK:将 set 中指定的信号添加到当前信号屏蔽字中。相当于 mask | set
SIG_UNBLOCK:将 set 中指定的信号从当前信号屏蔽字中移除。相当于 mask & ~set
SIG_SETMASK:将当前信号屏蔽字设置为 set 中指定的值。相当于 mask = set
set:指定要修改的信号集合。
oldset:用于获取之前的信号屏蔽字。
注:SIGSTOP(19)和 SIGKILL(9)不能被忽略。
返回值:
调用成功则返回0,出错则返回‐1。
*/
注: 信号屏蔽并不是将未决信号丢弃,而是阻塞该信号
1.4.3 信号捕获
1.4.3.1 signal()函数
signal()
函数常用于设置信号处理程序,以便进程在接收到特定的信号时执行自定义的处理逻辑。可以通过指定不同的信号和处理函数来实现对各种信号的处理,比如捕获SIGINT信号来处理终端中断信号,或者捕获SIGSEGV信号来处理段错误。该函数定义如下:
#include <signal.h>
void (*signal(int signum, void (*handler)(int)));
参数说明
signum:要安装的信号值。
handler:信号对应的信号处理函数。参数为信号值
signal()函数主要用于前32种非实时信号的安装。
1.4.3.2 sigaction()函数
sigaction()函数和signal()函数相似,相比于前者,sigaction()函数提供了更为灵活和可靠的信号处理方式。该函数定义如下:
#include <signal.h>
/*****
* 参数说明
signum:要安装的信号值。
act:指向 struct sigaction 类型的结构体指针,用于指定新的信号处理方式。
oldact:指向 struct sigaction 类型的结构体指针,用于存储之前的信号处理方式。
* 返回值:函数执行成功,返回值为0。如果函数执行失败,返回值为-1
* */
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //早期的信号捕捉函数
void (*sa_sigaction)(int, siginfo_t *, void *);//新的捕捉函数,和sa_handler互斥,通过sa_flags 决定采用哪种捕捉函数。
sigset_t sa_mask;//在执行捕捉函数期间,屏蔽指定信号,当退出捕捉函数后,还原回原有的阻塞信号集。
int sa_flags;
void (*sa_restorer)(void);//无含义的保留字段
};
sa_flags
关键字的枚举值
标记 | 含义 |
---|---|
SA_RESTART | 如果设置了这个标志,则系统调用被信号中断后会自动重启,而不是返回-1并设置 errno 为 EINTR 。 sigaction在处理信号时会自动将信号掩码设置为阻塞状态,防止递归调用信号处理程序,这可能导致后续信号被忽略,需要显示设置该标记 |
SA_SIGINFO | 指定使用带有附加信息的信号处理程序,即使用sa_sigaction字段指定的函数 |
SA_NOCLDSTOP | 子进程暂停或继续运行时不会产生SIGCHLD信号 |
SA_NODEFER | 在信号处理程序执行期间不屏蔽该信号 |
1.4.3.3 示例
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
void sigHandler(int sig)
{
printf("Handle signal %d\n", sig);
sleep(10);
printf("Handle finish %d\n", sig);
}
int main(int argc, char** argv)
{
struct sigaction sigact;
sigact.sa_flags = 0;
sigact.sa_handler = sigHandler;
sigemptyset(&sigact.sa_mask);
sigaction(SIGINT, &sigact, NULL);//捕捉信号
getchar();
return 0;
}
1.5 发送信号的函数
并不是每个进程都可以向其他的进程发送信号,通常进程只能向具有相同 uid 和 gid 的进程发送信号,或向相同进程组中的其他进程发送信号。
kill()函数
kill()
函数用于向指定进程发送信号,信号可以是预定义的系统信号,也可以是用户自定义的信号。
#include <signal.h>
/*****
* 参数
* pid:目标进程的进程标识符
正整数:表示具体的进程PID。
负整数:表示发送信号给进程组ID等于该值的所有进程。
0:表示发送信号给调用进程同一进程组的所有进程;
-1:表示给除了自身以为所有进程发送信号。
sig:要发送的信号值
* 返回值:函数执行成功,返回值为0。如果函数执行失败,返回值为-1
*****/
int kill(pid_t pid, int sig);
raise()函数
raise()
函数用于向当前进程发送信号,该函数定义如下:
#include <signal.h>
/**
参数说明
sig:要发送的信号值。
返回值
如果函数执行成功,返回值为0。如果函数执行失败,则返回一个非零值。
**/
int raise(int sig);
alarm()函数
alarm()
函数用于设置一个定时器,在指定的时间间隔后发送一个 SIGALRM
信号给调用进程。默认情况下,当定时器到达设定的时间时,进程会终止。我们可以通过捕捉 SIGALRM
信号并自定义处理函数,来实现在定时器到达时执行特定的操作,而非终止进程。
/*****
参数说明:
seconds:单位为秒。在指定的 seconds 秒后,将给自己发送一个SIGALRM 信号。当参数 seconds 为0时,将清除当前进程的 alarm 设置。
返回值:
调用alarm()函数时,如果进程已经有一个未结束的 alarm,那么旧的 alarm 将被删除,并返回旧的 alarm 的剩余时间。否则 alarm() 函数返回0。
注:
alarm()信号不会周期响应,在产生一次信号后,需要重新调用 alarm() 函数创建定时器。
定时器有且只能有一个,多次设置会覆盖之前的定时设置。
****/
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sigHandler(int sig)
{
static int i = 0;
printf("alarm signal, i = %d\n", i++);
alarm(1); //需要重新激活
}
int main(int argc, char** argv)
{
signal(SIGALRM, sigHandler);
alarm(1);
getchar();
return 0;
}
settimer函数
setitimer()
函数也是用于设置定时器的函数,该函数可以实现更为精确的定时器控制,包括指定定时器的精度、间隔和触发信号。该函数定义如下:
#include <sys/time.h>
/*****
* 参数:
* which:指定定时器类型。ITIMER_REAL,ITIMER_VIRTUAL,ITIMER_PROF
* new_value:用于设置定时器的第一次触发时间和之后每次触发的间隔时间
* old_value:用于获取之前设置的定时器的值,如果不需要获取,则可以传递 NULL。
*
******/
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
struct itimerval {
struct timeval it_interval; /*每隔多少秒发送一次信号 */
struct timeval it_value; /* 第一次定时时间 */
};
struct timeval {
time_t tv_sec; //seconds
suseconds_t tv_usec; //microseconds
};
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/time.h>
#include<string.h>
void sigHandler(int sig)
{
static int i = 0;
printf("alarm signal, i = %d\n", i++);
}
int main(int argc, char** argv)
{
signal(SIGALRM, sigHandler);
struct itimerval timerval;
memset(&timerval, 0, sizeof(timerval));
timerval.it_interval.tv_usec = 500 * 1000; //500毫秒
timerval.it_value.tv_sec = 2;
int nRet = setitimer(ITIMER_REAL, &timerval, NULL);
if(nRet == -1)
{
perror("setitimer");
return -1;
}
getchar();
return 0;
}