blog

Linuxの共有メモリ使用に関するよくある落とし穴と分析

いわゆる共有メモリは、複数のプロセスが同じメモリ空間にアクセスできるようにすることであり、IPCの利用可能な最速の形式です。これは、あまり効率的に実行される他の通信メカニズム用に設計されています。多く...

Jun 26, 2025 · 12 min. read
シェア

いわゆる共有メモリは、複数のプロセスが同じメモリ空間にアクセスすることを可能にし、利用可能なIPCの最速形態です。これは、あまり効率的に動作しない他の通信メカニズムのために設計されています。シグナルなどの他の通信機構と組み合わせて、プロセス間同期や相互排他を実現するためによく使われます。他のプロセスは、同じ共有メモリーセグメントを自分のアドレス空間に「リンク」することができます。すべてのプロセスは共有メモリ内のアドレスにアクセスできます。あるプロセスがこの共有メモリに書き込むと、その変更は同じ共有メモリにアクセスできる他のプロセスから即座に見えるようになります。共有メモリの使用は、大規模なデータ処理時のメモリ消費を大幅に削減しますが、共有メモリの使用には多くの落とし穴があり、注意しないと簡単にプログラムがクラッシュしてしまいます。

共有メモリのサイズ制限を超える?

Linuxサーバーでは、SHMMAXパラメーターで定義される共有メモリー全体のサイズに制限があり、以下のコマンドを実行することでSHMMAXの値を知ることができます:

# cat /proc/sys/kernel/shmmax 

マシン上に作成された共有メモリの合計サイズがこの制限を超えた場合、プログラム内で標準エラーperrorを使用すると、次のようなメッセージが表示されることがあります:

unable to attach to shared memory 

解決策

1.SHMMAXの設定

SHMMAXのデフォルト値は32MBです。SHMMAXパラメータは通常、以下のいずれかの方法で2GBに設定します:

procファイルシステムを直接変更すれば、マシンを再起動せずにSHMMAXのデフォルト設定を変更できます。私が使っている方法は、/>etc/rc.localスタートアップファイルに以下のコマンドを入れる方法です:

# echo "2147483648" > /proc/sys/kernel/shmmax 

sysctlコマンドを使用してSHMMAX値を変更することもできます:

# sysctl -w kernel.shmmax=2147483648 

最後に、このカーネル・パラメーターを/etc/sysctl.confスタートアップ・ファイルに挿入することで、この変更を恒久的にすることができます:

# echo "kernel.shmmax=2147483648" >> /etc/sysctl.conf 

2.SHMMNIの設定

次にSHMMNIパラメータを見てください。このカーネル・パラメータは、システム全体の共有メモリ・セグメントの最大数を設定するために使用されます。このパラメータのデフォルト値は4096です。この値で十分であり、通常は変更する必要はありません。

SHMMNIの値は、以下のコマンドを実行することで確認できます:

# cat /proc/sys/kernel/shmmni 
4096 

3.SHMALLの設定

最後に、SHMALL共有メモリ・カーネル・パラメータを見てください。このパラメータは、システムが一度に使用できる共有メモリの総量を制御します。要するに、このパラメーターの値は常に最低でなければなりません:

ceil(SHMMAX/PAGE_SIZE) 

SHMALLのデフォルトサイズは2097152で、以下のコマンドで照会できます:

# cat /proc/sys/kernel/shmall 
2097152 

SHMALLのデフォルト設定で十分です。

注: i386 プラットフォームの Red Hat Linux のページサイズは 4096 バイトです。しかし、より大きなインメモリページサイズの設定をサポートする bigpages を使用することができます。#p#

複数のシュマットで何が問題になりますか?

共有メモリ・セグメントが最初に作成されたときは、どのプロセスからもアクセスできません。共有メモリ・セクションにアクセスできるようにするには、shmat関数によってプロセス空間にアタッチする必要があります。この関数はlinux/shm.hで宣言されています:

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

引数shmidは、識別子であるshmget()の戻り値です;

パラメータshmflgはアクセス許可フラグであり、0であれば制限は設定されません。いくつかのパーミッションは

#define SHM_RDONLY 010000 /* attach read-only else read-write */ 
#define SHM_RND 020000 /* round attach address to SHMLBA */ 
#define SHM_REMAP 040000 /* take-over region on attach */ 

SHM_RDONLYが指定された場合、共有メモリ領域は読み取り専用となります。

パラメータshmaddrは共有メモリのアタッチメントポイントで、値が異なると意味も異なります:

?nullでない場合、戻りアドレスは、呼び出し元がshmflgパラメータにSHM_RND値を指定しているかどうかに依存します。shmaddrで指定されていない場合、共有メモリ領域はshmaddrで指定されたアドレスに追加されます。

Ø 通常、パラメータ shmaddr は NULL に設定されます。

shmat()コールは、成功すれば共有メモリ領域へのポインタを返し、それを使って共有メモリ領域にアクセスできます。

マッピングの関係を以下に示します:

図1.1 共有メモリ・マッピング・マップ

このうちshmaddrは、物理メモリ空間をプロセスの仮想メモリ空間にマッピングする際の、仮想メモリ空間内のメモリブロックの開始アドレスを示します。 使用上、プロセス内のどのアドレスが占有されていないかは一般に不明なので、プロセスの仮想メモリアドレスにマッピングする物理空間内のメモリを指定するのは好ましくなく、カーネル自身のアドレスを指定するのが一般的でしょう:

void ptr = shmat(shmid, NULL,0); 

この方法で共有メモリをマウントするのは、1回の呼び出しであれば問題ありませんが、プロセスが同じ共有メモリを複数回マウントする可能性があります shmat、物理メモリは同じブロックを指しており、shmaddrがNULLの場合、返されるリニアアドレス空間は毎回異なります。そして、この共有メモリを指す参照カウントは増えていきます。つまり、プロセスのリニア空間の複数のブロックが同じ物理アドレスを指すことになります。そのため、この共有メモリをマウントしたプロセスのリニアアドレスがshmdtされない、つまり要求されたリニアアドレスが1つも解放されない場合、プロセスの仮想メモリ空間を消費し続けることになり、最終的にプロセスのリニア空間が不足し、次のshmatやその他の操作が失敗する可能性があります。

解決策

要求する共有メモリ・ポインタがNULLかどうかを判断し、NULLであればマウントに使用し、NULLでなければ終了することで、共有メモリをマウントするのが初めてかどうかを識別できます。

void* ptr = NULL; 
... 
if (NULL != ptr) 
return; 
ptr = shmat(shmid,ptr,0666); 

添付ファイル

関数shmatは、識別番号shmidを持つ共有メモリを呼び出しプロセスのアドレス空間にマッピングします。マッピングされたアドレスは、パラメータshmaddrとshmflgと基準によって決定されます:

パラメータshmaddrが値NULLを取る場合、システムは自動的にプロセス空間への共有メモリリンクの先頭アドレスを決定します。

パラメータ shmaddr が NULL 以外の値を取り、パラメータ shmflg に SHM_RND フラグが指定されていない場合、システムはアドレス shmaddr を使用して共有メモリをリンクします。

パラメータ shmaddr が値 NULL を取らず、パラメータ shmflg に SHM_RND フラグ・ビットが指定されている場合、システムはアドレス shmaddr を整列してから共有メモリをリンクします。SHM_RNDオプションはラウンドアライメントを意味し、定数SHMLBAはローバウンダリアドレスの倍数を表し、式 "shmaddr - "はアドレスshmaddrをローバウンダリアアドレスの整数倍に移動することを意味します。

Shmgetは共有メモリを作成しますが、キーが同じ場合はどのようなエラーになりますか?

shmget()は、共有メモリ領域の作成や既存の共有メモリ領域へのアクセスに使用されます。この関数はヘッダーファイル linux/shm.h で定義されており、以下のプロトタイプを持っています:

#include 
#include 
int shmget(key_t key, size_t size, int shmflg); 

引数の key は、ftok() で得られたキー値です;

パラメータ size は、メモリのサイズをバイト単位で指定します;

パラメータshmflgは動作フラグビットで、そのマクロのいくつかを以下に定義します:

IPC_CREATE : shmgetが呼ばれると、その値を他の共有メモリ領域のキーと比較し、同じキーがあれば共有メモリ領域が既に存在することを意味し、その共有メモリ領域の識別子を返し、そうでなければ新しい共有メモリ領域を作成してその識別子を返します。

IPC_EXCL : このマクロは IPC_CREATE と一緒に使わなければ意味がありません。shmflg が IPC_CREATE | IPC_EXCL を取る場合、メモリ領域が存在すると -1 を返すことを意味し、エラーコードは EEXIST です。

新しい共有メモリ領域を作成する場合はsizeが0より大きくなければならず、既存の共有メモリ領域にアクセスする場合は0でなければならないことに注意してください。

通常、共有メモリを作成する場合、プロセス内でshmgetを使用して共有メモリを作成します。

Int shmid = shmget(key, size, IPC_CREATE|0666); 

もう一方のプロセスでは、shmgetと同じキーを使って、作成された共有メモリーを取り出します。

Int shmid = shmget(key, size, IPC_CREATE|0666); 

作成されたプロセスとマウントされたプロセスのキーが同じで、対応するサイズが異なる場合、shmgetは失敗しますか?

Ø 作成された共有メモリのサイズは調整できますが、作成された共有メモリのサイズを調整できるのはダウンのみで、アップはできません。

として

shm_id = shmget(key,4194304,IPC_CREAT); 

4Mの共有メモリが作成されます。

shm_id = shmget(key,10485760,IPC_CREAT); 

標準エラー出力を使用して10Mサイズの共有メモリを作成すると、以下のエラーメッセージが生成されます:

shmget error: Invalid argument 

ただし、使用する場合は:

shm_id = shmget(key,3145728,IPC_CREAT); 

これは、共有メモリを使用する場合、キーは共有メモリの一意の識別子として使用され、共有メモリのサイズは共有メモリを区別することができないことを示しています。

これはどのような問題を引き起こすのでしょうか?

複数のプロセスが共有メモリを作成できる場合、キーが同じで、一方のプロセスが他方のプロセスよりも小さい共有メモリを作成する必要があり、共有メモリが大きい方のプロセスが先に共有メモリを作成し、共有メモリが小さい方のプロセスが後に共有メモリを作成すると、共有メモリが小さい方のプロセスが共有メモリが大きい方のプロセスの共有メモリを取得し、その共有メモリのサイズと内容を変更するため、共有メモリが大きい方のプロセスがクラッシュする可能性があります。共有メモリの小さいプロセスは、共有メモリの大きいプロセスの共有メモリを取得し、その共有メモリのサイズと内容を変更します。

解決策

方法I:

すべての共有メモリが作成されるときに排他的作成を使用する、つまりIPC_EXCLフラグを使用します:

Shmget(key, size,IPC_CREATE|IPC_EXCL); 

共有メモリをフックアップする場合、まず排他的論理和で共有メモリが作成されているかどうかを判断し、まだ作成されていなければエラー処理を行い、作成されていればフックアップします:

Shmid = Shmget(key, size,IPC_CREATE|IPC_EXCL); 
If (-1 != shmid) 
{ 
Printf("error"); 
} 
Shmid = Shmget(key, size,IPC_CREATE); 

方法II:

自分のプログラムが他のプログラムと一意のキー値について事前に合意できることが望ましいのですが、自分のプログラムが共有メモリの一部のキー値を選択することはできないため、実際には必ずしも可能ではありません。したがって、キーをIPC_PRIVATEに設定することで、オペレーティングシステムはキーを無視し、新しい共有メモリを作成し、キー値を指定し、共有メモリのIPC識別子IDを返し、他のプロセスに新しい共有メモリの識別子IDを伝えます。つまり、このメソッドは共有メモリの作成にキーを使用せず、オペレーティングシステムによって一意性が保証されます。#p#

ftokは常にユニークなキー値を生成しますか?

IPC 通信を確立するには、システムで id 値を指定する必要があります。通常、この id 値は ftok 関数で取得します。

ftokのプロトタイプは以下の通り:

key_t ftok( char * pathname, int proj_id) 

pathnameは指定したファイル名、proj_idはサブオーダー番号です。

通常のUNIX実装では、key_tの戻り値を得るために、ファイルのインデックス・ノード番号を取り出し、その前にサブオーダー番号を付けます。ファイルのインデックス・ノード番号を65538(16進数に変換すると0×010002)と指定し、proj_idの値を38(16進数に変換すると0×26)と指定した場合、最終的なkey_tの戻り値は0×26010002となります。

ファイル・インデックスのノード番号を照会るには: ls -i

pathnameで指定されたファイル名とproj_idパラメータで指定された番号に基づいて、ftok関数はIPCオブジェクトの一意なキー値を生成します。実際には、同じproj_idの場合、ファイル名が同じである限り、ftokが常に一貫したキー値を返すことを保証することが可能であることを理解するのは簡単です。しかし、この理解は完全には正しくなく、アプリケーション開発にとって非常に微妙な罠をしかける可能性があります。ftokの実装は、このようなリスクがあるため、つまり、複数のプロセスの同じ共有メモリへのアクセスで連続して時間内にftok関数を呼び出すと、ファイルで指定されたパス名が削除され、再作成されている場合、ファイルシステムは、新しいiノード情報の同じ名前のファイルに与えられるので、これらのプロセスは、ftokは正常であることができますが、キーの値が同じであることが保証されていないと呼ばれます。その結果、元のファイルは元のファイルとは異なります。その結果、プロセスは同じ共有メモリ・オブジェクトにアクセスしようとしていますが、キーが異なるため、同じ共有メモリを指すことはなくなります。共有メモリが作成された場合、アプリケーションはエラーを報告しませんが、共有メモリ・オブジェクトを介してデータを転送するという目的は果たされません。これらの共有メモリが作成された場合、アプリケーション全体でエラーは報告されませんが、共有メモリオブジェクトを通じてデータを転送するという目的は達成されません。

そのため、key_tの値が変わらないようにしたい場合は、ftokファイルが削除されないようにするか、ftokする必要がなくkey_tの値を固定で指定する必要があります。

key_t値を生成したファイルが削除されている場合、以下のケースのように、現在使用している共有メモリのkey_t値が他のプロセスのkey_t値と衝突する可能性が高くなります:

解決策

方法I:

方法II:

また

他のプロセスにフックアップするよう通知するプロセスを作成する場合、ftokメソッドでKeyを取得するのではなく、ファイルやプロセス間通信を使って通知することをお勧めします。#p#

共有メモリ削除の落とし穴?

プロセスが共有メモリ領域の使用を終了すると、関数 shmdt によって共有メモリ領域から切り離されます。この関数は sys/shm.h で宣言されており、以下のプロトタイプを持っています:

#include 
#include 
int shmdt(const void *shmaddr); 

引数shmaddrはshmat関数の戻り値。

共有メモリ領域からプロセスが削除されると、データ構造 shmid_ds の shm_nattch が 1 つデクリメントされます。しかし、共有セグメントはまだ存在し、shm_attch が 0 になったとき、つまり、共有メモリ領域を使用するプロセスがなくなったときにのみ、カーネルから削除されます。一般に、プロセスが終了すると、アタッチされていた共有メモリ領域は自動的に切り離されます。

採用:

int shmctl( int shmid , int cmd , struct shmid_ds *buf ); 

を使用して、すでに存在する共有メモリを削除します。

最初の引数shmidは、shmgetが返すトークンです。

2番目のパラメータ cmd は実行するアクションです。cmd には3つの値があります:

コマンド説明

  • IPC_STATは、共有メモリに関連付けられたshmid_ds構造体のデータ反映の値を設定します。
  • IPC_SET プロセスが適切な特権を持っている場合、共有メモリに関連する値を shmid_ds データ構造で提供される値に設定します。
  • IPC_RMID 共有メモリ・セグメントを削除します。

3番目のパラメータbufは、共有メモリのモードとパーミッションを含む構造体へのポインタです。

共有メモリにアクセスしているすべてのプロセスから共有メモリが切断されている場合、IPC_RMIDサブコマンドを呼び出すと、共有メモリの識別子が直ちに削除され、共有メモリ領域と関連するすべてのデータ構造が削除されます;

共有メモリにまだ別のプロセスが接続されている場合、IPC_RMIDサブコマンドが呼び出された直後には共有メモリはシステムから削除されず、IPC_PRIVATEに設定されて「削除済み」とマークされます。既存の接続がすべて切断されるまで、共有メモリはシステムから削除されません。

shmctlによって共有メモリが削除されると、たとえそれがシステム上にまだ存在していたとしても、新しい接続を受け付けることができなくなることに注意することが重要です!したがって、削除後に新しい接続ができないことが分かっている場合は、共有メモリを削除しても安全です。そうでない場合は、削除後に新しい接続を行うと失敗する可能性があります!

shmdtとshmctlの違い:

Shmdtはプロセス空間から共有メモリを切り離し、プロセス内のshmidを無効にして使用不能にします。しかし、空間は保持されます。

そして、shmctl(sid,IPC_RMID,0)は共有メモリを削除し、完全に利用できなくして領域を解放します。

Read next

推奨iOS開発ツール

この記事は Guo Li Cheng ブログから抜粋したもので、著者が推奨する iOS 開発ツールをいくつか紹介していますが、著者自身が普段使用しているものです。

Jun 26, 2025 · 2 min read