Linux——信号的补充
文章目录
目录
前言
一、可重写函数
这张图就是我们单链表的头插;
我们知道单链表在头插的时候,是先new一个p(图中的node1和node2)节点,然后然让node节点的next指向头节点,然后头节点指向node;
那么如果我们在头插入节点时,我们刚让节点p的next指向head然后就接收到了信号,然后信号信号中再插入一个新的节点2,那么我们跑完handler函数以后就是图3的效果,然后在返回就是图4的效果;
这种情况就是node2节点丢失,没有指针指向它,就会出现内存泄露;这种情况就是不可重写;
main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因 为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函 数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从 sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步 之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只 有一个节点真正插入链表中了。
像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称 为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之, 如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。想一下,为什么两个不同的 控制流程调用同一个函数,访问它的同一个局部变量或参数就不会造成错乱?
如果一个函数符合以下条件之一则是不可重入的:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
二、volatile函数
- 在 C 语言中,
volatile
是一个类型修饰符。它的主要作用是告诉编译器,被volatile
修饰的变量是易变的,其值可能会在程序的控制流之外被改变。例如,一个变量可能被硬件(如中断服务例程)或者其他并发运行的线程修改。 - 编译器在进行优化时,通常会对代码进行一些假设。如果没有
volatile
关键字,编译器可能会认为一个变量在某个代码段中不会改变,从而对涉及该变量的代码进行优化。但是当变量的值可能被外部因素改变时,这种优化可能会导致错误的结果。
也就是说,被volatile函数修饰的变量不会被代码块外部的改变而改变;
代码
#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
int x=0;
void handler(int signo)
{
x+=2;
}
int main()
{
signal(2,handler);
while(!x)
{
cout<<getpid()<<" x的值没有改变"<<endl;
sleep(2);
}
cout<<"x的值被修改为"<<x<<endl;
return 0;
}
代码
#include <stdio.h>
#include <signal.h>
int flag = 0;
void handler(int sig)
{
printf("chage flag 0 to 1\n");
flag = 1;
}
int main()
{
signal(2, handler);
while(!flag);
printf("process quit normal\n");
return 0;
}
我们将这个代码分别用gcc $@ $^和gcc $@ $^ -o3编译
我们会发现,没有加选项的发送型号会将flag的值改变,然后退出,而加了-o3选项的不会改变flag,从而死循环;
为了改变这个情况我们只需将flag用volatile修饰;
三、 SIGCHLD信号
1、概念
当一个子进程终止时,内核会向其父进程发送 SIGCHLD
信号,以便父进程可以采取相应的操作,例如回收子进程的资源(如释放内存、关闭文件描述符等)。
2、信号处理函数
- 为了处理
SIGCHLD
信号,父进程可以设置一个信号处理函数。在 C 语言中,使用signal
或sigaction
函数来设置信号处理函数。以下是使用signal
函数的示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
// SIGCHLD 信号处理函数
void sigchld_handler(int signum) {
if (signum == SIGCHLD) {
// 等待子进程的状态信息
int status;
pid_t pid;
// 等待任意一个子进程,并获取其 pid 和状态信息
pid = waitpid(-1, &status, WNOHANG);
if (pid > 0) {
// 子进程正常终止
if (WIFEXITED(status)) {
printf("Child %d terminated normally with exit status %d\n", pid, WEXITSTATUS(status));
}
// 子进程异常终止
else if (WIFSIGNALED(status)) {
printf("Child %d terminated by signal %d\n", pid, WTERMSIG(status));
}
}
}
}
int main() {
// 注册 SIGCHLD 信号处理函数
signal(SIGCHLD, sigchld_handler);
// 创建子进程
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("Child process: %d\n", getpid());
// 子进程执行一些操作
sleep(2);
exit(0);
} else if (pid > 0) {
// 父进程
printf("Parent process: %d\n", getpid());
// 父进程继续执行其他操作
while (1) {
sleep(1);
}
} else {
perror("fork");
exit(1);
}
return 0;
}
在这个示例中:
signal(SIGCHLD, sigchld_handler);
注册了SIGCHLD
的信号处理函数sigchld_handler
。- 在
sigchld_handler
函数中,使用waitpid(-1, &status, WNOHANG)
来检查子进程的状态。waitpid
函数用于等待子进程状态的改变。-1
表示等待任意一个子进程。&status
存储子进程的状态信息。WNOHANG
表示如果没有子进程状态改变,不阻塞父进程,立即返回。
3、使用 sigaction
函数:
- 更推荐使用
sigaction
函数来设置信号处理函数,因为它提供了更多的功能和更好的可移植性。以下是使用sigaction
的示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
// SIGCHLD 信号处理函数
void sigchld_handler(int signum) {
if (signum == SIGCHLD) {
int status;
pid_t pid;
// 等待任意一个子进程,并获取其 pid 和状态信息
pid = waitpid(-1, &status, WNOHANG);
if (pid > 0) {
if (WIFEXITED(status)) {
printf("Child %d terminated normally with exit status %d\n", pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child %d terminated by signal %d\n", pid, WTERMSIG(status));
}
}
}
}
int main() {
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
// 注册 SIGCHLD 信号处理函数
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
// 创建子进程
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("Child process: %d\n", getpid());
sleep(2);
exit(0);
} else if (pid > 0) {
// 父进程
printf("Parent process: %d\n", getpid());
while (1) {
sleep(1);
}
} else {
perror("fork");
exit(1);
}
return 0;
}
在这个示例中:
struct sigaction sa;
定义了一个sigaction
结构。sa.sa_handler = sigchld_handler;
设置信号处理函数。sigemptyset(&sa.sa_mask);
清空信号屏蔽字。sa.sa_flags = SA_RESTART;
设置信号处理的标志,这里SA_RESTART
表示某些系统调用在信号处理函数返回后可以自动重新启动。
总结
- 信号是 Linux 系统中一种强大的进程间通信和进程控制机制。
- 通过
kill
命令或kill
函数发送信号,通过signal
或sigaction
函数处理信号。 - 信号处理函数需要考虑可重入性和信号屏蔽字,避免使用不可重入函数。
- 信号可以是同步或异步的,根据不同的来源和情况进行处理。
原文地址:https://blog.csdn.net/2301_80687320/article/details/145193288
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!