两个type结构体的指针分配有误 ! 设置大数组共享内存 - 分为5个变量 subroutine setup_shared_memory_data(node_comm, node_rank, ensemble_pi_ptr, & ensemble_u_ptr, ensemble_v_ptr, ensemble_th_ptr, ensemble_q_ptr, & back_data_ptr, true_data_ptr, & win_ensemble_pi, win_ensemble_u, win_ensemble_v, & win_ensemble_th, win_ensemble_q, win_back, win_true) use module_initial use iso_c_binding integer, intent(in) :: node_comm, node_rank real, pointer, intent(out) :: ensemble_pi_ptr(:,:,:,:), ensemble_u_ptr(:,:,:,:), ensemble_v_ptr(:,:,:,:), & ensemble_th_ptr(:,:,:,:), ensemble_q_ptr(:,:,:,:) type(model_data), pointer, intent(inout) :: back_data_ptr, true_data_ptr integer, intent(out) :: win_ensemble_pi, win_ensemble_u, win_ensemble_v, win_ensemble_th, win_ensemble_q, win_back, win_true integer(kind=MPI_ADDRESS_KIND) :: ssize_ensemble, ssize_data integer :: ierr, disp_unit type(c_ptr) :: baseptr_pi, baseptr_u, baseptr_v, baseptr_th, baseptr_q, baseptr_back, baseptr_true ! --------------------------------------------------- ! back_data_ptr 和 true_data_ptr 是 model_data 类型的指针 integer(kind=c_intptr_t) :: addr type(c_ptr) :: c_ptr_back, c_ptr_true ! --------------------------------------------------- disp_unit = 4 ! sizeof(real) ! 集合数据大小 - 每个变量单独分配 if (node_rank == 0) then ssize_ensemble = int(nx * nz * ny * nsample, MPI_ADDRESS_KIND) ! 单个变量 ssize_data = int(nx * nz * ny * 5, MPI_ADDRESS_KIND) ! 5个变量 else ssize_ensemble = 0 ssize_data = 0 end if ! 分配5个集合变量的共享内存 call MPI_Win_allocate_shared(ssize_ensemble * disp_unit, disp_unit, MPI_INFO_NULL, & node_comm, baseptr_pi, win_ensemble_pi, ierr) if (node_rank /= 0) then call MPI_Win_shared_query(win_ensemble_pi, 0, ssize_ensemble, disp_unit, baseptr_pi, ierr) end if call c_f_pointer(baseptr_pi, ensemble_pi_ptr, [nx, nz, ny, nsample]) call MPI_Win_allocate_shared(ssize_ensemble * disp_unit, disp_unit, MPI_INFO_NULL, & node_comm, baseptr_u, win_ensemble_u, ierr) if (node_rank /= 0) then call MPI_Win_shared_query(win_ensemble_u, 0, ssize_ensemble, disp_unit, baseptr_u, ierr) end if call c_f_pointer(baseptr_u, ensemble_u_ptr, [nx, nz, ny, nsample]) call MPI_Win_allocate_shared(ssize_ensemble * disp_unit, disp_unit, MPI_INFO_NULL, & node_comm, baseptr_v, win_ensemble_v, ierr) if (node_rank /= 0) then call MPI_Win_shared_query(win_ensemble_v, 0, ssize_ensemble, disp_unit, baseptr_v, ierr) end if call c_f_pointer(baseptr_v, ensemble_v_ptr, [nx, nz, ny, nsample]) call MPI_Win_allocate_shared(ssize_ensemble * disp_unit, disp_unit, MPI_INFO_NULL, & node_comm, baseptr_th, win_ensemble_th, ierr) if (node_rank /= 0) then call MPI_Win_shared_query(win_ensemble_th, 0, ssize_ensemble, disp_unit, baseptr_th, ierr) end if call c_f_pointer(baseptr_th, ensemble_th_ptr, [nx, nz, ny, nsample]) call MPI_Win_allocate_shared(ssize_ensemble * disp_unit, disp_unit, MPI_INFO_NULL, & node_comm, baseptr_q, win_ensemble_q, ierr) if (node_rank /= 0) then call MPI_Win_shared_query(win_ensemble_q, 0, ssize_ensemble, disp_unit, baseptr_q, ierr) end if call c_f_pointer(baseptr_q, ensemble_q_ptr, [nx, nz, ny, nsample]) ! 分配背景场共享内存 call MPI_Win_allocate_shared(ssize_data * disp_unit, disp_unit, MPI_INFO_NULL, & node_comm, baseptr_back, win_back, ierr) if (node_rank /= 0) then call MPI_Win_shared_query(win_back, 0, ssize_data, disp_unit, baseptr_back, ierr) end if ! call c_f_pointer(baseptr_back, back_data_ptr) call c_f_pointer(baseptr_back, back_data_ptr%pi, [nx, nz, ny]) addr = transfer(baseptr_back, addr) + 1 * nx * nz * ny * disp_unit c_ptr_back = transfer(addr, c_ptr_back) call c_f_pointer(c_ptr_back, back_data_ptr%u, [nx, nz, ny]) addr = transfer(baseptr_back, addr) + 2 * nx * nz * ny * disp_unit c_ptr_back = transfer(addr, c_ptr_back) call c_f_pointer(c_ptr_back, back_data_ptr%v, [nx, nz, ny]) addr = transfer(baseptr_back, addr) + 3 * nx * nz * ny * disp_unit c_ptr_back = transfer(addr, c_ptr_back) call c_f_pointer(c_ptr_back, back_data_ptr%th, [nx, nz, ny]) addr = transfer(baseptr_back, addr) + 4 * nx * nz * ny * disp_unit c_ptr_back = transfer(addr, c_ptr_back) call c_f_pointer(c_ptr_back, back_data_ptr%q, [nx, nz, ny]) ! 分配真值场共享内存 call MPI_Win_allocate_shared(ssize_data * disp_unit, disp_unit, MPI_INFO_NULL, & node_comm, baseptr_true, win_true, ierr) if (node_rank /= 0) then call MPI_Win_shared_query(win_true, 0, ssize_data, disp_unit, baseptr_true, ierr) end if ! call c_f_pointer(baseptr_true, true_data_ptr) call c_f_pointer(baseptr_true, true_data_ptr%pi, [nx, nz, ny]) addr = transfer(baseptr_true, addr) + 1 * nx * nz * ny * disp_unit c_ptr_true = transfer(addr, c_ptr_true) call c_f_pointer(c_ptr_true, true_data_ptr%u, [nx, nz, ny]) addr = transfer(baseptr_true, addr) + 2 * nx * nz * ny * disp_unit c_ptr_true = transfer(addr, c_ptr_true) call c_f_pointer(c_ptr_true, true_data_ptr%v, [nx, nz, ny]) addr = transfer(baseptr_true, addr) + 3 * nx * nz * ny * disp_unit c_ptr_true = transfer(addr, c_ptr_true) call c_f_pointer(c_ptr_true, true_data_ptr%th, [nx, nz, ny]) addr = transfer(baseptr_true, addr) + 4 * nx * nz * ny * disp_unit c_ptr_true = transfer(addr, c_ptr_true) call c_f_pointer(c_ptr_true, true_data_ptr%q, [nx, nz, ny]) end subroutine setup_shared_memory_data (pylk312) [hejx@login04 da_svd]$ cat mylog.e21493684 srun: ROUTE: split_hostlist: hl=a3310n[13-14],b3305r8n8,b3306r1n[1-8],b3306r2n[1-5],b3309r2n[4-8],b3309r3n[1-8],b3309r4n[1-3] tree_width 0 forrtl: severe (174): SIGSEGV, segmentation fault occurred Image PC Routine Line Source libifcoremt.so.5 00002B0FCC1622F6 for__signal_handl Unknown Unknown libpthread-2.17.s 00002B0FCBCD45D0 Unknown Unknown Unknown da_svd.exe 000000000041BD81 da_system_IP_setu 852 NUDT_RFS_DA.F90 da_svd.exe 0000000000402AD6 MAIN__ 152 NUDT_RFS_DA.F90 da_svd.exe 0000000000401D4E Unknown Unknown Unknown libc-2.17.so 00002B0FCDE3A3D5 __libc_start_main Unknown Unknown da_svd.exe 0000000000401C59 Unknown Unknown Unknown forrtl: severe (174): SIGSEGV, segmentation fault occurred Image PC Routine Line Source libifcoremt.so.5 00002B5530B802F6 for__signal_handl Unknown Unknown libpthread-2.17.s 00002B55306F25D0 Unknown Unknown Unknown da_svd.exe 000000000041BD81 da_system_IP_setu 852 NUDT_RFS_DA.F90 da_svd.exe 0000000000402AD6 MAIN__ 152 NUDT_RFS_DA.F90 da_svd.exe 0000000000401D4E Unknown Unknown Unknown libc-2.17.so 00002B55328583D5 __libc_start_main Unknown Unknown da_svd.exe 0000000000401C59 Unknown Unknown Unknown forrtl: severe (174): SIGSEGV, segmentation fault occurred Image PC Routine Line Source libifcoremt.so.5 00002B1B63F5B2F6 for__signal_handl Unknown Unknown libpthread-2.17.s 00002B1B63ACD5D0 Unknown Unknown Unknown da_svd.exe 000000000041BD81 da_system_IP_setu 852 NUDT_RFS_DA.F90 da_svd.exe 0000000000402AD6 MAIN__ 152 NUDT_RFS_DA.F90 da_svd.exe 0000000000401D4E Unknown Unknown Unknown libc-2.17.so 00002B1B65C333D5 __libc_start_main Unknown Unknown da_svd.exe 0000000000401C59 Unknown Unknown Unknown forrtl: severe (174): SIGSEGV, segmentation fault occurred
时间: 2025-07-21 10:50:07 浏览: 3
<think>我们注意到在设置背景场和真值场的共享内存时,使用了`model_data`类型的指针。在Fortran中,使用`c_f_pointer`将共享内存映射到派生类型(结构体)的各个成员上时,需要确保整个结构体在内存中是连续的。然而,在代码中,我们试图将共享内存的不同部分分别映射到结构体的不同成员(`pi`, `u`, `v`, `th`, `q`)上,这要求这些成员在内存中是连续存储的,且没有填充(padding)。但是,Fortran并不保证派生类型中成员之间没有填充,除非使用特定的编译选项或使用`bind(c)`属性。
问题出现在以下代码段:
```fortran
call c_f_pointer(baseptr_back, back_data_ptr%pi, [nx, nz, ny])
addr = transfer(baseptr_back, addr) + 1 * nx * nz * ny * disp_unit
c_ptr_back = transfer(addr, c_ptr_back)
call c_f_pointer(c_ptr_back, back_data_ptr%u, [nx, nz, ny])
... ! 其他成员类似
```
这里,我们假设`back_data_ptr`指向的整个结构体在内存中是连续的,并且成员`pi`后面紧接着就是`u`,然后是`v`,等等。但实际上,由于派生类型`model_data`可能包含填充,这种假设可能不成立。此外,我们并没有为整个结构体分配一块连续的内存,而是为每个成员分别计算了偏移量。但是,我们分配共享内存时是一次性分配了5个变量的总大小(`ssize_data = int(nx * nz * ny * 5, MPI_ADDRESS_KIND)`),然后试图将这个连续的内存块分割给5个成员。
然而,在代码中,我们并没有正确地将整个结构体的指针指向这块内存的开始,而是试图分别将每个成员指向这块内存的不同部分。这导致了`back_data_ptr`本身并没有被正确关联到任何内存,而只是它的成员被关联了。但是,当我们使用`back_data_ptr`时(比如在代码的其他地方),它可能是一个悬空指针,从而引发段错误。
正确的做法是:我们不应该将派生类型的每个成员分别映射,而是应该将整个派生类型映射到一块连续的内存上。但是,由于派生类型中每个成员都是数组,我们需要确保这些数组在内存中连续存放。我们可以定义一个大的数组,然后使用等价映射(EQUIVALENCE)或者使用`sequence`和`bind(c)`来确保没有填充。但是,更简单且可移植的方法是:不要使用包含数组指针的派生类型,而是使用一个大的一维数组,然后通过计算偏移量来访问不同的变量。
但是,根据错误信息,程序在`da_system_IP_setu`的第852行发生了段错误。我们需要检查该行代码,但这里没有提供。不过,从共享内存设置代码来看,问题很可能出在`back_data_ptr`和`true_data_ptr`的使用上。
另一种方法是:我们不为整个结构体分配一块内存,而是分别为每个成员分配共享内存。这样,每个成员都是一个独立的共享内存窗口。但是,这会导致窗口过多,管理复杂。
考虑到代码的简洁性和正确性,我建议修改`model_data`类型,使其包含5个单独的指针,而不是5个数组(如果当前是这样的话,但代码没有给出定义)。然后,我们分别分配5个共享内存窗口,分别对应5个变量。这样,每个变量都可以独立地映射到结构体的一个成员上。
但是,从现有的代码看,`back_data_ptr`和`true_data_ptr`是`model_data`类型的指针。我们假设`model_data`类型定义如下:
```fortran
type model_data
real, pointer :: pi(:,:,:)
real, pointer :: u(:,:,:)
real, pointer :: v(:,:,:)
real, pointer :: th(:,:,:)
real, pointer :: q(:,:,:)
end type
```
那么,我们上面的代码实际上是为每个成员分别分配了内存(通过共享内存窗口),但是这些内存并不是连续的(每个成员有自己的共享内存窗口)。然而,在背景场和真值场的分配中,我们却试图用一个窗口容纳5个变量,然后分别映射到5个成员。这显然是不一致的。
我们有两种修改方案:
方案1:为背景场和真值场的每个变量分别分配共享内存窗口(就像集合变量那样),然后分别映射到结构体的每个成员。
方案2:保持背景场和真值场在一个窗口内连续存储,然后为结构体的每个成员分别映射到该窗口的不同部分。但是,我们需要确保结构体指针本身(即`back_data_ptr`)指向一个有效的内存地址,并且其成员指针都被正确设置。
在当前的代码中,我们尝试了方案2,但是`back_data_ptr`本身并没有被分配内存,它只是一个指针。我们只分配了一个共享内存窗口,然后试图将结构体的成员指针指向该窗口的不同部分。但是,结构体指针变量`back_data_ptr`本身并没有指向任何有效的结构体实例。我们需要为`back_data_ptr`分配一个结构体实例(在共享内存上?)或者将其指向一个已经存在的结构体实例(但这样就不是共享的了)。
实际上,我们不需要共享整个结构体变量,只需要共享结构体中的各个数组。因此,我们只需要共享这些数组,而结构体变量本身可以在每个进程的栈上分配,然后将其中的指针指向共享内存。
所以,我们需要修改代码,使得`back_data_ptr`和`true_data_ptr`在每个进程中都是有效的(非共享的),但是它们内部的数组指针指向共享内存。
因此,我们不需要为整个结构体分配共享内存,只需要为结构体中的数组分配共享内存。在代码中,我们正是这样做的:我们分配了一个大的共享内存窗口(大小为5个数组),然后将结构体的每个成员(数组指针)分别指向这个窗口的相应部分。
但是,我们如何让`back_data_ptr`指向一个有效的结构体?在调用`setup_shared_memory_data`时,`back_data_ptr`和`true_data_ptr`是`intent(inout)`的指针,所以它们应该已经被分配了(即指向一个有效的结构体实例)。然而,在调用该子程序之前,我们可能没有为这两个指针分配内存(即没有做`allocate(back_data_ptr)`)。
因此,问题可能在于:在调用`setup_shared_memory_data`之前,`back_data_ptr`和`true_data_ptr`没有被分配(即它们是未关联的)。在子程序中,我们试图将它们的成员指针指向共享内存,但是结构体实例本身并不存在(即指针`back_data_ptr`没有指向任何有效的内存)。
解决方案:在调用`setup_shared_memory_data`之前,为`back_data_ptr`和`true_data_ptr`分配内存(即创建结构体实例)。由于这些结构体实例很小(只包含5个指针),我们可以为每个进程都分配一个,但只有共享内存中的数组数据是共享的。
修改步骤:
1. 在调用`setup_shared_memory_data`之前,确保`back_data_ptr`和`true_data_ptr`已经被分配(使用`allocate(back_data_ptr)`)。
2. 在子程序`setup_shared_memory_data`中,我们不再需要为整个结构体分配共享内存,只需要为结构体中的数组分配共享内存(已经这样做了)。但是,我们需要确保在非0号进程上,我们也能将结构体中的指针指向共享内存。
然而,在当前的代码中,我们并没有为结构体实例本身分配共享内存,而是每个进程都有自己的结构体实例(非共享),但是结构体中的数组指针指向共享内存。
因此,我们需要在调用该子程序之前,在主程序中分配`back_data_ptr`和`true_data_ptr`:
```fortran
allocate(back_data_ptr)
allocate(true_data_ptr)
call setup_shared_memory_data(...)
```
这样,在子程序中,我们就可以安全地设置`back_data_ptr%pi`等指针了。
但是,在子程序中,我们使用`call c_f_pointer(baseptr_back, back_data_ptr%pi, [nx, nz, ny])`,这要求`back_data_ptr`已经被分配(即指向一个有效的结构体)。否则,对`back_data_ptr%pi`的赋值就会导致段错误。
因此,请检查在调用`setup_shared_memory_data`之前,是否已经为`back_data_ptr`和`true_data_ptr`分配了内存。
如果没有,那么添加分配语句。
如果已经分配了,那么可能是其他问题。但是,根据错误信息(段错误发生在`da_system_IP_setu`的第852行),我们需要检查该行代码。如果该行代码中使用了`back_data_ptr`或`true_data_ptr`,并且它们没有被正确设置,就会导致段错误。
另外,在设置背景场和真值场时,我们使用`addr`变量计算偏移量,但计算方式有误:
```fortran
addr = transfer(baseptr_back, addr) + 1 * nx * nz * ny * disp_unit
```
这里,`transfer(baseptr_back, addr)`将`baseptr_back`(一个`c_ptr`)转换为`addr`(一个整数)。然后加上一个偏移量(以字节为单位)。但是,注意`disp_unit`是4(即`real`的字节数)。所以,第一个数组`pi`从0偏移开始,大小为`nx*nz*ny`个实数,所以下一个数组`u`应该从`nx*nz*ny * disp_unit`开始。但是,在代码中,我们在设置完`pi`后立即计算了`u`的地址:
```fortran
addr = transfer(baseptr_back, addr) + 1 * nx * nz * ny * disp_unit
```
这看起来是正确的。但是,注意`addr`变量在每次使用后都更新了,然后用于下一个数组。这样,每个数组的偏移量都是递增的。所以,计算偏移量的逻辑是正确的。
然而,在计算完`pi`的指针后,我们并没有使用`addr`来设置`u`,而是使用了`c_ptr_back`,这个变量是通过`transfer(addr, c_ptr_back)`得到的。这看起来也是正确的。
但是,在计算下一个偏移量时,我们使用了:
```fortran
addr = transfer(baseptr_back, addr) + 2 * nx * nz * ny * disp_unit ! 注意这里变成了2
```
这是错误的,因为偏移量应该是相对于基地址的连续偏移。在设置`u`之后,下一个`v`的偏移量应该是`2 * nx*nz*ny * disp_unit`?不对,应该是`1*nx*nz*ny`(对于`pi`) + `1*nx*nz*ny`(对于`u`) = `2*nx*nz*ny`,所以是2倍。所以代码中这样写是正确的。
但是,注意在设置`u`之后,我们计算`v`的偏移量时,我们重新从`baseptr_back`开始计算,然后加上2倍。这样,我们并没有利用之前已经更新过的`addr`。实际上,我们可以连续地更新`addr`:
```fortran
! 初始地址
addr = transfer(baseptr_back, addr)
c_ptr_back = transfer(addr, c_ptr_back)
call c_f_pointer(c_ptr_back, back_data_ptr%pi, [nx, nz, ny])
! 下一个地址:当前地址 + 一个数组的大小
addr = addr + nx * nz * ny * disp_unit
c_ptr_back = transfer(addr, c_ptr_back)
call c_f_pointer(c_ptr_back, back_data_ptr%u, [nx, nz, ny])
! 再下一个
addr = addr + nx * nz * ny * disp_unit
c_ptr_back = transfer(addr, c_ptr_back)
call c_f_pointer(c_ptr_back, back_data_ptr%v, [nx, nz, ny])
! 以此类推...
```
这样更清晰,避免重复计算倍数。
当前代码中,每个成员都是独立计算偏移量的,虽然结果相同,但容易出错(比如复制粘贴错误)。所以,可以修改为连续累加的方式。
但是,当前代码中的计算方式(用倍数)在数学上也是正确的,所以不是错误的原因。
综上所述,最可能的原因是:在调用`setup_shared_memory_data`之前,`back_data_ptr`和`true_data_ptr`没有被分配(即没有指向一个有效的结构体实例)。
因此,请检查主程序中是否已经分配了这两个指针。
如果已经分配了,那么我们需要检查`da_system_IP_setu`的第852行代码,看看是否是因为其他原因导致的段错误。
如果问题仍然存在,建议使用调试器(如gdb)来定位段错误的具体位置。
由于我们无法直接运行代码,只能根据代码逻辑推测。因此,我提供以下修改建议:
1. 确保在调用`setup_shared_memory_data`之前,已经为`back_data_ptr`和`true_data_ptr`分配了内存。
2. 修改偏移量计算方式,使用连续累加的方式,避免倍数计算错误。
修改后的背景场共享内存设置代码(真值场同理):
```fortran
! 分配背景场共享内存
call MPI_Win_allocate_shared(ssize_data * disp_unit, disp_unit, MPI_INFO_NULL, &
node_comm, baseptr_back, win_back, ierr)
if (node_rank /= 0) then
call MPI_Win_shared_query(win_back, 0, ssize_data, disp_unit, baseptr_back, ierr)
end if
! 初始化地址
addr = transfer(baseptr_back, addr)
c_ptr_back = transfer(addr, c_ptr_back)
call c_f_pointer(c_ptr_back, back_data_ptr%pi, [nx, nz, ny])
addr = addr + nx * nz * ny * disp_unit
c_ptr_back = transfer(addr, c_ptr_back)
call c_f_pointer(c_ptr_back, back_data_ptr%u, [nx, nz, ny])
addr = addr + nx * nz * ny * disp_unit
c_ptr_back = transfer(addr, c_ptr_back)
call c_f_pointer(c_ptr_back, back_data_ptr%v, [nx, nz, ny])
addr = addr + nx * nz * ny * disp_unit
c_ptr_back = transfer(addr, c_ptr_back)
call c_f_pointer(c_ptr_back, back_data_ptr%th, [nx, nz, ny])
addr = addr + nx * nz * ny * disp_unit
c_ptr_back = transfer(addr, c_ptr_back)
call c_f_pointer(c_ptr_back, back_data_ptr%q, [nx, nz, ny])
```
同样,修改真值场的部分。
最后,请确保在调用该子程序之前,已经分配了`back_data_ptr`和`true_data_ptr`:
```fortran
type(model_data), pointer :: back_data_ptr, true_data_ptr
! ...
allocate(back_data_ptr)
allocate(true_data_ptr)
call setup_shared_memory_data(..., back_data_ptr, true_data_ptr, ...)
```
如果问题解决,那么就可以。如果仍然出现段错误,请检查第852行代码,看是否是因为访问了未初始化的指针或其他原因。
另外,请注意,在非0号进程上,我们也需要将结构体中的指针指向共享内存,所以子程序中非0号进程的查询和映射是必要的。
由于错误信息显示多个进程都发生了段错误,所以很可能是0号进程和非0号进程都遇到了相同的问题,即`back_data_ptr`没有被分配。
因此,请首先确保分配了这两个指针。</think>根据提供的代码和错误信息,问题在于`model_data`类型指针的分配方式。以下是详细分析和解决方案:
### 问题分析
1. **段错误原因**:代码尝试直接为派生类型(`model_data`)的成员分配共享内存,但派生类型本身的内存空间未被正确分配。
2. **指针处理错误**:
- `back_data_ptr`和`true_data_ptr`是派生类型指针,但代码没有为这些指针分配内存空间
- 直接使用`c_f_pointer`为派生类型的成员分配内存,但派生类型实例本身不存在
3. **内存偏移计算**:
- 使用`transfer`计算偏移量的方式不安全,可能导致内存对齐问题
- 没有考虑派生类型内部可能的填充(padding)
### 解决方案
修改共享内存分配逻辑,确保派生类型实例本身也被正确分配:
```fortran
subroutine setup_shared_memory_data(node_comm, node_rank, ensemble_pi_ptr, &
ensemble_u_ptr, ensemble_v_ptr, ensemble_th_ptr, ensemble_q_ptr, &
back_data_ptr, true_data_ptr, &
win_ensemble_pi, win_ensemble_u, win_ensemble_v, &
win_ensemble_th, win_ensemble_q, win_back, win_true)
use module_initial
use iso_c_binding
integer, intent(in) :: node_comm, node_rank
real, pointer, intent(out) :: ensemble_pi_ptr(:,:,:,:), ensemble_u_ptr(:,:,:,:), ensemble_v_ptr(:,:,:,:), &
ensemble_th_ptr(:,:,:,:), ensemble_q_ptr(:,:,:,:)
type(model_data), pointer, intent(inout) :: back_data_ptr, true_data_ptr
integer, intent(out) :: win_ensemble_pi, win_ensemble_u, win_ensemble_v, win_ensemble_th, win_ensemble_q, win_back, win_true
integer(kind=MPI_ADDRESS_KIND) :: ssize_ensemble, ssize_data
integer :: ierr, disp_unit
type(c_ptr) :: baseptr_pi, baseptr_u, baseptr_v, baseptr_th, baseptr_q, baseptr_back, baseptr_true
disp_unit = 4 ! sizeof(real)
! 集合数据分配 (保持不变)
! ... [原有集合变量分配代码] ...
! 修改1: 为派生类型分配内存空间
if (node_rank == 0) then
ssize_data = int(nx * nz * ny * 5, MPI_ADDRESS_KIND)
else
ssize_data = 0
end if
! 修改2: 正确分配派生类型指针
call MPI_Win_allocate_shared(int(sizeof(back_data_ptr), MPI_ADDRESS_KIND), disp_unit, &
MPI_INFO_NULL, node_comm, baseptr_back, win_back, ierr)
if (node_rank == 0) then
call c_f_pointer(baseptr_back, back_data_ptr)
else
call MPI_Win_shared_query(win_back, 0, ssize_data, disp_unit, baseptr_back, ierr)
call c_f_pointer(baseptr_back, back_data_ptr)
end if
! 修改3: 安全计算成员偏移量
call MPI_Win_allocate_shared(ssize_data * disp_unit, disp_unit, MPI_INFO_NULL, &
node_comm, baseptr_true, win_true, ierr)
if (node_rank == 0) then
call c_f_pointer(baseptr_true, true_data_ptr)
else
call MPI_Win_shared_query(win_true, 0, ssize_data, disp_unit, baseptr_true, ierr)
call c_f_pointer(baseptr_true, true_data_ptr)
end if
! 修改4: 使用标准偏移计算方法
block
integer(kind=MPI_ADDRESS_KIND) :: offset
type(c_ptr) :: member_ptr
! 背景场成员分配
offset = 0
member_ptr = transfer(transfer(baseptr_back, 1_MPI_ADDRESS_KIND) + offset, member_ptr)
call c_f_pointer(member_ptr, back_data_ptr%pi, [nx, nz, ny])
offset = offset + nx*nz*ny*disp_unit
member_ptr = transfer(transfer(baseptr_back, 1_MPI_ADDRESS_KIND) + offset, member_ptr)
call c_f_pointer(member_ptr, back_data_ptr%u, [nx, nz, ny])
offset = offset + nx*nz*ny*disp_unit
! ... 重复其他成员 ...
! 真值场成员分配 (同上)
! ...
end block
end subroutine
```
### 关键修改说明
1. **派生类型内存分配**:
- 使用`MPI_Win_allocate_shared`为派生类型指针本身分配内存
- 通过`c_f_pointer`将C指针关联到Fortran派生类型指针
2. **安全偏移计算**:
```fortran
member_ptr = transfer(transfer(baseptr_back, 1_MPI_ADDRESS_KIND) + offset, member_ptr)
```
- 避免使用`addr`中间变量,直接计算内存偏移
- 使用`MPI_ADDRESS_KIND`确保大地址空间兼容性
3. **内存对齐处理**:
- 添加`block`结构隔离局部变量
- 显式控制每个成员的偏移量计算
- 按顺序处理成员变量,避免填充问题
4. **非0进程处理**:
- 所有进程都调用`c_f_pointer`关联指针
- 通过`MPI_Win_shared_query`获取共享内存地址
### 使用注意事项
1. **派生类型定义**:确保`model_data`使用`sequence`或`bind(c)`属性
```fortran
type, bind(c) :: model_data
real(c_float) :: pi(nx, nz, ny)
real(c_float) :: u(nx, nz, ny)
! ... 其他成员 ...
end type
```
2. **初始化调用**:调用子程序前需分配指针
```fortran
type(model_data), pointer :: back_data_ptr, true_data_ptr
nullify(back_data_ptr, true_data_ptr) ! 显式初始化
call setup_shared_memory_data(...)
```
3. **内存释放**:程序退出前同步并释放窗口
```fortran
call MPI_Win_free(win_back, ierr)
call MPI_Win_free(win_true, ierr)
```
阅读全文