用C语言实现两个进程共享内存?

6
我希望做到以下几点:
父进程创建一个子进程。然后,子进程从用户读取n个整数,并将它们存储在共享内存中。接着,父进程展示这些整数。
我已经达到了以下目标:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#define SHMSIZE 27
int main() {
   int shmid;
   int *shm;
   int *n;

   if(fork() == 0) {
      shmid = shmget(2009, SHMSIZE, 0);
      shm = shmat(shmid, 0, 0);
      n = shm;
      int i;
      for(i=0; i<5; i++) {
         printf("Enter number<%i>: ", i);
         scanf("%d", n++);
      }
      printf ("Child wrote <%d>\n",shm);
      shmdt(shm);
   }
   else {
      wait();
      int *s;
      shmid = shmget(2009, SHMSIZE, 0666 | IPC_CREAT);
      shm = shmat(shmid, 0, 0);
      s = shm;
      wait(NULL);
      printf ("Parent reads <%d>\n",shm) ;
      shmdt(shm);
      shmctl(shmid, IPC_RMID, NULL);
   }
   return 0;
}

输出结果只有这一行:

Enter number<1>:

如果我输入一个数字,比如说25,它会输出这个结果:
Parent reads <r>

r: 随机负数每次执行代码时都会更改

它从未通过子进程代码!我这样做错了吗?


为什么不在父进程中创建共享内存,然后再运行子进程呢? - K-ballo
在父进程中,第一次调用wait是什么? - Some programmer dude
另外,您传递给 scanf 的参数是错误的。 - Some programmer dude
@K-ballo 这就是我的要求,我更愿意坚持这个。 - iTurki
@JoachimPileborg 的意思是使用 wait() 函数强制父进程等待子进程执行完毕。那么,scanf() 函数有什么问题吗? - iTurki
4个回答

14

好的,最好将所有问题都集中在一个答案中...

您的程序存在几个问题。如果在构建时启用警告(我使用-Wall -Wextra),许多问题将变得很明显。

我已经在我的评论中提到了前两个问题,但我在这里解释一下:

  1. 第一个是调用wait()。在C或POSIX中没有不带参数的wait函数。
  2. 第二个问题是scanf调用,您正在使用*++调用它,其中*n获取n指向的内存的值,这很可能会导致崩溃。删除星号。
  3. 第三个问题是您将共享内存视为整数数组(使用n)和字符串。您不能真正同时执行两者,选择其中之一。
  4. 您在父进程中创建共享内存,但在创建内存之前等待子进程完成。
  5. 父进程和子进程之间存在竞争条件,因为共享内存可能在子进程尝试访问它之后才被创建。

编辑 我想到了这个替代方案,它对我来说似乎有效。我添加了对我所做更改的注释。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/wait.h>  /* Needed for the wait function */
#include <unistd.h>    /* needed for the fork function */
#include <string.h>    /* needed for the strcat function */
#define SHMSIZE 27
int main() {
   int shmid;
   char *shm;

   if(fork() == 0) {
      shmid = shmget(2009, SHMSIZE, 0);
      shm = shmat(shmid, 0, 0);
      char *s = (char *) shm;
      *s = '\0';  /* Set first location to string terminator, for later append */
      int i;
      for(i=0; i<5; i++) {
         int n;  /* Variable to get the number into */
         printf("Enter number<%i>: ", i);
         scanf("%d", &n);

         char number[20];
         sprintf(number, "%d", n);  /* Convert the number to string */
         strcat(s, number);  /* Append the number to the string */
      }
      strcat(s, "\n");  /* Append newline */
      printf ("Child wrote <%s>\n",shm);
      shmdt(shm);
   }
   else {
      /* Variable s removed, it wasn't used */
      /* Removed first call to wait as it held up parent process */
      shmid = shmget(2009, SHMSIZE, 0666 | IPC_CREAT);
      shm = shmat(shmid, 0, 0);
      wait(NULL);
      printf ("Parent reads <%s>\n",shm) ;
      shmdt(shm);
      shmctl(shmid, IPC_RMID, NULL);
   }
   return 0;
}

请注意,上述列表中的第5点尚未解决。

我现在要编辑我的代码。主要问题是代码正在工作,但从未执行子块,即 if(fork() == 0) 块。 - iTurki
已更新。现在请检查。输出结果仍然相同,只有一点点的差异。 - iTurki
@iturki,我在我的答案中添加了代码,包含了我对你的程序所做的更改。 - Some programmer dude
1
为了修复一个11年前的bug而付出的努力值得点赞。 - Craig Estey

1

我的问题非常愚蠢。我需要让子进程能够写入SHM。if块中的这行代码:

shmid = shmget(2009, SHMSIZE, 0);

会变成这样:

shmid = shmget(2009, SHMSIZE, 0666 | IPC_CREAT);

感谢大家,特别是@JoachimPileborg :)

接受他的答案,这样你们两个都可以获得一些积分。 :) - gnometorule
我为他的回答投了赞成票。如果一个答案不能解决问题,我是不能接受的,对吧? :) - iTurki
假设fork()之后的代码是同时运行的,或者任何一个可能先运行。因此,两个shmget都必须具有IPC_CREAT,因此先运行的进程将创建它。 - PaulS

0

你的描述似乎不正确,因为没有代码输出“Parent Wrote <>”。

你正在读取数字并将它们存储为int在*n++中,但是然后你正在将'\n'字符附加到n-int数组中,并且你正在将shm视为字符串?

在我的看来,在你的子进程中,你创建了一个共享内存,写入它,然后关闭(丢弃)共享内存。然后你的第二部分使用相同的段打开一个新的共享内存,但它仍然是一个新的共享内存。通常,一个进程创建一个共享内存,然后第二个进程打开它,当最后一个进程关闭共享内存时,它才会被操作系统释放。


关闭共享内存并不意味着丢弃它!shmdt() 表示解除共享内存连接。shmctl() 表示销毁共享内存。至于输出,它是错误的。我将进行更正。 - iTurki
抱歉,我不熟悉SHM API。但是如果子进程调用shmget并稍后终止,Linux是否会隐式销毁SHM(在父进程打开之前)?为什么您在父进程上使用IPC_CREAT而不是在子进程上使用? - Werner Henze
子进程只打开和关闭SHM。然后,父进程打开相同的SHM,检索数据,关闭并销毁它。这就是它的工作原理。 - iTurki

0
一个问题是子进程在父进程创建共享内存之前尝试获取共享内存。父进程在创建共享内存之前有一个wait()调用,因此当客户端尝试检索ID时,共享内存不存在。即使将wait()调用移动,也可能无法工作,因为存在竞争条件。调用shmget可能需要在fork调用之前(或使用一些同步来确保在子进程中检索它之前实际存在)。
而且(正如其他人已经指出的那样),子进程尝试将整数写入内存,而读取(打印)则尝试将其视为字符字符串。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接