目录
一、信号概述
1.1. 定义
1.2. 特点
二、常见信号及含义
2.1. SIGINT(2)
2.2. SIGTERM(15)
2.3. SIGKILL(9)
2.4. SIGSTOP(19)
2.5. SIGCONT(18)
2.6. SIGSEGV(11)
三、信号的发送
3.1. 使用 kill 命令
3.2. 使用 kill 函数
3.3. 使用 raise 函数
3.4. 使用 alarm函数
四、信号的处理
4.1. 默认处理动作
4.2. 忽略信号
4.3. 捕获信号并处理
五、信号阻塞与未决信号
5.1. 信号阻塞
5.2. 未决信号
六、信号处理函数设计要点
七、关键注意事项
八、典型应用场景
九、总结
十、参考资料
信号(Signal)是Linux系统中用于进程间异步通知的一种机制。当一个进程接收到信号时,它可以执行相应的信号处理函数,从而实现进程间的简单通信或事件通知。信号机制是Unix系统中最为古老的进程间通信机制之一,由操作系统内核提供。
在嵌入式 Linux 系统中,信号(Signal)是一种异步的进程间通信机制,用于通知进程发生了某种特定的事件。信号可以由系统内核产生,也可以由一个进程发送给另一个进程。信号的本质是一种软件中断,它可以打断进程当前的执行流程,转而执行信号处理函数,处理完信号后再返回到原来的执行点继续执行。
信号在嵌入式系统中具有重要的作用,例如用于处理程序的异常情况(如除零错误、段错误等)、实现进程间的异步通知(如父进程通知子进程终止)等。
轻量级、不可靠(可能丢失)、无优先级,适用于简单事件通知。
Linux 系统定义了一系列的信号,每个信号都有一个唯一的编号和名称。以下是一些常见的信号及其含义。
Ctrl+C
组合键时,会向当前前台进程发送该信号。在嵌入式 Linux 中,可以使用以下几种方式发送信号。
kill
命令在终端中,可以使用 kill
命令向指定的进程发送信号。例如,要向进程 ID 为 1234
的进程发送 SIGTERM
信号,可以使用以下命令:
kill -15 1234
其中,-15
表示信号编号,也可以使用信号名称,如 kill -TERM 1234
。
kill
函数在 C 语言程序中,可以使用 kill
函数向指定的进程发送信号。kill
函数的原型如下:
#include
#include
int kill(pid_t pid, int sig);
参数说明:
pid
:目标进程的 ID。如果 pid > 0
,则表示向指定 ID 的进程发送信号;如果 pid == 0
,则表示向调用进程所在的进程组中的所有进程发送信号;如果 pid == -1
,则表示向所有有权限发送信号的进程发送信号;如果 pid < -1
,则表示向 ID 为 -pid
的进程组中的所有进程发送信号。sig
:要发送的信号编号。返回值:成功时返回 0,失败时返回 -1,并设置 errno
变量。
raise
函数raise
函数用于向调用进程自身发送信号。其原型如下:
#include
int raise(int sig);
sig
为要发送的信号编号。alarm
函数设置定时器(发送SIGALRM
)。
unsigned int alarm(unsigned int seconds);
进程可以对信号采取不同的处理动作,主要有以下三种。
每个信号都有一个默认的处理动作,如上述常见信号中提到的终止进程、暂停进程等。当进程接收到信号后,如果没有对该信号进行特殊处理,就会执行默认处理动作。
进程可以选择忽略某些信号,即不做任何处理。可以使用 signal
或 sigaction
函数将信号的处理动作设置为 SIG_IGN
。例如,忽略 SIGINT
信号的代码如下:
#include
void ignore_signal() {
signal(SIGINT, SIG_IGN);
}
进程可以捕获信号,并执行自定义的信号处理函数。可以使用 signal
或 sigaction
函数来设置信号处理函数。
①使用 signal
函数
signal()
:简单注册信号处理函数(不推荐,兼容性差)。
signal
函数的原型如下:
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数说明:
signum
:要处理的信号编号。handler
:信号处理函数的指针。可以是自定义的信号处理函数,也可以是 SIG_IGN
(忽略信号)或 SIG_DFL
(执行默认处理动作)。返回值:返回之前的信号处理函数指针,失败时返回 SIG_ERR
。
以下是一个使用 signal
函数捕获 SIGINT
信号的示例:
#include
#include
#include
void sigint_handler(int signum) {
printf("Received SIGINT signal. Exiting...\n");
// 可以在这里进行一些清理工作
_exit(0);
}
int main() {
// 设置信号处理函数
signal(SIGINT, sigint_handler);
while (1) {
printf("Running...\n");
sleep(1);
}
return 0;
}
②使用 sigaction
函数
sigaction
函数比 signal
函数更强大和灵活,它可以更精确地控制信号的处理。其原型如下:
#include
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
函数捕获 SIGINT
信号的示例:
#include
#include
#include
void sigint_handler(int signum) {
printf("Received SIGINT signal. Exiting...\n");
// 可以在这里进行一些清理工作
_exit(0);
}
int main() {
struct sigaction act;
act.sa_handler = sigint_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
// 设置信号处理动作
if (sigaction(SIGINT, &act, NULL) == -1) {
perror("sigaction");
return 1;
}
while (1) {
printf("Running...\n");
sleep(1);
}
return 0;
}
进程可以选择阻塞某些信号,即暂时不处理这些信号。被阻塞的信号不会立即被处理,而是处于未决状态,直到进程解除对该信号的阻塞。可以使用 sigprocmask
函数来设置信号掩码,从而实现信号的阻塞和解除阻塞。
sigprocmask
函数的原型如下:
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how
:指定如何修改信号掩码。可以取值为 SIG_BLOCK
(将 set
中的信号添加到当前信号掩码中)、SIG_UNBLOCK
(从当前信号掩码中移除 set
中的信号)或 SIG_SETMASK
(将当前信号掩码设置为 set
)。set
:指向 sigset_t
类型的信号集的指针,用于指定要操作的信号。oldset
:指向 sigset_t
类型的信号集的指针,用于保存旧的信号掩码。未决信号是指已经发送给进程,但由于进程对该信号进行了阻塞,而暂时未被处理的信号。可以使用 sigpending
函数来获取当前进程的未决信号集。
#include
int sigpending(sigset_t *set);
set
指向 sigset_t
类型的信号集的指针,用于保存当前进程的未决信号集。errno
变量。保持简洁:避免复杂操作(如调用malloc
、printf
等非异步安全函数)。
使用volatile
变量:确保信号处理函数与主程序共享变量时可见性。
阻塞相关信号:通过sa_mask
防止处理函数被嵌套调用。
原子操作:修改全局变量时使用原子操作或锁机制。
不可重入函数:避免在信号处理函数中使用如printf
、malloc
等。
实时信号:使用SIGRTMIN
到SIGRTMAX
的信号支持排队(通过sigqueue
发送)。
多线程环境:信号处理是进程级的,需谨慎处理线程与信号的交互。
硬件中断模拟:如通过信号响应外部事件。
进程管理:监控子进程退出(SIGCHLD
)。
超时控制:结合SIGALRM
实现任务超时机制。
信号是嵌入式 Linux 系统中一种重要的进程间通信机制,它提供了一种异步的事件通知方式。通过合理地使用信号的发送、处理、阻塞和未决等机制,可以实现进程间的高效通信和异常处理。在实际开发中,需要注意信号处理函数的编写规范,避免在信号处理函数中调用不可重入函数,以确保系统的稳定性和可靠性。同时,要根据具体的应用场景选择合适的信号和处理方式,以满足系统的需求。
man
命令查看信号相关的手册页,如 man 7 signal
可以获取 Linux 系统中信号的详细信息,包括信号编号、名称、默认动作等。这些手册页是最权威和准确的参考资料,反映了当前系统所支持的信号机制。signal
、sigaction
、kill
等)进行了详细的说明,包括函数的原型、参数解释、返回值以及使用示例等。文档地址为:Top (The GNU C Library)。include/linux/signal.h
、kernel/signal.c
等,深入研究信号在内核中的具体实现细节。