目录
Linux 中信号是一种进程间通信的机制,用于在异步事件发生时通知进程。实际信号是软中断,许多重要的程序都需要处理信号。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。
具体的信号名称可以使用kill -l
来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0有特殊的应用。
这里介绍一些常用的信号
SIGHUP (1): 挂起信号。通常在与终端连接的会话结束时发送给进程。
SIGINT (2): 中断信号。通常由用户在终端按下 Ctrl+C 时生成,用于中断正在运行的程序。
SIGQUIT (3): 退出信号。类似于 SIGINT,但当用户在终端按下 Ctrl+\ 时生成,通常用于生成核心转储文件。
SIGILL (4): 非法指令信号。表示进程尝试执行一个非法的指令,通常是因为程序错误导致的。
SIGTRAP (5): 跟踪/断点陷阱信号。通常由调试器使用,用于实现断点等调试功能。
SIGABRT (6): 中止信号。通常由
abort
函数生成,表示进程发生了严重错误。SIGFPE (8): 浮点异常信号。表示进程执行了一个无效的浮点操作,例如除以零。
SIGKILL (9): 杀死信号。用于强制终止进程。该信号不能被捕获或忽略。
SIGSEGV (11): 段错误信号。表示进程访问了无效的内存地址,通常是因为编程错误导致的。
SIGPIPE (13): 管道破裂信号。通常在进程尝试向已关闭的管道写入数据时生成。
SIGALRM (14): 定时器超时信号。通常由
alarm
函数生成,表示定时器已过期。SIGTERM (15): 终止信号。用于请求进程正常终止。进程可以捕获此信号,并执行清理工作后终止。
SIGUSR1 (10): 用户定义信号 1。可以由用户定义为特定用途。
SIGUSR2 (12): 用户定义信号 2。可以由用户定义为特定用途。
SIGCHLD (17): 子进程状态改变信号。通常在子进程退出或停止时生成。
SIGCONT (18): 继续执行信号。通常用于从停止状态继续进程的执行。
SIGSTOP (19): 停止信号。用于暂停进程的执行。与 SIGKILL 不同,SIGSTOP 可以被捕获并处理。
SIGTSTP (20): 终端停止信号。通常由用户在终端按下 Ctrl+Z 时生成,用于暂停正在运行的进程。
SIGTTIN (21): 后台进程尝试读取标准输入时的信号。
SIGTTOU (22): 后台进程尝试写入标准输出时的信号。
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
忽略信号:大多数信号都可以通过将其处理动作设置为 SIG_IGN
(表示忽略)来屏蔽,例如
#include <signal.h>
int main() {
// 忽略 SIGUSR1 信号
signal(SIGUSR1, SIG_IGN);
// 进程执行其他任务
return 0;
}
但是,对于 SIGKILL
和 SIGSTOP
两个信号,是不能被忽略的。这是因为它们是系统用于可靠终止和停止进程的方式
捕捉信号:用户可以通过注册一个信号处理函数来捕捉信号。当信号发生时,内核将调用用户定义的函数。
系统默认动作:每个信号都有一个默认的处理动作,通常比较粗暴,例如终止进程。可以通过 man 7 signal
命令查看系统对每个信号的默认处理动作。用户可以选择采用默认动作,也可以通过设置信号处理函数来改变默认行为。
信号处理函数的注册
信号处理函数的注册不只一种方法,分为入门版和高级版
signal
sigaction
信号处理发送函数
信号发送函数也不止一个,同样分为入门版和高级版
1.入门版:kill
2.高级版:sigqueue
kill
函数用于向指定进程发送信号
函数原型
int kill(pid_t pid, int sig);
pid
参数是目标进程的进程 ID(Process ID)。可以使用getpid
函数获取当前进程的进程 ID,或者使用其他手段获取要发送信号的目标进程的进程 ID。sig
参数是要发送的信号编号。可以使用预定义的宏(如SIGTERM
、SIGKILL
等)来表示信号,也可以使用具体的信号编号。
通过命令行参数向指定进程发送信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc, char **argv) {
// 检查命令行参数数量
if (argc < 3) {
printf("用法:%s <信号编号> <进程ID>\n", argv[0]);
exit(-1);
}
// 打印命令行参数信息
printf("Command: %s, Signal: %s, Process ID: %s\n", argv[0], argv[1], argv[2]);
// 将命令行参数转换为整数,得到信号编号和目标进程的进程 ID
int sig = atoi(argv[1]);
int pid = atoi(argv[2]);
// 打印目标进程的进程 ID 和要发送的信号编号
printf("目标进程ID:%d,信号编号:%d\n", pid, sig);
// 使用 kill 函数向指定进程发送信号
if (kill(pid, sig) == 0) {
printf("信号发送成功 %d\n", pid);
} else {
perror("kill");
exit(-1);
}
return 0;
}
用于设置信号处理函数,捕捉到相应的信号后调用处理函数
函数原型
typedef void (*sighandler_t)(int);(信号处理函数)typedef将这个类型的指针函数取了一个别名sighandler_t
sighandler_t signal(int signum, sighandler_t handler);
signal函数用于注册信号处理函数,接受两个参数:
- signum:要处理的信号的编号。
- handler:指向用户定义的信号处理函数的指针。设置成SIG_IGN表示忽略对于的信号
函数返回一个先前与该信号相关联的处理函数的指针,如果调用失败返回SIG_ERR,返回值的作用为了方便后续的操作,比如在程序结束时回复原来的信号处理函数。
使用注册信号处理函数捕捉(Ctrl+C) 键
#include <signal.h>
#include <stdio.h>
// 定义信号处理函数的类型
typedef void (*sighandler_t)(int);
// 函数原型:注册信号处理函数
// sighandler_t signal(int signum, sighandler_t handler);
// 信号处理函数,处理接收到的信号
void handle(int signum)
{
printf("接收到信号 %d\n", signum);
}
int main()
{
// 定义保存信号处理函数返回值的变量
sighandler_t handler_t;
// 使用 signal 函数注册信号处理函数
handler_t = signal(SIGINT, handle);
// 检查 signal 函数的返回值是否出错
if (handler_t == SIG_ERR) {
printf("signal 函数执行出错!\n");
}
// 进入无限循环,等待信号的到来
while (1);
return 0;
}
函数用于向指定进程发送信号,与 kill
函数相比,sigqueue
提供了更多的灵活性。它可以携带一个整数值或指针作为辅助数据,用于传递更多信息给接收信号的进程
函数原型
int sigqueue(pid_t pid, int sig, const union sigval value);
pid
:目标进程的进程 ID,指定信号要发送到的进程。sig
:要发送的信号的编号。value
:一个union sigval
类型的结构,用于携带辅助数据
union sigval
结构定义如下
union sigval {
? ? int sival_int; ? ? ?// 整数值
? ? void *sival_ptr; ? ?// 指针值
};
?函数返回值为 0 表示成功,-1 表示失败,错误原因存储在 errno
中。
#include <signal.h>
#include <stdio.h>
// int sigqueue(pid_t pid, int sig, const union sigval value);
// union sigval {
// int sival_int;
// void *sival_ptr;
// };
int main(int argc, char **argv) {
// 定义 sigval 结构体用于携带辅助数据
union sigval value;
value.sival_int = 100; // 替换成要传递的整数值
// 从命令行参数获取目标进程的进程 ID 和要发送的信号编号
int pid = atoi(argv[2]);
int sig = atoi(argv[1]);
// 使用 sigqueue 函数向指定的进程发送信号,并携带辅助数据
if (sigqueue(pid, sig, value) == 0) {
// 信号发送成功
printf("信号成功发送至进程 %d,携带的整数值为 %d\n", pid, value.sival_int);
} else {
perror("sigqueue");
}
return 0;
}
sigaction
函数用于检查或修改指定信号的处理方式。这个函数允许程序员指定一个信号的处理函数,并提供了一些额外的控制选项。
函数原型
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
signum
:要操作的信号的编号。act
:一个指向struct sigaction
结构体的指针,用于指定新的信号处理方式。oldact
:一个指向struct sigaction
结构体的指针,用于保存原来的信号处理方式
struct sigaction
结构体定义如下
struct sigaction {
void (*sa_handler)(int); // 函数指针,指定信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *); // 备用的信号处理函数
sigset_t sa_mask; // 在信号处理函数执行期间阻塞的信号集
int sa_flags; // 用于指定一些额外的标志
void (*sa_restorer)(void); // 废弃,无需关心
};
?sigaction
函数返回 0 表示成功,-1 表示出错,错误信息存储在 errno
中
sigaction函数的详细介绍有小红旗的是我们重点需要注意的?
?
#include <signal.h>
#include <stdio.h>
// 信号处理函数
void handler(int signum, siginfo_t *data, void *estimate)
{
printf("Received signal: %d\n", signum);
// 检查是否有传递的附加信息
if (estimate != NULL)
{
printf("Sender PID: %d, Data: %d, Value: %d\n", data->si_pid, data->si_int, data->si_value.sival_int);
}
}
int main()
{
// 定义 struct sigaction 结构体
struct sigaction act;
act.sa_sigaction = handler; // 设置信号处理函数为 handler
act.sa_flags = SA_SIGINFO; // 设置 sa_flags,表示使用 sa_sigaction 作为处理函数
// 注册信号处理函数
sigaction(10, &act, NULL);
printf("Process PID: %d\n", getpid());
// 让程序保持运行,等待信号
while (1);
return 0;
}