Fortran结合OpenMP出现Segmentation Fault的解决咨询
系统环境:Debian 12(Bookworm),Intel(R) Core(TM) i7-3770 CPU @ 3.40 GHz
尝试用OpenMP并行化调用FFTW的Fortran多模块代码,使用如下Makefile编译:
.... .... LIBS =-lfftw3 CC = g++ CFLAGS = -O2 FC = gfortran FFLAGS = -O F90 = gfortran F90FLAGS = -O3 -pipe -fomit-frame-pointer -fopenmp #F90FLAGS = -O3 -pipe -fomit-frame-pointer LDFLAGS = -fopenmp all: main_gradient: $(OBJS) main_gradient.f90 $(F90) -I$(INCLUDE) $(F90FLAGS) -c $@.f90 $(F90) -I$(INCLUDE) $(LDFLAGS) -L/usr/lib:/usr/local/lib $(OBJS) $@.o $(LIBS) -o $@.exe .SUFFIXES: $(SUFFIXES) .f90 %.o: %.f90 $(F90) -I$(INCLUDE) $(F90FLAGS) -c $< %.o: $(DIR_GENERAL)/%.f90 $(F90) -I$(INCLUDE) $(F90FLAGS) -c $< %.o: $(DIR_GEOMETRY)/%.f90 $(F90) -I$(INCLUDE) $(F90FLAGS) -c $< %.o: $(DIR_POLYMERS)/%.f90 $(F90) -I$(INCLUDE) $(F90FLAGS) -c $< %.o: $(DIR_INTERACTIONS)/%.f90 $(F90) -I$(INCLUDE) $(F90FLAGS) -c $< clean: rm -f *.o *.mod fort.* dens_* phi* *.exe .... ....
尚未对代码做并行化处理,已将大型数组(646464*100)改为动态分配解决编译时段错误,但运行时仍出现Segmentation Fault,即使设置OMP_NUM_THREADS=1也一样。Valgrind检测结果:
==463360== Warning: client switching stacks? SP change: 0x1ffefff958 --> 0x1ff97ff6f0 ==463360== to suppress, use: --max-stackframe=92275304 or greater ==463360== Invalid write of size 8 ==463360== at 0x11C7BA: mix_fields.1 (in ==463360== Address 0x1ff97ff6f8 is on thread 1's stack ==463360== ==463360== Can't extend stack to 0x1ff97fe7a8 during signal delivery for thread 1: ==463360== no stack segment ==463360== ==463360== Process terminating with default action of signal 11 (SIGSEGV) ==463360== Access not within mapped region at address 0x1FF97FE7A8 ==463360== at 0x11C7BA: mix_fields.1 (in ==463360== If you believe this happened as a result of a stack ==463360== overflow in your program's main thread (unlikely but ==463360== possible), you can try to increase the size of the ==463360== main thread stack using the --main-stacksize= flag. ==463360== The main thread stack size used in this run was 8388608. ==463360== ==463360== HEAP SUMMARY: ==463360== in use at exit: 71,488,141 bytes in 755 blocks ==463360== total heap usage: 298,496 allocs, 297,741 frees, 1,390,223,513 bytes allocated ==463360== ==463360== LEAK SUMMARY: ==463360== definitely lost: 80,000 bytes in 1 blocks ==463360== indirectly lost: 0 bytes in 0 blocks ==463360== possibly lost: 0 bytes in 0 blocks ==463360== still reachable: 71,408,141 bytes in 754 blocks ==463360== suppressed: 0 bytes in 0 blocks ==463360== Rerun with --leak-check=full to see details of leaked memory ==463360== ==463360== Use --track-origins=yes to see where uninitialised values come from ==463360== For lists of detected and suppressed errors, rerun with: -s ==463360== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
疑问:这是数组分配到栈上导致空间不足的问题吗?该如何解决?使用-fopenmp标志是否会改变数组的分配方式?
问题定位
从Valgrind输出看,确实是栈溢出导致的段错误——错误地址位于线程栈上,且提示无法扩展栈空间,当前主线程栈大小仅8MB,远小于你提到的大型数组所需空间(6464641008字节≈2048MB)。即使你把全局/主程序的大型数组改成动态分配,可能还有其他**局部数组(比如子程序/函数内的数组)**仍分配在栈上,这是当前最明确的问题。
解决方法
修改局部数组的分配方式
对于子程序/函数内的大型数组,不要用静态声明(比如real :: arr(64,64,64,100)),改用显式动态分配:在子程序内用allocate分配内存,退出前用deallocate释放(Fortran 2003及以上支持的自动数组默认仍分配在栈上,优先选择显式动态分配到堆)。
也可以添加编译选项-fstack-arrays,让gfortran自动将大的局部数组分配到堆上,但这种方式依赖编译器实现,不如显式动态分配可靠。增大栈空间
- 调整主线程栈大小:运行程序前在终端执行命令临时设置,仅对当前终端有效
# 设置栈大小为无限制 ulimit -s unlimited # 或设置具体大小(比如2GB) ulimit -s 2097152 - 调整OpenMP线程栈大小:通过环境变量设置
即使设置export OMP_STACKSIZE=2GOMP_NUM_THREADS=1,OpenMP仍会初始化线程环境,该变量对主线程同样生效。
- 调整主线程栈大小:运行程序前在终端执行命令临时设置,仅对当前终端有效
优化编译选项
保留-fomit-frame-pointer选项,它能节省少量栈空间,但不要依赖它解决核心的栈溢出问题。
关于-fopenmp的说明
-fopenmp标志仅用于启用OpenMP并行编译支持:识别代码中的OpenMP指令、生成多线程代码、链接OpenMP运行时库。它不会改变数组的默认分配方式——静态数组和局部自动数组依然分配在栈上,只有显式动态分配的数组才会到堆上。
不过启用OpenMP后,每个线程都有独立的栈空间,若并行区域内存在局部大数组,更容易触发栈溢出,这时需要配合OMP_STACKSIZE调整线程栈大小。
内容的提问来源于stack exchange,提问作者Marios Giannakou

