8

共有メモリを使用する場合、各プロセスは、共有領域をそれぞれのアドレス空間の異なる領域にマップすることがあります。これは、共有領域内にポインタを格納する場合、共有領域の開始位置のオフセットとして格納する必要があることを意味します。残念ながら、これはアトミック命令の使用を複雑にします (例えば、ロックフリーのアルゴリズムを書こうとしている場合))。たとえば、単一のライターによって作成された、共有メモリ内に多数の参照カウント ノードがあるとします。ライターは、正の参照カウントを持つ有効なノードを指すように、ポインター 'p' を定期的にアトミックに更新します。リーダーは、最初の要素が参照カウントであるノード (構造体) の先頭を指しているため、「p」にアトミックに書き込みたいと考えています。p は常に有効なノードを指しているため、ref カウントのインクリメントは安全であり、'p' の逆参照や他のメンバーへのアクセスを安全に行うことができます。ただし、これはすべて、すべてが同じアドレス空間にある場合にのみ機能します。ノードと「p」ポインタが共有メモリに格納されている場合、クライアントは競合状態になります。

  1. x = p を読む
  2. y = x + オフセット
  3. y で refcount をインクリメントする

ステップ 2 で、p が変更され、x が有効なノードを指しなくなる場合があります。私が考えることができる唯一の回避策は、オフセットではなく実際のポインターを mmap された領域に格納できるように、すべてのプロセスが共有メモリをマップする場所に同意するよう強制することです。それを行う方法はありますか?mmap のドキュメントに MAP_FIXED がありますが、安全なアドレスを選択する方法がわかりません。

編集: x86 でインライン アセンブリと 'lock' プレフィックスを使用すると、「値 Z によるオフセット Y で ptr X をインクリメントする」ことは可能でしょうか? 他のアーキテクチャの同等のオプション? 多くのアセンブリを書いていません。必要な指示が存在するかどうかわかりません。

4

5 に答える 5

3

これは、UNIX システムでは些細なことです。共有メモリ関数を使用するだけです:

shgmet、shmat、shmctl、shmdt

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmat() は、shmid によって識別される共有メモリ セグメントを、呼び出しプロセスのアドレス空間にアタッチします。接続アドレスは、次の基準のいずれかで shmaddr によって指定されます。

shmaddr が NULL の場合、システムはセグメントを接続する適切な (未使用の) アドレスを選択します。

ここで自分のアドレスを指定するだけです。例: 0x20000000000

すべてのプロセスで同じキーとサイズを使用して shmget() を実行すると、同じ共有メモリ セグメントが得られます。同じアドレスで shmat() を実行すると、仮想アドレスはすべてのプロセスで同じになります。カーネルは、通常割り当てられる場所と競合しない限り、使用するアドレス範囲を気にしません。(アドレスを省略すると、配置するのが好きな一般的な領域がわかります。また、スタック上のアドレスと malloc() / new[] から返されたアドレスを確認してください。)

Linux では、root が /proc/sys/kernel/shmmax の SHMMAX を、共有メモリ セグメントに対応できる十分な数に設定していることを確認してください (デフォルトは 32MB)。

アトミック操作に関しては、Linux カーネル ソースからすべて取得できます。

include/asm-x86/atomic_64.h

/*
 * Make sure gcc doesn't try to be clever and move things around
 * on us. We need to use _exactly_ the address the user gave us,
 * not some alias that contains the same information.
 */
typedef struct {
        int counter;
} atomic_t;

/**
 * atomic_read - read atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically reads the value of @v.
 */
#define atomic_read(v)          ((v)->counter)

/**
 * atomic_set - set atomic variable
 * @v: pointer of type atomic_t
 * @i: required value
 *
 * Atomically sets the value of @v to @i.
 */
#define atomic_set(v, i)                (((v)->counter) = (i))


/**
 * atomic_add - add integer to atomic variable
 * @i: integer value to add
 * @v: pointer of type atomic_t
 *
 * Atomically adds @i to @v.
 */
static inline void atomic_add(int i, atomic_t *v)
{
        asm volatile(LOCK_PREFIX "addl %1,%0"
                     : "=m" (v->counter)
                     : "ir" (i), "m" (v->counter));
}

64 ビット版:

typedef struct {
        long counter;
} atomic64_t;

/**
 * atomic64_add - add integer to atomic64 variable
 * @i: integer value to add
 * @v: pointer to type atomic64_t
 *
 * Atomically adds @i to @v.
 */
static inline void atomic64_add(long i, atomic64_t *v)
{
        asm volatile(LOCK_PREFIX "addq %1,%0"
                     : "=m" (v->counter)
                     : "er" (i), "m" (v->counter));
}
于 2010-04-20T01:48:30.620 に答える
3

低レベルでは、x86 アトミック命令は、このツリーのすべてのステップを一度に実行できます。

  1. x = p を読む
  2. y = x + オフセットの増分
  3. y での参照カウント
//
      mov  edi, Destination
      mov  edx, DataOffset
      mov  ecx, NewData
 @Repeat:
      mov  eax, [edi + edx]    //load OldData
//Here you can also increment eax and save to [edi + edx]          
      lock cmpxchg dword ptr [edi + edx], ecx
      jnz  @Repeat
//
于 2010-03-22T10:44:19.233 に答える
2

カーネルは嫌いなアドレス(競合するアドレス)を拒否するだけなので、ランダムにアドレスを作成することを恐れないでください。0x20000000000を使用して、上記の私のshmat()答えを参照してください

mmapあり:

void * mmap(void * addr、size_t length、int prot、int flags、int fd、off_t offset);

addrがNULLでない場合、カーネルはそれをマッピングを配置する場所に関するヒントとして受け取ります。Linuxでは、マッピングは次に高いページ境界で作成されます。呼び出しの結果として、新しいマッピングのアドレスが返されます。

flags引数は、マッピングの更新が同じリージョンをマッピングしている他のプロセスに表示されるかどうか、および更新が基になるファイルに引き継がれるかどうかを決定します。この動作は、フラグに次の値の1つを正確に含めることによって決定されます。

MAP_SHAREDこのマッピングを共有します。マッピングの更新は、このファイルをマップする他のプロセスに表示され、基になるファイルに引き継がれます。msync(2)またはmunmap()が呼び出されるまで、ファイルは実際には更新されない場合があります。

エラー

EINVAL addr、length、またはoffsetは好きではありません(たとえば、大きすぎる、またはページ境界に配置されていない)。

于 2010-04-20T02:00:10.890 に答える
2

問題の説明に似たコードがあります。メモリ マップ ファイル、オフセット、およびファイル ロックを使用します。代替案は見つかりませんでした。

于 2010-03-22T03:59:14.010 に答える
1

ポインターにオフセットを追加しても、競合の可能性は作成されません。競合は既に存在します。少なくともARMもx86もポインターをアトミックに読み取ってメモリにアクセスできないため、オフセットを追加するかどうかに関係なく、ポインターアクセスをロックで保護する必要があります。

于 2016-08-18T16:23:00.117 に答える