【Linux】Linux信号

目录

信号的概念

生活中的信号

Linux中的信号

kill命令

kill 命令的使用

常见的信号

命令行代码示例

注意事项

信号的处理方式

产生信号

信号的捕捉

信号捕捉示意图

内核如何实现信号捕捉

信号的捕捉与处理

小结

阻塞信号

信号在内核中的表示图

信号集操作函数

可重入函数

可重入函数示意图

可重入函数概念

可重入函数的使用

volatile关键字

SIGHLD信号

SIGHLD信号介绍

SIGCHLD信号的概念

SIGCHLD信号的作用

SIGCHLD信号使用示例

示例代码实现

示例代码解释

示例代码总结


信号的概念

在Linux中,信号是一种软件中断,用于通知进程发生了某个事件。这些事件可能是用户请求(如按下Ctrl+C),也可能是内核通知(如进程访问了无效的内存地址)。信号提供了一种机制,使得进程能够响应这些事件,并采取适当的行动。

信号的递达与未决

一、信号的递达

信号的递达是指进程实际执行信号的处理动作。当信号被发送给一个进程后,操作系统会进行相应的处理,确保信号能够准确地传递给目标进程。一旦信号被递达给进程,进程就会根据信号的类型和设定的处理方式执行相应的动作。

在信号的递达过程中,进程可以自定义信号的处理函数,用于执行特定的逻辑。当信号递达时,进程会调用相应的处理函数,并执行其中的代码。这样,进程可以根据不同的信号进行不同的处理,以满足不同的需求。

二、信号的未决

信号的未决状态是指信号从产生到递达之间的状态。当一个信号被发送给一个进程后,该信号首先会处于未决状态。这意味着信号已经产生,但还没有被进程实际处理。

在信号的未决状态中,操作系统会维护一个信号未决表,用于记录当前进程尚未处理的信号。这个表记录了哪些信号已经产生但尚未递达给进程。进程可以通过检查信号未决表来了解当前有哪些信号等待处理。

进程的信号未决状态可以被阻塞或解除阻塞。当进程选择阻塞某个信号时,该信号将保持在未决状态,不会被递达给进程。只有当进程解除对该信号的阻塞时,信号才会从未决状态转变为递达状态,进而执行相应的处理动作。

通过信号的递达和未决机制,操作系统能够灵活地管理进程对信号的处理。进程可以根据需要自定义信号的处理方式,并在合适的时机执行相应的处理动作。这种机制为进程间的通信和异常处理提供了强大的支持。

生活中的信号

  1. 闹钟信号:假设你设定了一个闹钟,在早上7点响铃。这个闹钟响铃就是一个信号,通知你该起床了。在Linux中,这可以类比为一个进程设定了一个定时器(如使用setitimer系统调用),当定时器到期时,内核会向该进程发送一个信号(如SIGALRM),通知它定时器已经到期。

  2. 电话铃声:当你正在做其他事情时,电话铃声响起,这是一个信号,告诉你有人正在呼叫你。你可以选择接听电话、挂断电话或忽略它。在Linux中,当一个进程接收到一个信号时,它可以选择忽略该信号、执行默认操作或自定义一个信号处理函数来处理该信号。

  3. 交通信号灯:交通信号灯是道路上的重要信号,告诉驾驶员何时可以前进、何时需要停止或等待。如果驾驶员违反信号灯指示,可能会引发交通事故。类似地,在Linux中,如果进程不正确地处理信号(如忽略重要的信号),可能会导致不可预期的行为或程序崩溃。

  4. 敲门声:当你正在房间里工作时,突然听到敲门声,这是一个信号,告诉你有人想要和你交流。你可以继续工作不理会敲门声,也可以停下来去开门。在Linux中,进程可以选择忽略某些信号(如SIGCHLD,当子进程状态改变时发送),也可以对它们进行特殊处理。

Linux中的信号

Linux中的信号是一种进程间通信(Inter-Process Communication, IPC)机制,用于向其他进程发送通知或指示,通常是为了通知特定事件的发生,如程序终止、用户按下特定按键等。信号提供了一种异步的、非阻塞的通信方式,因此在某些情况下非常有用。

信号全称为软中断信号,也有人称作软中断。信号机制是进程之间相互传递消息的一种方法。进程之间可以互相通过系统调用kill发送软中断信号,通知进程发生了某个事件。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。例如,当中断用户键入中断键(CTRL+C)时,会通过信号机制停止一个程序。

Linux系统定义了64种信号,这些信号可以分为两类:可靠信号与不可靠信号。前32种信号为不可靠信号,也称为非实时信号,它们不支持排队,信号可能会丢失。比如,发送多次相同的信号,进程只能收到一次。而后32种信号为可靠信号,也称为实时信号,它们支持排队,信号不会丢失,发多少次就可以收到多少次。

每种信号都有其特定的用途和处理方式。例如,SIGTERM信号用于请求终止进程,它可以被捕获或忽略;SIGQUIT信号请求进程退出并产生core文件,同样可以被捕获或忽略;而SIGSTOP信号会暂停进程的执行,并且不能被捕获或忽略。

在Linux中,进程可以选择忽略某些信号,也可以自定义信号处理函数来处理特定信号。当进程接收到一个信号时,它会根据信号的类型和当前的状态来决定如何响应。如果进程没有为某个信号指定处理函数,那么它会执行该信号的默认操作。

总的来说,Linux中的信号机制为进程间的通信和同步提供了强大的支持,使得进程能够灵活地响应各种事件和条件。通过合理地使用和处理信号,我们可以编写出更加健壮和高效的程序。

kill命令

kill通常,kill 命令用于终止进程,但实际上它可以发送任何有效的信号给进程。默认情况下,kill命令发送SIGTERM信号,该信号会请求进程优雅地终止。如果进程没有响应SIGTERM信号,可以使用kill -9命令发送SIGKILL` 信号强制终止进程。

kill 命令的使用

kill 命令的基本语法如下:

kill [选项] <进程ID> [进程ID ...]

或者:

kill -s <信号> <进程ID>

其中,<进程ID> 是你想要发送信号的进程的进程ID,<信号> 是你想要发送的信号名或信号编号。

常见的信号

  • SIGTERM(默认信号,编号 15):请求进程终止。进程在接收到此信号后,可以执行清理操作并退出。

  • SIGKILL(编号 9):强制终止进程。进程在接收到此信号后不能捕获或忽略它,会立即停止执行。

  • SIGINT(编号 2):通常由 Ctrl+C 产生,请求进程中断。

  • SIGHUP(编号 1):当控制终端挂起时,通知进程。

命令行代码示例

  1. 发送 SIGTERM 信号终止进程

如果你知道进程的ID,比如 1234,你可以使用以下命令来发送 SIGTERM 信号:

kill 1234

如果进程没有响应,你可以使用 kill -l 来查看所有可用的信号列表,并决定发送哪种信号。

  1. 发送 SIGKILL 信号强制终止进程

如果 SIGTERM 没有效果,你可以发送 SIGKILL 信号来强制终止进程:

kill -9 1234

这条命令会强制终止进程ID为 1234 的进程。

  1. 发送特定信号

如果你想发送一个特定的信号,比如 SIGHUP,你可以使用 -s 选项:

kill -s SIGHUP 1234

或者你也可以使用信号的编号:

kill -1 1234

这条命令会向进程ID为 1234 的进程发送 SIGHUP 信号。

  1. 发送信号给多个进程

你可以一次向多个进程发送信号:

kill 1234 5678

这条命令会向进程ID为 12345678 的进程发送 SIGTERM 信号。 5. 查看信号列表 你可以查看信号列表

kill -l

注意事项

  • 在使用 kill -9 之前,请确保你已经尝试了 SIGTERM 信号,因为 SIGKILL 会立即停止进程,不给进程任何清理的机会。

  • 不是所有的进程都可以被 kill 命令终止。有些系统进程或守护进程可能设计为忽略某些信号。

  • 错误的 kill 命令可能会导致系统不稳定或数据丢失,所以请确保你知道正在终止什么进程,以及这个操作可能带来的后果。

kill 命令是一个非常强大的工具,但也需要谨慎使用。在发送信号之前,最好先了解进程的用途和重要性,以避免不必要的系统干扰或数据损失。

信号的处理方式

在Linux中,常见的信号处理方法主要有以下几种:

  1. 忽略信号:当进程接收到某个信号时,可以选择忽略它,即不执行任何操作。这通常用于处理那些对进程来说不重要的信号。

  2. 执行默认处理动作:对于每个信号,系统都定义了一个默认的处理动作。当进程接收到信号且没有自定义处理函数时,就会执行这个默认动作。例如,对于SIGTERM信号,默认的处理动作是终止进程。

  3. 自定义信号处理函数:进程可以注册一个信号处理函数,当接收到特定信号时,内核会调用这个函数来处理信号。这种方式允许进程根据信号的类型和当前的状态来执行特定的操作,如清理资源、记录日志等。通过自定义信号处理函数,进程可以更加灵活地响应各种事件和条件。

需要注意的是,并非所有的信号都可以被忽略或自定义处理。例如,SIGKILLSIGSTOP等信号是不能被忽略或捕获的,它们会强制终止进程或暂停进程的执行。

产生信号

在Linux中,信号的产生主要有以下几种方式:

  1. 通过键盘产生

    • 用户可以通过特定的按键组合来产生信号。例如,按下Ctrl+C会产生SIGINT信号,通常用于终止前台进程。这种信号产生方式实际上是操作系统捕获到键盘上的按键组合,并将其解析成相应的信号,然后发送给目标进程。

  2. 通过系统调用产生

    • Linux提供了多个系统调用来产生信号,其中最常见的是killraise函数。
      • kill函数允许一个进程向另一个进程发送信号。通过指定目标进程的进程ID和要发送的信号,可以实现对目标进程的控制,如终止进程、暂停进程等。

      • raise函数则允许一个进程向自身发送信号,常用于实现进程内部的信号处理逻辑。

  3. 由软件条件产生

    • 当某些软件条件满足时,操作系统会自动向进程发送信号。例如,当进程访问了非法内存地址时,会产生SIGSEGV信号;当进程执行了除零操作时,会产生SIGFPE信号。此外,定时器到期、I/O操作完成等事件也会触发相应的信号。

  4. 由硬件异常产生

    • 当硬件发生异常时,如CPU异常、总线错误等,操作系统会向相关进程发送信号。这些信号通常用于通知进程硬件层面的错误,以便进程能够采取相应的处理措施。

信号的捕捉

信号捕捉示意图

内核如何实现信号捕捉

在Linux内核中,信号捕捉的实现涉及多个组件和步骤。信号捕捉允许进程在接收到信号时执行自定义的处理逻辑,而不是采用默认的信号处理行为。以下是信号捕捉在内核中的实现细节的详细介绍:

  1. 信号注册与信号处理器设置

    • 当进程通过signal()sigaction()系统调用设置信号处理器时,内核会记录该进程的信号处理器信息。

    • 内核维护了一个数据结构(如task_struct中的字段),该结构包含指向每个信号对应的处理器函数的指针。

  2. 信号产生

    • 信号可以由多种原因产生,如用户按下Ctrl+C(产生SIGINT信号),或者内核检测到某种异常条件(如段错误产生SIGSEGV信号)。

    • 当信号产生时,内核会更新目标进程的信号状态,将其标记为未决状态。

  3. 信号传递

    • 内核定期检查进程的信号状态,并尝试将未决信号传递给进程。

    • 在信号传递之前,内核会检查进程是否阻塞了该信号。如果信号被阻塞,则保持未决状态;否则,进入信号处理流程。

  4. 信号处理

    • 内核调用进程为该信号设置的处理器函数(也称为信号处理程序)。

    • 信号处理程序可以在用户空间执行,这意味着当处理程序被调用时,内核会切换到用户模式并执行处理程序中的代码。

    • 信号处理程序可以执行任何合法的用户空间代码,包括改变程序的控制流、修改变量值或执行其他任务。

  5. 信号恢复

    • 信号处理程序执行完毕后,控制流返回到内核,内核负责恢复进程的上下文并继续执行。

    • 内核还负责清理信号处理过程中的任何临时状态,确保系统的稳定性。

  6. 信号与阻塞

    • 进程可以使用sigprocmask()等系统调用来修改其信号屏蔽字(即阻塞的信号集)。

    • 当进程阻塞某个信号时,该信号即使产生也不会被立即处理,而是保持未决状态,直到进程解除对该信号的阻塞。

  7. 实时信号与排队

    • Linux支持实时信号,这些信号与普通信号的主要区别在于它们可以排队等待处理。

    • 对于实时信号,内核会为每个信号维护一个队列,允许多个相同的实时信号被同时接收并排队等待处理。

  8. 安全性与可靠性

    • 内核在信号处理过程中要确保系统的安全性和可靠性。

    • 例如,内核会检查信号处理程序的有效性,防止恶意代码的执行。

    • 内核还会处理信号处理过程中的异常情况,如处理程序中的错误或异常退出。

信号的捕捉与处理

一、信号的捕捉

在Linux中,信号的捕捉是指进程在接收到信号时,不是按照信号的默认行为来处理,而是执行自定义的信号处理函数。这样,进程就可以根据信号的种类和当前的状态来决定如何响应信号。

二、使用sigaction设置信号处理函数

sigaction函数提供了更丰富的功能,并且更加安全,因此在实际应用中更为常用。下面将介绍如何使用sigaction函数来设置信号处理函数。

三、捕捉空指针异常(SIGSEGV)的示例

下面以捕捉空指针异常(即SIGSEGV信号)为例,展示如何使用sigaction函数来设置信号处理函数。

3.1 定义信号处理函数

首先,需要定义一个信号处理函数,该函数将在接收到信号时被调用。在这个例子中,我们定义了一个名为handle_segv的函数,用于处理SIGSEGV信号。

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

void handle_segv(int sig, siginfo_t *info, void *context) {
    // 处理SIGSEGV信号的逻辑,如打印调用栈信息
}

3.2 设置信号处理函数

接下来,在程序的主函数中,使用sigaction函数来设置SIGSEGV信号的处理函数。

int main() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = handle_segv;
    sa.sa_flags = SA_SIGINFO;

    if (sigaction(SIGSEGV, &sa, NULL) == -1) {
        // 处理设置信号处理函数失败的情况
    }

    // 程序的正常逻辑,可能会触发SIGSEGV信号
    return 0;
}

在上述代码中,sigaction函数用于设置SIGSEGV信号的处理函数为handle_segv。当程序因为解引用空指针而触发SIGSEGV信号时,会调用handle_segv函数来处理该信号。

3.3 触发空指针异常并处理

在程序的正常逻辑中,可以故意触发一个空指针异常来测试信号捕捉的处理逻辑。例如,通过解引用一个空指针来触发SIGSEGV。

// 在程序的正常逻辑中触发空指针异常
int *ptr = NULL;
*ptr = 1; // 这将导致SIGSEGV信号

当程序执行到这一行时,会触发SIGSEGV信号,并调用之前设置的handle_segv函数来处理该信号。在handle_segv函数中,可以执行自定义的处理逻辑,如打印调用栈信息、记录错误日志等。

请注意,在实际的生产环境中,对于空指针异常,通常更好的做法是修复程序中的缺陷,避免解引用空指针的情况发生,而不是尝试捕捉并恢复。

小结

  1. 所有的信号都由操作系统来产生,因为操作系统是进程的管理者

  2. 信号不是立即处理的,而是在适合的情况进行处理

阻塞信号

信号在内核中的表示图

信号在Linux内核中的表示涉及到信号的三种状态:抵达态、未决态和阻塞态(也称为屏蔽态)。

  1. 抵达态:这是执行信号的处理动作的状态。也就是说,当信号的处理动作被执行时,信号就处于抵达态。抵达态下的信号处理方式有三种:忽略、默认或执行自定义函数。

  2. 未决态:这是从信号的产生到信号抵达之间的状态。当信号被发送,但还未被进程处理时,信号就处于未决态。这种状态下的信号在内核中会被记录,等待处理。

  3. 阻塞态(屏蔽态):这是用户可以手动设置的状态。当信号处于阻塞态时,即使信号已经产生,也不会被立即处理,而是保持未决状态。只有当用户解除了对该信号的阻塞(屏蔽)后,信号才会转变为抵达状态,执行相应的处理动作。

在内核中,这三种状态通常使用结构体来存储,每种状态对应一个结构体。这些结构体内部包含足够多的比特位,每一个比特位对应一个信号值,以此来记录和表示每个信号的状态。

另外,Linux中的每个进程都拥有两个位向量:pending位向量和blocked位向量。pending位向量包含了那些内核发送给进程但还没有被进程处理掉的信号,而blocked位向量则包含了那些被进程屏蔽掉的信号。这两个位向量共同决定了进程将如何处理信号。

在信号处理机制中,当内核发送一个信号给进程时,它会修改进程的pending位向量。同样地,当进程屏蔽掉一个信号时,它会修改blocked位向量。只有当进程解除了对某个信号的屏蔽后,如果pending位向量中对应该信号的值被设置为1(表示该信号已产生且未处理),那么该信号才会被递达给进程进行处理。

信号集操作函数

在Linux中,信号集操作函数是用于操作信号集的一系列函数。信号集是一个包含多个信号的数据结构,用于表示一组信号。这些函数提供了添加、删除、测试信号集中信号的功能,以及用于阻塞和解除阻塞信号的功能。

下面是对这些函数的详细介绍和简单的示范代码:

  1. sigemptyset

sigemptyset函数用于初始化一个信号集,将其所有位都设置为0,即清空信号集。

#include <signal.h>

int sigemptyset(sigset_t *set);

示例

sigset_t emptyset;
sigemptyset(&emptyset); // 将emptyset初始化为空信号集
  1. sigfillset

sigfillset函数用于将信号集中的所有位都设置为1,即包含所有可能的信号。

#include <signal.h>

int sigfillset(sigset_t *set);

示例

sigset_t fullset;
sigfillset(&fullset); // 将fullset初始化为包含所有信号的信号集
  1. sigaddset

sigaddset函数用于将一个指定的信号添加到信号集中。

#include <signal.h>

int sigaddset(sigset_t *set, int signum);

示例

sigset_t myset;
sigemptyset(&myset);
sigaddset(&myset, SIGINT); // 将SIGINT信号添加到myset信号集中
  1. sigdelset

sigdelset函数用于从信号集中删除一个指定的信号。

#include <signal.h>

int sigdelset(sigset_t *set, int signum);

示例

sigset_t myset;
sigfillset(&myset);
sigdelset(&myset, SIGINT); // 从myset信号集中删除SIGINT信号
  1. sigismember

sigismember函数用于检查一个指定的信号是否存在于信号集中。

#include <signal.h>

int sigismember(const sigset_t *set, int signum);

示例

sigset_t myset;
sigaddset(&myset, SIGINT);
if (sigismember(&myset, SIGINT)) {
    // SIGINT存在于myset信号集中
}
  1. sigprocmask

sigprocmask函数用于检查和/或更改当前进程的信号屏蔽字(即阻塞的信号集)。

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • how参数决定了如何更改信号屏蔽字:SIG_BLOCK(添加信号到当前阻塞集)、SIG_UNBLOCK(从当前阻塞集中移除信号)或SIG_SETMASK(设置当前阻塞集为指定的信号集)。

  • set参数指向包含新信号集的指针。

  • oldset参数指向存储当前信号屏蔽字的指针,或者为NULL。

示例

sigset_t blockset, oldset;
sigemptyset(&blockset);
sigaddset(&blockset, SIGINT);
sigprocmask(SIG_BLOCK, &blockset, &oldset); // 阻塞SIGINT信号
  1. sigpending

sigpending函数用于获取当前进程被阻塞的、但尚未处理的信号集。

#include <signal.h>

int sigpending(sigset_t *set);

示例

sigset_t pendingset;
sigpending(&pendingset); // 获取当前进程的pending信号集

注意:在使用信号集操作函数时,需要包含头文件<signal.h>。同时,不同的操作可能需要不同的权限,因此在某些情况下可能需要以root权限运行程序。

可重入函数

可重入函数示意图

可重入函数概念

可重入函数(Reentrant Function)是指在执行过程中,可以被多个线程同时调用的函数,且每个线程调用该函数时,都会得到正确的结果,不会因为其他线程的影响而导致数据混乱或错误。在多线程编程中,使用可重入函数是非常重要的,以确保线程安全。

可重入函数的使用

为了确保函数是可重入的,通常需要注意以下几点:

  1. 避免使用全局变量:全局变量在多线程环境中可能导致数据竞争和不一致的结果。

  2. 避免使用静态局部变量:静态局部变量在函数调用之间保持其值,这可能导致线程间的数据混淆。

  3. 避免调用不可重入的函数:如果函数调用了其他不可重入的函数,那么该函数本身也将是不可重入的。

  4. 保护共享资源:如果函数需要访问共享资源(如文件、数据库等),应使用适当的同步机制(如互斥锁)来确保同时只有一个线程可以访问。

下面是一个简单的可重入函数的示范代码:

#include <stdio.h>
#include <pthread.h>

// 这是一个可重入函数,因为它不依赖任何全局或静态变量
int add(int a, int b) {
    return a + b;
}

// 线程函数,它将调用可重入函数add
void* thread_func(void* arg) {
    int a = *((int*)arg);
    int b = 5;
    int result = add(a, b);
    printf("Thread %ld: %d + %d = %d\n", (long)pthread_self(), a, b, result);
    return NULL;
}

int main() {
    pthread_t threads[5];
    int values[] = {1, 2, 3, 4, 5};

    // 创建5个线程,每个线程都调用add函数
    for (int i = 0; i < 5; ++i) {
        pthread_create(&threads[i], NULL, thread_func, (void*)&values[i]);
    }

    // 等待所有线程完成
    for (int i = 0; i < 5; ++i) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}

在这个示例中,add函数是一个可重入函数,因为它只依赖于其参数,并不使用任何全局或静态变量。thread_func是线程函数,它调用了add函数。我们创建了5个线程,每个线程都调用add函数,但由于add是可重入的,所以每个线程都会得到正确的结果,而不会相互干扰。

volatile关键字

volatile关键字在C和C++编程中,尤其是在嵌入式系统和多线程编程中,是一个非常重要的概念。在Linux编程中,它同样扮演着重要角色。volatile告诉编译器不要对该变量进行任何优化,每次访问这个变量时都应该直接从其内存地址中读取,而不是使用寄存器中的值或进行其他可能的优化。

这是因为有些变量的值可能会在编译器无法检测到的情况下改变,例如:

  1. 硬件寄存器:在嵌入式编程中,我们可能会直接操作硬件寄存器,这些寄存器的值可能会在任何时候由硬件改变。

  2. 多线程共享变量:在多线程环境中,一个线程可能会修改一个变量的值,而另一个线程需要立即看到这个改变。

  3. 信号或中断服务例程:在操作系统中,中断服务例程可能会改变某些变量的值,而这些改变是编译器无法预测的。

在这些情况下,使用volatile可以确保每次访问变量时都会从内存中读取其最新值,而不是使用编译器可能缓存的“旧”值。

示例代码

假设我们正在编写一个驱动程序,该驱动程序需要读取一个硬件寄存器的值。

#include <stdio.h>

// 假设这是一个硬件寄存器的地址
volatile int *hardware_register = (volatile int*)0x12345678;

int main() {
    int value;
    
    // 读取硬件寄存器的值
    value = *hardware_register;
    
    printf("The value of the hardware register is: %d\n", value);
    
    return 0;
}

在这个例子中,hardware_register是一个指向硬件寄存器地址的指针,并且它被声明为volatile。这意味着每次我们读取*hardware_register时,都会直接从硬件寄存器中读取其值,而不是使用任何可能的编译器优化或缓存的值。

需要注意的是,volatile不提供任何形式的原子性保证。在多线程环境中,如果多个线程可能同时修改一个volatile变量,那么仍然需要使用适当的同步机制(如互斥锁或原子操作)来确保数据的一致性。同样,volatile也不能防止编译器重新排序读写操作。如果需要这些更高级的特性,通常需要使用特定的编译器扩展或语言特性(如C11中的_Atomic类型或C++中的std::atomic)。

SIGHLD信号

SIGHLD信号介绍

SIGCHLD信号的概念

SIGCHLD信号是一个在Unix和类Unix系统中用于通知父进程其子进程状态改变的信号。

SIGCHLD信号的作用

当子进程状态改变时,父进程会收到SIGCHLD信号,这使得父进程能够适当地清理子进程的资源,例如回收僵尸进程。

SIGCHLD信号使用示例

示例代码实现

下面是一个简单的示例代码,展示了如何使用SIGCHLD信号来回收子进程的资源。

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <signal.h>  
#include <sys/wait.h>  
  
void handle_sigchld(int sig) {  
    // 当收到SIGCHLD信号时,这个函数会被调用  
    pid_t pid;  
    int status;  
  
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {  
        // 使用WNOHANG标志来尝试非阻塞地等待子进程  
        // 如果成功回收了一个子进程,waitpid会返回该子进程的PID  
        // 如果没有子进程需要回收,waitpid会返回0  
        // 如果发生错误,waitpid会返回-1  
  
        printf("Child %d exited with status %d\n", pid, WEXITSTATUS(status));  
    }  
}  
  
int main() {  
    pid_t pid;  
    struct sigaction sa;  
  
    // 设置SIGCHLD信号的处理函数  
    sa.sa_handler = handle_sigchld;  
    sigemptyset(&sa.sa_mask);  
    sa.sa_flags = 0;  
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {  
        perror("sigaction");  
        exit(EXIT_FAILURE);  
    }  
  
    // 创建子进程  
    pid = fork();  
    if (pid == -1) {  
        perror("fork");  
        exit(EXIT_FAILURE);  
    } else if (pid == 0) {  
        // 子进程代码  
        printf("Child process (PID %d) running...\n", getpid());  
        sleep(2);  // 子进程休眠2秒  
        printf("Child process exiting...\n");  
        exit(42);  // 子进程以状态码42退出  
    } else {  
        // 父进程代码  
        printf("Parent process (PID %d) waiting for child...\n", getpid());  
        // 父进程可以继续执行其他任务,当子进程退出时,SIGCHLD信号会被发送并处理  
    }  
  
    // 父进程等待一段时间以确保子进程有机会运行和退出  
    sleep(5);  
  
    return 0;  
}
示例代码解释
  1. 信号处理器设置

定义了一个名为handle_sigchld的函数,作为SIGCHLD信号的处理器。该函数会在收到SIGCHLD信号时被调用,并尝试非阻塞地回收子进程。

  1. 子进程创建与退出

main函数中,使用fork创建了一个子进程。子进程执行一些操作后,以特定的状态码退出。

  1. 父进程等待与处理

父进程等待一段时间,以便子进程有机会运行和退出。当子进程退出时,父进程会收到SIGCHLD信号,并调用之前设置的信号处理器函数handle_sigchld来回收子进程资源。

示例代码总结

通过示例代码展示了如何使用SIGCHLD信号来异步地回收子进程资源,避免了僵尸进程的问题。在实际应用中,可以根据具体需求调整代码以适应不同场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/552512.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何学习嵌入式Linux?

如何去学习嵌入式 Linux 呢&#xff1f;嵌入式底层开发毫无疑问是一项极为关键重要的技术&#xff0c;其被广泛地应用于形形色色的嵌入式系统之中。伴随科技的迅猛飞速发展&#xff0c;嵌入式系统已然成为了我们生活中不可或缺的一个组成部分&#xff0c;这也极为凸显出了嵌入式…

基于 Bazel 的 iOS Monorepo 工程实践

在之前很长一段时间里&#xff0c;哔哩哔哩 iOS 工程是使用 Polyrepo&#xff08;或者说 Multirepo&#xff0c;即多仓库&#xff09;的传统模式进行开发。但是随着业务的发展&#xff0c;我们的代码仓库的数量也随之膨胀&#xff0c;我们慢慢发现 Polyrepo 模式并不一定是适合…

DDoS攻击愈演愈烈,谈如何做好DDoS防御

DDoS攻击是目前最常见的网络攻击方式之一&#xff0c;各种规模的企业包括组织机构都在受其影响。对于未受保护的企业来讲&#xff0c;每次DDoS攻击的平均成本为20万美元。可见&#xff0c;我们显然需要开展更多的DDoS防御工作。除考虑如何规避已发生的攻击外&#xff0c;更重要…

手机副业赚钱秘籍:让你的手机变成赚钱利器

当今社会&#xff0c;智能手机已然成为我们生活不可或缺的一部分。随着技术的飞速进步&#xff0c;手机不再仅仅是通讯工具&#xff0c;而是化身为生活伴侣与工作助手。在这个信息爆炸的时代&#xff0c;我们时常会被一种焦虑感所困扰&#xff1a;如何能让手机超越消磨时光的定…

关于Git的一些基础用法

关于Git的一些基础用法 1. 前言2. 使用GitHub/gitee创建项目2.1 创建账号2.2 创建项目2.3 下载仓库到本地2.4 提交代码到远端仓库2.5 查看日志2.6 同步远端仓库和本地仓库 1. 前言 首先说一个冷知识&#xff08;好像也不是很冷&#xff09;&#xff0c;Linux和git的创始人是同…

CC254X 8051芯片手册介绍

1 8051CPU 8051是一种8位元的单芯片微控制器&#xff0c;属于MCS-51单芯片的一种&#xff0c;由英特尔(Intel)公司于1981年制造。Intel公司将MCS51的核心技术授权给了很多其它公司&#xff0c;所以有很多公司在做以8051为核心的单片机&#xff0c;如Atmel、飞利浦、深联华等公…

C++:类型转换

目录 1、C语言中的类型转换 2、C的四种类型转换 2.1 static_cast 2.2 reinterpret_cast 2.3 const_cast 2.4 dynamic_cast 3 RTTI 1、C语言中的类型转换 如果 赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者返回值类型与 接收返回值…

TexStudio + MikTex 手动安装宏包

遇到上面这个 “宏包安装” 提示窗口后&#xff0c;设置来源为本地&#xff0c;随后在这个网址 https://mirrors.ustc.edu.cn/CTAN/systems/win32/miktex/tm/packages/ 下载所需的宏包&#xff0c;放到本地仓库里&#xff0c;即可 有三个宏包是必须要有的&#xff0c;它们是索…

上下文输入无限制,谷歌发布Infini-Transformer

去年&#xff0c;百川智能发布号称全球最长的上下文窗口大模型Baichuan2-192K&#xff0c;一次性可输入35万字&#xff0c;超越GPT-4。 今年3月&#xff0c;Kimi智能助手宣布在上下文窗口技术上突破200万字。 紧追其后&#xff0c;国内各大互联网巨头纷纷布局升级自家大模型产…

JAVA基础08- 继承,重写,super以及this

目录 继承&#xff08;extends&#xff09; 定义 说明 作用 方法的重写 定义 重写关键点 方法重写与重载的区别 练习 练习1&#xff08;方法继承与重写的简单练习&#xff09; 练习2&#xff08;方法继承与重写的进阶练习&#xff09; This的使用 定义 作用以及注…

Postman之版本信息查看

Postman之版本信息查看 一、为何需要查看版本信息&#xff1f;二、查看Postman的版本信息的步骤 一、为何需要查看版本信息&#xff1f; 不同的版本之间可能存在功能和界面的差异。 二、查看Postman的版本信息的步骤 1、打开 Postman 2、打开设置项 点击页面右上角的 “Set…

MyBatis 源码分析 - SQL 的执行过程

MyBatis 源码分析 - SQL 的执行过程 * 本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程。该过程本身比较复杂&#xff0c;牵涉到的技术点比较多。包括但不限于 Mapper 接口代理类的生成、接口方法的解析、SQL 语句的解析、运行时参数的绑定、查询结果自动映射、延…

基于SpringBoot+Vue的二手车交易系统的设计与实现(源码+文档+包运行)

一.系统概述 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统二手车交易信息管理难度大&#xff0c;容错率低&…

Connection: keep-alive 简介

一、在使用fiddler抓包工具会出现如下场景 二、keep-alive 保持连接 "Connection: keep-alive" 是 HTTP 协议中的一个头部字段&#xff0c;用于指示客户端和服务器之间的连接是否保持活跃状态。 当客户端发送一个 HTTP 请求给服务器时&#xff0c;可以在请求头部中包…

阿里云4核8G云服务器价格多少钱?700元1年

阿里云4核8G云服务器价格多少钱&#xff1f;700元1年。阿里云4核8G服务器租用优惠价格700元1年&#xff0c;配置为ECS通用算力型u1实例&#xff08;ecs.u1-c1m2.xlarge&#xff09;4核8G配置、1M到3M带宽可选、ESSD Entry系统盘20G到40G可选&#xff0c;CPU采用Intel(R) Xeon(R…

储能系统--BMS电流采样详解

一、行业标准介绍 汽车电池管理系统 储能电池管理系统 二、BMS电流采样 &#xff08;1&#xff09;电流采样的作用 电流传感器一般会位于动力电池系统主正或主副回路测量整个电池包的电流&#xff0c;电流信号会送到BMS&#xff0c;给BMS做充放电控制&#xff0c;电池SOC、SO…

pip安装swig@FreeBSD

SWIG (Simplified Wrapper and Interface Generator) 是一个用于连接 C/C 代码与其他高级编程语言&#xff08;如Python、Java、C# 等&#xff09;的工具。它允许开发人员将现有的 C/C 代码封装成可以在其他语言中调用的接口&#xff0c;而无需手动编写大量的代码。 SWIG 的工…

编程入门(三)【GPT工具的使用】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 前言背景了解GPT工具使用技巧GPT工具在学习和工作中的应用 前言 背景了解 2…

OpenStack云平台实战

1、环境准备 主机CPU数量内存硬盘IPV4发行版controller48GB100GBens33: 192.168.110.27/24 esn34: 192.168.237.131/24CentOS 7.9compute48GB200GB、100GBens33: 192.168.110.26/24 esn34: 192.168.237.132/24CentOS 7.9 1.1 虚拟机安装部署 1.1.1 创建虚拟机 这里16或者17都…

工业级3D可视化工具HOOPS Visualize, 快速构建移动端和PC端工程应用程序!

HOOPS Visualize是一款强大的工业级3D渲染引擎&#xff0c;帮助您打造出众的工程应用程序。HOOPS Visualize的基石是图形内核&#xff0c;这是一种全功能的&#xff0c;以工程为重点的场景图技术&#xff0c;我们称为Core Graphics。Core Graphics集成到一个框架中&#xff0c;…
最新文章