Linuxカーネル:SystemV IPC get系操作の共通実装部分を読む

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 }

次にsemget(2)

  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つの関数の処理は以下のようになっています。

  1. struct ipc_opsにget操作で必要な固有の関数を設定する

  2. IPC namespaceはカレントプロセスの名前空間を使用する

  3. struct ipc_paramsにパラメータを設定(ipc_opsで設定した関数の引数)

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

クラウドを支える技術 ―データセンターサイズのマシン設計法入門 (WEB+DB PRESS plus)