execve后无法访问共享内存中动态分配的struct destinations指针问题
问题描述
我重新整理了完整的最小可复现代码,希望能解决这个问题:
struct destinations* 在getDestinations和createDestination函数中动态分配,写入共享内存shmp的airplane_array后,当前进程能正常读取它的city和airport元素。但通过execve启动的新进程尝试访问airplane_array里的动态分配元素时,程序直接停住了(最后的printf根本没执行)。
注意:
- 新进程会先脱离共享内存,再重新挂载到之前初始化的内存区域(地址存在
shmatAddress里) - 就算不重新挂载,访问
int company字段也完全没问题
我已经确认共享内存地址和元素地址都是正确的,输出日志可以证明。想请教怎么在execve之后访问city和airport变量,或者该怎么重新设计struct destinations*这个变量?
CLASS.C
#define SHM_KEY 0x105 #include <stddef.h> #include <stdlib.h> #include <time.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <time.h> #include <sys/shm.h> #include <sys/sem.h> #include <stdbool.h> #include <sys/wait.h> #include <signal.h> #include <sys/msg.h> #include <sys/types.h> struct destinations *getDestinations(); struct destinations createDestination(); struct destinations{ int city; int airport; }; struct airplane { int company; struct destinations *destinations; }; struct shmseg { void *shmatAddress; struct airplane airplane_array[]; }; int num_airplanes = 10; int shmid; pid_t childPid; int main(int argc, char *argv[]){ struct shmseg *shmp; int i, j, k = 0; size_t shmp_size = sizeof(struct shmseg) + (num_airplanes * sizeof(struct airplane)); shmp = (struct shmseg *) malloc(shmp_size); if (shmp == NULL) {} if((shmid = shmget(SHM_KEY, sizeof(struct shmseg) + (num_airplanes * sizeof(struct airplane)), IPC_CREAT | S_IRUSR | S_IWUSR)) == -1){ } if ((shmp = shmat(shmid, NULL, 0)) == (void*) -1){ } shmp->shmatAddress = (void *)shmp; for(i=0; i<1; i++){ switch(childPid = fork()){ case -1:{} case 0:{ char *argv[] = {NULL}; char *envp[] = {NULL}; struct destinations *destinations_array = NULL; destinations_array = getDestinations(); shmp->airplane_array[i].destinations = malloc(num_airplanes * sizeof(struct destinations)); for( j=0;j<4;j++){ memcpy(&shmp->airplane_array[i].destinations[j], &destinations_array[j], sizeof(struct destinations)); } for ( k = 0; k < 4; k++) { printf("SHMP: %d - %d\n", shmp->airplane_array[i].destinations[k].city, shmp->airplane_array[i].destinations[k].airport); } printf("Address of destinations before execve: %p\n", (void *)shmp->airplane_array[0].destinations); printf("Address of destinations[0] before execve: %p\n", (void *)&shmp->airplane_array[0].destinations[0]); printf("Address of destinations[0].city before execve: %p\n", (void *)&shmp->airplane_array[0].destinations[0].city); printf("Address of destinations[0].airport before execve: %p\n", (void *)&shmp->airplane_array[0].destinations[0].airport); printf("shmp address before execve: %p\n", (void *)shmp); execve("./newprogram", argv, envp); } default: } } return 0; } struct destinations *getDestinations(){ struct destinations *destinationsRet = NULL; int countDestinations = 0; int i = 0; for(i=0; i<4; i++){ struct destinations my_destination = createDestination(); destinationsRet = (struct destinations *) realloc(destinationsRet, (countDestinations + 1) * sizeof(struct destinations)); destinationsRet[i] = my_destination; countDestinations++; } return destinationsRet; } struct destinations createDestination(){ struct destinations my_destination; my_destination.city = 100; my_destination.airport = 200; return my_destination; }
NEWPROGRAM.C
#define SHM_KEY 0x105 #include <stddef.h> #include <stdlib.h> #include <time.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <time.h> #include <sys/shm.h> #include <sys/sem.h> #include <stdbool.h> #include <sys/wait.h> #include <signal.h> #include <sys/msg.h> #include <sys/types.h> struct shmseg *shmp; struct destinations{ int city; int airport; }; struct airplane { int company; struct destinations *destinations; }; struct shmseg { void *shmatAddress; struct airplane airplane_array[]; }; int shmid; int main(int argc, char *argv[]){ int num_airplanes = 10; int k = 0; void *address; if ((shmid = shmget(SHM_KEY, sizeof(struct shmseg) + (num_airplanes * sizeof(struct destinations)), 0)) == -1) { } if ((shmp = (struct shmseg *)shmat(shmid, NULL, 0)) == (void *)-1) { } address = shmp->shmatAddress; printf("shmp address after execve (before re-attaching): %p\n", (void *)shmp); if (shmdt(shmp) == -1) { } if ((shmp = (struct shmseg *)shmat(shmid, address, SHM_RND || SHM_REMAP)) == (void *)-1) { } printf("shmp address after execve (after re-attaching): %p\n", (void *)shmp); printf("Address of destinations after execve: %p\n", (void *)shmp->airplane_array[0].destinations); printf("Address of destinations[0] after execve: %p\n", (void *)&shmp->airplane_array[0].destinations[0]); printf("Address of destinations[0].city after execve: %p\n", (void *)&shmp->airplane_array[0].destinations[0].city); printf("Address of destinations[0].airport after execve: %p\n", (void *)&shmp->airplane_array[0].destinations[0].airport); for ( k = 0; k < 4; k++) { printf("SHMP from new program: %d - %d\n", shmp->airplane_array[0].destinations[k].city, shmp->airplane_array[0].destinations[k].airport); } printf("program will die before this.\n"); return 0; }
输出日志
SHMP: 100 - 200 SHMP: 100 - 200 SHMP: 100 - 200 SHMP: 100 - 200 Address of destinations before execve: 0x1045380 Address of destinations[0] before execve: 0x1045380 Address of destinations[0].city before execve: 0x1045380 Address of destinations[0].airport before execve: 0x1045384 shmp address before execve: 0x7f3ceeb60000 shmp address after execve (before re-attaching): 0x7ff2ff80d000 shmp address after execve (after re-attaching): 0x7f3ceeb60000 Address of destinations after execve: 0x1045380 Address of destinations[0] after execve: 0x1045380 Address of destinations[0].city after execve: 0x1045380 Address of destinations[0].airport after execve: 0x1045384
问题根源与解决方法
问题根源
核心问题很明确:shmp->airplane_array[i].destinations指向的是原进程私有堆内存的地址,不是共享内存区域。
原进程用malloc分配的内存属于该进程独有的堆空间,execve启动的新进程是完全独立的地址空间,这个0x1045380地址在新进程里是无效的,访问时直接触发段错误,程序崩溃,所以最后的printf根本执行不到。
而company字段是直接存在共享内存的struct airplane结构体中,属于共享内存区域,所以跨进程访问完全正常。
解决方法
有两种简单可行的改造方案:
方案1:直接把目标数组嵌入共享内存结构体
放弃使用指针,直接在struct airplane里定义固定大小的数组(和你的代码逻辑匹配):
struct airplane { int company; struct destinations destinations[4]; // 直接包含4个目标结构体 };
这样所有数据都存在共享内存里,跨进程访问不会有任何问题。如果需要动态数量,也可以用柔性数组,在创建共享内存时计算好总大小即可。
方案2:在共享内存内部分配目标数组空间
如果一定要用指针,不能用malloc,而是要从共享内存的预留空间里分配。比如创建共享内存时,把总大小设置为包含所有需要的空间:
// 总大小 = shmseg结构 + 10个airplane + 10个airplane各自的4个destinations size_t total_shm_size = sizeof(struct shmseg) + num_airplanes * sizeof(struct airplane) + num_airplanes * 4 * sizeof(struct destinations); shmid = shmget(SHM_KEY, total_shm_size, IPC_CREAT | S_IRUSR | S_IWUSR);
然后在原进程里,把destinations指针指向共享内存内部的预留地址:
// 计算当前airplane对应的destinations起始地址 struct destinations *shared_dest = (struct destinations*)((char*)shmp + sizeof(struct shmseg) + num_airplanes * sizeof(struct airplane) + i * 4 * sizeof(struct destinations)); shmp->airplane_array[i].destinations = shared_dest; // 直接赋值,无需memcpy for(j=0;j<4;j++){ shared_dest[j] = destinations_array[j]; }
这样指针指向的是共享内存内部的地址,新进程挂载共享内存后,这个地址在新进程的地址空间里也是有效的。
另外注意:代码里的SHM_RND || SHM_REMAP应该用位或运算符|,不是逻辑或||,虽然这不是当前崩溃的原因,但会导致重新挂载共享内存的参数错误。
内容的提问来源于stack exchange,提问作者Alessandro

