次のページ 前のページ 目次へ

5. IPC機構

本章ではLinux 2.4 カーネルで実装されている IPC 機構のセマフォ、共有メモリおよびメッセージキューについて記述します。 四つの節で構成され、最初の三つの節では セマフォメッセージキュー共有メモリを順に取り上げて、インターフェースとサポート関数を説明します。また、 最後の節では、三つの機構で共有される共通関数群とデータ構造を説明します。

5.1 セマフォ

この節で説明している関数はユーザレベルのセマフォ機構の実装になっています。この実装部分はカーネルスピンロックとカーネルセマフォの利用に頼っていることに注意しましょう。 混乱を避けるため、カーネルのセマフォを参照するときは「カーネルセマフォ」と呼ぶことにします。その他の単に「セマフォ」というときは、ユーザレベルのセマフォを指すことにします。

セマフォシステムコールのインターフェース

sys_semget()

全てのsys_semget()への呼出しは、カーネルグローバルな sem_ids.semカーネルセマフォで保護されます。 新しいセマフォのセットを作らなければならない場合は、 newary() 関数が呼ばれて、新しいセマフォセットの作成と初期化をします。そして新しいセットID が呼出し元に戻されます。

キーの値が既存のセマフォセットに対して発行されたものだった場合は、 ipc_findkey() が呼び出され、相当するセマフォディスクリプタ配列インデックスを見つけます。パラメータと呼出し元の パーミッションは、セマフォのセットIDを返却する前に確認されます。

sys_semctl()

IPC_INFOSEM_INFO そして SEM_STAT コマンドについては、必要な関数処理を行う semctl_nolock() を呼び出します。

GETALLGETVALGETPIDGETNCNTGETZCNTIPC_STATSETVALSETALL コマンドについては、 必要な関数処理を行う semctl_main() を呼び出します。

IPC_RMIDIPC_SET コマンドについては、必要な関数処理を行う semctl_down() を呼び出します。 両方の演算の間は、グローバルな sem_ids.sem カーネルセマフォを保持します。

sys_semop()

呼出し引数の有効性を確認した後、セマフォの操作データをユーザ空間から一時バッファへコピーします。 小さい一時バッファで大きさが十分であればスタックバッファが使われます。不十分なら、大きいバッファが割り当てられます。セマフォの操作データのコピーが終わった後に、グローバルなセマフォスピンロックをロックします。そしてユーザが指定したセマフォセット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_queuesleeper要素がこのタスクが休止していることを示すため設定されます。 そしてグローバルセマフォスピンロックを解除し、現在のタスクを休止するためschedule()を呼び出します。

起こされたとき、なぜ起こされたか、どのように反応すべきかを判断するため、タスクはグローバルセマフォロックを再ロックします。以下のような場合が処理されます。

セマフォ独自にサポートする構造体

以下の構造体は、セマフォのサポートに特に使われます。

struct sem_array


/* 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 */
};

struct sem


/* One semaphore structure for each semaphore in the system. */
struct sem {
        int     semval;         /* current value */
        int     sempid;         /* pid of last operation */
};

struct seminfo


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 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;
};

struct sem_queue


/* 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 */
};

struct sembuf


/* 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 */
};

struct sem_undo


/* 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()

newary() は、新しいセマフォセットに必要となるメモリを割り当てるために、 ipc_alloc()を利用しています。そして、セマフォセット記述子とセット中の各セマフォの全てに十分なメモリを割り当てます。 割り当てられたメモリはクリアされ、セマフォセットディスクリプタの最初の要素のアドレスが、 ipc_addid() へと渡されます。 ipc_addid() は、新しいセマフォセット記述子のエントリ配列を予約し、データ( struct kern_ipc_perm)を初期化します。 グローバルなused_sems 変数は、新しいセットのセマフォ数で更新されて、新しいセットのためのデータ( struct kern_ipc_perm)の初期化が完了します。このセットに関わる他の初期化は以下の通りです。

ipc_addid()の呼出しに続く全ての操作は、グローバルセマフォスピンロックを保持した状態で実行します。グローバルセマフォスピンロックをアンロックした後に、newary() は、(sem_buildid()経由で) ipc_buildid() を呼出します。この関数は、セマフォセットディスクリプタのインデックスを使用し、ユニークな ID を生成します。そして、newary() の呼出し元に戻ります。

freeary()

freeary() は semctl_down()によって呼び出され、以下に列挙する機能を実行します。グローバルなセマフォスピンロックがロックされた状態で呼び出され、スピンロックが解除された状態で戻されます。

semctl_down()

semctl_down() は、 semctl()システムコールの IPC_RMIDIPC_SET 操作を提供します。セマフォセットIDとパーミッションは、これらの操作に先立って確認され、どちらの場合も、操作の間はグローバルセマフォスピンロックを保持します。

IPC_RMID

IPC_RMID 操作では、セマフォセットの削除を行うため、 freeary()を呼び出します。

IPC_SET

IPC_SET 操作では、セマフォセットのuidgidmodeおよびctime 要素を更新します。

semctl_nolock()

semctl_nolock() は sys_semctl() から呼び出され、IPC_INFO、SEM_INFO および SEM_STAT 機能を実行します。

IPC_INFO と SEM_INFO

IPC_INFO と SEM_INFO は、臨時に seminfo バッファを初期化し、変更されなかったセマフォの統計データを読み込ませます。そして、グローバルのsem_ids.sem カーネルセマフォを確保し、 seminfo 構造体の semuszsemaem 要素を与えられた(IPC_INFO または SEM_INFO)コマンドに従い更新します。システムコールの返り値は、セマフォセットIDの最大値になります。

SEM_STAT

SEM_STAT は、臨時に semid64_dsバッファを初期化します。その後、グローバルセマフォスピンロックを保持し、sem_otimesem_ctimesem_nsems の値をバッファにコピーします。このデータはその後ユーザ空間にコピーされます。

semctl_main()

semctl_main() は、 sys_semctl() により呼び出され、以下の節で示している多くのサポートする機能を実行します。全ての操作に先だって、semctl_main() は、グローバルセマフォロックをロックし、セマフォセットIDとパーミッションの有効性を確認します。スピンロックは戻る前に解除されます。

GETALL

GETALL 操作は、現在のセマフォ値を一時的なカーネルバッファへ読み込み、ユーザ空間へコピーします。もしセマフォバッファが小さければ、小さなスタックバッファが使われます。そうでなければ、スピンロックが一時的に落とされ、大きなバッファを確保します。セマフォ値を一時バッファへコピーする間、スピンロックを保持します。

SETALL

SETALL 操作は、セマフォ値をユーザ空間からカーネルの一時バッファへコピーし、その後セマフォセットへコピーします。一時バッファへユーザ空間から値をコピーする間、および正しい値か確認する間、スピンロックを落とします。もしセマフォセットが小さければ、スタックバッファが使われ、そうでなければ大きなバッファが確保されます。以下の操作をセマフォセットへ行う間、スピンロックを再取得し、保持します。

IPC_STAT

IPC_STAT 操作では、sem_otimesem_ctimesem_nsems 値をスタックバッファへコピーする。そのデータは、その後スピンロックを落としてからユーザ空間へコピーする。

GETVAL

エラーのない場合、GETVALでは、システムコールの返り値に指定したセマフォの値が設定される。

GETPID

エラーのない場合、GETPIDでは、システムコールの返り値にセマフォでの最後の操作に対応する pid を設定します。

GETNCNT

エラーのない場合、GETNCNT ではシステムコールの返り値に、セマフォが 0 未満になるのを待っているプロセスの数を設定します。この数値は count_semncnt() 関数が計算します。

GETZCNT

エラーのない場合、GETZCNT ではシステムコールの返り値に、セマフォが0に設定されるのを待っているプロセスの数を設定します。この数値は count_semzcnt() 関数が計算します。

SETVAL

新しいセマフォ値の有効性を確認したあと、以下の機能が実行されます。

count_semncnt()

count_semncnt() は、セマフォ値が 0 より小さくなるのを待っているタスク数を数えます。

count_semzcnt()

count_semzcnt() は、セマフォ値が 0 になるのを待っているタスク数を数えます。

update_queue()

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()

try_atomic_semop() は、 sys_semop()update_queue() により呼び出され、一連のセマフォ操作が全て成功するかどうか判断します。これは、各操作の実行を試みることによって判断が行われます。

もしブロックされた操作に遭遇したなら、プロセスはアボートし、全ての操作は差し戻されます。 もしIPC_NOWAIT が設定されていたなら -EAGAIN が返されます。そうでなければ 1 が返され、一連のセマフォ操作がブロックされたことを示します。

もしセマフォ値がシステムの制限を越えて変更されるならば、全ての操作は差し戻され、 -ERANGE が返されます。

もしシーケンス中の全ての操作が成功しても、do_undoパラメータが0でなければ、 全ての操作が差し戻されて、0 が返されます。もし do_undo パラメータが 0 なら、 全ての操作が成功し、強制的に残され、セマフォセットのsem_otime メンバが更新されます。

sem_revalidate()

sem_revalidate() は、グローバルセマフォスピンロックが一時的に落とされ、再度ロックしなければならないときに呼び出されます。 つまり、 semctl_main()alloc_undo() によって呼び出されます。セマフォIDとパーミッションの有効性を確認し、良ければグローバルセマフォスピンロックをロックして戻ります。

freeundos()

freeundos() は、プロセスの undo リストを走査し、要求された undo 構造体を探します。 もし見つかれば、undo 構造体はリストから削除され、解放されます。 そして、プロセスリストの次の undo 構造体へのポインタが返されます。

alloc_undo()

alloc_undo() は、グローバルセマフォスピンロックを獲得して呼ばれることを期待しています。エラーがあった場合は、スピンロックを解除して戻ります。

グローバルセマフォスピンロックが解除され、 sem_undo構造体と、セット中の各セマフォの修正値の配列の両方に十分なメモリを確保するため、kmalloc()を呼び出します。 成功すれば、グローバルスピンロックが sem_revalidate()を呼び出すことで再確保されます。

新しい semundo 構造体が初期化され、この構造体のアドレスが呼出し元によって与えられたアドレスへ格納されます。新しい undo 構造体は、現在のタスクの undo リストの先頭へ位置させられます。

sem_exit()

sem_exit() は、do_exit() により呼び出され、存在するタスクの全ての undo 修正を実行する責任があります。

もし現在のプロセスがセマフォでブロックされているなら、 グローバルセマフォスピンロックを保持して sem_queueリストから削除します。

そして現在のタスクのための undo リストが走査され、リストの各要素の処理毎にグローバルセマフォスピンロックの取得と解除が行われつつ、以下の操作が実行されます。以下の操作は、各 undo 要素ごとに実行されます。

リストの処理が完了したならば、current->semundo の値をクリアします。

5.2 メッセージキュー

メッセージシステムコールインターフェース

sys_msgget()

sys_msgget()へのコール全体は、グローバルメッセージキューセマフォ( msg_ids.sem)により保護されています。

新しいメッセージキューを作らなければならない場合、 newque()関数が 呼び出され、新しいメッセージキューの作成と初期化を行い、新しいキューIDを呼出し元に返します。

もし与えられたキーの値が既存のメッセージキューへなら、 ipc_findkey()が呼び出され、グローバルメッセージキューデスクリプタ配列(msg_ids.entries)の対応するインデックスを探します。呼出し元のパラメータとパーミッションは、メッセージキューIDを戻す前に確認されます。検索操作と確認は、グローバルメッセージキュースピンロック(msg_ids.ary)が保持された状態で行われます。

sys_msgctl()

sys_msgctl()へ渡されるパラメータは、メッセージキューID(msqid)、操作(cmd)、タイプ msgid_dsのユーザ空間バッファへのポインタ(buf)です。 6 つの操作がこの関数で提供されています。IPC_INFO, MSG_INFO, IPC_STAT, MSG_STAT, IPC_SET, IPC_RMIDです。メッセージキューIDと操作パラメータが確認され、操作(cmd)が以下のように実行されます。

IPC_INFO (または MSG_INFO)

グローバルメッセージキューの情報がユーザ空間にコピーされます。

IPC_STAT (または MSG_STAT)

タイプ struct msqid64_ds の一時バッファが初期化され、グローバルメッセージキュースピンロックを取得します。 呼び出したプロセスのアクセス権を確認したら、メッセージキューIDに対応しているメッセージキューの情報を、一時バッファへ読み込みます。そしてメッセージキュースピンロックを解除します。一時バッファの内容は、ユーザ空間へ copy_msqid_to_user() により書き出します。

IPC_SET

ユーザデータは、 copy_msqid_to_user()によりコピーされます。グローバルメッセージキューセマフォとスピンロックはこのとき取得され、最後に解除されます。メッセージキューID と現在のプロセスのアクセス権の有効性が確認された後、メッセージキューの情報がユーザから与えられたデータで更新されます。その後、 expunge_all()ss_wakeup() が呼び出され、全てのメッセージキューの受け手と送り手のウエイトキューで休止しているプロセスを起こします。これは、そのときには受け手が厳密なアクセス権により排除されていたり、送り手がキューサイズが増したことによりメッセージの送信が可能になる場合があるためです。

IPC_RMID

グローバルメッセージキューセマフォが取得され、グローバルメッセージキュースピンロックがロックされます。メッセージキューIDと現在のタスクのアクセス権の有効性が確認されたあと、 freeque()が呼び出されてメッセージキューIDに対応づけられているリソースを解放します。 そして、グローバルメッセージキューセマフォとスピンロックが解除されます。

sys_msgsnd()

sys_msgsng() は、パラメータとしてメッセージキューID(msqid)、 struct msg_msg タイプのバッファへのポインタ(msgp)、送るメッセージのサイズ(msgsz)と、待ちか否かを示すフラグ(msgflg)を受け取ります。メッセージキューIDに対応するのは、2つのタスクウエイトキューと、1つのメッセージウエイトキューがあります。受け手ウエイトキューにこのメッセージを待っているタスクがあれば、メッセージは直接受け手へ届けられ、受け手は起こされます。 そうでなければ、メッセージウエイトキューに十分な余裕があれば、メッセージはこのキューへ格納されます。結局は、送り手のタスクは自身を送り手ウエイトキューへいれます。sys_msgsnd()が処理する操作のより詳細な内容は次のようになります。

  1. ユーザバッファアドレスとメッセージタイプの有効性を確認し、その後 load_msg() を呼び出して、ユーザメッセージの内容を、 struct msg_msgタイプの一時オブジェクト msgへ読み込みます。 msgのメッセージタイプとメッセージサイズ領域も初期化されます。
  2. グローバルメッセージキュースピンロックをロックして、メッセージキューIDに対応づけられるメッセージキュー記述子を取得します。そのようなメッセージキューが存在しなければ、EINVAL を返します。
  3. メッセージキューIDが有効であるか確認するため、(msg_checkid()経由で) ipc_checkid()を呼び出します。そして、 ipcperms() を呼び出して、呼出し元のプロセスのアクセス権をチェックします。
  4. メッセージのサイズとメッセージウエイトキューの残りサイズをチェックして、メッセージを格納するのに十分余裕があるかどうか調べます。もし不十分であれば、以下の副項目を実施します。
    1. もし IPC_NOWAIT がmsgflg に指定されていれば、グローバルメッセージキュースピンロックが解除され、メッセージ用のメモリ領域が解放されて、EAGAIN を返します。
    2. ss_add() を起動し、現在のタスクを送り元ウエイトキューへといれます。これはまた、グローバルキュースピンロックを解除し、schedule() を呼び出して、現在のタスクを休止します。
    3. 起こされたら、再度グローバルスピンロックを取得し、メッセージキューIDがまだ有効か確認します。もしメッセージキューIDが有効でなければ、ERMID を返します。
    4. ss_del() を起動し、送り元ウエイトキューから送り手のタスクを削除します。もしタスクに保留されたシグナルがあれば、 sys_msgsnd() はグローバルスピンロックを解除し、 free_msg() を起動して、メッセージバッファを解放してEINTR を返します。そうでなければ、関数は 戻り、メッセージウエイトキューに十分な余裕があるか再度チェックします。
  5. pipelined_send() を起動して、メッセージを待っている受け手に直接送ります。
  6. もしこのメッセージを待つ受け手がなければ、msg をメッセージウエイトキュー(msg->q_messages) へ格納します。 大域変数のmsg_bytesmsg_hdrs はメッセージに使われる総バイト数やシステム全体のメッセージ数を意味していますが、 これらと同様、メッセージキュー記述子の q_cbytesq_qnum 領域も更新します。 メッセージの送信が成功したり、キューにいれられた場合は、メッセージキュー記述子のq_lspidq_stime 領域を更新し、グローバルメッセージスピンロックを解除します。

sys_msgrcv()

sys_msgrcv() 関数は、パラメータとしてメッセージキューID(msqid)、 msg_msg 型のバッファへのポインタ(msgp)、希望するメッセージのサイズ(msgsz)とフラグ(msgflg)を受け取ります。メッセージキューIDに関連するメッセージウエイトキューを検索し、要求する型にマッチするキューの最初のメッセージを見つけ、与えられたユーザバッファへコピーします。もし、そのようなメッセージが、メッセージウエイトキューに見つからなかったら、要求元のタスクは希望するメッセージがくるまで受け手ウエイトキューへ入れられます。ss_msgrcv() による操作の、より詳細な説明は以下の通りです。

  1. まず、msgtypから検索モードから派生して convert_mode()を起動する。そして sys_msgrcv() はグローバルメッセージキュースピンロックをロックする。そしてメッセージキューID に対応するメッセージキュー記述子を得る。もしメッセージキューがなければ EINVAL を返す。
  2. 現在のタスクがメッセージキューをアクセスするパーミッションを持っているかどうかチェックします。
  3. メッセージウエイトキューの最初のメッセージから順に testmsg() を呼び出し、そのメッセージタイプが要求されたタイプと一致するか確認する。 sys_msgrcv() は、適合するメッセージが見つかるか、全てのウエイトキューを探し尽くすまで検索を継続する。もし検索モードが SEARCH_LESSEQUAL であれば、msgtyp 以下で最も小さいタイプのキューの最初のメッセージを探す。
  4. もしメッセージが見つかったら、 sys_msgrcv() は以下のサブステップを実行する。
    1. もしメッセージサイズが想定されたサイズより大きく、msgflg がエラー許容なしを示していたら、グローバルメッセージキュースピンロックを解除し、E2BIG を返す。
    2. メッセージウエイトキューからメッセージを削除し、メッセージキューの統計情報を更新する。
    3. 送り手ウエイトキューで休止している全てのタスクを起こす。前のステップでキューからメッセージを削除したことで、送り手の中には処理を継続できるものがあるからだ。 そして、 最後のステップへと進む。
  5. もしメッセージウエイトキューに受け手の基準に合致するメッセージが見つからなかったら、msgflgが確認される。もし IPC_NOWAIT が設定されていたら、グローバルメッセージキュースピンロックを解除し、ENOMSG を返す。そうでなければ、受け手は以下のように受け手ウエイトキューへ入れられる。
    1. msg_receiver データ構造体の msr を割り当て、ウエイトキューの先頭に追加する。
    2. msrr_tsk メンバに現在のタスクを設定する。
    3. r_msgtyper_mode メンバは初期化され、メッセージのタイプとモードをそれぞれ要求された値にする。
    4. もし msgflg が MSG_NOERROR となっていたら、 msr の r_maxsize メンバは msgsz の値が設定される。そうでなければ INT_MAX が設定される。
    5. メンバ r_msg はメッセージをまだ受け取っていない状態を表すように初期化される。
    6. 初期化の完了後、受け手のタスクの状態は TASK_INTERRUPTIBLE に設定される。そしてグローバルメッセージキュースピンロックは解除され、 schedule() が呼び出される。
  6. 受け手が起きたあと、msrr_msg メンバ がチェックされる。 このメンバはパイプラインのメッセージの格納に使われ、エラーの場合にはエラー状態が格納される。 もし、r_msg メンバが要求されたメッセージでいっぱいになったら、 最後のステップ へ進む。それ以外の場合は、グローバルメッセージキュースピンロックが再度ロックされる。
  7. スピンロックの取得後、r_msg メンバは、スピンロックを待っている間にメッセージが受け取られていないか、再度確認される。もしメッセージが受け取られているなら、 最後のステップ へ進む。
  8. もし、r_msg メンバが変更されていないなら、再試行のためタスクが起こされる。この場合、msr はキューから削除される。もしタスクに保留されたシグナルがあれば、グローバルメッセージキュースピンロックは解除され、 EINTR が返される。 それ以外では、関数は 戻って 、再試行される。
  9. もしr_msgメンバにより、休止中にエラーが発生したことが分かれば、グローバルメッセージキュースピンロックは解除され、エラーが返される。
  10. ユーザバッファのアドレスmspの有効性を確認したあと、メッセージタイプがmspmtypeメンバに書き込まれ、 store_msg() が呼び出されて、メッセージの内容がmsrmtextメンバへコピーされます。最後にメッセージ用のメモリが関数 free_msg()により解放されます。

メッセージ独自の構造体

メッセージキューのデータ構造体は msg.c で定義されています。

struct msg_queue


/* 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;
};

struct msg_msg


/* 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 */
};

struct msg_msgseg


/* message segment for each message */
struct msg_msgseg {
        struct msg_msgseg* next;
        /* the next part of the message follows immediately */
};

struct msg_sender


/* one msg_sender for each sleeping sender */
struct msg_sender {
        struct list_head list;
        struct task_struct* tsk;
};

struct msg_receiver


/* 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 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 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 */
};

msg_setbuf


struct msq_setbuf {
        unsigned long   qbytes;
        uid_t           uid;
        gid_t           gid;
        mode_t          mode;
};

メッセージサポート関数

newque()

newque() は、新しいメッセージキュー記述子( struct msg_queue)のためのメモリを割り当て、 ipc_addid() を呼び出して、新しいメッセージキュー記述子用にメッセージキュー配列のエントリを予約させます。 メッセージキュー記述子は以下のように初期化されます。

ipc_addid() の呼出しにつづく全ての操作は、グローバルメッセージキュースピンロックを保持して実行されます。スピンロックを解除後、newque() は、 ipc_buildid() に直接マップされている msg_buildid() を呼び出します。 ipc_buildid() は、メッセージキュー記述子のインデックスを用いてユニークなメッセージキュー ID を作りだします。そしてこれは、 newque() により呼出し元に戻されます。

freeque()

メッセージキューが削除されるときは、 freeque() 関数が呼ばれます。この関数は、グローバルメッセージキュースピンロックが、呼出し元関数によって既にロックされているものと仮定します。指定されるメッセージキューに関連している全てのカーネルリソースを解放します。まず、(msg_rmid経由で) ipc_rmid() を呼び出し、グローバルメッセージキュー記述子の配列からメッセージキュー記述子を削除します。そして、 expunge_all を呼び出して、全ての受け手を起こし、 ss_wakeup()を呼び出して、このメッセージキューで休止している全ての送り手を起こします。 最後に、グローバルメッセージキュースピンロックを解除します。このメッセージキューに格納されている全てのメッセージは解放され、メッセージキュー記述子のメモリも解放されます。

ss_wakeup()

ss_wakeup() は与えられたメッセージ送り手ウエイトキューで待ちになっている全てのタスクを起こします。 この関数が freeque()から呼ばれたなら、キューにある全ての送り手はキューから削除されます。

ss_add()

ss_add() はパラメータとしてメッセージキュー記述子とメッセージ送り手データ構造体を受け取ります。現在のプロセスをもとにメッセージ送り手データ構造体のtsk メンバを埋め、現在のプロセスの状態を TASK_INTERRUPTIBLE に変更し、そして与えられたメッセージキューの送り手ウエイトキューの先頭へメッセージ送り手データ構造体を挿入します。

ss_del()

与えられたメッセージ送り手データ構造体(mss) はまだ対応するウエイトキューに保管されたままです。そして、ss_del() は、mssをキューから削除します。

expunge_all()

expunge_all() は、パラメータとしてメッセージキュー記述子(msq)と受け手が起こされた理由を示す整数値(res)を受け取ります。それぞれのmsqに対応して休止中の受け手に対して、r_msg メンバが起こされた理由(res)を設定し、その対応する受け手のタスクが起こされます。 この関数はメッセージキューが削除されるときと、メッセージ制御操作が行われるときに呼び出されます。

load_msg()

プロセスがメッセージを送るとき、 sys_msgsnd() 関数は、最初に load_msg() 関数を呼び出し、メッセージをユーザ空間からカーネル空間へと読み込みます。 メッセージはカーネルメモリ内ではデータブロックのリンクリストとして表現されています。 最初のデータブロックに関連しているのは、全てのメッセージを表す msg_msg 構造体です。msg_msg 構造体に関連づけられているデータブロックは、DATA_MSG_LEN のサイズに制限されています。 データブロックと構造体はメモリで一ページで取りうる最大の一つの連続したメモリブロックに割り当てられています。もし全てのメッセージが最初のデータブロックに入りきらなければ、追加のデータブロックが割り当てられ、リンクリストに登録されます。これら追加のデータブロックは、DATA_SEG_LEN にサイズが制限されており、各々は関連する msg_msgseg) 構造体を読み込みます。msg_msgseg 構造体と対応するデータブロックはメモリで一ページで取りうる最大の一つの連続したメモリブロックに割り当てられています。この関数は、成功時には新しい msg_msg 構造体のアドレスを返り値にもって戻ります。

store_msg()

store_msg() 関数は、 sys_msgrcv() により呼び出され、受けたメッセージを呼出し元の提供するユーザ空間バッファへ再度組み立てます。 msg_msg 構造体と msg_msgseg構造体により示されるデータは、ユーザ空間バッファへ順次コピーされます。

free_msg()

free_msg() 関数はメッセージデータ構造体 msg_msg とメッセージセグメントのメモリを解放します。

convert_mode()

convert_mode() は sys_msgrcv() から呼び出されます。パラメータとして、指定するメッセージタイプのポインタ(msgtyp)とフラグ(msgflg)を受け取ります。 この関数は、msgtypmsgflgの値に基づいて、呼出し元へ検索モードを返します。もし、msgtypが0ならば、 SEARCH_ANYを返します。 もし、msgtypの値が0より小さければ、msgtypeはその絶対値に設定され、SEARCH_LESSEQUAL を返します。もし MSG_EXCEPT がmsgflg に指定されていれば、SEARCH_NOTEQUAL が返されます。そうでなければ、SEARCH_EQUAL が返されます。

testmsg()

testmsg() 関数は、メッセージが受け手の指定する条件に合致するか調べます。もし、以下の条件の一つが真となれば、1を返します。

pipelined_send()

プロセスは pipelined_send() を使うことで、対応づけられているメッセージキューへ入れるのではなく、直接待機中の受け手へメッセージを送ることができます。 与えられたメッセージを待つ最初の受け手を見つけるため、 testmsg() 関数を呼び出します。もし見つかれば、待っている受け手は受け手ウエイトキューから削除され、関連する受け手のタスクは起こされます。メッセージは受け手のr_msgメンバへ格納されて、1を返します。メッセージを待っている受け手がいない場合、0を返します。

受け手を探すプロセスでは、与えられたメッセージに対して小さすぎるサイズを要求していることで、潜在的受け手が分かるでしょう。そのような受け手はキューから削除され、起こされてエラー状態 E2BIG を渡されます。このエラーは、r_msgメンバに格納されます。この検索は、有効な受け手が見つかるか、キューを見尽くすまで継続します。

copy_msqid_to_user()

copy_msqid_to_user() はカーネルバッファの内容をユーザバッファにコピーします。パラメータとしてユーザバッファと msqid64_ds 型のカーネルバッファと、新しいIPC バージョンか古い IPC バージョンかを示すバージョンフラグを受け取ります。 もしバージョンフラグが IPC_64 ならば、 copy_to_user() が起動され、カーネルバッファからユーザバッファへ直接コピーします。そうでなければ、struct msqid_ds 型の一時バッファが初期化され、カーネルデータはこの一時バッファへ変換されます。そして、copy_to_user() が呼び出され、一時バッファの内容がユーザバッファへコピーされます。

copy_msqid_from_user()

copy_msqid_from_user() 関数は、パラメータとしてstruct msq_setbuf型のカーネルメッセージバッファとユーザバッファと、新しいIPC バージョンか古い IPC バージョンかを示すバージョンフラグを受け取ります。IPCバージョンが新しい場合、copy_from_user()が呼び出されて、ユーザバッファの内容を msqid64_ds 型の一時バッファへコピーします。 そしてカーネルバッファのqbytes, uid, gidmode領域を一時バッファの対応する領域の値にします。古い IPC のバージョンならば、 msqid_ds 型の一時バッファを代りに用います。

5.3 共有メモリ

共有メモリシステムコールインターフェース

sys_shmget()

sys_shmget() は、呼出し全体をグローバル共有メモリセマフォにより保護します。

新しい共有メモリセグメントを作る必要のある場合、 関数 newseg() が呼び出され、新しい共有メモリセグメントを作成し初期化します。新しいセグメントのIDが呼出し元へ返されます。

既存の共有メモリセグメントの キー値が与えられている場合、共有メモリ記述子配列の対応するインデックスを見つけ出し、パラメータと呼出し元のパーミッションを確認後、共有メモリセグメントIDを返します。 検索操作と有効性の確認はグローバル共有メモリスピンロックを保持した状態で行われます。

sys_shmctl()

IPC_INFO

一時バッファ shminfo64 が システムワイドの共有メモリパラメータとともに読み込まれ、呼出し元のアプリケーションからアクセスするユーザ空間へコピーします。

SHM_INFO

共有メモリのシステムワイドの統計情報を集める間は、グローバル共有メモリセマフォとグローバル共有メモリスピンロックを取得します。 関数 shm_get_stat() を呼び出し、メモリ中に存在する共有メモリページ数とスワップアウトした共有メモリページ数を計算します。 swap_attemptsswap_successesの計数は、常に0 とされています。これらの統計値は一時バッファ shm_info へ格納され、呼び出したアプリケーションのユーザ空間へとコピーされます。

SHM_STAT, IPC_STAT

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 の双方について、呼出し元のアクセス権の有効性を確認します。要求された統計情報は一時バッファへ読み込まれ、その後呼出し元へコピーされます。

SHM_LOCK, SHM_UNLOCK

アクセス権の有効性を確認後、グローバル共有メモリスピンロックをロックし、共有メモリセグメントIDの有効性を確認します。SHM_LOCK と SHM_UNLOCK 双方で、その処理を行うため、 shmem_lock() を呼び出します。 shmem_lock()のパラメータには、処理すべき関数の識別子を渡します。

IPC_RMID

IPC_RMID の処理の間は、グローバル共有メモリセマフォとグローバル共有メモリスピンロックが取得されます。共有メモリID の有効性を確認したあと、もし current attachment がなければ、 shm_destroy() を呼出し、共有メモリセグメントを破壊します。そうでなければ、SHM_DEST フラグをセットし、これが破壊するという印になります。そして、他のプロセスを共有メモリIDの参照から守るために、IPC_PRIVATE フラグをセットします。

IPC_SET

共有メモリセグメントIDとユーザのアクセス権の有効性を確認後、共有メモリセグメントのuidgid、そしてmode フラグをユーザデータで更新します。shm_ctime領域も更新します。これらの変更は、グローバル共有メモリセマフォとグローバル共有メモリスピンロックを保持した状態で処理されます。

sys_shmat()

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()

sys_shmdt() の処理中はグローバル共有メモリセマフォが保持されます。現在のプロセスのmm_struct について、共有メモリアドレスに対応する vm_area_struct を検索します。 もし見つかれば、 do_munmap() が呼び出されて、共有メモリセグメントの仮想アドレスへのマッピングが解除されます。

ここで、 do_munmap() は共有メモリの予約を維持する関数の shm_close()をコールバックし、他の割り当てがない共有メモリセグメントの資源を解放することに注意してください。

sys_shmdt() は無条件に0を返します。

共有メモリサポート構造体

struct shminfo64


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


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


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 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


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()

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()

shm_get_stat() は、全ての共有メモリ構造体を順に辿っていき、共有メモリに使われているメモリの全ページ数と、スワップアウトされている全メモリページ数を計算します。各共有メモリセグメントにはファイル構造体とiノード構造体があります。求めるデータをiノード経由で取得するため、アクセスされる各iノード構造体のスピンロックのロックとロック解除が、それぞれに行われます。

shmem_lock()

shmem_lock() はパラメータに共有メモリ記述子へのポインタとロックかアンロックを示すフラグを受け取ります。共有メモリセグメントのロック状態は対応するiノードへ格納されます。 この状態は、要求されたロック状態と比較されます。shmem_lock() は、一致した場合に単に返ります。

対応するiノードのセマフォを保持している間に、iノードのロック状態が設定されます。各共有メモリセグメントの各ページにおいて、以下のリストの項目が実行されます。

shm_destroy()

shm_destroy() の間、共有メモリページ総数は、共有メモリセグメントの削除の責任をとって調整されます。 ipc_rmid() は、(shm_rmid() 経由で)呼び出され、共有メモリID を削除します。 shmem_lock が呼び出され、共有メモリページをアンロックし、各ページの参照カウントを効率的に 0 に減算します。 fput() が呼び出され、対応するファイルオブジェクトの利用カウンタf_countを1減算します。そして、必要であれば、ファイルオブジェクトの資源を解放します。kfree() が呼び出され、共有メモリセグメント記述子を解放します。

shm_inc()

shm_inc() は PID を設定し、現在の時間を設定し、そして与えられた共有メモリセグメントの付属数を1増加させます。これらの操作は、グローバル共有メモリスピンロックを保持して行われます。

shm_close()

shm_close() は、shm_lpridshm_dtim メンバを更新し、割り当て共有メモリセグメント数を 1 減算します。共有メモリセグメントが他に割り当てられていなければ、 shm_destroy() を呼び出し、共有メモリセグメントの資源を解放します。これらの操作は全てグローバル共有メモリセマフォとグローバル共有メモリスピンロックの両方を保持して行われます。

shmem_file_setup()

関数 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_modef_vfsmnt メンバが適切に初期化されます。関数 shmem_truncate() が呼び出され、iノード オブジェクトの初期化を完了させます。成功したなら、 shmem_file_setup() は新しいファイルディスクリプタを返します。

5.4 Linux IPC プリミティブ

セマフォ、メッセージおよび共有メモリで使用する汎用 Linux IPCプリミティブ

Linux のセマフォ、メッセージおよび共有メモリ機構は一連の共通プリミティブで構成されています。これらのプリミティブを以下で説明します。

ipc_alloc()

メモリ割り当てが PAGE_SIZE より大きければ、vmalloc() がメモリ割り当てに使われます。そうでなければ、 kmalloc() が GFP_KERNEL 付きで呼び出されメモリを割り当てます。

ipc_addid()

新しいセマフォセット、メッセージキュー、ないしはメモリセグメントが追加されると、 ipc_addid() は、 関連するディスクリプタの配列のサイズがシステムの最大値に対して十分に大きいことを保証するため、最初に grow_ary() を呼び出します。そしてディスクリプタの配列の最初の未使用要素を検索します。もし未使用の要素が見つかったら、使用中のディスクリプタのカウンタを1増加します。 新しい資源のディスクリプタのために kern_ipc_perm 構造体を初期化し、新しいディスクリプタの配列のインデックスを返します。 ipc_addid() が成功したら、与えられたIPC のタイプのグローバルスピンロックを取得して戻ります。

ipc_rmid()

ipc_rmid() は、IPCディスクリプタをIPCタイプのグローバルディスクリプタ配列から削除し、 使用されている ID のカウントを更新します。そして必要なら対応するディスクリプタ配列の最大 ID を調整します。 与えられた IPC ID に対応する IPC ディスクリプタへのポインタを返します。

ipc_buildid()

ipc_buildid() は与えられたIPCタイプの各ディスクリプタに対応するユニークなIDを作成します。この ID は新しいIPC要素が追加されるときに作成されます(例: 新しい共有メモリセグメントや新しいセマフォセット)。 IPC ID は対応するディスクリプタ配列のインデックスへ容易に変換できます。各IPCタイプは、ディスクリプタが追加される毎に増加させるシーケンス番号を管理しています。 ID はシーケンス番号をSEQ_MULTIPLIER 倍し、この積にディスクリプタ配列のインデックスを加算することで生成します。シーケンス番号が存在することにより、古い IPC ID の利用を検出することができます。

ipc_checkid()

ipc_checkid() は、与えられた IPC IDを SEQ_MULTIPLIER で除算し、その商を対応するディスクリプタに保存されているシーケンス値と比較します。もし値が等しいなら、IPC ID は有効であると考えられ、1を返します。そうでなければ、0 を返します。

grow_ary()

grow_ary() は、与えられた IPC タイプの 最大の(設定可能な) ID 数を動的に変更できる機能を取り扱います。これは現在の最大限度が恒久的なシステムの限度(IPCMNI)を越えないように強制し、必要なら引き下げます。これはまた既存のディスクリプタ配列が十分に大きくなるよう保証します。 もし既存の配列サイズが十分に大きかったら、現在の最大限度を返します。そうでなければ、新しい大きな配列を割り当てます。そして古い配列を新しい配列へコピーし、古い配列を解放します。与えられた IPC タイプのディスクリプタ配列を更新するときは、対応するグローバルスピンロックを保持します。

ipc_findkey()

ipc_findkey() は、特定の ipc_ids オブジェクトのディスクリプタの配列を検索して特定のキーを探します。見つかったら、対応するディスクリプタのインデックスを返します。もしキーが見つからなかったら、-1 を返します。

ipcperms()

ipcperms() は、IPC 資源にアクセスするユーザ、グループと他のパーミッションをチェックします。もしパーミッションが有効なら0を返し、そうでなければ-1を返します。

ipc_lock()

ipc_lock() は、 IPC IDをパラメータの一つとして受け取ります。与えられた IPC タイプのグローバルスピンロックをロックし、特定のIPC IDに対応するディスクリプタへのポインタを返します。

ipc_unlock()

ipc_unlock() は、示された IPC タイプのグローバルスピンロックを解除します。

ipc_lockall()

ipc_lockall() は、与えられたIPC機構(つまり共有メモリとセマフォとメッセージング)用のグローバルスピンロックをロックします。

ipc_unlockall()

ipc_unlockall() は、与えられたIPC機構(つまり共有メモリとセマフォとメッセージング)用のグローバルスピンロックを解除します。

ipc_get()

ipc_get() は、特定の IPC タイプ(つまり共有メモリとセマフォとメッセージング)へのポインタとディスクリプタIDを取り、対応する IPC ディスクリプタへのポインタを返します。ここで、各 IPC タイプのディスクリプタが違うデータタイプであっても、それぞれの場合で、共通の kern_ipc_perm 構造体タイプが最初のエンティティとして埋め込まれます。 ipc_get() 関数は、この共有のデータタイプを返します。データ型を正しいディスクリプタのデータ型へキャストするラッパー関数(たとえば shm_get())を通して、ipc_get() が呼び出されることを期待するモデルになっています。

ipc_parse_version()

ipc_parse_version() は、 もしあればIPC_64 フラグをコマンドから削除します。そして、 IPC_64か IPC_OLDを返します。

セマフォ、メッセージおよび共有メモリで使われる汎用 IPC 構造体

セマフォ、メッセージおよび共有メモリ機構は全て以下の共有の構造体を使うようになっています。

struct kern_ipc_perm

各 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;
};

struct ipc_ids

ipc_ids 構造体は、セマフォとメッセージキューと共有メモリの共通データを表しています。このデータ構造体には三つのグローバルインスタンスがあります。それぞれセマフォ、メッセージ、共有メモリ用に、semid_dsmsgid_dsshmid_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;
};

struct ipc_id

構造体 ipc_id の配列は、 ipc_ids 構造体の各インスタンスに存在します。この配列は動的に割り当てられ、必要に応じて grow_ary()によってより大きな配列に置き換えられることになります。 kern_ipc_perm データタイプが IPC 汎用関数によって共通ディスクリプタデータタイプとして使われるため、この配列は時々ディスクリプタ配列として参照されます。


struct ipc_id {
    struct kern_ipc_perm* p;
};


次のページ 前のページ 目次へ