Linux 信号机制
- 一、Linux信号的基本概念
- 二、怎么理解Linux信号机制
- 三、Linux信号的用途
- 四、Linux信号的原理
- 五、Linux信号的处理方法
- 1、默认处理
- 2、忽略信号
- 3、自定义信号处理
- 六、Linux信号处理的细节
- 1、信号掩码
- 2、实时信号
- 3、Linux信号 Alarm
- 七、Linux信号处理库函数
- 1. signal() 函数
- 函数原型:
- 示例代码:
- 2. sigaction() 函数
- 函数原型:
- 示例代码:
- 3. sigprocmask() 函数
- 函数原型:
- 示例代码:
- 4. kill() 函数
- 函数原型:
- 示例代码:
- 八、Linux信号列表
Linux信号是操作系统中进程间通信的一种重要机制,信号提供了一种简洁而强大的方式,用于通知进程发生了某些事件或需要执行特定的操作
一、Linux信号的基本概念
信号是一种异步的、软件中断的机制,用于通知进程某些事件的发生。进程接收到信号时,会中断其正常执行,并转而执行信号处理函数,处理完信号后再继续执行。信号主要用于进程之间的通信和控制。
每个信号在内核中都有唯一的编号,并且每种信号都有特定的意义。例如,SIGTERM
请求进程正常终止,SIGKILL
强制终止进程,SIGSEGV
表示段错误等。
二、怎么理解Linux信号机制
Linux信号机制是一种轻量级的进程间通信方式。它的设计初衷是让进程能够快速响应操作系统或其他进程发出的事件通知。信号的发送和接收通常不依赖于共享内存或复杂的同步机制,因此它比其他通信方式(如消息队列、共享内存等)更加简单和高效。
当一个进程接收到信号时,它的执行会被中断,操作系统会根据信号类型来决定如何处理这个信号。信号机制主要由两部分组成:信号发送和信号处理。
三、Linux信号的用途
信号的用途非常广泛,主要用于以下几个方面:
-
进程控制:信号可用于控制进程的执行。例如,
SIGSTOP
可以暂停进程,SIGCONT
可以恢复进程的执行,SIGTERM
用于请求进程正常终止。 -
进程间通信:进程可以通过发送信号来通知另一个进程某些事件的发生。例如,父进程可以通过
SIGCHLD
信号接收到子进程的状态信息。 -
错误处理和异常通知:信号常用于报告程序中的错误或异常,如
SIGSEGV
表示段错误,SIGFPE
表示浮点运算错误。 -
定时任务和超时管理:信号还可用于定时任务,例如使用
SIGALRM
来设置定时器,一旦超时就会触发指定的信号。
四、Linux信号的原理
Linux信号的原理基于异步通知的概念。每当一个进程接收到信号时,内核会中断进程的当前操作,跳转到对应的信号处理函数或执行默认行为。信号本身并不包含任何数据,除了某些特定类型的信号(如实时信号)可以携带附加数据外。
信号的发送是通过内核的kill()
系统调用实现的。一个进程可以向另一个进程发送信号,进程根据信号类型来执行相应的操作。如果信号的目标进程正在执行某些任务,进程的执行会被中断。
信号的发送过程通常包括以下几个步骤:
- 信号队列:每个进程都有一个信号队列,用于存储接收到的信号。
- 信号调度:当进程进入就绪状态时,内核会调度信号的处理。
- 信号处理:进程可以选择捕获信号并执行自定义的处理,或者直接使用默认的信号处理行为。
五、Linux信号的处理方法
Linux信号处理主要有三种方式:默认处理、忽略信号和自定义处理
1、默认处理
每种信号都有一个默认的处理方式。例如,SIGTERM
信号会导致进程退出,SIGKILL
会强制终止进程,SIGSTOP
会暂停进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>int main() {printf("This is a demo program. Press Ctrl+C or send SIGTERM to terminate.\n");while(1) {sleep(1); // 模拟进程持续运行}
}
在这个示例中,默认行为是接收到SIGTERM
信号时进程退出。
2、忽略信号
进程可以选择忽略某些信号。例如,SIGINT
信号通常由Ctrl+C
触发,我们可以选择忽略它:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>void signal_handler(int sig) {// 忽略信号并不执行任何动作
}int main() {signal(SIGINT, signal_handler); // 忽略 SIGINT 信号printf("Signal SIGINT is now ignored. Press Ctrl+C, but nothing will happen.\n");while(1) {sleep(1);}
}
3、自定义信号处理
进程可以定义一个函数来处理接收到的信号,并在该函数中执行特定的操作。例如,处理SIGTERM
信号时执行资源清理:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>void cleanup(int signal) {printf("Custom handler: Received SIGTERM. Performing cleanup.\n");// 执行清理操作// exit(0);
}int main() {signal(SIGTERM, cleanup); // 捕获 SIGTERM 信号printf("Waiting for SIGTERM signal...\n");while(1) {sleep(1); // 模拟进程持续运行}
}
六、Linux信号处理的细节
1、信号掩码
信号掩码(Signal Mask)用于阻止某些信号在进程中被捕获,尤其在多线程环境下,信号掩码可以控制哪些线程能够接收某些信号。例如,进程可以屏蔽SIGINT
信号:
#include <signal.h>
#include <stdio.h>int main() {sigset_t set;sigemptyset(&set);sigaddset(&set, SIGINT);sigprocmask(SIG_BLOCK, &set, NULL); // 屏蔽 SIGINT 信号printf("SIGINT is blocked. Press Ctrl+C, but it won't be processed.\n");while(1) {sleep(1);}return 0;
}
2、实时信号
实时信号(SIGRTMIN
到SIGRTMAX
)是Linux提供的一类特殊信号,它们允许携带更多的附加数据,且具有较高的优先级。实时信号不与标准信号冲突,通常用于需要精确控制的进程间通信。
3、Linux信号 Alarm
SIGALRM
信号用于定时器,进程可以设置一个定时器,当定时器超时后触发该信号。通常用于超时处理或定时任务。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void alarm_handler(int sig) {printf("Received SIGALRM: Timer expired!\n");
}int main() {signal(SIGALRM, alarm_handler);alarm(5); // 设置一个5秒的定时器printf("Waiting for alarm...\n");while(1) {sleep(1);}return 0;
}
七、Linux信号处理库函数
在Linux中,信号处理是通过一系列系统调用和库函数实现的。常见的信号处理库函数有:signal()
、sigaction()
、sigprocmask()
、kill()
等
1. signal() 函数
signal()
函数用于设置信号的处理函数。当进程接收到特定信号时,会调用对应的处理函数。
函数原型:
void (*signal(int signum, void (*handler)(int)))(int);
signum
:指定信号的编号(如SIGINT
、SIGTERM
等)。handler
:指定一个处理该信号的函数。当信号到达时,系统会调用这个函数。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>void signal_handler(int sig) {printf("Received signal: %d\n", sig);exit(0); // 处理完信号后退出
}int main() {signal(SIGINT, signal_handler); // 捕获Ctrl+C (SIGINT)printf("Waiting for SIGINT...\n");while (1) {sleep(1); // 持续运行}return 0;
}
在此例中,进程通过signal()
函数捕获SIGINT
信号(通常由Ctrl+C触发)。当接收到SIGINT
时,程序调用signal_handler()
函数处理信号,并退出。
2. sigaction() 函数
sigaction()
是一个更为强大且灵活的信号处理函数,比signal()
提供了更多的控制选项。sigaction()
可以指定信号处理函数,并允许设置信号处理的其他行为,如信号掩码、信号的处理动作等。
函数原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum
:指定信号的编号。act
:指定新的信号处理结构体,包含了信号处理函数和其他行为。oldact
:如果非NULL,保存原来的信号处理方式。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>void sigaction_handler(int sig) {printf("Received signal: %d\n", sig);exit(0);
}int main() {struct sigaction sa;sa.sa_handler = sigaction_handler;sigemptyset(&sa.sa_mask); // 不屏蔽任何信号sa.sa_flags = 0; // 使用默认行为sigaction(SIGINT, &sa, NULL); // 捕获SIGINT信号printf("Waiting for SIGINT...\n");while (1) {sleep(1);}return 0;
}
在这个示例中,我们使用sigaction()
来捕获SIGINT
信号,并通过自定义的sigaction_handler()
函数处理信号。sigemptyset()
用于清空信号掩码,表示在处理信号时不阻塞任何其他信号。
3. sigprocmask() 函数
sigprocmask()
用于控制进程的信号掩码。信号掩码指定了哪些信号在进程中被阻塞,即不允许被处理。
函数原型:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how
:指定信号掩码的操作方式,常用的值有:SIG_BLOCK
:阻塞信号。SIG_UNBLOCK
:解除信号阻塞。SIG_SETMASK
:将信号掩码设置为给定的set
。
set
:指定信号集,用于设置或修改信号掩码。oldset
:如果非NULL,保存旧的信号掩码。
示例代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main() {sigset_t new_mask, old_mask;sigemptyset(&new_mask);sigaddset(&new_mask, SIGINT); // 将SIGINT加入到信号集sigprocmask(SIG_BLOCK, &new_mask, &old_mask); // 阻塞SIGINT信号printf("SIGINT is blocked. Press Ctrl+C, but it won't be processed.\n");sleep(5); // 休眠5秒钟sigprocmask(SIG_SETMASK, &old_mask, NULL); // 恢复原信号掩码printf("SIGINT is now unblocked. You can press Ctrl+C.\n");while (1) {sleep(1);}return 0;
}
在这个示例中,SIGINT
信号被阻塞,直到信号掩码恢复为原来的状态,SIGINT
才会被处理。
4. kill() 函数
kill()
函数用于向指定进程发送信号,触发信号处理。
函数原型:
int kill(pid_t pid, int sig);
pid
:目标进程的PID。可以是指定进程的ID,或者一些特殊值(如0
表示向进程组发送信号,-1
表示向所有进程发送信号)。sig
:指定要发送的信号类型。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>int main() {pid_t pid = getpid(); // 获取当前进程IDprintf("Sending SIGTERM to process with PID %d...\n", pid);if (kill(pid, SIGTERM) == -1) {perror("Error sending SIGTERM");}return 0;
}
此示例向当前进程发送SIGTERM
信号,要求其终止。
八、Linux信号列表
信号名称 | 信号编号 | 说明 |
---|---|---|
SIGHUP | 1 | 挂起信号,通常用于终端断开或重新加载配置文件 |
SIGINT | 2 | 中断信号,通常由Ctrl+C 触发 |
SIGQUIT | 3 | 退出信号,进程会生成核心转储文件(core dump) |
SIGILL | 4 | 非法指令信号,进程执行非法指令时触发 |
SIGABRT | 6 | 异常终止信号,通常由abort() 触发 |
SIGFPE | 8 | 浮点异常信号,如除以零或浮点溢出 |
SIGKILL | 9 | 强制终止信号,无法被捕获或忽略 |
SIGSEGV | 11 | 段错误信号,访问非法内存时触发 |
SIGPIPE | 13 | 管道破裂信号,向一个没有读取端的管道写数据时触发 |
SIGALRM | 14 | 定时器信号,用于超时或定时任务 |
SIGTERM | 15 | 终止信号,通常用于请求进程优雅退出 |
SIGUSR1 | 10 | 用户定义信号1,用户可以自定义的信号 |
SIGUSR2 | 12 | 用户定义信号2,用户可以自定义的信号 |
SIGCHLD | 17 | 子进程状态改变信号,通常用于父进程监控子进程的退出 |
SIGSTOP | 19 | 暂停信号,暂停进程的执行,不能被捕获或忽略 |
SIGCONT | 18 | 恢复信号,恢复一个被暂停的进程 |