首页 > 系统相关 >进程间通信-信号

进程间通信-信号

时间:2024-02-29 10:44:59浏览次数:18  
标签:函数 int SIGINT 间通信 信号 进程 include

信号

信号(signal)机制是Linux系统中最为古老的进程之间的通信机制。Linux信号也可以称为软中断,是在软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,进程不需要通过任何操作等待信号到达。

信号来源

  • 硬件来源:硬件信号触发,硬件异常,如除零运算,内存非法访问等。
  • 软件来源:系统函数 kill() raise() alarm() 和 setitimer() 等函数,ctl+c 发出 SIGINT 等

信号分类

Linux系统中定义了一系列的信号,总共64种,分为非实时信号和实时信号。

  • 非实时信号:编号范围是从 1 到 31。这些标准信号是由操作系统内核发出,用于通知进程发生了某种事件或错误。
  • 实时信号:编号范围是从 32 到 64。有如下特点:
    • 实时信号可以排队传递,当多个相同类型的实时信号同时到达时,系统会将其排队传递给进程,直到进程处理完所有实时信号。
    • 实时信号的优先级高于标准信号,进程在接收实时信号时会暂时把标准信号放入挂起状态。
    • 实时信号支持使用 sigqueue() 函数向目标进程发送带有额外数据的信号。

可以使用 kill ‐l 命令查看所有的信号。

前31种信号含义如下:

  • SIGHUP(1):当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作是终止进程。
  • SIGINT(2):当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作是终止进程。
  • SIGQUIT(3):当用户按下<Ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作是终止进程。
  • SIGILL(4):非法指令信号,当一个进程执行了一条非法的、不支持的或者无效的指令时,操作系统会向该进程发送SIGILL信号。默认动作是终止进程并产生core文件。
  • SIGTRAP(5):该信号由断点指令或其他trap指令产生,当一个进程执行了一个与调试器相关的操作(打断点)时,操作系统会向该进程发送SIGTRAP信号。默认动作为终止里程并产生core文件。
  • SIGABRT(6):调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
  • SIGBUS(7):非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
  • SIGFPE(8):在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认 动作为终止进程并产生core文件。 
  • SIGKILL(9):无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。 
  • SIGUSE1(10):用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。 
  • SIGSEGV(11):指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
  • SIGUSR2(12):这是另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
  • SIGPIPE(13): 管道破裂信号,向一个没有读端的管道写数据。默认动作为终止进程。
  • SIGALRM(14):定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
  • SIGTERM(15):程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来告示程序正常退出。执行 kill 命令时,缺省产生这个信号。默认动作为终止进程。
  • SIGSTKFLT(16):段错误信号,当一个进程访问了无效的内存地址或者试图对不可写的内存进行写操作时,会产生该信号。默认动作为终止进程并产生core文件。
  • SIGCHLD(17):子进程状态(终止、暂停、继续)发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。
  • SIGCONT(18):用于通知一个被停止(暂停)的进程继续执行。
  • SIGSTOP(19):用于暂停(停止)一个正在执行的进程,本信号不能被忽略,处理和阻塞。
  • SIGTSTP(20):当用户按下<Ctrl+Z>组合键时产生该信号,用于将一个进程暂停(停止)执行并放入后台。
  • SIGTTIN(21):当一个进程在后台运行,并尝试从终端读取输入时,如果该终端处于非控制状态(即不是当前正在与之交互的终端),那么该进程将会被操作系统发送SIGTTIN信号,以提示它正在尝试从非控制终端读取输入。默认动作为暂停进程。
  • SIGTTOU(22):类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
  • SIGURG(23):套接字上有紧急数据时,向当前正在运行的进程发送该信号,报告有紧急数据到达。默认动作为忽略该信号。
  • SIGXCPU(24):表示进程已经超过了它被允许的CPU时间限制。当一个进程超过了被操作系统设定的CPU时间限制时,操作系统会向该进程发送SIGXCPU信号。这通常发生在进程占用了太多的CPU时间或者执行了一个长时间运行的计算任务。默认动作为终止进程。
  • SIGXFSZ(25):超过文件的最大长度设置。默认动作为终止进程。
  • SIGVTALRM(26):按照进程在用户态占用的CPU时间定时,默认动作为终止进程。
  • SIGPROF(27):按照进程在用户态和内核态占用的CPU时间定时,默认动作为终止进程。
  • SIGWINCH(28):表示窗口尺寸已更改,当用户调整终端窗口的大小时,会触发此信号。默认动作为忽略该信号。
  • SIGIO(29):此信号向进程指示发出了一个异步IO事件。默认动作为忽略。
  • SIGPWR(30):关机。默认动作为终止进程。
  • SIGSYS(31):无效的系统调用。默认动作为终止进程并产生core文件。

信号传递和响应

在 Linux 中,信号的传递流程通常遵循以下步骤:

  1. 信号产生:信号可以由内核、操作系统或者应用程序生成。常见的信号产生方式包括用户在终端按下键盘快捷键产生的信号,以及系统调用执行过程中出现的异常情况。
  2. 信号传递:一旦信号被产生,内核会把信号传递给目标进程。目标进程可以是正在等待信号的进程,或者正在执行的进程。如果信号没有被阻塞并且未被忽略,内核将会将信号传递给进程。
  3. 信号处理:一旦进程接收到信号,内核会检查信号的处理方式。根据注册的信号处理方式,可以有以下几种情况:
  • 忽略信号:即对信号不做任何处理,但是两个信号不能忽略: SIGKILL 以及 SIGSTOP 。
  • 捕捉信号:当信号发生时,执行用户定义的信号处理函数。
  • 执行默认操作:Linux对每种信号都规定了默认操作,man 7 signal查看。 
  1. 信号处理过程:如果信号的处理方式是执行自定义的信号处理函数,进程将会调用该函数来处理信号。处理函数可以完成一系列操作,比如修改全局变量、执行清理操作等。在信号处理函数执行期间,进程可能会被阻塞在信号处理函数中。
  2. 信号返回:一旦信号处理函数执行完毕,程序将会从信号处理函数中返回,程序恢复执行之前的代码。

注:信号传递不支持排队,当在很短时间内传递多个相同信号,该信号只会被处理一次。

在 Linux 中,信号的传递流程通常是由内核负责进行管理和调度,确保信号被准确传递给目标进程,并按照指定的处理方式进行处理。对于开发人员来说,可以通过信号处理函数来实现对信号的处理和响应,以应对不同的信号产生场景。

sigset_t类型

信号集是信号的集合,用sigset_t类型描述。每一种信号用1bit来表示,前面我们提到信号有64种,那么这个sigset_t类型至少占64bit。当有信号传递到该进程的时候,未决信号集中的对应位设置为1,其他位保持不变。之后,信号传递到信号屏蔽字,信号屏蔽字中为1的信号,将被阻塞,其他为0且未决的信号,将交由进程的信号处理函数负责处理。

通过维护未决信号集和信号屏蔽字,进程能够控制接收到的信号以及在处理信号时需要被阻塞的信号,确保信号能够按照预期方式处理,同时避免因处理信号而引起的竞争条件或意外中断问题。

sigset_t 数据类型提供了一些函数来操作信号集合:

#include <signal.h>

//将set每一位都置0
int sigemptyset(sigset_t *set) 

//将set每一位都置1
int sigfillset(sigset_t *set) 

//将set中signo信号对应的bit置1
int sigaddset(sigset_t *set, int signo) 

//将set中signo信号对应的bit置0
int sigdelset(sigset_t *set, int signo) 

//判断set中signo信号对应bit是否为1,返回1或者0。
int sigismember(const sigset_t *set, int signo) 

未决信号集

未决信号集是指进程当前已经接收到但尚未被处理的信号的集合。当进程接收到一个信号时,该信号会被添加到进程的未决信号集中,直到进程处理完该信号或将其阻塞。

可以通过系统调用sigpending()函数来获取当前进程的未决信号集,该函数定义如下:

#include <signal.h>

int sigpending(sigset_t *set);

参数说明

  • set:要安装的信号值。 

返回值

调用成功则返回0,出错则返回‐1。

信号屏蔽字

信号屏蔽字是一个掩码,用于指定在信号处理期间需要被阻塞的信号集合。当某个信号被加入到信号屏蔽字中时,该信号在信号处理期间将被阻塞,即不会被传递给进程。

可以通过系统调用sigprocmask()函数来查看或更改进程的信号屏蔽字。该函数定义如下:

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数说明

  • how:指定了函数的操作,有如下取值:
    • SIG_BLOCK:将 set 中指定的信号添加到当前信号屏蔽字中。相当于 mask | set
    • SIG_UNBLOCK:将 set 中指定的信号从当前信号屏蔽字中移除。相当于 mask & ~set
    • SIG_SETMASK:将当前信号屏蔽字设置为 set 中指定的值。相当于 mask = set
  • set:指定要修改的信号集合。
  • oldset:用于获取之前的信号屏蔽字。

注:SIGSTOP(19)和 SIGKILL(9)不能被忽略。

返回值

调用成功则返回0,出错则返回‐1。

注:信号屏蔽并不是将未决信号丢弃,而是阻塞该信号。

信号捕获-signal()函数

signal()函数常用于设置信号处理程序,以便进程在接收到特定的信号时执行自定义的处理逻辑。可以通过指定不同的信号和处理函数来实现对各种信号的处理,比如捕获SIGINT信号来处理终端中断信号,或者捕获SIGSEGV信号来处理段错误。该函数定义如下:

#include <signal.h>

void (*signal(int signum, void (*handler)(int)));

参数说明

  • signum:要安装的信号值。 
  • handler:信号对应的信号处理函数。

signal()函数主要用于前32种非实时信号的安装。

信号捕获-sigaction()函数

sigaction()函数和signal()函数相似,相比于前者,sigaction()函数提供了更为灵活和可靠的信号处理方式。该函数定义如下:

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, 
            struct sigaction *oldact);
            
struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};

参数说明

  • signum:要安装的信号值。 
  • act:指向 struct sigaction 类型的结构体指针,用于指定新的信号处理方式。
  • oldact:指向 struct sigaction 类型的结构体指针,用于存储之前的信号处理方式。

返回值

如果函数执行成功,返回值为0。如果函数执行失败,返回值为-1,并且可以通过 errno 变量来获取具体的错误信息。

struct sigaction结构体

  • sa_handler:早期的信号捕捉函数。
  • sa_sigaction:新添加的捕捉函数,支持传参, 和sa_handler互斥,两者通过 sa_flags 标识来决定采用哪种捕捉函数。
  • sa_mask: 在执行捕捉函数期间,屏蔽指定信号,当退出捕捉函数后,还原回原有的阻塞信号集。
  • sa_flags:设置信号处理的标志。
    • SA_RESTART:如果设置了这个标志,则系统调用被信号中断后会自动重启,而不是返回-1并设置 errno 为 EINTR 。
    • SA_SIGINFO:指定使用带有附加信息的信号处理程序,即使用sa_sigaction字段指定的函数。
    • SA_NOCLDSTOP:子进程暂停或继续运行时不会产生SIGCHLD信号。
    • SA_NODEFER:在信号处理程序执行期间不屏蔽该信号。
  • sa_restorer:无含义的保留字段。

SA_RESTART标志

SA_RESTART标志可以重启被打断的系统调用,例如,在之前介绍read()函数时,该函数被信号打断时,会设置 errno 为EINTR。下面程序模拟被打断的情况:

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<signal.h>
 4 #include<errno.h>
 5 
 6 void signalHandler(int sig)
 7 {
 8     printf("recv signal : %d\n", sig);
 9 }
10 
11 int main(int argc, char** argv)
12 {
13     struct sigaction sigact;
14     //sigact.sa_flags = SA_RESTART;
15     sigact.sa_handler = signalHandler;
16     sigemptyset(&sigact.sa_mask);
17 
18     sigaction(SIGINT, &sigact, NULL);
19 
20     
21     char buf[32];
22     int nRet = read(STDIN_FILENO, buf, sizeof(buf));
23     if(nRet == -1)
24     {
25         if(errno == EINTR)
26             printf("read interrupted!\n");
27     }
28 
29     return 0;
30 }

输出:

^Crecv signal : 2        //ctrl + c 发送 SIGINT 信号
read interrupted!

为了防止被信号中断,可以封装read函数,之前已经介绍过。也可以使用 SA_RESTART 标志重启中断,例如:

sigact.sa_flags = SA_RESTART;

增加下面代码后,输出如下:

^Crecv signal : 2
                        //等待输入

等待信号-pause() 函数

pause() 函数的作用是使当前进程进入睡眠状态,直到接收到一个信号为止。该函数定义如下:

#include <unistd.h>

int pause(void);

返回值

当进程收到一个信号,pause() 函数会返回 -1,并将 errno 设置为 EINTR。

用法:

 1 #include<stdio.h>
 2 #include<signal.h>
 3 #include<unistd.h>
 4 #include<errno.h>
 5 
 6 void sigHandler(int sig)
 7 {
 8     printf("sigHandler\n");
 9 }
10 
11 int main(int argc, char** argv)
12 {
13     signal(SIGINT, sigHandler);
14 
15     printf("Begin Pause!\n");
16     int nRet = pause();
17     if(nRet == -1 && errno == EINTR)
18             printf("Recv Signal, End Pause!\n");
19 
20     while(1);
21 
22     return 0;
23 }

输出:

Begin Pause!
^CsigHandler                //发送 SIGINT 信号
Recv Signal, End Pause!

示例1

下面示例展示了再信号处理函数执行期间,屏蔽其他信号:

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<stdlib.h>
 4 #include<signal.h>
 5 
 6 void sigHandler(int sig)
 7 {
 8     if(sig == SIGHUP)
 9     {
10         printf("Handle SIGHUP \n");
11         exit(0);
12     }
13     else if(sig == SIGINT)
14     {
15         sigset_t set, oldSet;
16         sigemptyset(&set);
17         sigemptyset(&oldSet);
18         sigaddset(&set, SIGHUP);
19 
20         sigprocmask(SIG_BLOCK, &set, &oldSet);
21         
22         for(int i = 0; i < 10; i++)
23         {
24             printf("Handle SIGINT i = %d\n", i);
25             sleep(1);
26         }
27 
28         sigprocmask(SIG_UNBLOCK, &oldSet, NULL);
29     }
30 }
31 
32 int main(int argc, char** argv)
33 {
34     printf("pid = %d\n", getpid());
35     signal(SIGINT, sigHandler);
36     signal(SIGHUP, sigHandler);
37     while(1);
38     return 0;
39 }

编译启动程序后,使用 Ctrl + c 发送 SIGINT 信号:

pid = 10531
^CHandle SIGINT i = 0
Handle SIGINT i = 1
Handle SIGINT i = 2
Handle SIGINT i = 3

如果此时在另一个终端发送 kill 命令:

 kill -1 10531

最终输出如下:

pid = 10531
^CHandle SIGINT i = 0
Handle SIGINT i = 1
Handle SIGINT i = 2
Handle SIGINT i = 3
...
Handle SIGINT i = 8
Handle SIGINT i = 9
Handle SIGHUP 

如果屏蔽代码中的两个 sigprocmask() 函数调用,发送 kill 命令:

pid = 10581
^CHandle SIGINT i = 0
Handle SIGINT i = 1
Handle SIGINT i = 2
Handle SIGHUP 

示例2

sigaction函数简单用法。

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<stdlib.h>
 4 #include<signal.h>
 5 
 6 void sigHandler(int sig)
 7 {
 8     printf("Handle signal %d\n", sig);
 9     sleep(10);
10     printf("Handle finish %d\n", sig);
11 }
12 
13 int main(int argc, char** argv)
14 {
15     struct sigaction sigact;
16     sigact.sa_flags = 0;
17     sigact.sa_handler = sigHandler;
18     sigemptyset(&sigact.sa_mask);
19     //sigaddset(&sigact.sa_mask, SIGQUIT);
20     sigaction(SIGINT, &sigact, NULL);
21     while(1);
22     return 0;
23 }

输出:

^CHandle signal 2        //ctrl+c发送 SIGINT 信号
^C^C^C^C^C^C             //ctrl+c发送多个 SIGINT 信号
Handle finish 2
Handle signal 2
Handle finish 2          //最终仅处理一次,证明信号不会排队

如果在 SIGINT 信号处理期间,发送 SIGQUIT 信号,运行结果如下:

^CHandle signal 2
^\退出 (核心已转储)

如果在处理期间不想被其他信号打扰,可以向 sigact.sa_mask 中添加屏蔽信号。

发送信号的函数

并不是每个进程都可以向其他的进程发送信号,通常进程只能向具有相同 uid 和 gid 的进程发送信号,或向相同进程组中的其他进程发送信号。

kill()函数

kill()函数用于向指定进程发送信号,信号可以是预定义的系统信号,也可以是用户自定义的信号。该函数定义如下:

#include <signal.h>

int kill(pid_t pid, int sig);

参数说明

  • pid:目标进程的进程标识符。有下列取值:
    • 正整数:表示具体的进程PID。
    • 负整数:表示发送信号给进程组ID等于该值的所有进程。
    • 0:表示发送信号给调用进程同一进程组的所有进程;
    • -1:表示给除了自身以为所有进程发送信号。
  • sig:要发送的信号值。

返回值

如果函数执行成功,返回值为0。如果函数执行失败,返回值为-1,并且可以通过 errno 变量来获取具体的错误信息。

常见错误如下:

  • EINVAL:传递的信号编号无效。
  • ESRCH:目标进程不存在或进程已经终止,处于僵尸状态。
  • EPERM:没有向目标进程发送信号的权利。

例如:

 1 #include<stdio.h>
 2 #include<signal.h>
 3 #include<errno.h>
 4 #include<string.h>
 5 #include<stdlib.h>
 6 
 7 int main(int argc, char** argv)
 8 {
 9     if(argc != 3)
10     {
11         printf("Usage: %s [signal] [pid]\n", argv[0]);
12         return -1;
13     }
14 
15     int nRet = kill(atoi(argv[2]), atoi(argv[1]));
16     if(nRet == -1)
17     {
18         if(errno == EINVAL)
19             printf("pid error!\n");
20         else if(errno == EPERM)
21             printf("permission error!\n");
22         else if(errno == ESRCH)
23             printf("target process does not exist!\n");
24 
25         return -1;
26     }
27 
28     return 0;
29 }

输出:

./a.out 9 11111111       //发送不存在的pid
target process does not exist!

./a.out 666 10967        //发送不存在的信号
pid error

./a.out 9 10967          //向root进程发送 SIGKILL 信号
permission error!

raise()函数

raise()函数用于向当前进程发送信号,该函数定义如下:

#include <signal.h>

int raise(int sig);

参数说明

  • sig:要发送的信号值。

返回值

如果函数执行成功,返回值为0。如果函数执行失败,则返回一个非零值。

alarm()函数

alarm()函数用于设置一个定时器,在指定的时间间隔后发送一个 SIGALRM 信号给调用进程。默认情况下,当定时器到达设定的时间时,进程会终止。我们可以通过捕捉 SIGALRM 信号并自定义处理函数,来实现在定时器到达时执行特定的操作,而非终止进程。该函数定义如下:

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

参数说明

  • seconds:定时器的时间,单位为秒。在指定的 seconds 秒后,将给自己发送一个SIGALRM 信号。当参数 seconds 为0时,将清除当前进程的 alarm 设置。

返回值

调用alarm()函数时,如果进程已经有一个未结束的 alarm,那么旧的 alarm 将被删除,并返回旧的 alarm 的剩余时间。否则 alarm() 函数返回0。

注:

  • alarm()信号不会周期响应,在产生一次信号后,需要重新调用 alarm() 函数创建定时器。
  • 定时器有且只能有一个,多次设置会覆盖之前的定时设置。

用法:

 1 #include<stdio.h>
 2 #include<signal.h>
 3 #include<unistd.h>
 4 
 5 void sigHandler(int sig)
 6 {
 7     static int i = 0;
 8     printf("alarm signal, i = %d\n", i++);
 9     alarm(1);    //需要重新激活
10 }
11 
12 int main(int argc, char** argv)
13 {
14     signal(SIGALRM, sigHandler);
15     alarm(1);
16 
17     while(1);
18     return 0;
19 }

输出:

alarm signal, i = 0
alarm signal, i = 1
alarm signal, i = 2
...

setitimer()函数

setitimer()函数也是用于设置定时器的函数,该函数可以实现更为精确的定时器控制,包括指定定时器的精度、间隔和触发信号。该函数定义如下:

#include <sys/time.h>

int setitimer(int which, const struct itimerval *new_value,
                 struct itimerval *old_value);
       
struct itimerval {
     struct timeval it_interval;     /*每隔多少秒发送一次信号 */
     struct timeval it_value;        /* 第一次定时时间 */
 };

 struct timeval {
     time_t      tv_sec;         //seconds
     suseconds_t tv_usec;        //microseconds
};

参数说明

  • which:指定定时器类型。有如下取值:
    • ITIMER_REAL:以实际时间计算定时器的触发,触发 SIGALRM 信号。
    • ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,触发 SIGVTALRM 信号。
    • ITIMER_PROF :以该进程在用户态下和内核态下所费的时间来计算,触发 SIGPROF 信号。
  • new_value:一个 struct itimerval 结构体指针,用于设置定时器的第一次触发时间和之后每次触发的间隔时间。
  • old_value:一个 struct itimerval 结构体指针,用于获取之前设置的定时器的值,如果不需要获取,则可以传递 NULL。

返回值

如果函数执行成功,返回值为0。如果函数执行失败,返回值为-1,并且可以通过 errno 变量来获取具体的错误信息。

示例:

 1 #include<stdio.h>
 2 #include<signal.h>
 3 #include<unistd.h>
 4 #include<sys/time.h>
 5 #include<string.h>
 6 
 7 void sigHandler(int sig)
 8 {
 9     static int i = 0;
10     printf("alarm signal, i = %d\n", i++);
11 }
12 
13 int main(int argc, char** argv)
14 {
15     signal(SIGALRM, sigHandler);
16 
17     struct itimerval timerval;
18     memset(&timerval, 0, sizeof(timerval));
19     timerval.it_interval.tv_usec = 500 * 1000;    //500毫秒
20     timerval.it_value.tv_sec = 2;
21 
22     int nRet = setitimer(ITIMER_REAL, &timerval, NULL);
23     if(nRet == -1)
24     {
25         perror("setitimer");
26         return -1;
27     }
28 
29     while(1);
30     return 0;
31 }

abort()函数

abort()函数用于终止当前进程执行,当调用 abort() 函数时,会向当前进程发送 SIGABRT 信号,进程会异常终止。该函数定义如下:

#include <stdlib.h>

void abort(void);

注意事项:

  • abort() 函数会直接导致进程异常终止,不会执行任何退出处理程序,包括 atexit() 注册的函数。
  • abort() 函数退出时,会执行清理操作,包括刷新流缓冲区(调用 fflush(NULL) 函数)、关闭流(调用 fclose() 函数)等。
  • 在调用 abort() 函数之前,建议在标准错误流中输出相应的错误信息,以便识别程序终止的原因。
  • 当进程接收到 SIGABRT 信号时,通常会执行默认的信号处理函数,该函数会在标准错误流中打印信息,之后生成一个核心转储文件(core dump)。

可重入函数与不可重入函数

可重入函数:指的是一个函数可以被多个任务同时调用而不会产生错误或不确定的行为。可重入函数通常具有以下特点:

  • 不使用全局变量、静态变量或其他共享资源。
  • 不会修改传入的参数。
  • 不会调用不可重入的函数。
  • 不会使用不可重入的系统调用。

由于可重入函数的设计可以让多个任务同时安全地调用它,因此在并发环境中通常更可靠更安全。

不可重入函数:指的是一个函数在多个任务同时调用时可能会产生错误或不确定的结果。不可重入函数通常具有以下特点:

  • 使用全局变量、静态变量或其他共享资源。
  • 会修改传入的参数。
  • 会调用不可重入的函数。
  • 可能使用不可重入的系统调用。

不可重入函数在多任务环境中可能会导致线程安全问题,在并发编程中,尽量避免使用不可重入函数,以确保程序的可靠性和健壮性。

示例:

strtok() 就是一个不可重入函数,因为strtok内部维护了一个内部静态指针,保存上一次切割到的位置,如果信号的捕捉函数中也去调用strtok函数,则会造成切割字符串混乱。

 1 #include<stdio.h>
 2 #include<signal.h>
 3 #include<string.h>
 4 #include<unistd.h>
 5 
 6 static char pBuf1[] = "1 2 3 4 5";
 7 static char pBuf2[] = "a b c d e";
 8 
 9 void sigHandler(int sig)
10 {
11     strtok(pBuf2, " ");
12 }
13 
14 int main(int argc, char** argv)
15 {
16     signal(SIGINT, sigHandler);
17 
18     char* pStr = strtok(pBuf1, " ");
19     while(pStr)
20     {
21         printf("%s\n", pStr);
22         sleep(1);
23         pStr = strtok(NULL, " ");
24     }
25 
26     return 0;
27 }

输出:

1
2
3
^Cb
c
d
e

应该使用 strtok_r 版本,r表示可重入,将所有 strtok() 函数替换为 strtok_r() 函数,上面问题即可解决。系统提供的不可重入函数,都会提供对应的可重入版本,在对应的 man 帮助中,一般是在末尾加上 _t。

信号方式回收子进程PCB

系统提供了名为 SIGCHLD 的信号,可以用来回收子进程。该信号产生条件如下:

  • 子进程终止时。
  • 子进程接收到 SIGSTOP 信号被挂起时。
  • 子进程处在停止态,接受到 SIGCONT 信号后被唤醒时。

使用方法如下:

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

void signalHandler(int sig)
{
    int status;
    pid_t pid = waitpid(-1, &status, WUNTRACED | WCONTINUED);
    printf("recv child process pid = %d\n", pid);

    if(WIFEXITED(status))
        printf("child process exited with %d\n", WEXITSTATUS(status));
    else if(WIFSIGNALED(status))
        printf("child process signaled with %d\n", WTERMSIG(status));
    else if(WIFSTOPPED(status))
        printf("child process stoped\n");
    else if(WIFCONTINUED(status))
        printf("child process continued\n");
}

int main(int argc, char** argv)
{
    signal(SIGCHLD, signalHandler);      //订阅 SIGCHLD 信号

    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork");
        return -1;
    }
    else if(pid == 0)
    {
        printf("child process pid = %d\n", getpid());
        sleep(5);
        exit(10);
    }
    else
    {
        printf("parent process pid = %d\n", getpid());
        while(1);
    }
    
    return 0;
}

标签:函数,int,SIGINT,间通信,信号,进程,include
From: https://www.cnblogs.com/BroccoliFighter/p/18042922

相关文章

  • windows系统下 nssm 注册jar为windows服务并且守护进程
    1、下载nssmhttp://nssm.cc/download下载nssm2.24(2014-08-31)即可解压放在E:\nssm-2.242、新建启动jar脚本的bat文件D:\code2\1.bat内容是java-jarD:\code2\app.jar>log.log2>&1&\3、用nssm命令挂服务cdE:\nssm-2.24\win64nssminstallceshi"D:\code2\1.b......
  • 突然新增进程
    在运行代码时top显示突然多了很多pid连续的新进程而我并没有显示的创建他们。最后找到问题:因为我使用了GridSearchCV但未设置n_jobs参数,因此GridSearchCV内部并行处理产生了大量新进程来处理不同的参数组合。n_jobs取值范围:**-1**:表示使用所有可用的CPU核心,即最大并......
  • FastAPI系列:后台任务进程
    注:后台任务应附加到响应中,并且仅在发送响应后运行用于将单个后台任务添加到响应中fromfastapiimportFastAPIfromfastapi.responsesimportJSONResponsefromstarlette.backgroundimportBackgroundTaskfrompydanticimportBaseModelapp=FastAPI()classUser(B......
  • (8)宽带信号的空间谱估计算法
    空间谱估计理论与算法(8)宽带信号的空间谱估计1引言目前,对宽带信号处理算法的研究主要分为两类:1基于不相干信号的处理方法(ISM)。这类算法处理的主要思想是将宽带数据分解到不重叠频带上的窄带数据,然后对每个频带进行窄带信号子空间处理,从而获得初始角度的估计;再通过对这些初始估......
  • linux进程操作
    linux进程操作查看用户进程top//查看系统中实时的进程信息htop//top的增强版,可以交互的方式显示系统中的进程信息ps //列出当前用户的所有进程信息pstree //以树形结构显示当前用户的而所有进程信息pidof //可以查找指定进程名的进程IDhtop安装:sudoapt-getinstallh......
  • Session 0 是一个特殊的会话(Session),用于运行系统级别的服务和进程,而不是用户交互式会
    在Windows操作系统中,Session0是一个特殊的会话(Session),用于运行系统级别的服务和进程,而不是用户交互式会话。在WindowsVista及更高版本的操作系统中,引入了“会话0隔离”(Session0Isolation)的概念,即将服务与用户会话分离开来,以提高系统的安全性和稳定性。Session0中运行的......
  • 基于FPGA的ECG信号滤波与心率计算verilog实现,包含testbench
    1.算法运行效果图预览 其RTL结构如下:  2.算法运行软件版本vivado2019.2  3.算法理论概述        心电图(ECG)是医学领域中常用的一种无创检测技术,用于记录和分析心脏的电活动。由于ECG信号微弱且易受到噪声干扰,因此在采集和处理过程中需要进行滤波以提取......
  • Qt 无法连接到进程内QML调试器
    问题:由于在Qt5以上版本默认开启QML调试器造成的。用于告知用户,这将打开一个到运行QML的Javascript解释器的端口,以便从中获得调试输出。显然,这会造成一个安全漏洞,所以在不安全的地方使用时应该关闭它(在释放运行时自动关闭)。Qt4默认不开启QML调试器,而Qt5版本以上默认开启。......
  • 进程与线程
    什么是线程?​ 线程(thread)是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。​ 说实话这个答案并不能打破我对线程陌生的认知,举个例子,假设你经营着一家物业管理公司。最初,业务量很小,事事都需要你亲力亲为。给老张家修完暖气管道,立马再去老李家换......
  • taskkill杀掉Windows上进程
    命令:taskkill/F/IMTLSvpn2.exe/T当出现以下问题,可能是需要管理员权限D:\playwright\tests>taskkill/F/IMTLSvpn2.exe/T成功:已终止PID29812(属于PID11384子进程)的进程。错误:无法终止PID32500(属于PID11384子进程)的进程。原因:拒绝访问。错误:......