3

以下は、foo.c共有メモリにデータを書き込むプログラムです。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
    key_t key;
    int shmid;
    char *mem;

    if ((key = ftok("ftok", 0)) == -1) {
        perror("ftok");
        return 1;
    }

    if ((shmid = shmget(key, 100, 0600 | IPC_CREAT)) == -1) {
        perror("shmget");
        return 1;
    }

    printf("key: 0x%x; shmid: %d\n", key, shmid);

    if ((mem = shmat(shmid, NULL, 0)) == (void *) -1) {
        perror("shmat");
        return 1;
    }

    sprintf(mem, "hello");
    sleep(10);
    sprintf(mem, "exit");

    return 1;
}

bar.c同じ共有メモリからデータを読み取る別のプログラムを次に示します。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
    key_t key;
    int shmid;
    volatile char *mem;

    if ((key = ftok("ftok", 0)) == -1) {
        perror("ftok");
        return 1;
    }

    if ((shmid = shmget(key, sizeof (int), 0400 | IPC_CREAT)) == -1) {
        perror("shmget");
        return 1;
    }

    printf("key: 0x%x; shmid: %d\n", key, shmid);

    if ((mem = shmat(shmid, NULL, 0)) == (void *) -1) {
        perror("shmat");
        return 1;
    }

    printf("looping ...\n");
    while (strncmp((char *) mem, "exit", 4) != 0)
        ;

    printf("exiting ...\n");

    return 0;
}

最初に 1 つの端末でライター プログラムを実行します。

touch ftok && gcc foo.c -o foo && ./foo

ライター プログラムがまだ実行されている間に、別のターミナルでリーダー プログラムを実行します。

gcc -O1 bar.c -o bar && ./bar

リーダー プログラムは無限ループに入ります。オプティマイザーが次のコードを最適化したようです

    while (strncmp((char *) mem, "exit", 4) != 0)
        ;

    while (1)
        ;

mem一度読み取られた後にデータを変更できるものは何もループ内にないためです。

しかし、私はこの理由のために正確に宣言memしました。volatileコンパイラがそれを最適化するのを防ぎます。

volatile char *mem;

コンパイラがまだ の読み取りを最適化しないのはなぜmemですか?

ところで、私はうまくいく解決策を見つけました。機能する解決策は、変更することです

    while (strncmp((char *) mem, "exit", 4) != 0)
        ;

    while (mem[0] != 'e' || mem[1] != 'x' || mem[2] != 'i' || mem[3] != 't')
        ;

両方のケースであると宣言されているにもかかわらず、コンパイラstrncmp((char *) mem, "exit", 4) != 0が最適化するのに最適化しないのはなぜですか?mem[0] != 'e' || mem[1] != 'x' || mem[2] != 'i' || mem[3] != 't'char *memvolatile

4

2 に答える 2

6

6.7.3 型修飾子

6 [...] volatile 修飾されていない型の左辺値を使用して、volatile 修飾された型で定義されたオブジェクトを参照しようとした場合、動作は未定義です。133)

133)これは、プログラム内で実際にオブジェクトとして定義されていない場合でも、修飾された型で定義されているかのように動作するオブジェクトに適用されます (メモリマップされた入出力アドレスのオブジェクトなど)。

それはまさにあなたのコードで観察したことです。コンパイラは基本的に、「とにかく動作は未定義」という野生の自由の下でコードを最適化しています。

strncmpつまり、揮発性データに直接正しく適用することは不可能です。

あなたができることは、修飾子を破棄しない独自の比較を実装するかvolatile(これはすでに行ったことです)、揮発性データを不揮発性ストレージにコピーする揮発性を認識する方法を使用し、それらstrncmpを後者に適用することです。

于 2016-12-09T01:44:39.730 に答える
4

書くことで、実際には揮発性バッファではないことを関数に(char *)mem伝えています。strncmp実際、strncmp他の C ライブラリ関数は、揮発性バッファで動作するようには設計されていません。

実際、揮発性バッファで C ライブラリ関数を使用しないようにコードを変更する必要があります。オプションは次のとおりです。

  • 揮発性バッファで動作する C ライブラリ関数に代わる独自の関数を記述します。
  • 適切なメモリ バリアを使用します。

最初のオプションを選択しました。しかし、他のプロセスが 4 回の読み取りの間にメモリを変更した場合はどうなるか考えてみてください。この種の問題を回避するには、2 番目のオプションであるプロセス間メモリ バリアを使用する必要があります。この場合、バッファは不要になりvolatile、C ライブラリ関数の使用に戻ることができます。(コンパイラは、バリア チェックによってバッファが変更される可能性があると想定する必要があります)。

于 2016-12-09T02:01:05.417 に答える