LinuxカーネルのSystemV IPC実装で共有メモリ、セマフォ、メッセージキューのget操作はある程度共通化されているのでその辺を読んでみます。
と、その前にまずはglibc側をちょっと見てます。glibcの中でLinuxのシステムコールはsysdeps/unix/sysv/linux/にあります。
まずはmsgget(2)
30 int 31 msgget (key, msgflg) 32 key_t key; 33 int msgflg; 34 { 35 return INLINE_SYSCALL (ipc, 5, IPCOP_msgget, key, msgflg, 0, NULL); 36 }
30 int 31 semget (key, nsems, semflg) 32 key_t key; 33 int nsems; 34 int semflg; 35 { 36 return INLINE_SYSCALL (ipc, 5, IPCOP_semget, key, nsems, semflg, NULL); 37 }
そしてshmget(2)
30 int 31 shmget (key, size, shmflg) 32 key_t key; 33 size_t size; 34 int shmflg; 35 { 36 return INLINE_SYSCALL (ipc, 5, IPCOP_shmget, key, size, shmflg, NULL); 37 }
ここまで見て分かるように各関数は実際にはipc(2)を呼ぶようになっています。これは他のIPCのシステムコールも同様です。socket系の関数がsocketcall(2)にまとまってるようなものですね。なんでこういう実装になったかというのはLinuxプログラミングインタフェースに書いてあります。
では、カーネル側に。最初はipcシステムコールが呼ばれるのでipc/syscall.cにあるこれが呼ばれ、引数「call」の値に応じてディスパッチします。
16 SYSCALL_DEFINE6(ipc, unsigned int, call, int, first, unsigned long, second, 17 unsigned long, third, void __user *, ptr, long, fifth) 18 {
msgget(2)はipc/msg.cにあり、このような処理になっています。
241 SYSCALL_DEFINE2(msgget, key_t, key, int, msgflg) 242 { 243 struct ipc_namespace *ns; 244 static const struct ipc_ops msg_ops = { 245 .getnew = newque, 246 .associate = msg_security, 247 }; 248 struct ipc_params msg_params; 249 250 ns = current->nsproxy->ipc_ns; 251 252 msg_params.key = key; 253 msg_params.flg = msgflg; 254 255 return ipcget(ns, &msg_ids(ns), &msg_ops, &msg_params); 256 }
shmget(2)はipc/shm.cにあり、このような処理です。
616 SYSCALL_DEFINE3(shmget, key_t, key, size_t, size, int, shmflg) 617 { 618 struct ipc_namespace *ns; 619 static const struct ipc_ops shm_ops = { 620 .getnew = newseg, 621 .associate = shm_security, 622 .more_checks = shm_more_checks, 623 }; 624 struct ipc_params shm_params; 625 626 ns = current->nsproxy->ipc_ns; 627 628 shm_params.key = key; 629 shm_params.flg = shmflg; 630 shm_params.u.size = size; 631 632 return ipcget(ns, &shm_ids(ns), &shm_ops, &shm_params); 633 }
semget(2)はipc/sem.cにあって、このような関数うです。
564 SYSCALL_DEFINE3(semget, key_t, key, int, nsems, int, semflg) 565 { 566 struct ipc_namespace *ns; 567 static const struct ipc_ops sem_ops = { 568 .getnew = newary, 569 .associate = sem_security, 570 .more_checks = sem_more_checks, 571 }; 572 struct ipc_params sem_params; 573 574 ns = current->nsproxy->ipc_ns; 575 576 if (nsems < 0 || nsems > ns->sc_semmsl) 577 return -EINVAL; 578 579 sem_params.key = key; 580 sem_params.flg = semflg; 581 sem_params.u.nsems = nsems; 582 583 return ipcget(ns, &sem_ids(ns), &sem_ops, &sem_params); 584 }
これら3つの関数の処理は以下のようになっています。
struct ipc_opsにget操作で必要な固有の関数を設定する
IPC namespaceはカレントプロセスの名前空間を使用する
struct ipc_paramsにパラメータを設定(ipc_opsで設定した関数の引数)
ipcget()を呼び、get操作を実行
ipcget()の2番めの引数はnamespace(ns)にあるkeyを渡しています。
msg_idsの場合。
75 #define msg_ids(ns) ((ns)->ids[IPC_MSG_IDS])
sem_idsの場合
143 #define sem_ids(ns) ((ns)->ids[IPC_SEM_IDS])
shm_idsの場合。
62 #define shm_ids(ns) ((ns)->ids[IPC_SHM_IDS])
これらはstruct ipc_namespaceのidsを参照しています。IPC_MSG_IDSとかはindexですね。
29 struct ipc_namespace { 30 atomic_t count; 31 struct ipc_ids ids[3]; 32 33 int sem_ctls[4]; 34 int used_sems;
ではipcget()です。これはユーザーランド側のget系関数呼び出し時にkeyにIPC_PRIVATEを指定したかで処理が変わります。
680 int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids, 681 const struct ipc_ops *ops, struct ipc_params *params) 682 { 683 if (params->key == IPC_PRIVATE) 684 return ipcget_new(ns, ids, ops, params); 685 else 686 return ipcget_public(ns, ids, ops, params); 687 }
keyにIPC_PRIVATEが指定されていた場合はipcget_new()を使います。これは単純で、struct ipc_opsで設定したgetnew()を呼び、各IPC操作固有の処理に後を任す形です。
319 static int ipcget_new(struct ipc_namespace *ns, struct ipc_ids *ids, 320 const struct ipc_ops *ops, struct ipc_params *params) 321 { 322 int err; 323 324 down_write(&ids->rwsem); 325 err = ops->getnew(ns, params); 326 up_write(&ids->rwsem); 327 return err; 328 }
keyがIPC_PRIVATEで無い場合はipcget_public()を使います。こちらはIPC keyがある・ないの2パターンで処理が別れます。
最初にipc_findkey()でkeyが存在するかチェックします。
377 static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids, 378 const struct ipc_ops *ops, struct ipc_params *params) 379 { 380 struct kern_ipc_perm *ipcp; 381 int flg = params->flg; 382 int err; 383 384 /* 385 * Take the lock as a writer since we are potentially going to add 386 * a new entry + read locks are not "upgradable" 387 */ 388 down_write(&ids->rwsem); 389 ipcp = ipc_findkey(ids, params->key);
該当のkeyが存在しなかった場合は新規に作成する必要がありますのでipcget_new()の場合と同様にstruct ipc_opsに設定されているgetnew()を呼びます。ロックは既に取ってあるのでここではロックを取る必要はありません。
390 if (ipcp == NULL) { 391 /* key not used */ 392 if (!(flg & IPC_CREAT)) 393 err = -ENOENT; 394 else 395 err = ops->getnew(ns, params); 396 } else {
こちらは該当のkeyが存在した場合です。フラグのチェック処理は見ての通りなので飛ばしましょう。
397 /* ipc object has been locked by ipc_findkey() */ 398 399 if (flg & IPC_CREAT && flg & IPC_EXCL) 400 err = -EEXIST; 401 else {
opsにmore_checks()が設定されている場合はそれを呼び出します。このmore_check()の説明はstruct ips_opsを定義している箇所にコメントで書かれています。説明としてはroutine to call for an extra check if neededとあります。どのようなチェックが必要かはIPCの種別次第ですね。
402 err = 0; 403 if (ops->more_checks) 404 err = ops->more_checks(ipcp, params);
次はパーミッションのチェックです。ipc_check_perms()は2個のチェックを行っていて、1つはuser・groupの権限によるパーミッションのチェックでipcperms()にて実行、もうひとつはstruct ips_opsのassociateに設定された関数の呼び出しでこれはIPCの種別によって内容が変わります。
405 if (!err) 406 /* 407 * ipc_check_perms returns the IPC id on 408 * success 409 */ 410 err = ipc_check_perms(ns, ipcp, ops, params); 411 }
else文の最後でipcオブジェクトのロックを解除しています。このロックはipc_findkey()が見つかったipcオブジェクトをreturnする前に取っています。
412 ipc_unlock(ipcp); 413 }
最後に関数の冒頭で取ったセマフォのロックを解除して終了です。
414 up_write(&ids->rwsem); 415 416 return err; 417 } 418