Linux系统编程-信号(signal)详解

信号(signal)是一种用于进程间通信的机制,用来通知进程某些事件的发生。信号可以由操作系统、其他进程或者进程自身产生。信号提供了一种异步事件处理方式,使进程可以在事件发生时执行预定义的处理函数(信号处理程序),或采取默认动作。

进程A给进程B发送信号,进程B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕后再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。每个进程收到的所有信号,都是由内核负责发送的。

1.信号的生命周期

  1. 产生信号:信号由某个事件触发,如用户输入、硬件异常、系统调用等。
  2. 传递信号:信号传递给目标进程,操作系统会将信号添加到目标进程的信号队列中。
  3. 处理信号:目标进程处理信号。处理方式可以是默认处理、忽略信号或捕获信号并执行用户定义的处理函数。

2.信号的处理方式

进程可以对信号采取以下三种处理方式:

  1. 忽略信号:进程不处理该信号,继续执行。
  2. 捕获信号:进程定义信号处理函数,在信号发生时执行特定操作。
  3. 默认处理:执行系统默认的信号处理方式。

3.信号相关函数

3.1设置信号处理

3.1.1signal()

signal() 函数用于设置信号处理程序。它能够指定当进程接收到特定信号时要执行的处理程序。

函数原型:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数说明:
signum:要捕获的信号编号,例如 SIGINT,SIGTERM 等。
handler:信号处理函数的指针,可以是以下三种之一:
指向函数的指针:指定的信号处理函数将在信号发生时执行。
SIG_IGN:忽略信号。
SIG_DFL:执行信号的默认动作。
返回值:
成功:返回之前的信号处理程序的指针。
失败:返回 SIG_ERR,并设置 errno。
示例代码:

以下是一个使用 signal() 函数设置 SIGINT 信号处理程序的示例代码。SIGINT 信号通常由用户按下 Ctrl+C 触发。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

// 信号处理函数
void handle_sigint(int sig) {
    printf("Caught signal %d (SIGINT). Exiting gracefully...\n", sig);
    exit(0);
}

int main() {
    // 设置SIGINT的信号处理程序
    if (signal(SIGINT, handle_sigint) == SIG_ERR) {
        perror("signal");
        exit(EXIT_FAILURE);
    }

    printf("Press Ctrl+C to send SIGINT signal...\n");

    // 无限循环,等待信号
    while (1) {
        printf("Running...\n");
        sleep(1);
    }

    return 0;
}
3.1.2sigaction()

sigaction 是一个用于设置信号处理程序的函数,提供了比 signal 更灵活和强大的功能。它可以设置特定的信号处理程序,并且可以在信号处理程序执行时控制阻塞哪些信号。

函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数说明
signum: 要捕获的信号编号,例如 SIGINT, SIGTERM 等。
act: 指向 struct sigaction 结构体的指针,该结构体指定了新的信号处理程序和相关设置。
oldact: 指向 struct sigaction 结构体的指针,用于保存之前的信号处理程序和相关设置(可以为 NULL)。

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); // 已弃用,通常设为 NULL
};
常用标志位 (sa_flags)
SA_RESTART: 使被信号中断的系统调用自动重启。
SA_NOCLDSTOP: 防止子进程停止或继续时发送 SIGCHLD 信号。
SA_RESETHAND: 处理完信号后恢复为默认处理方式。
SA_SIGINFO: 使用 sa_sigaction 代替 sa_handler,以获取更多信号信息。
使用方法
  1. 定义信号处理函数

    • sa_handler 用于基本的信号处理。
    • sa_sigaction 用于需要获取更多信号信息的处理。
  2. 初始化 struct sigaction 结构体

    • 设置 sa_handlersa_sigaction
    • 设置 sa_mask 来阻塞在信号处理程序执行期间需要阻塞的信号。
    • 设置 sa_flags 以控制信号处理的行为。
  3. 调用 sigaction 函数

    • 使用 sigaction 来设置信号处理程序。
示例代码

以下是一个使用 sigaction 函数设置 SIGINT 信号处理程序的示例代码。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

// 信号处理函数,处理 SIGINT 信号
void handle_sigint(int sig) {
    printf("Caught signal %d (SIGINT). Exiting gracefully...\n", sig);
    exit(0);
}

int main() {
    struct sigaction sa;

    // 设置信号处理函数
    sa.sa_handler = handle_sigint;
    sa.sa_flags = SA_RESTART; // 确保被信号中断的系统调用自动重启
    sigemptyset(&sa.sa_mask); // 初始化信号集为空

    // 使用 sigaction 设置 SIGINT 信号处理程序
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    printf("Press Ctrl+C to send SIGINT signal...\n");

    // 无限循环,等待信号
    while (1) {
        printf("Running...\n");
        sleep(1);
    }

    return 0;
}

3.2发送信号

3.2.1kill()

kill() 函数用于向指定进程或进程组发送信号。它不仅可以用来终止进程,还可以发送其他信号来控制进程的行为。

函数原型:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数说明
pid: 指定接收信号的进程或进程组。
pid > 0:信号发送到进程 ID 为 pid 的进程。
pid == 0:信号发送到与发送进程属于同一进程组的所有进程。
pid < -1:信号发送到进程组 ID 为 -pid 的所有进程。
pid == -1:信号发送到发送进程有权限发送信号的所有进程。
sig: 要发送的信号编号,例如 SIGTERM,SIGKILL,SIGINT 等。
返回值
成功:返回 0。
失败:返回 -1,并设置 errno。
示例代码

以下是一个示例代码,展示了如何使用 kill() 函数发送信号来终止子进程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) { // 子进程
        printf("Child process PID: %d\n", getpid());
        // 无限循环,等待信号
        while (1) {
            printf("Child running...\n");
            sleep(2);
        }
    } else { // 父进程
        sleep(5); // 父进程等待一段时间
        printf("Sending SIGTERM to child process\n");

        // 使用 kill 函数发送 SIGTERM 信号给子进程
        if (kill(pid, SIGTERM) == -1) {
            perror("kill");
            exit(EXIT_FAILURE);
        }

        // 等待子进程结束
        int status;
        waitpid(pid, &status, 0);

        // 检查子进程的终止状态
        if (WIFEXITED(status)) {
            printf("Child exited with status %d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("Child killed by signal %d\n", WTERMSIG(status));
        } else if (WIFSTOPPED(status)) {
            printf("Child stopped by signal %d\n", WSTOPSIG(status));
        } else {
            printf("Child terminated abnormally\n");
        }
    }

    return 0;
}
3.2.2raise()

raise() 函数用于向调用进程自身发送信号。

函数原型:
#include <signal.h>
int raise(int sig);
参数说明
sig: 要发送的信号编号,例如 SIGTERM, SIGKILL, SIGINT 等。
返回值
成功:返回 0。
失败:返回非零值,并设置 errno。
示例代码

以下是一个使用 raise() 函数发送 SIGINT 信号的示例代码。SIGINT 信号通常由用户按下 Ctrl+C 触发,但在这里我们通过 raise() 函数手动发送。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

// 信号处理函数,处理 SIGINT 信号
void handle_sigint(int sig) {
    printf("Caught signal %d (SIGINT). Exiting gracefully...\n", sig);
    exit(0);
}

int main() {
    // 设置 SIGINT 的信号处理程序
    if (signal(SIGINT, handle_sigint) == SIG_ERR) {
        perror("signal");
        exit(EXIT_FAILURE);
    }

    printf("Press Enter to raise SIGINT signal...\n");
    getchar(); // 等待用户按下 Enter 键

    // 使用 raise 函数发送 SIGINT 信号给自身
    printf("Raising SIGINT signal...\n");
    if (raise(SIGINT) != 0) {
        perror("raise");
        exit(EXIT_FAILURE);
    }

    // 无限循环,等待信号
    while (1) {
        printf("Running...\n");
        sleep(1);
    }

    return 0;
}

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值