コンテキスト:使っていますPOSIX 共有メモリプロセスグループ用の共有メモリスペースを提供します。私はデータを共有するためにしばらくこの方法を使用してきました。ところで、最近、特定の種類のプログラムで奇妙な問題が発生しました。
質問:私は、各プロセスが共有メモリ空間の共有合計に値を寄与する必要があるプログラムを作成しました。以前に共有オブジェクトがメモリにマップされた場合、合計はゼロに初期化されました。ただし、各プロセスが共有合計に自分の一部を追加しようとすると最新の値を見ることができますが、加算の結果は常に自分の値にゼロを加えたものと同じです。。以下を参照してください。
[21017] Adding 6 to 0!
[21020] Adding 33 to 0!
[21016] Adding 15 to 0!
[21018] Adding 24 to 0!
[21017] Got access! (0x7fe953fcb000 = 0)
[21017] Done (0x7fe953fcb000 = 6)
[21016] Got access! (0x7fe953fcb000 = 6)
[21016] Done (0x7fe953fcb000 = 15)
[21018] Got access! (0x7fe953fcb000 = 15)
[21018] Done (0x7fe953fcb000 = 24)
[21020] Got access! (0x7fe953fcb000 = 24)
[21020] Done (0x7fe953fcb000 = 33)
Sum = 33
各プロセスは、作成された最新の値を「見ますが」独自のコンポーネントを追加した後は、既存の値を無視するようです。共有メモリ空間に誰が書くことができるかを管理するアクセス制御システムがあるため、各アクセスが順番に整列していることがわかります。使用されるテストプログラムは次のとおりです(読者が実行したくありませんが)。
int main (void) {
int local_sum = 0, gid = -1;
volatile int *sum;
// Fork for four processes.
for (int i = 1; i < 4; i++) {
if (fork() == 0) break;
}
// Initialize the DSM. Set GID.
sum = (int *)dsm_init(&cfg);
gid = dsm_get_gid();
// Compute range.
for (int i = 0; i < 3; i++) {
local_sum += array[(gid * 3) + i];
}
// Add to local sum.
printf("[%d] Adding %d to %d!\n", getpid(), local_sum, *sum);
*sum = *sum + local_sum;
// Barrier.
dsm_barrier();
// Print sum if process zero.
if (gid == 0) printf("Sum = %d\n", *sum);
// Exit.
dsm_exit();
}
各プロセスが共有スペースのアドレスで正しい値を「見る」ことができますが、0x7fe953fcb000
追加後に追加中にそのアドレスの値がまだゼロであるかのように動作するのはなぜですか?
この問題について私を悩ませているのは次のとおりです。
- キャッシュの問題の場合、算術演算の前に正しい値を印刷できますが、それが正しくないのはなぜですか?
- プロセスヒープに共有値を追加しています。コンパイラは値が0であると仮定し、何も最適化できません。
なぜこれが起こるのかについての説明はありますか?私は何が起こったのかを知るために私のプログラムでGDBを使ってみました。しかし、私が知っている限り、それはメモリアドレスの値をレジスタに移動するだけです。最適化の問題は見たことがありません。
答え1
私が見ることができるのは、4つのプロセスがすばやく連続して生成され、それぞれ* sum + = some_value操作を実行しようとしていることです。加算する前に両方 *sum が 0 であると考えることは完全に可能です。
抽象アセンブリ構文を試してみましょう。 C文
*sum = *sum + local_sum
としてコンパイルされます
LOAD *sum into R0
LOAD local_sum into R1
ADD R1 to R0
STORE R0 to *sum
4つのプロセスがシーケンスを実行するために競合します。どちらかがR0を* sumに保存する前に、両方ともLOAD * sumを* sumに保存することは完全に可能です。実際にSTORE R0によってトリガされたシステムコールがあることを考慮すると(再計画ポイントも同様です)*まあ、良い機会ですね。たとえば、セマフォを使用して共有変数へのアクセスを同期する必要があります。