可重入函数与volatile限定符
可重入函数
进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理函数中断,进程就会跳转去执行信号处理函数。当执行完信号处理函数返回后,再返回之前主程序执行流继续按原指令执行。
此时会出现一个问题,假设主执行流正在执行一个链表的插入操作,在一个无头的单链表中,当主程序刚将一个节点头插到首节点上,但还未将头指针前移时,捕捉到信号,而信号处理函数的处理动作也是向首节点头插一个节点,当执行完信号处理函数返回后,继续执行主控制流指令,即将头指针前移到第一个头插的节点上,此时,在信号处理函数中插入的节点将丢失,因此造成内存泄漏。
所以在信号处理过程中会造成程序安全的函数为不可冲入函数
不可重入函数有以下特点
- 已知它们使用静态结构
- 调用malloc或free
- 是标准I/O函数
而在信号处理程序中保证调用安全的函数称为可重入函数
Volatile
保持内存的可见性
[wens@bogon _volatile]$ cat makefile
test:test.cpp
g++ -o $@ $^ -O2 //O2防止编译器的优化
.PHONY:clean
clean:
rm -f test
[wens@bogon _volatile]$
不加volatile
[wens@bogon _volatile]$ ./test
^Cflags change
^Cflags change
^Cflags change
^Cflags change
^Cflags change
^Cflags change
^\Quit (core dumped)
添加volatile
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
volatile int flags=0;
void handler(int sig)
{
flags=1;
cout<<"flags change"<<endl;
}
int main()
{
signal(SIGINT,handler);
while(!flags);
cout<<"flags= "<<flags<<endl;
return 0;
}
[wens@bogon _volatile]$ ./test
^Cflags change
flags= 1
当程序只有单执行流的时候,如果编译器没有检测到flsgs的改变,就会将其优化,放入寄存器中,省去了每次从内存中读取数据的操作,提高了效率。但当出现多执行流时,虽然编译器检测到了flags的改变,但是却无法判断两个执行流之间的关系,无法识别多执行流,只能按找原方案优化。之所以存在多执行流的的原因是因为执行了系统调用如sigaction等,所以需要我们自己进行判断。
需要volatile限定符修饰的场景
- 程序中存在多个执行流访问同一个全局变量
- 变量的内存单元中的数据不需要写操作就会自己发生改变,每次读的数都不一样
- 多次向变量的内存单元中写数据,即使不读取也是有意义的,如映射到内存地址空间的硬件寄存器
- 串口的接受寄存器是第一种情况,发送寄存器是第二种情况
- sig_atomic_t修饰的变量
一个简单的赋值运算,在不同的平台下底层的操作也不同,在32位的平台下对64位的long long 的变量进行赋值,底层因为寄存器为32位,所以要执行两步,导致一个赋值的操作不再是原子操作。而要保证这些读写操作要是原子操作,所以在c标准中定义了一个类型sig_atomic_t,在不同平台的c语言库中取不同的类型。在32位的机器上,sig_atomic_t的类型为int型。