赞
踩
在Linux系统中,信号是进程之间通信的重要方式之一。前面的两篇文章已经介绍了信号的产生和保存,本篇文章将进一步探讨信号的捕捉、处理以及使用sigaction()函数的方法。信号捕捉是指进程在接收到信号时采取的行动,而信号处理则是指对接收到的信号进行适当的处理逻辑。通过使用sigaction()函数,我们可以在程序中设置对特定信号的处理方式,从而实现更加灵活和精确的信号处理机制。本文将详细介绍信号捕捉的原理和使用方法,以及sigaction()函数的具体用法,帮助读者更好地理解和应用信号处理的相关知识。无论是开发基于Linux的应用程序,还是进行系统级编程,信号处理都是一个至关重要的主题,相信通过学习本文,您将对信号处理有更深入的了解。
当信号的处理动作是用户自定义函数,并且在信号到达时调用该函数,这被称为捕捉信号。由于信号处理函数的代码运行在用户空间,处理过程可能会比较复杂,下面举一个例子来说明:
sighandler
来捕捉SIGINT
信号。main
函数时,若发生中断或异常导致切换到内核态。main
函数之前,检测到有SIGINT
信号递达。main
函数的上下文继续执行,而是调用sighandler
函数。sighandler
函数和main
函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。sighandler
函数执行完毕后,会自动执行特殊的系统调用sigreturn
,再次进入内核态。main
函数的上下文,并继续执行。sigaction()
函数是一个用于设置信号处理函数的系统调用。它允许用户程序指定对特定信号的处理方式,包括捕捉信号、忽略信号或使用默认处理方式。
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()
函数返回值为0表示操作成功,-1表示出现了错误。如果发生错误,可以通过errno
变量获取错误码。常见的错误码包括:
使用sigaction()
函数进行信号处理的一般步骤如下:
struct sigaction
结构体对象,并根据需要设置其中的成员,特别是sa_handler
或sa_sigaction
成员来指定信号处理函数。sigaction()
函数,传入要设置处理方式的信号编号、指向上述结构体对象的指针以及可选的保存之前处理方式的结构体指针。sigaction()
函数的返回值判断操作是否成功。下面是一个简单的C语言示例,演示如何使用sigaction()
函数来捕获和处理SIGINT
信号(即Ctrl + C
):
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> void sigint_handler(int signo) { printf("Caught SIGINT, exiting...\n"); exit(1); } int main() { struct sigaction sa; // 设置信号处理函数为sigint_handler sa.sa_handler = sigint_handler; // 清空sa_mask,即不阻塞任何其他信号 sigemptyset(&sa.sa_mask); // 设置一些标志位,这里使用默认值0 sa.sa_flags = 0; // 注册对SIGINT信号的处理方式 if (sigaction(SIGINT, &sa, NULL) == -1) { perror("sigaction"); return 1; } printf("Press Ctrl+C to send a SIGINT...\n"); // 进入一个无限循环,等待信号 while (1) { sleep(1); } return 0; }
在这个示例中,首先定义了一个名为sigint_handler的函数,用于处理SIGINT信号。然后在main函数中,创建了一个struct sigaction对象sa,并设置了其中的成员,包括sa_handler指向sigint_handler函数地址,sa_mask为空,sa_flags为0。接着调用sigaction()函数注册对SIGINT信号的处理方式。最后进入一个无限循环,等待信号的到来。
当用户按下Ctrl+C时,会发送SIGINT信号,程序会捕获该信号并调用sigint_handler函数进行处理,打印一条消息并退出程序。这样就实现了对SIGINT信号的自定义处理。
⭕main
函数调用insert
函数向一个链表head
中插入节点node1
,插入操作分为两步,刚做完第一步的时候因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler
函数。sighandler
也调用insert
函数向同一个链表head
中插入节点node2
,插入操作的两步都做完之后从sighandler
返回内核态,再次回到用户态就从main
函数调用的insert
函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是 main
函数和sighandler
先后向链表中插入两个节点,而最后只有一个节点真正插入链表中了。
⭕像上例这样,insert
函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入。insert
函数访问一个全局链表有可能因为重入而造成错乱。像这样的函数称为不可重入函数,反之如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。想一下,为什么两个不同的控制流程调用同一个函数,访问它的同一个局部变量或参数就不会造成错乱?
✅可重入函数必须满足以下条件:
不使用全局变量或静态变量,或者只读取这些变量的值。
不修改非本地的内存区域,或者仅修改线程本地的内存区域。
不调用可能导致线程挂起或阻塞的函数,如sleep()
和wait()
等。
一些示例可重入函数包括:memcpy()
、strlen()
、sprintf()
、strtok_r()
等。
本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/959510
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。