购物分享网站流量排名,物流网络规划,成都企业网站建设那家好,大连做网站比较好的可重入函数 volatile关键字 以及SIGCHLD信号 一、可重入函数1、引入2、可重入函数的判断 二、volatile关键字1、引入2、关于编译器的优化的简单讨论 三、SIGCHLD信号 一、可重入函数
1、引入
我们来先看一个例子来帮助我们理解什么是可重入函数#xff1a;
假设我们现在要对… 可重入函数 volatile关键字 以及SIGCHLD信号 一、可重入函数1、引入2、可重入函数的判断 二、volatile关键字1、引入2、关于编译器的优化的简单讨论 三、SIGCHLD信号 一、可重入函数
1、引入
我们来先看一个例子来帮助我们理解什么是可重入函数
假设我们现在要对一个链表进行头插在执行到第10行代码时突然进程的时间片到了进程被切换了一会等进程再度切换回来时当前进程要处理信号而信号处理函数是sighandler而sighandler里面也进行了头插等进程从内核态返回到用户态时继续执行第11行的代码这时我们再观察链表的结构会发现链表中出现了节点丢失的问题而造成这种问题的根源是我们的insert函数同时被两个执行流给进入了。
node_t node1, node2, *head;
int main()
{...insert(node1);...
}void insert(node_t*p)
{p-next head;head p;
}void sighandler(int signo)
{insert(node2);
}由这个问题衍生出了一种函数分类的方式
如果一个函数同时被多个执行流进入所产生的结果没有问题该函数被称为可重入函数如果一个函数同时被多个执行流进入所产生的结果有问题该函数被称为不可重入函数 可重入函数主要用于多任务环境中一个可重入的函数通常来说就是可以被中断的函数也就是说可以在这个函数执行的任何时刻中断它转入OS调度下去执行另外一段代码而返回控制时不会出现什么错误不可重入的函数由于使用了一些系统资源比如全局变量区中断向量表等所以它如果被中断的话可能会出现问题这类函数是不能运行在多任务环境下的。 2、可重入函数的判断
如果一个函数符合以下条件之一则是不可重入的:
函数体内使用了静态static的数据结构或者变量调用了malloc或free,因为malloc也是用全局链表来管理堆的。调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
二、volatile关键字
1、引入
volatile是C语言的一个关键字该关键字的作用是保证内存数据的可见性。
我们来先来看一段代码这里我们不加入volatile关键字并开启编译器优化选项优化级别是-O2。
这段代码的意思是我们让进程一直运行直到我们给进程发送2号信号以后进程再退出。
#include stdio.h
#include unistd.h
#include signal.hint flag 0;void handler(int signo)
{printf(捕捉到了%d号信号\n, signo);// 将flag置为1flag 1;printf(已经将flag置为%d\n, flag);
}int main()
{signal(2, handler);printf(进程正在运行...\n);while (!flag); // 当flag 1时进程退出。printf(运行结束\n);return 0;
}运行结果 可以看到我们明明都已经让flag 1了但是进程中的循环依然没有结束这时为什么呢下面我们一起来分析这个过程: 代码中的main函数和handler函数在触发时是两个独立的执行流而while循环是在main函数当中的而且main执行流里面并没有使用过handler函数signal函数只是对2号信号进行了捕捉没有调用过handler函数所以在编译器编译时检测到在main函数中对flag变量没有做过修改操作而且由于while循环运行时需要频繁使用flag变量所以编译器可以将flag变量的值用一个寄存器进行保存以后每次使用flag变量直接去寄存器里面取数据不必每次都要将内存中的flag搬运到寄存器里面然后让CPU去计算。
可是不巧的是我们给当前进程发送了2号信号让另外一个执行流更改了内存中的flag变量而由于编译器的优化认为flag变量不会改变导致内存中的flag变量改变以后也没有将寄存器中的数据同步修改而CPU运算使用的数据又是寄存器中的数据这就导致了内存数据的不可见于是while循环就会一直运行导致了上面的问题。 为了让编译器每次都要去内存取数据来进行计算我们可以在flag变量前面加上volatile关键字。
#include stdio.h
...
volatile int flag 0;void handler(int signo)
{...
}
int main()
{...
}再次运行程序发现运行结果符合预期 2、关于编译器的优化的简单讨论
上面的代码如果我们不开启优化就算不加上volatile关键字也是能正常运行的可见编译器的优化不是越高越好。
如何理解编译器的优化?
编译器的本质是将代码翻译成01的二进制序列所以编译器的优化是在你编写的代码上动手脚也就是说编译器的优化其实改变了一些最终翻译成01二进制以后的执行逻辑。
三、SIGCHLD信号
在一前我们讲过用wait和waitpid函数清理僵尸进程父进程可以阻塞等待子进程结束也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式父进程阻塞了就不能处理自己的工作了采用第二种方式父进程在处理自己的工作的同时还要记得时不时地轮询一 下也很麻烦。 《wait与waitpid的使用介绍》
上面使用wait与waitpid其实都是父进程主动检查子进程是否处于僵尸状态那么有没有一种方法能够让子进程主动告诉父进程自己处于僵尸状态呢
其实子进程在终止时会给父进程发SIGCHLD信号该信号的默认处理动作是忽略父进程可以自定义SIGCHLD信号的处理函数这样父进程只需专心处理自己的工作不必关心子进程了子进程终止时会通知父进程父进程在信号处理函数中调用wait或waitpid清理子进程即可。
下面就是一个对SIGCHLD信号的一个使用
在父进程中我们创建了10个子进程这10个子进程退出时都会给父进程发送SIGCHLD信号由于父进程回收其中一个子进程时其他子进程也有可能同时给父进程发送SIGCHLD信号而pending表又没有办法同时存储多个信号所以我们就要进行循环回收子进程而为了不影响父进程的执行流程我们可以选择非阻塞等待。
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include signal.hpid_t id 0;void WaitProcess(int signo)
{printf(捕捉到了%d号信号正在处理...\n, signo);while (1){pid_t ret waitpid(-1, NULL, WNOHANG);if (ret 0){printf(等待子进程%d成功,父进程%d\n, ret, id);}else{break;}}printf(WaitProcess, done\n);
}int main()
{signal(SIGCHLD, WaitProcess);int i 0;// 创建10个子进程for (i 0; i 10; i){id fork();// 子进程if (id 0){int cnt 5;//睡眠cnt秒以后退出while (cnt--){printf(我是子进程,我的pid是%dppid是%d\n, getpid(), getppid());sleep(1);}exit(0);}}// 父进程一直休眠while (1){sleep(1);}return 0;
}事实上由于UNIX 的历史原因要想不产生僵尸进程还有另外一种办法父进程调用signal将SIGCHLD的处理动作置为SIG_IGN这样fork出来的子进程在终止时会自动清理掉不会产生僵尸进程也不会通知父进程。系统默认的忽略动作和用户用signal函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include signal.hpid_t id 0;int main()
{// 对SIGCHLD设置为忽略这样产生的子进程退出时不会形成僵尸状态。signal(SIGCHLD, SIG_IGN);int i 0;// 创建10个子进程for (i 0; i 10; i){id fork();// 子进程if (id 0){int cnt 5;//睡眠cnt秒以后退出while (cnt--){printf(我是子进程,我的pid是%dppid是%d\n, getpid(), getppid());sleep(1);}exit(0);}}// 父进程一直休眠while (1){sleep(1);}return 0;
}