【问题标题】:Is this piece of code correct?这段代码正确吗?
【发布时间】:2014-02-04 15:27:39
【问题描述】:

我在 Linux 上编译它:g++ test.c -o test

我重写了原来的例子。 现在让第一个进程等待 2 秒,(以便 process2 可以在共享内存上写入),然后我让 process1 从该内存中读取。这个测试正确吗?

第二个问题:我应该放在哪里:

shmdt(tests[0]); // or 1
shmctl(statesid, IPC_RMID, 0); 

//Global scope
char *state[2];
//...
//...

struct teststruct {
  int stateid;
  teststruct *next;
  //other things
};

void write(teststruct &t, char* what)
{
  strcpy(state[t.next->stateid], what);   
  printf("\n\nI am (%d), I wrote on: %d", t.stateid, t.next->stateid); 
}

void read(teststruct &t)
{
  printf("\n\nI am (%d), I read: **%s**", t.stateid, state[t.stateid]);        
}


int main() {

  key_t key;
  if ((key = ftok(".", 'a')) == -1) {
    perror("ftok");
    exit(1);
  }

   int statesid;
  if ((statesid = shmget(key, sizeof(char*)*50, 0600 | IPC_CREAT )) == -1) {
    perror("shmget error");
    exit(1);
  }

  state[0] = (char*)shmat(statesid, NULL, 0);
  state[1] = (char*)shmat(statesid, NULL, 0);

  teststruct tests[2];
  tests[0].stateid = 0;
  tests[0].next = &tests[1];
  tests[1].stateid = 1;
  tests[1].next = &tests[0];

  int t0, t1;
  switch (t0 = fork()) {
  case (0):
    sleep(2);
    read(tests[0]);

    exit(0);

  case (-1):
    printf("\nError!");
    exit(-1);

  default:
    wait();
  }

   switch (t1 = fork()) {
  case (0):
    write(tests[1], "1 write on 0 in theory.");

    exit(0);

  case (-1):
    printf("\nError!");
    exit(-1);

  default:
    wait();
  }

  return 0;
}

我特别问的是“状态”是否真的在两个进程之间共享,以及我所做的是否是这样做的好方法。

我的目标是在 fork 之后让 char *state[2] 在两个进程之间共享(读取/修改)。

【问题讨论】:

  • 我建议你先写一段非常简单的代码,然后用它来好好感受一下共享内存的含义。现在这个程序充满了陷阱,只能在偶然的情况下工作(因为分叉的进程通常继承相同的虚拟映射,但不能保证在另一个操作系统上仍然可以工作)。
  • 好的,谢谢。我只需要它作为“锻炼”目的在 Linux 上工作。

标签: c++ c fork shared-memory


【解决方案1】:

您无需致电shmat() 两次。您只为两个指针分配了足够的空间,因此您无法在两个进程之间进行太多通信。而且您不能依赖能够将指向第一个进程中的内存的指针复制到共享内存中,然后让第二个进程读取并使用它。该地址可能在第一个过程中有效,而在第二个过程中无效;它很可能指向第二个过程中完全不同的数据(特别是动态内存分配可能会搞砸)。您只能依靠共享内存的内容在两个进程中相同。您应该分配足够的共享内存来保存共享数据。

但是,话虽如此,两个进程应该共享那一小块共享内存,并且在两个进程中,state[0]state[1] 将指向共享内存,您应该能够在两者之间进行通信通过写入共享内存。请注意,在 fork 之后,如果任一进程更改了存储在其 state[0]state[1] 中的值,则另一个进程将不会看到该更改 - 另一个进程只能看到这些指针指向的共享内存中的哪些更改。

当然,你没有设置任何同步机制,所以访问可能会很混乱。

如何修改我的代码以使其按预期工作(不考虑同步问题)?

它的工作原理并不完全清楚,这使回答问题变得复杂。但是,如果您希望(例如)子进程将单词写入共享内存,而父进程从共享内存中读取单词,那么您将为您愿意处理的最大单词分配足够的共享内存,然后安排子进程将一个单词从其进程内存中复制到共享内存中(并通知父进程它已经这样做了),然后父进程可以从共享内存中复制或读取该单词并与数据进行比较从其每个进程的内存中。

因为你有一个父子进程,它们是同一个进程的fork,你会发现这两个进程共享很多包含相同信息的相同内存地址。然而,这是巧合。您可以让不相关的进程连接到共享内存,它们不需要有任何共同的地址。因此,从您当前的设置中获得虚假结果是微不足道的。


工作代码

对于“工作”的某些定义,以下 C++ 代码可以。代码是巧妙的 C++;代码假定struct teststruct 声明类型teststruct,并使用引用作为参数。

请注意,问题中的(修改后的)代码的 wait() 调用不合理。

shm2.cpp

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

static char *state = 0;

struct teststruct
{
    int stateid;
    teststruct *next;
};

void sm_write(teststruct &t, char* /*what*/)
{
    //strcpy(state[t.next->stateid], what);   
    printf("[%5d] I am (%d), I wrote on: %d\n", (int)getpid(), t.stateid, t.next->stateid); 
}

void sm_read(teststruct &t)
{
    printf("[%5d] I am (%d), I read: **%s**\n", (int)getpid(), t.stateid, state);        
}

int main(void)
{
    key_t key;
    if ((key = ftok(".", 'a')) == -1) {
        perror("ftok");
        exit(1);
    }

    int statesid;
    if ((statesid = shmget(key, sizeof(char)*512, 0600 | IPC_CREAT )) == -1) {
        perror("shmget error");
        exit(1);
    }

    if ((state = (char*)shmat(statesid, NULL, 0)) == 0)
    {
        perror("shmat");
        exit(1);
    }

    sprintf(state, "This is a string in shared memory %d", 919);

    teststruct tests[2];
    tests[0].stateid = 0;
    tests[0].next = &tests[1];
    tests[1].stateid = 0;
    tests[1].next = &tests[0];

    int t0, t1;
    if ((t0 = fork()) < 0)
    {
        perror("fork-1");
        exit(1);
    }
    else if (t0 == 0)
    {
            sm_read(tests[0]);
            printf("[%5d] sleeping\n", (int)getpid());
            sleep(2);
            printf("[%5d] waking\n", (int)getpid());
            sm_read(tests[0]);
            exit(0);
    }
    else if ((t1 = fork()) < 0)
    {
        perror("fork-2");
        exit(-1);
    }
    else if (t1 == 0)
    {
        printf("[%5d] sleeping\n", (int)getpid());
        sleep(1);
        printf("[%5d] waking\n", (int)getpid());
        strcpy(state, "1 write on 0 in theory.");
        sm_write(tests[1], state);
        exit(0);
    }

    int corpse;
    int status;
    while ((corpse = wait(&status)) > 0)
        printf("PID %5d died with status 0x%.4X\n", corpse, status);

    return 0;
}

示例运行

[20440] sleeping
[20440] waking
[20440] I am (0), I wrote on: 0
[20439] I am (0), I read: **This is a string in shared memory 919**
[20439] sleeping
[20439] waking
[20439] I am (0), I read: **1 write on 0 in theory.**
PID 20440 died with status 0x0000
PID 20439 died with status 0x0000

【讨论】:

  • 如何修改我的代码以使其按预期工作? (不考虑同步问题)
  • 顺便说一句,调用程序test 通常是个坏主意。 shell 有一个名为test 的内置命令,这可能会导致各种混乱。
  • 您写道:“请注意,在分叉后,如果任一进程更改了存储在其 state[0] 或 state[1] 中的值,则其他进程将看不到该更改”,您能否发布一个小而简单的代码 sn-p 来显示如何使它工作?特别是在函数“dosomething”中,我希望一个进程能够修改/读取共享的“状态”数组。
  • 非常感谢,请教最后两个问题: 1. shmctl e shmdt应该放在哪里? 2. 如果 state 是一个字符串数组,我可以做 tests[0] = schmat, ecc 吗?
  • (1) shmdt() 对于子进程是可选的(它们在死亡时必然会分离)。在父级中,您可以在 wait() 循环之前或之后使用 shmdt()。销毁共享内存段的shmctl() 应该在wait() 循环之后;在孩子们完成共享内存之前,您不想(或不能?)破坏共享内存。为了保持一致性,我会将两者都放在 wait() 循环之后,尽管这意味着父级在不使用共享内存时会挂在共享内存上。
【解决方案2】:

您对共享内存的大小有疑问。在:

 (statesid = shmget(key, sizeof(char*)*2, 0600 | IPC_CREAT )

您只是为 2 个指向 char 的指针保留空间。您需要为所有数据分配足够的空间,基于结构的数据是一种链接结构。虽然 fork() 和共享内存的用途对我来说不是很清楚,但代码可能类似于以下内容:

struct teststruct {
    int stateid;
    teststruct *next;
    //other things
};

void dosomething(teststruct &t){
    //forget about global space, you don't need it
}

int main() {

    key_t key;
    if ((key = ftok(".", 'a')) == -1) {
        perror("ftok");
        exit(1);
    }

    int statesid;
    int size_struct = sizeof(teststruct)*2; //assuming you will have only 1 level of linking
    if ((statesid = shmget(key, size_struct, 0600 | IPC_CREAT )) == -1) {
        perror("shmget error");
        exit(1);
    }

    //if you need to hold just one teststruct object data, you can do
    teststruct* p_test_struct = (teststruct*)shmat(statesid, NULL, 0);

    for (int i=0; i<2; i++){
        *p_test_struct = tests[i]; //this actually writes tests[i] into shared mem
        int t0, t1;
        switch (t0 = fork()) {
        case (0):
            dosomething(*p_test_struct);
            exit(0);
        case (-1):
            printf("\nError!");
            exit(-1);
        default:
            wait();
        }
    }

    return 0;
}

【讨论】:

  • fork 的目的只是为了“玩”“char *state[2]”,在 fork 之后需要在两个进程之间正确共享(读取/修改)。
【解决方案3】:

不,它没有。因为您使用的是 fork(多进程)而不是线程(多线程)。内存区域不共享给父进程和子进程。您将对孩子有相同的价值,但之后它将独立于另一个。

【讨论】:

  • 你必须想办法在两个进程之间建立通信。你有很多选项,套接字,文件,数据库。在我看来,Redis 是最好的选择。但是如果你只想用 C 来解决它,你可以尝试使用文件。我不知道这是否是最好的选择。
  • 通过shmat使用共享内存正在建立进程间的通信。
猜你喜欢
  • 2015-09-21
  • 1970-01-01
  • 2011-06-26
  • 2013-03-06
  • 1970-01-01
  • 2017-01-07
  • 1970-01-01
  • 2011-06-10
  • 2011-08-02
相关资源
最近更新 更多