本章ではLinux 2.4 カーネルで実装されている IPC 機構のセマフォ、共有メモリおよびメッセージキューについて記述します。 四つの節で構成され、最初の三つの節では セマフォと メッセージキュー、 共有メモリを順に取り上げて、インターフェースとサポート関数を説明します。また、 最後の節では、三つの機構で共有される共通関数群とデータ構造を説明します。
この節で説明している関数はユーザレベルのセマフォ機構の実装になっています。この実装部分はカーネルスピンロックとカーネルセマフォの利用に頼っていることに注意しましょう。 混乱を避けるため、カーネルのセマフォを参照するときは「カーネルセマフォ」と呼ぶことにします。その他の単に「セマフォ」というときは、ユーザレベルのセマフォを指すことにします。
全てのsys_semget()への呼出しは、カーネルグローバルな sem_ids.semカーネルセマフォで保護されます。 新しいセマフォのセットを作らなければならない場合は、 newary() 関数が呼ばれて、新しいセマフォセットの作成と初期化をします。そして新しいセットID が呼出し元に戻されます。
キーの値が既存のセマフォセットに対して発行されたものだった場合は、 ipc_findkey() が呼び出され、相当するセマフォディスクリプタ配列インデックスを見つけます。パラメータと呼出し元の パーミッションは、セマフォのセットIDを返却する前に確認されます。
IPC_INFOと SEM_INFO そして SEM_STAT コマンドについては、必要な関数処理を行う semctl_nolock() を呼び出します。
GETALL、 GETVAL、 GETPID、 GETNCNT、 GETZCNT、 IPC_STAT、 SETVAL、 SETALL コマンドについては、 必要な関数処理を行う semctl_main() を呼び出します。
IPC_RMIDや IPC_SET コマンドについては、必要な関数処理を行う semctl_down() を呼び出します。 両方の演算の間は、グローバルな sem_ids.sem カーネルセマフォを保持します。
呼出し引数の有効性を確認した後、セマフォの操作データをユーザ空間から一時バッファへコピーします。 小さい一時バッファで大きさが十分であればスタックバッファが使われます。不十分なら、大きいバッファが割り当てられます。セマフォの操作データのコピーが終わった後に、グローバルなセマフォスピンロックをロックします。そしてユーザが指定したセマフォセットIDが確認されます。セマフォセットへのパーミッションも有効か確認されます。
ユーザが指定したセマフォ操作が全て解析されます。この処理の間、カウントは SEM_UNDO フラグセットをもつ全ての操作を保持します。
セマフォ値を減算する操作のときには、 decrease
フラグが設定されます。そして、セマフォの値を変更する(つまり増加もしくは減少する)もの全てについて、alter
フラグが設定されます。そして変更される各セマフォの値が有効か確認されます。
もし、これらの操作のいずれかがセマフォの状態を変化させるのであれば、このセマフォセットに関連する undo 構造体が 現在のタスクの undo リストから探されます。この検索の間、もし undo 構造体の任意のセマフォセットIDが -1 であると分かったならば、 freeundos() が呼ばれ、 undo 構造体が解放されてリストから削除されます。もし、undo 構造体がこのセマフォセットについて見つからなければ、 alloc_undo() が呼ばれて割り当ておよび初期化が行われます。
一連の操作を実行するために
try_atomic_semop() 関数を do_undo
パラメータを 0 にして呼び出します。返り値は、操作が処理されたか失敗したか、あるいはブロックの必要があったため実行されなかったかを示しています。各々の場合を以下に示します。
try_atomic_semop() 関数は、一連の全ての操作が成功したときに、ゼロを返します。この場合には、 update_queue() が呼び出され、セマフォセットのうち保留されているセマフォの操作のキューを走査して、もうブロックの必要がない休止タスクを起こします。この処理によって、この場合の sys_semop() システムコールの実行が完了します。
もし、 try_atomic_semop()が負の値を返した場合、 障害状態に遭遇したことになります。この場合、操作は全て実行されません。 これは、セマフォ操作が、無効なセマフォ値になったり、IPC_NOWAITにマークづけられた操作が実行完了できなかった場合に起ります。エラー状態はその後、sys_semop()の呼出し元に返されます。
sys_memop()から戻る前に、セマフォセットの保留されたセマフォ操作のキューを走査するため update_queue() を呼出し、もうブロックの必要のない休止タスクを起こします。
try_atomic_semop()関数は、セマフォ操作の一つがブロックされ、 一連のセマフォ操作が実行されなかった場合には 1 を返します。この場合、新しい sem_queueの要素がこれらのセマフォ操作を含めて初期化されます。もし、これらの操作のいずれかがセマフォの状態を変化させそうであれば、新しいキューの要素はキューの末尾に追加されます。 そうでなければ新しいキューの要素がキューの先頭に挿入されます。
現在のタスクのsemsleeping
要素は、タスクがこの
sem_queue 要素
で休止されているかどうかを示しています。現在のタスクが TASK_INTERRUPTIBLE とマークされ、
sem_queueのsleeper
要素がこのタスクが休止していることを示すため設定されます。
そしてグローバルセマフォスピンロックを解除し、現在のタスクを休止するためschedule()を呼び出します。
起こされたとき、なぜ起こされたか、どのように反応すべきかを判断するため、タスクはグローバルセマフォロックを再ロックします。以下のような場合が処理されます。
status
要素が 1 に設定されていたら、タスクはセマフォ操作を再び試みるために起こされた。もう1度
try_atomic_semop() 呼出しが一連のセマフォ操作の実行を行う。もし try_atomic_sweep() が 1 を返すなら、タスクは上記のように再度ブロックされなければならない。そうでなければ成功して 0 が戻ってくるか、失敗した場合は適切なエラーコードが返される。
sys_semop() が戻る前に、current->semsleeping がクリアされる。そして、
sem_queueは、キューから削除される。もし指定されたセマフォ操作がいずれも変更の操作(増分、減分)なら、
update_queue() が、セマフォセットの保留されたセマフォ操作のキューを走査するために呼び出され、もうブロックの必要のない休止タスクを起こす。
status
要素が 1 で 「ない」うえに
sem_queue 要素がキューからはずされなければ、タスクは割り込みにより起こされたことになる。この場合システムコールは失敗し EINTR を返す。戻る前に current->semsleeping はクリアされ、
sem_queueはキューから削除される。さらに、もし変更を加える操作があったら
update_queue() も呼び出される。
status
要素が1に設定されて『なく』、
sem_queue 要素がキューから削除されてなければ、セマフォ操作は既に
update_queue() により実行されている。キューstatus
は、成功すれば0に、失敗時に負のエラーコードになり、システムコールの返り値になる。以下の構造体は、セマフォのサポートに特に使われます。
/* One sem_array data structure for each set of semaphores in the system. */
struct sem_array {
struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */
time_t sem_otime; /* last semop time */
time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct sem_queue *sem_pending; /* pending operations to be processed */
struct sem_queue **sem_pending_last; /* last pending operation */
struct sem_undo *undo; /* undo requests on this array * /
unsigned long sem_nsems; /* no. of semaphores in array */
};
/* One semaphore structure for each semaphore in the system. */
struct sem {
int semval; /* current value */
int sempid; /* pid of last operation */
};
struct seminfo {
int semmap;
int semmni;
int semmns;
int semmnu;
int semmsl;
int semopm;
int semume;
int semusz;
int semvmx;
int semaem;
};
struct semid64_ds {
struct ipc64_perm sem_perm; /* permissions .. see
ipc.h */
__kernel_time_t sem_otime; /* last semop time */
unsigned long __unused1;
__kernel_time_t sem_ctime; /* last change time */
unsigned long __unused2;
unsigned long sem_nsems; /* no. of semaphores in
array */
unsigned long __unused3;
unsigned long __unused4;
};
/* One queue for each sleeping process in the system. */
struct sem_queue {
struct sem_queue * next; /* next entry in the queue */
struct sem_queue ** prev; /* previous entry in the queue, *(q->pr
ev) == q */
struct task_struct* sleeper; /* this process */
struct sem_undo * undo; /* undo structure */
int pid; /* process id of requesting process */
int status; /* completion status of operation */
struct sem_array * sma; /* semaphore array for operations */
int id; /* internal sem id */
struct sembuf * sops; /* array of pending operations */
int nsops; /* number of operations */
int alter; /* operation will alter semaphore */
};
/* semop system calls takes an array of these. */
struct sembuf {
unsigned short sem_num; /* semaphore index in array */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
/* Each task has a list of undo requests. They are executed automatically
* when the process exits.
*/
struct sem_undo {
struct sem_undo * proc_next; /* next entry on this process */
struct sem_undo * id_next; /* next entry on this semaphore set */
int semid; /* semaphore set identifier */
short * semadj; /* array of adjustments, one per
semaphore */
};
以下の関数は特にセマフォのサポートのために使われます。
newary() は、新しいセマフォセットに必要となるメモリを割り当てるために、
ipc_alloc()を利用しています。そして、セマフォセット記述子とセット中の各セマフォの全てに十分なメモリを割り当てます。
割り当てられたメモリはクリアされ、セマフォセットディスクリプタの最初の要素のアドレスが、
ipc_addid() へと渡されます。
ipc_addid() は、新しいセマフォセット記述子のエントリ配列を予約し、データ(
struct kern_ipc_perm)を初期化します。
グローバルなused_sems
変数は、新しいセットのセマフォ数で更新されて、新しいセットのためのデータ(
struct kern_ipc_perm)の初期化が完了します。このセットに関わる他の初期化は以下の通りです。
sem_base
要素が、新たに割り当てられたデータの(
struct sem_array)のすぐあとのアドレスに初期化される。これは、セット中の最初のセマフォの場所に対応する。
sem_pending
キューは、空に初期化される。ipc_addid()の呼出しに続く全ての操作は、グローバルセマフォスピンロックを保持した状態で実行します。グローバルセマフォスピンロックをアンロックした後に、newary() は、(sem_buildid()経由で) ipc_buildid() を呼出します。この関数は、セマフォセットディスクリプタのインデックスを使用し、ユニークな ID を生成します。そして、newary() の呼出し元に戻ります。
freeary() は semctl_down()によって呼び出され、以下に列挙する機能を実行します。グローバルなセマフォスピンロックがロックされた状態で呼び出され、スピンロックが解除された状態で戻されます。
semctl_down() は、 semctl()システムコールの IPC_RMID と IPC_SET 操作を提供します。セマフォセットIDとパーミッションは、これらの操作に先立って確認され、どちらの場合も、操作の間はグローバルセマフォスピンロックを保持します。
IPC_RMID 操作では、セマフォセットの削除を行うため、 freeary()を呼び出します。
IPC_SET 操作では、セマフォセットのuid
、gid
、mode
およびctime
要素を更新します。
semctl_nolock() は sys_semctl() から呼び出され、IPC_INFO、SEM_INFO および SEM_STAT 機能を実行します。
IPC_INFO と SEM_INFO は、臨時に
seminfo バッファを初期化し、変更されなかったセマフォの統計データを読み込ませます。そして、グローバルのsem_ids.sem
カーネルセマフォを確保し、
seminfo 構造体の semusz
と semaem
要素を与えられた(IPC_INFO または SEM_INFO)コマンドに従い更新します。システムコールの返り値は、セマフォセットIDの最大値になります。
SEM_STAT は、臨時に
semid64_dsバッファを初期化します。その後、グローバルセマフォスピンロックを保持し、sem_otime
、sem_ctime
とsem_nsems
の値をバッファにコピーします。このデータはその後ユーザ空間にコピーされます。
semctl_main() は、 sys_semctl() により呼び出され、以下の節で示している多くのサポートする機能を実行します。全ての操作に先だって、semctl_main() は、グローバルセマフォロックをロックし、セマフォセットIDとパーミッションの有効性を確認します。スピンロックは戻る前に解除されます。
GETALL 操作は、現在のセマフォ値を一時的なカーネルバッファへ読み込み、ユーザ空間へコピーします。もしセマフォバッファが小さければ、小さなスタックバッファが使われます。そうでなければ、スピンロックが一時的に落とされ、大きなバッファを確保します。セマフォ値を一時バッファへコピーする間、スピンロックを保持します。
SETALL 操作は、セマフォ値をユーザ空間からカーネルの一時バッファへコピーし、その後セマフォセットへコピーします。一時バッファへユーザ空間から値をコピーする間、および正しい値か確認する間、スピンロックを落とします。もしセマフォセットが小さければ、スタックバッファが使われ、そうでなければ大きなバッファが確保されます。以下の操作をセマフォセットへ行う間、スピンロックを再取得し、保持します。
sem_ctime
値が設定される。
IPC_STAT 操作では、sem_otime
、sem_ctime
とsem_nsems
値をスタックバッファへコピーする。そのデータは、その後スピンロックを落としてからユーザ空間へコピーする。
エラーのない場合、GETVALでは、システムコールの返り値に指定したセマフォの値が設定される。
エラーのない場合、GETPIDでは、システムコールの返り値にセマフォでの最後の操作に対応する pid を設定します。
エラーのない場合、GETNCNT ではシステムコールの返り値に、セマフォが 0 未満になるのを待っているプロセスの数を設定します。この数値は count_semncnt() 関数が計算します。
エラーのない場合、GETZCNT ではシステムコールの返り値に、セマフォが0に設定されるのを待っているプロセスの数を設定します。この数値は count_semzcnt() 関数が計算します。
新しいセマフォ値の有効性を確認したあと、以下の機能が実行されます。
sem_ctime
値を更新する。count_semncnt() は、セマフォ値が 0 より小さくなるのを待っているタスク数を数えます。
count_semzcnt() は、セマフォ値が 0 になるのを待っているタスク数を数えます。
update_queue() は、セマフォセットの保留中 semops キューを走査して、
try_atomic_semop() を呼び出し、
どのセマフォ操作のシーケンスが成功しそうかを判断します。
もしキュー要素の状態により、ブロックされたタスクがすでに起こされていることが示されていたら、
そのときはそのキュー要素はスキップされます。
キューのその他の要素では、q-alter
フラグが
try_atomic_semop() の undo パラメータとして渡され、変更の操作から戻る前に実行される操作を表します。
もし一連の操作がブロックされるなら、update_queue() は一切変更されることなく戻ります。
もしセマフォ操作の一つが無効なセマフォ値を引き起こしたり、IPC_NOWAIT とマークされている操作が完了できなかったならば、一連の操作は失敗します。そのような場合は、セマフォ操作によりブロックされていたタスクは起こされ、キューの状態として適切なエラーコードが設定されます。キュー要素はもちろんキューから削除されます。
もし一連の操作が変更を生じないならば、
try_atomic_semop()
の undo パラメータとして 0 を渡します。
もしそれらの操作が成功すれば、処理は完了したと見なして、キューから削除します。ブロックされたタスクが起こされ、キュー要素のstatus
が成功を示すよう設定されます。
もしセットの操作がセマフォの値を変更する場合で、処理が成功できるなら、もうブロックされる必要のない休止タスクが起こされます。操作が実行されなくても、キュー要素がキューから削除されることはありません。セマフォ操作は、起こされたタスクによって実行されます。
try_atomic_semop() は、 sys_semop() と update_queue() により呼び出され、一連のセマフォ操作が全て成功するかどうか判断します。これは、各操作の実行を試みることによって判断が行われます。
もしブロックされた操作に遭遇したなら、プロセスはアボートし、全ての操作は差し戻されます。 もしIPC_NOWAIT が設定されていたなら -EAGAIN が返されます。そうでなければ 1 が返され、一連のセマフォ操作がブロックされたことを示します。
もしセマフォ値がシステムの制限を越えて変更されるならば、全ての操作は差し戻され、 -ERANGE が返されます。
もしシーケンス中の全ての操作が成功しても、do_undo
パラメータが0でなければ、
全ての操作が差し戻されて、0 が返されます。もし do_undo
パラメータが 0 なら、
全ての操作が成功し、強制的に残され、セマフォセットのsem_otime
メンバが更新されます。
sem_revalidate() は、グローバルセマフォスピンロックが一時的に落とされ、再度ロックしなければならないときに呼び出されます。 つまり、 semctl_main() と alloc_undo() によって呼び出されます。セマフォIDとパーミッションの有効性を確認し、良ければグローバルセマフォスピンロックをロックして戻ります。
freeundos() は、プロセスの undo リストを走査し、要求された undo 構造体を探します。 もし見つかれば、undo 構造体はリストから削除され、解放されます。 そして、プロセスリストの次の undo 構造体へのポインタが返されます。
alloc_undo() は、グローバルセマフォスピンロックを獲得して呼ばれることを期待しています。エラーがあった場合は、スピンロックを解除して戻ります。
グローバルセマフォスピンロックが解除され、 sem_undo構造体と、セット中の各セマフォの修正値の配列の両方に十分なメモリを確保するため、kmalloc()を呼び出します。 成功すれば、グローバルスピンロックが sem_revalidate()を呼び出すことで再確保されます。
新しい semundo 構造体が初期化され、この構造体のアドレスが呼出し元によって与えられたアドレスへ格納されます。新しい undo 構造体は、現在のタスクの undo リストの先頭へ位置させられます。
sem_exit() は、do_exit() により呼び出され、存在するタスクの全ての undo 修正を実行する責任があります。
もし現在のプロセスがセマフォでブロックされているなら、 グローバルセマフォスピンロックを保持して sem_queueリストから削除します。
そして現在のタスクのための undo リストが走査され、リストの各要素の処理毎にグローバルセマフォスピンロックの取得と解除が行われつつ、以下の操作が実行されます。以下の操作は、各 undo 要素ごとに実行されます。
sem_otime
パラメータが更新される。リストの処理が完了したならば、current->semundo の値をクリアします。
sys_msgget()へのコール全体は、グローバルメッセージキューセマフォ( msg_ids.sem)により保護されています。
新しいメッセージキューを作らなければならない場合、 newque()関数が 呼び出され、新しいメッセージキューの作成と初期化を行い、新しいキューIDを呼出し元に返します。
もし与えられたキーの値が既存のメッセージキューへなら、 ipc_findkey()が呼び出され、グローバルメッセージキューデスクリプタ配列(msg_ids.entries)の対応するインデックスを探します。呼出し元のパラメータとパーミッションは、メッセージキューIDを戻す前に確認されます。検索操作と確認は、グローバルメッセージキュースピンロック(msg_ids.ary)が保持された状態で行われます。
sys_msgctl()へ渡されるパラメータは、メッセージキューID(msqid
)、操作(cmd
)、タイプ
msgid_dsのユーザ空間バッファへのポインタ(buf
)です。
6 つの操作がこの関数で提供されています。IPC_INFO, MSG_INFO, IPC_STAT, MSG_STAT, IPC_SET, IPC_RMIDです。メッセージキューIDと操作パラメータが確認され、操作(cmd)が以下のように実行されます。
グローバルメッセージキューの情報がユーザ空間にコピーされます。
タイプ struct msqid64_ds の一時バッファが初期化され、グローバルメッセージキュースピンロックを取得します。 呼び出したプロセスのアクセス権を確認したら、メッセージキューIDに対応しているメッセージキューの情報を、一時バッファへ読み込みます。そしてメッセージキュースピンロックを解除します。一時バッファの内容は、ユーザ空間へ copy_msqid_to_user() により書き出します。
ユーザデータは、 copy_msqid_to_user()によりコピーされます。グローバルメッセージキューセマフォとスピンロックはこのとき取得され、最後に解除されます。メッセージキューID と現在のプロセスのアクセス権の有効性が確認された後、メッセージキューの情報がユーザから与えられたデータで更新されます。その後、 expunge_all() と ss_wakeup() が呼び出され、全てのメッセージキューの受け手と送り手のウエイトキューで休止しているプロセスを起こします。これは、そのときには受け手が厳密なアクセス権により排除されていたり、送り手がキューサイズが増したことによりメッセージの送信が可能になる場合があるためです。
グローバルメッセージキューセマフォが取得され、グローバルメッセージキュースピンロックがロックされます。メッセージキューIDと現在のタスクのアクセス権の有効性が確認されたあと、 freeque()が呼び出されてメッセージキューIDに対応づけられているリソースを解放します。 そして、グローバルメッセージキューセマフォとスピンロックが解除されます。
sys_msgsng() は、パラメータとしてメッセージキューID(msqid
)、
struct msg_msg タイプのバッファへのポインタ(msgp
)、送るメッセージのサイズ(msgsz
)と、待ちか否かを示すフラグ(msgflg
)を受け取ります。メッセージキューIDに対応するのは、2つのタスクウエイトキューと、1つのメッセージウエイトキューがあります。受け手ウエイトキューにこのメッセージを待っているタスクがあれば、メッセージは直接受け手へ届けられ、受け手は起こされます。
そうでなければ、メッセージウエイトキューに十分な余裕があれば、メッセージはこのキューへ格納されます。結局は、送り手のタスクは自身を送り手ウエイトキューへいれます。sys_msgsnd()が処理する操作のより詳細な内容は次のようになります。
msg
へ読み込みます。
msg
のメッセージタイプとメッセージサイズ領域も初期化されます。
msgflg
に指定されていれば、グローバルメッセージキュースピンロックが解除され、メッセージ用のメモリ領域が解放されて、EAGAIN を返します。msg
をメッセージウエイトキュー(msg->q_messages) へ格納します。
大域変数のmsg_bytes
や msg_hdrs
はメッセージに使われる総バイト数やシステム全体のメッセージ数を意味していますが、 これらと同様、メッセージキュー記述子の q_cbytes
と q_qnum
領域も更新します。
メッセージの送信が成功したり、キューにいれられた場合は、メッセージキュー記述子のq_lspid
とq_stime
領域を更新し、グローバルメッセージスピンロックを解除します。sys_msgrcv() 関数は、パラメータとしてメッセージキューID(msqid
)、
msg_msg 型のバッファへのポインタ(msgp
)、希望するメッセージのサイズ(msgsz
)とフラグ(msgflg
)を受け取ります。メッセージキューIDに関連するメッセージウエイトキューを検索し、要求する型にマッチするキューの最初のメッセージを見つけ、与えられたユーザバッファへコピーします。もし、そのようなメッセージが、メッセージウエイトキューに見つからなかったら、要求元のタスクは希望するメッセージがくるまで受け手ウエイトキューへ入れられます。ss_msgrcv() による操作の、より詳細な説明は以下の通りです。
msgtyp
から検索モードから派生して
convert_mode()を起動する。そして sys_msgrcv() はグローバルメッセージキュースピンロックをロックする。そしてメッセージキューID に対応するメッセージキュー記述子を得る。もしメッセージキューがなければ EINVAL を返す。
msgtyp
以下で最も小さいタイプのキューの最初のメッセージを探す。msgflg
がエラー許容なしを示していたら、グローバルメッセージキュースピンロックを解除し、E2BIG を返す。msgflg
が確認される。もし IPC_NOWAIT が設定されていたら、グローバルメッセージキュースピンロックを解除し、ENOMSG を返す。そうでなければ、受け手は以下のように受け手ウエイトキューへ入れられる。
msr
を割り当て、ウエイトキューの先頭に追加する。msr
の r_tsk
メンバに現在のタスクを設定する。r_msgtype
と r_mode
メンバは初期化され、メッセージのタイプとモードをそれぞれ要求された値にする。
msgflg
が MSG_NOERROR となっていたら、 msr
の r_maxsize メンバは msgsz
の値が設定される。そうでなければ INT_MAX が設定される。
r_msg
はメッセージをまだ受け取っていない状態を表すように初期化される。
msr
の r_msg
メンバ がチェックされる。
このメンバはパイプラインのメッセージの格納に使われ、エラーの場合にはエラー状態が格納される。
もし、r_msg
メンバが要求されたメッセージでいっぱいになったら、
最後のステップ へ進む。それ以外の場合は、グローバルメッセージキュースピンロックが再度ロックされる。r_msg
メンバは、スピンロックを待っている間にメッセージが受け取られていないか、再度確認される。もしメッセージが受け取られているなら、
最後のステップ へ進む。r_msg
メンバが変更されていないなら、再試行のためタスクが起こされる。この場合、msr
はキューから削除される。もしタスクに保留されたシグナルがあれば、グローバルメッセージキュースピンロックは解除され、 EINTR が返される。
それ以外では、関数は
戻って 、再試行される。
r_msg
メンバにより、休止中にエラーが発生したことが分かれば、グローバルメッセージキュースピンロックは解除され、エラーが返される。msp
の有効性を確認したあと、メッセージタイプがmsp
の mtype
メンバに書き込まれ、
store_msg() が呼び出されて、メッセージの内容がmsr
のmtext
メンバへコピーされます。最後にメッセージ用のメモリが関数
free_msg()により解放されます。
メッセージキューのデータ構造体は msg.c で定義されています。
/* one msq_queue structure for each present queue on the system */
struct msg_queue {
struct kern_ipc_perm q_perm;
time_t q_stime; /* last msgsnd time */
time_t q_rtime; /* last msgrcv time */
time_t q_ctime; /* last change time */
unsigned long q_cbytes; /* current number of bytes on queue */
unsigned long q_qnum; /* number of messages in queue */
unsigned long q_qbytes; /* max number of bytes on queue */
pid_t q_lspid; /* pid of last msgsnd */
pid_t q_lrpid; /* last receive pid */
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
};
/* one msg_msg structure for each message */
struct msg_msg {
struct list_head m_list;
long m_type;
int m_ts; /* message text size */
struct msg_msgseg* next;
/* the actual message follows immediately */
};
/* message segment for each message */
struct msg_msgseg {
struct msg_msgseg* next;
/* the next part of the message follows immediately */
};
/* one msg_sender for each sleeping sender */
struct msg_sender {
struct list_head list;
struct task_struct* tsk;
};
/* one msg_receiver structure for each sleeping receiver */
struct msg_receiver {
struct list_head r_list;
struct task_struct* r_tsk;
int r_mode;
long r_msgtype;
long r_maxsize;
struct msg_msg* volatile r_msg;
};
struct msqid64_ds {
struct ipc64_perm msg_perm;
__kernel_time_t msg_stime; /* last msgsnd time */
unsigned long __unused1;
__kernel_time_t msg_rtime; /* last msgrcv time */
unsigned long __unused2;
__kernel_time_t msg_ctime; /* last change time */
unsigned long __unused3;
unsigned long msg_cbytes; /* current number of bytes on queue */
unsigned long msg_qnum; /* number of messages in queue */
unsigned long msg_qbytes; /* max number of bytes on queue */
__kernel_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_pid_t msg_lrpid; /* last receive pid */
unsigned long __unused4;
unsigned long __unused5;
};
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
struct msq_setbuf {
unsigned long qbytes;
uid_t uid;
gid_t gid;
mode_t mode;
};
newque() は、新しいメッセージキュー記述子( struct msg_queue)のためのメモリを割り当て、 ipc_addid() を呼び出して、新しいメッセージキュー記述子用にメッセージキュー配列のエントリを予約させます。 メッセージキュー記述子は以下のように初期化されます。
q_stime
とq_rtime
メンバは 0 に初期化される。q_ctime
メンバは CURRENT_TIME に設定される。q_qbytes
)が MSGMNB に設定され、キューにより現在使われているバイト数(q_cbytes
)が0に初期化される。q_messages
)、待ち手ウエイトキュー(q_receivers
)、送り手ウエイトキュー(q_senders
)がそれぞれ空に初期化される。メッセージキューが削除されるときは、 freeque() 関数が呼ばれます。この関数は、グローバルメッセージキュースピンロックが、呼出し元関数によって既にロックされているものと仮定します。指定されるメッセージキューに関連している全てのカーネルリソースを解放します。まず、(msg_rmid経由で) ipc_rmid() を呼び出し、グローバルメッセージキュー記述子の配列からメッセージキュー記述子を削除します。そして、 expunge_all を呼び出して、全ての受け手を起こし、 ss_wakeup()を呼び出して、このメッセージキューで休止している全ての送り手を起こします。 最後に、グローバルメッセージキュースピンロックを解除します。このメッセージキューに格納されている全てのメッセージは解放され、メッセージキュー記述子のメモリも解放されます。
ss_wakeup() は与えられたメッセージ送り手ウエイトキューで待ちになっている全てのタスクを起こします。 この関数が freeque()から呼ばれたなら、キューにある全ての送り手はキューから削除されます。
ss_add() はパラメータとしてメッセージキュー記述子とメッセージ送り手データ構造体を受け取ります。現在のプロセスをもとにメッセージ送り手データ構造体のtsk
メンバを埋め、現在のプロセスの状態を TASK_INTERRUPTIBLE に変更し、そして与えられたメッセージキューの送り手ウエイトキューの先頭へメッセージ送り手データ構造体を挿入します。
与えられたメッセージ送り手データ構造体(mss
) はまだ対応するウエイトキューに保管されたままです。そして、ss_del() は、mss
をキューから削除します。
expunge_all() は、パラメータとしてメッセージキュー記述子(msq
)と受け手が起こされた理由を示す整数値(res
)を受け取ります。それぞれのmsq
に対応して休止中の受け手に対して、r_msg
メンバが起こされた理由(res
)を設定し、その対応する受け手のタスクが起こされます。
この関数はメッセージキューが削除されるときと、メッセージ制御操作が行われるときに呼び出されます。
プロセスがメッセージを送るとき、 sys_msgsnd() 関数は、最初に load_msg() 関数を呼び出し、メッセージをユーザ空間からカーネル空間へと読み込みます。 メッセージはカーネルメモリ内ではデータブロックのリンクリストとして表現されています。 最初のデータブロックに関連しているのは、全てのメッセージを表す msg_msg 構造体です。msg_msg 構造体に関連づけられているデータブロックは、DATA_MSG_LEN のサイズに制限されています。 データブロックと構造体はメモリで一ページで取りうる最大の一つの連続したメモリブロックに割り当てられています。もし全てのメッセージが最初のデータブロックに入りきらなければ、追加のデータブロックが割り当てられ、リンクリストに登録されます。これら追加のデータブロックは、DATA_SEG_LEN にサイズが制限されており、各々は関連する msg_msgseg) 構造体を読み込みます。msg_msgseg 構造体と対応するデータブロックはメモリで一ページで取りうる最大の一つの連続したメモリブロックに割り当てられています。この関数は、成功時には新しい msg_msg 構造体のアドレスを返り値にもって戻ります。
store_msg() 関数は、 sys_msgrcv() により呼び出され、受けたメッセージを呼出し元の提供するユーザ空間バッファへ再度組み立てます。 msg_msg 構造体と msg_msgseg構造体により示されるデータは、ユーザ空間バッファへ順次コピーされます。
free_msg() 関数はメッセージデータ構造体 msg_msg とメッセージセグメントのメモリを解放します。
convert_mode() は
sys_msgrcv() から呼び出されます。パラメータとして、指定するメッセージタイプのポインタ(msgtyp
)とフラグ(msgflg
)を受け取ります。
この関数は、msgtyp
とmsgflg
の値に基づいて、呼出し元へ検索モードを返します。もし、msgtyp
が0ならば、 SEARCH_ANYを返します。
もし、msgtyp
の値が0より小さければ、msgtype
はその絶対値に設定され、SEARCH_LESSEQUAL を返します。もし MSG_EXCEPT がmsgflg
に指定されていれば、SEARCH_NOTEQUAL が返されます。そうでなければ、SEARCH_EQUAL が返されます。
testmsg() 関数は、メッセージが受け手の指定する条件に合致するか調べます。もし、以下の条件の一つが真となれば、1を返します。
プロセスは pipelined_send() を使うことで、対応づけられているメッセージキューへ入れるのではなく、直接待機中の受け手へメッセージを送ることができます。
与えられたメッセージを待つ最初の受け手を見つけるため、
testmsg() 関数を呼び出します。もし見つかれば、待っている受け手は受け手ウエイトキューから削除され、関連する受け手のタスクは起こされます。メッセージは受け手のr_msg
メンバへ格納されて、1を返します。メッセージを待っている受け手がいない場合、0を返します。
受け手を探すプロセスでは、与えられたメッセージに対して小さすぎるサイズを要求していることで、潜在的受け手が分かるでしょう。そのような受け手はキューから削除され、起こされてエラー状態 E2BIG を渡されます。このエラーは、r_msg
メンバに格納されます。この検索は、有効な受け手が見つかるか、キューを見尽くすまで継続します。
copy_msqid_to_user() はカーネルバッファの内容をユーザバッファにコピーします。パラメータとしてユーザバッファと msqid64_ds 型のカーネルバッファと、新しいIPC バージョンか古い IPC バージョンかを示すバージョンフラグを受け取ります。 もしバージョンフラグが IPC_64 ならば、 copy_to_user() が起動され、カーネルバッファからユーザバッファへ直接コピーします。そうでなければ、struct msqid_ds 型の一時バッファが初期化され、カーネルデータはこの一時バッファへ変換されます。そして、copy_to_user() が呼び出され、一時バッファの内容がユーザバッファへコピーされます。
copy_msqid_from_user() 関数は、パラメータとしてstruct msq_setbuf型のカーネルメッセージバッファとユーザバッファと、新しいIPC バージョンか古い IPC バージョンかを示すバージョンフラグを受け取ります。IPCバージョンが新しい場合、copy_from_user()が呼び出されて、ユーザバッファの内容を
msqid64_ds 型の一時バッファへコピーします。
そしてカーネルバッファのqbytes
, uid
, gid
とmode
領域を一時バッファの対応する領域の値にします。古い IPC のバージョンならば、
msqid_ds 型の一時バッファを代りに用います。
sys_shmget() は、呼出し全体をグローバル共有メモリセマフォにより保護します。
新しい共有メモリセグメントを作る必要のある場合、 関数 newseg() が呼び出され、新しい共有メモリセグメントを作成し初期化します。新しいセグメントのIDが呼出し元へ返されます。
既存の共有メモリセグメントの キー値が与えられている場合、共有メモリ記述子配列の対応するインデックスを見つけ出し、パラメータと呼出し元のパーミッションを確認後、共有メモリセグメントIDを返します。 検索操作と有効性の確認はグローバル共有メモリスピンロックを保持した状態で行われます。
一時バッファ shminfo64 が システムワイドの共有メモリパラメータとともに読み込まれ、呼出し元のアプリケーションからアクセスするユーザ空間へコピーします。
共有メモリのシステムワイドの統計情報を集める間は、グローバル共有メモリセマフォとグローバル共有メモリスピンロックを取得します。
関数
shm_get_stat() を呼び出し、メモリ中に存在する共有メモリページ数とスワップアウトした共有メモリページ数を計算します。
swap_attempts
とswap_successes
の計数は、常に0 とされています。これらの統計値は一時バッファ
shm_info へ格納され、呼び出したアプリケーションのユーザ空間へとコピーされます。
SHM_STAT と IPC_STAT のため、 struct shmid64_ds 型の一時バッファが初期化され、グローバル共有メモリスピンロックをロックします。
SHM_STAT の場合は、共有メモリセグメント ID パラメータは、1次元のインデックス(つまり、0 から n で、n はシステムの共有メモリ ID の番号) を期待しています。 インデックスの有効性を確認後、 ipc_buildid() が(shm_buildid()経由で)呼び出され、インデックスを共有メモリ ID へ変換します。SHM_STAT を渡した場合、共有メモリ ID が返り値になります。この機能は文献にないけれども、ipcs(8) プログラムのために準備されていることに注意しましょう。
IPC_STAT の場合、共有メモリセグメント ID パラメータについて、IDが shmget() を呼出して生成されていることを期待します。 この ID は処理を行う前に有効性が確認されます。IPC_STAT を渡した場合、0 が返り値となります。
SHM_STATと IPC_STAT の双方について、呼出し元のアクセス権の有効性を確認します。要求された統計情報は一時バッファへ読み込まれ、その後呼出し元へコピーされます。
アクセス権の有効性を確認後、グローバル共有メモリスピンロックをロックし、共有メモリセグメントIDの有効性を確認します。SHM_LOCK と SHM_UNLOCK 双方で、その処理を行うため、 shmem_lock() を呼び出します。 shmem_lock()のパラメータには、処理すべき関数の識別子を渡します。
IPC_RMID の処理の間は、グローバル共有メモリセマフォとグローバル共有メモリスピンロックが取得されます。共有メモリID の有効性を確認したあと、もし current attachment がなければ、 shm_destroy() を呼出し、共有メモリセグメントを破壊します。そうでなければ、SHM_DEST フラグをセットし、これが破壊するという印になります。そして、他のプロセスを共有メモリIDの参照から守るために、IPC_PRIVATE フラグをセットします。
共有メモリセグメントIDとユーザのアクセス権の有効性を確認後、共有メモリセグメントのuid
やgid
、そしてmode
フラグをユーザデータで更新します。shm_ctime
領域も更新します。これらの変更は、グローバル共有メモリセマフォとグローバル共有メモリスピンロックを保持した状態で処理されます。
sys_shmat() は、共有メモリセグメントIDと共有メモリセグメントを配置するアドレス(shmaddr
)、そして以下に示すフラグをパラメータに取ります。
もしshmaddr
が0でなく、SHM_RND フラグが指定されていたなら、shmaddr
は SHMLBA の倍数へと丸められます。もし、shmaddr
がSHMLBAの倍数でなく、SHM_RND も指定されていなければ、EINVAL が返されます。
呼出し元のアクセス権の有効性が確認され、共有メモリセグメントのshm_nattch
領域が1つ増分され
ます。ここで、この増分は 割り当てカウントが0ではないことを保証し、共有メモリセグメントをセグメントの割り当て処理中の破壊から守るために行われます。これらの操作は、グローバル共有メモリスピンロックを保持した状態で行われます。
共有メモリセグメントページへの仮想メモリマップを作るため、 do_mmap() 関数を呼び出します。これはカレントタスクの mmap_sem
セマフォを保持した状態で行われます。MAP_SHARED フラグが do_mmap() へ渡されます。もしアドレスが呼出し元から渡されたなら、 MAP_FIXED フラグも do_mmap() へ渡されます。そうでなければ、 共有メモリセグメントへ割り当てる仮想アドレスは do_mmap() が選びます。
注:
shm_inc()が do_mmap() 関数コールの中で shm_file_operations
構造体を経て起動されます。この関数は、 PID を設定し、現在の時刻を設定し、この共有メモリセグメントへの割り当て数を増分するため呼び出されます。
do_mmap() を呼び出したあとは、グローバル共有メモリセマフォとグローバル共有メモリスピンロックの両方が取得されます。割り当てカウントは減少されます。
shm_inc() を呼び出すため、shmat()への呼び出しに対して割り当て数の正味の変更が 1 になります。もし割り当て数の減少した後で、セグメントが破壊にマーク(SHM_DEST)されたら、 shm_destroy() が呼び出され、共有メモリセグメントの資源が解放されます。`
最後に、共有メモリのマップされる仮想メモリが、呼出し元のユーザの指定したアドレスへ返されます。 do_mmap() よりエラーコードが返されたときには、このエラーコードがシステムコールの返り値として戻されます。
sys_shmdt() の処理中はグローバル共有メモリセマフォが保持されます。現在のプロセスのmm_struct
について、共有メモリアドレスに対応する vm_area_struct
を検索します。
もし見つかれば、 do_munmap() が呼び出されて、共有メモリセグメントの仮想アドレスへのマッピングが解除されます。
ここで、 do_munmap() は共有メモリの予約を維持する関数の shm_close()をコールバックし、他の割り当てがない共有メモリセグメントの資源を解放することに注意してください。
sys_shmdt() は無条件に0を返します。
struct shminfo64 {
unsigned long shmmax;
unsigned long shmmin;
unsigned long shmmni;
unsigned long shmseg;
unsigned long shmall;
unsigned long __unused1;
unsigned long __unused2;
unsigned long __unused3;
unsigned long __unused4;
};
struct shm_info {
int used_ids;
unsigned long shm_tot; /* total allocated shm */
unsigned long shm_rss; /* total resident shm */
unsigned long shm_swp; /* total swapped shm */
unsigned long swap_attempts;
unsigned long swap_successes;
};
struct shmid_kernel /* private to the kernel */
{
struct kern_ipc_perm shm_perm;
struct file * shm_file;
int id;
unsigned long shm_nattch;
unsigned long shm_segsz;
time_t shm_atim;
time_t shm_dtim;
time_t shm_ctim;
pid_t shm_cprid;
pid_t shm_lprid;
};
struct shmid64_ds {
struct ipc64_perm shm_perm; /* operation perms */
size_t shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
unsigned long __unused1;
__kernel_time_t shm_dtime; /* last detach time */
unsigned long __unused2;
__kernel_time_t shm_ctime; /* last change time */
unsigned long __unused3;
__kernel_pid_t shm_cpid; /* pid of creator */
__kernel_pid_t shm_lpid; /* pid of last operator */
unsigned long shm_nattch; /* no. of current attaches */
unsigned long __unused4;
unsigned long __unused5;
};
struct shmem_inode_info {
spinlock_t lock;
unsigned long max_index;
swp_entry_t i_direct[SHMEM_NR_DIRECT]; /* for the first blocks */
swp_entry_t **i_indirect; /* doubly indirect blocks */
unsigned long swapped;
int locked; /* into memory */
struct list_head list;
};
newseg() 関数は、新しい共有メモリセグメントを作る必要が出たときに呼ばれます。
これは、新しいセグメントのキー、フラグ、サイズの三つのパラメータを受け取ります。作成する共有メモリセグメントのサイズが SHMMIN と SHMMAX の間にあり、共有メモリセグメントの総数が SHMALL を越えないといった有効性を確認した後に、新しい共有メモリセグメント記述子を割り当てます。
shmem_file_setup() 関数は、tmpfs 型のアンリンクされたファイルを作成するために、その後に呼び出されます。返されるファイルポインタは、共有メモリセグメント記述子の対応するshm_file
領域へ保存されます。ファイルサイズはセグメントサイズと同じに設定されます。新しい共有メモリセグメント記述子は初期化され、グローバル IPC 共有メモリ記述子配列へ挿入されます。共有メモリセグメント ID は (
ipc_buildid()経由で) shm_buildid() により作成されます。このセグメントIDは、inode に対応する i_ino
領域と同様に共有メモリセグメント記述子の id
領域へ保存されます。
これに加えて、shm_file_operation
構造体で定義される共有メモリ操作のアドレスは、対応するファイルへと保存されます。
システム全体での共有メモリセグメントの総数を示すグローバル変数 shm_tot
の値も、この変更を反映して増分されます。
成功した場合、セグメント ID が呼出し元アプリケーションへ返されます。
shm_get_stat() は、全ての共有メモリ構造体を順に辿っていき、共有メモリに使われているメモリの全ページ数と、スワップアウトされている全メモリページ数を計算します。各共有メモリセグメントにはファイル構造体とiノード構造体があります。求めるデータをiノード経由で取得するため、アクセスされる各iノード構造体のスピンロックのロックとロック解除が、それぞれに行われます。
shmem_lock() はパラメータに共有メモリ記述子へのポインタとロックかアンロックを示すフラグを受け取ります。共有メモリセグメントのロック状態は対応するiノードへ格納されます。 この状態は、要求されたロック状態と比較されます。shmem_lock() は、一致した場合に単に返ります。
対応するiノードのセマフォを保持している間に、iノードのロック状態が設定されます。各共有メモリセグメントの各ページにおいて、以下のリストの項目が実行されます。
shm_destroy() の間、共有メモリページ総数は、共有メモリセグメントの削除の責任をとって調整されます。
ipc_rmid() は、(shm_rmid() 経由で)呼び出され、共有メモリID を削除します。
shmem_lock が呼び出され、共有メモリページをアンロックし、各ページの参照カウントを効率的に 0 に減算します。 fput() が呼び出され、対応するファイルオブジェクトの利用カウンタf_count
を1減算します。そして、必要であれば、ファイルオブジェクトの資源を解放します。kfree() が呼び出され、共有メモリセグメント記述子を解放します。
shm_inc() は PID を設定し、現在の時間を設定し、そして与えられた共有メモリセグメントの付属数を1増加させます。これらの操作は、グローバル共有メモリスピンロックを保持して行われます。
shm_close() は、shm_lprid
と shm_dtim
メンバを更新し、割り当て共有メモリセグメント数を 1 減算します。共有メモリセグメントが他に割り当てられていなければ、
shm_destroy() を呼び出し、共有メモリセグメントの資源を解放します。これらの操作は全てグローバル共有メモリセマフォとグローバル共有メモリスピンロックの両方を保持して行われます。
関数 shmem_file_setup() は、 tmpfs ファイルシステムに存在するアンリンクされたファイルを、与えられた名前とサイズに設定します。もしこのファイルに対して十分なシステムメモリリソースがあれば、新しい dエントリを tmpfs のマウントルートに作成します。そして新しいファイルディスクリプタと tmpfs 型の新しい iノードオブジェクトを割り当てます。そして、d_instantiate() を呼出し、新しい d エントリオブジェクトに新しい i ノードオブジェクトを対応づけます。dエントリオブジェクトのアドレスをファイルディスクリプタへ保存します。iノードオブジェクトのi_size
メンバはファイルサイズに設定され、iノードがアンリンクされた印をつけるため i_nlink
を 0 に設定します。そして、 shmem_file_setup() は、shmem_file_operations
構造体のアドレスも f_op
メンバへ保存します。
そして、ファイルディスクリプタの f_mode
と f_vfsmnt
メンバが適切に初期化されます。関数 shmem_truncate() が呼び出され、iノード オブジェクトの初期化を完了させます。成功したなら、 shmem_file_setup() は新しいファイルディスクリプタを返します。
Linux のセマフォ、メッセージおよび共有メモリ機構は一連の共通プリミティブで構成されています。これらのプリミティブを以下で説明します。
メモリ割り当てが PAGE_SIZE より大きければ、vmalloc() がメモリ割り当てに使われます。そうでなければ、 kmalloc() が GFP_KERNEL 付きで呼び出されメモリを割り当てます。
新しいセマフォセット、メッセージキュー、ないしはメモリセグメントが追加されると、 ipc_addid() は、 関連するディスクリプタの配列のサイズがシステムの最大値に対して十分に大きいことを保証するため、最初に grow_ary() を呼び出します。そしてディスクリプタの配列の最初の未使用要素を検索します。もし未使用の要素が見つかったら、使用中のディスクリプタのカウンタを1増加します。 新しい資源のディスクリプタのために kern_ipc_perm 構造体を初期化し、新しいディスクリプタの配列のインデックスを返します。 ipc_addid() が成功したら、与えられたIPC のタイプのグローバルスピンロックを取得して戻ります。
ipc_rmid() は、IPCディスクリプタをIPCタイプのグローバルディスクリプタ配列から削除し、 使用されている ID のカウントを更新します。そして必要なら対応するディスクリプタ配列の最大 ID を調整します。 与えられた IPC ID に対応する IPC ディスクリプタへのポインタを返します。
ipc_buildid() は与えられたIPCタイプの各ディスクリプタに対応するユニークなIDを作成します。この ID は新しいIPC要素が追加されるときに作成されます(例: 新しい共有メモリセグメントや新しいセマフォセット)。 IPC ID は対応するディスクリプタ配列のインデックスへ容易に変換できます。各IPCタイプは、ディスクリプタが追加される毎に増加させるシーケンス番号を管理しています。 ID はシーケンス番号をSEQ_MULTIPLIER 倍し、この積にディスクリプタ配列のインデックスを加算することで生成します。シーケンス番号が存在することにより、古い IPC ID の利用を検出することができます。
ipc_checkid() は、与えられた IPC IDを SEQ_MULTIPLIER で除算し、その商を対応するディスクリプタに保存されているシーケンス値と比較します。もし値が等しいなら、IPC ID は有効であると考えられ、1を返します。そうでなければ、0 を返します。
grow_ary() は、与えられた IPC タイプの 最大の(設定可能な) ID 数を動的に変更できる機能を取り扱います。これは現在の最大限度が恒久的なシステムの限度(IPCMNI)を越えないように強制し、必要なら引き下げます。これはまた既存のディスクリプタ配列が十分に大きくなるよう保証します。 もし既存の配列サイズが十分に大きかったら、現在の最大限度を返します。そうでなければ、新しい大きな配列を割り当てます。そして古い配列を新しい配列へコピーし、古い配列を解放します。与えられた IPC タイプのディスクリプタ配列を更新するときは、対応するグローバルスピンロックを保持します。
ipc_findkey() は、特定の ipc_ids オブジェクトのディスクリプタの配列を検索して特定のキーを探します。見つかったら、対応するディスクリプタのインデックスを返します。もしキーが見つからなかったら、-1 を返します。
ipcperms() は、IPC 資源にアクセスするユーザ、グループと他のパーミッションをチェックします。もしパーミッションが有効なら0を返し、そうでなければ-1を返します。
ipc_lock() は、 IPC IDをパラメータの一つとして受け取ります。与えられた IPC タイプのグローバルスピンロックをロックし、特定のIPC IDに対応するディスクリプタへのポインタを返します。
ipc_unlock() は、示された IPC タイプのグローバルスピンロックを解除します。
ipc_lockall() は、与えられたIPC機構(つまり共有メモリとセマフォとメッセージング)用のグローバルスピンロックをロックします。
ipc_unlockall() は、与えられたIPC機構(つまり共有メモリとセマフォとメッセージング)用のグローバルスピンロックを解除します。
ipc_get() は、特定の IPC タイプ(つまり共有メモリとセマフォとメッセージング)へのポインタとディスクリプタIDを取り、対応する IPC ディスクリプタへのポインタを返します。ここで、各 IPC タイプのディスクリプタが違うデータタイプであっても、それぞれの場合で、共通の kern_ipc_perm 構造体タイプが最初のエンティティとして埋め込まれます。 ipc_get() 関数は、この共有のデータタイプを返します。データ型を正しいディスクリプタのデータ型へキャストするラッパー関数(たとえば shm_get())を通して、ipc_get() が呼び出されることを期待するモデルになっています。
ipc_parse_version() は、 もしあればIPC_64 フラグをコマンドから削除します。そして、 IPC_64か IPC_OLDを返します。
セマフォ、メッセージおよび共有メモリ機構は全て以下の共有の構造体を使うようになっています。
各 IPC ディスクリプタは、最初の要素にこの型のデータオブジェクトを持っています。これは、全ての汎用 IPC 関数からこのデータ型のポインタを用いて、全てのディスクリプタへアクセスできるようにするためです。
/* used by in-kernel data structures */
struct kern_ipc_perm {
key_t key;
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
unsigned long seq;
};
ipc_ids 構造体は、セマフォとメッセージキューと共有メモリの共通データを表しています。このデータ構造体には三つのグローバルインスタンスがあります。それぞれセマフォ、メッセージ、共有メモリ用に、semid_ds
、msgid_ds
とshmid_ds
です。
各インスタンスでは、sem
セマフォが使われ、構造体へのアクセスを保護しています。
entries
メンバは、IPC ディスクリプタ配列を指しており、ary
スピンロックがこの配列へのアクセスを保護しています。seq
メンバは、新しい IPC 資源を作ったときに1増加されるグローバルシーケンス番号になっています。
struct ipc_ids {
int size;
int in_use;
int max_id;
unsigned short seq;
unsigned short seq_max;
struct semaphore sem;
spinlock_t ary;
struct ipc_id* entries;
};
構造体 ipc_id の配列は、 ipc_ids 構造体の各インスタンスに存在します。この配列は動的に割り当てられ、必要に応じて grow_ary()によってより大きな配列に置き換えられることになります。 kern_ipc_perm データタイプが IPC 汎用関数によって共通ディスクリプタデータタイプとして使われるため、この配列は時々ディスクリプタ配列として参照されます。
struct ipc_id {
struct kern_ipc_perm* p;
};