【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.rhkb.cn/news/312377.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

部署wordpress

查看别名type ll ll 是 ls -l --colorauto 的别名 设置别名alias alias ymyum install -y 使用别名ym nginx 取消别名unalias ym 基于LNMP做一个wordpress nginx mysql 5.7 PHP 7.4 1、linux基本环境 修改主机名 hostnamectl set-hostname $name 关闭防火墙及selinux …

postman汉化

一、postman历史版本下载&#xff1a;Postman 10.24.16 Download for Windows / Old Versions / FileHorse.comhttps://www.filehorse.com/download-postman/old-versions/ 二、汉化包下载&#xff1a; Releases hlmd/Postman-cn GitHubPostman汉化中文版. Contribute to h…

哪个牌子的迷你洗衣机比较好?别错过五款高分内衣洗衣机品牌!

随着内衣洗衣机的流行&#xff0c;很多小伙伴在纠结该不该入手一款内衣洗衣机&#xff0c;专门来洗一些贴身衣物&#xff0c;答案是非常有必要的&#xff0c;因为我们现在市面上的大型洗衣机只能做清洁&#xff0c;无法对我们的贴身衣物进行一个高强度的清洁&#xff0c;而小小…

1:100万中国地貌类型数据

中国1&#xff1a;100万地貌类型空间分布数据来源于《中华人民共和国地貌图集&#xff08;1:100万&#xff09;》&#xff0c;是全面反映我国地貌宏观规律、揭示区域地貌空间分异的国家级基本比例尺图集&#xff0c;是我国目前已出版的百万系列专题图中海陆一体化的基本比例尺图…

【Python使用】python高级进阶知识md总结第8篇:TCP 网络应用程序开发流程,1. TCP 网络应用程序开发流程的介绍【附代码文档】

python高级进阶全知识知识笔记总结完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;操作系统&#xff0c;虚拟机软件。ls命令选项&#xff0c;mkdir和rm命令选项。压缩和解压缩命令&#xff0c;文件权限命令。编辑器 vim&#xff0c;软件安装。获取进程编号…

vue简单使用二(循环)

目录 属性绑定 if判断&#xff1a; for循环&#xff1a; 属性绑定 代码的形式来说明 三元表达式的写法&#xff1a; if判断&#xff1a; for循环&#xff1a; 完整代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"…

竞赛 基于GRU的 电影评论情感分析 - python 深度学习 情感分类

文章目录 1 前言1.1 项目介绍 2 情感分类介绍3 数据集4 实现4.1 数据预处理4.2 构建网络4.3 训练模型4.4 模型评估4.5 模型预测 5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于GRU的 电影评论情感分析 该项目较为新颖&#xff0c;适合作为竞…

2024年【高处安装、维护、拆除】试题及解析及高处安装、维护、拆除模拟试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【高处安装、维护、拆除】试题及解析及高处安装、维护、拆除模拟试题&#xff0c;包含高处安装、维护、拆除试题及解析答案和解析及高处安装、维护、拆除模拟试题练习。安全生产模拟考试一点通结合国家高处安装…

四维轻云|如何使用场景在线协作功能?

众所周知&#xff0c;四维轻云是一款轻量化的地理空间数据管理云平台&#xff0c;支持地理空间数据的在线管理、编辑以及分享。平台有项目管理、数据上传、场景搭建、发布分享、素材库等功能模块。现在&#xff0c;就为大家介绍一下如何使用场景协作编辑功能。 1、协作模式开启…

算法题解记录11+++从前序与中序遍历序列构造二叉树(百日筑基)

题目描述&#xff1a; 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,…

网上订餐系统|基于springboot的网上订餐系统设计与实现(源码+数据库+文档)

网上订餐系统目录 目录 基于springboot的网上订餐系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户功能模块的实现 &#xff08;1&#xff09;用户注册界面 &#xff08;2&#xff09;用户登录界面 &#xff08;3&#xff09;菜品详情界面 &#xff08…

解决Error (169281)、Error (169282)报错问题,QuartusII设置Virtual Pin虚拟管脚的详细操作方法

解决Error(169281)、Error(169282)报错问题,QuartusII设置Virtual Pin虚拟管脚的详细操作方法 1,QuartusII报错信息2,解决办法3,重新编译,成功参考文献: 1,Quartus如何设置虚拟管脚Virtual Pin(具体设置方法) 1,QuartusII报错信息 报错原因:    为了验证FPGA工…

vue学习日记22:非父子通信(拓展)-provideinject

一、概念 二、实践 代码 App <template><div class"app">我是APP组件<button click"change">修改数据</button><SonA></SonA><SonB></SonB></div> </template><script> import SonA …

5个常见的前端手写功能:New、call apply bind、防抖和节流、instanceof、ajax

实现New 首先创建一个新的空对象设置原型&#xff0c;将对象的原型设置为函数的prototype对象让函数的this指向这个对象&#xff0c;执行构造函数的代码判断函数的返回值类型&#xff0c;如果是值类型&#xff0c;返回创建的对象。如果是引用类型&#xff0c;就返回这个引用类…

UE5 C++ 射线检测

一.声明四个变量 FVector StartLocation;FVector ForwardVector;FVector EndLocation;FHitResult HitResult;二.起点从摄像机&#xff0c;重点为摄像机前9999m。射线检测 使用LineTraceSingleByChannel 射线直线通道检测&#xff0c;所以 void AMyCharacter::Tick(float Delt…

9、statefulSet 有状态pod

1、statefulSet与replicationController、replicaSet的区别 1.1 有无状态 statefulSet是有状态的&#xff0c;类似于数据库、配置中心等&#xff0c;移除后需要创建相同的pod rc与rs是无状态的&#xff0c;可替换的 1.2 命名规则 1.3创建规则 statefulSet修改完ymal文件&…

GVRP协议与动态、静态vlan

一、GVRP协议使用场景 1、当实际组网复杂到网络管理员无法短时间内了解网络的拓扑结构&#xff0c;或者是整个网络的VLAN太多时&#xff0c;工作量会非常大&#xff0c;而且非常容易配置错误。在这种情况下&#xff0c;用户可以通过GVRP的VLAN自动注册功能完成VLAN的配置。 2、…

HarmonyOS开发实例:【分布式数据管理】

介绍 本示例展示了在eTS中分布式数据管理的使用&#xff0c;包括KVManager对象实例的创建和KVStore数据流转的使用。 通过设备管理接口[ohos.distributedDeviceManager]&#xff0c;实现设备之间的kvStore对象的数据传输交互&#xff0c;该对象拥有以下能力 ; 1、注册和解除注…

Compose 简单组件

文章目录 Compose 简单组件TextText属性使用AnnotatedStringSpanStyleParagraphStyle SelectionContainer 和 DisableSelectionClickableText TextFieldTextField属性使用OutlinedTextFieldBasicTextFieldKeyboardOptions 键盘属性KeyboardActions IME动作 ButtonButton属性使用…

基于SpringBoot+Vue的校园商铺管理系统设计与实现(源码+文档+包运行)

一.系统概述 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补…