Linux SystemV IPC: shmctl()でIPC_RMIDの処理

shmctl()はセグメントの削除だけ見ておけば良いかなというところで。

shmctl()はipc()よりsys_shmctl()の呼び出しという形で呼ばれます。 そして、shmctl()の第二引数にあたるcmdの値を使ったswitchがあり、IPC_RMIDとIPC_SETの場合はshmctl_down()が呼ばれます。

963         case IPC_RMID:
964         case IPC_SET:
965                 return shmctl_down(ns, shmid, cmd, buf, version);

ではshmctl_down()を見ていきます。
最初は変数定義とIPC_SETの場合の処理なので飛ばします。

775 static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
776                        struct shmid_ds __user *buf, int version)
777 {
778         struct kern_ipc_perm *ipcp;
779         struct shmid64_ds shmid64;
780         struct shmid_kernel *shp;
781         int err;
782 
783         if (cmd == IPC_SET) {
784                 if (copy_shmid_from_user(&shmid64, buf, version))
785                         return -EFAULT;
786         }

次にロックを取ります。shm_ids()はマクロで、namespaceにあるSystemV IPCのidのうち共有メモリのIDを取得します。

788         down_write(&shm_ids(ns).rwsem);
789         rcu_read_lock();

ipcctl_pre_down_nolock()はカレントプロセスのipc namespaceからshmidに該当するものがあるかチェックし、存在した場合はeuid、capabilityによるパーミッションのチェックを行います。

91         ipcp = ipcctl_pre_down_nolock(ns, &shm_ids(ns), shmid, cmd,
792                                       &shmid64.shm_perm, 0);
793         if (IS_ERR(ipcp)) {
794                 err = PTR_ERR(ipcp);
795                 goto out_unlock1;
796         }

ipcpよりstruct shmid_kernelのデータを取得。

797 
798         shp = container_of(ipcp, struct shmid_kernel, shm_perm);

ここは毎度ながらselinux関連なので無視。

800         err = security_shm_shmctl(shp, cmd);
801         if (err)
802                 goto out_unlock1;
803 

IPC_RMIDの場合はdo_shm_rmid()を呼びます。ここは後で見ましょう。

804         switch (cmd) {
805         case IPC_RMID:
806                 ipc_lock_object(&shp->shm_perm);
807                 /* do_shm_rmid unlocks the ipc object and rcu */
808                 do_shm_rmid(ns, ipcp);
809                 goto out_up;
810         case IPC_SET:
811                 ipc_lock_object(&shp->shm_perm);
812                 err = ipc_update_perm(&shmid64.shm_perm, ipcp);
813                 if (err)
814                         goto out_unlock0;
815                 shp->shm_ctim = get_seconds();
816                 break;
817         default:
818                 err = -EINVAL;
819                 goto out_unlock1;
820         }

後はロックを解除して終了です。

821 
822 out_unlock0:
823         ipc_unlock_object(&shp->shm_perm);
824 out_unlock1:
825         rcu_read_unlock();
826 out_up:
827         up_write(&shm_ids(ns).rwsem);
828         return err;
829 }

do_shm_rmid()ですが、もし他のプロセスなりがアタッチしている場合は削除できないので後で削除できるようにフラグを設定します。そうじゃなければ削除に行きます。この辺はman Man page of SHMCTLにあるとおりの挙動です。

 89 static void do_shm_rmid(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp)
 90 {
 91         struct shmid_kernel *shp;
 92         shp = container_of(ipcp, struct shmid_kernel, shm_perm);
 93 
 94         if (shp->shm_nattch) {
 95                 shp->shm_perm.mode |= SHM_DEST;
 96                 /* Do not find it any more */
 97                 shp->shm_perm.key = IPC_PRIVATE;
 98                 shm_unlock(shp);
 99         } else
100                 shm_destroy(ns, shp);
101 }

ところで、shm_nattachが0じゃない場合はshm_unlock()を呼んでますがshmctl()の流れではこのロックは取っていません。じゃあどこでロックしたのかというとdo_shmat()の最後のほうで取っているこれじゃないかと思います。shmdt()ではロックの解除してませんし。

1198         down_write(&shm_ids(ns).rwsem);
1199         shp = shm_lock(ns, shmid);
1200         BUG_ON(IS_ERR(shp));

それはさておき、shm_destroy()を見ましょう。
最初にshpに設定されているfileオブジェクトがNULLに設定されます。

210 static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp)
211 {
212         struct file *shm_file;
213 
214         shm_file = shp->shm_file;
215         shp->shm_file = NULL;

次に削除するセグメントで使っていたページ数をトータルのページ数から減らします。

216         ns->shm_tot -= (shp->shm_segsz + PAGE_SIZE - 1) >> PAGE_SHIFT;

shm_rmid()は後で見ます。

217         shm_rmid(ns, shp);

shpがホールドしてるロックを解除。

218         shm_unlock(shp);

使っていたページがhugetlbなのか普通のなのかで処理が変わりますが、やることはロックの解除です。

219         if (!is_file_hugepages(shm_file))
220                 shmem_lock(shm_file, 0, shp->mlock_user);
221         else if (shp->mlock_user)
222                 user_shm_unlock(file_inode(shm_file)->i_size, shp->mlock_user);

共有メモリの擬似ファイルを削除して、最後にロックを解除して完了です。

223         fput(shm_file);
224         ipc_rcu_putref(shp, shm_rcu_free);
225 }

先ほど飛ばしたshm_rmid()はこのような関数です。 sをリストから消すのとipc_rmid()でidの削除をします。

179 static inline void shm_rmid(struct ipc_namespace *ns, struct shmid_kernel *s)
180 {
181         list_del(&s->shm_clist);
182         ipc_rmid(&shm_ids(ns), &s->shm_perm);
183 }
184 

ipc_rmid()はこんな関数です。

428 void ipc_rmid(struct ipc_ids *ids, struct kern_ipc_perm *ipcp)
429 {
430         int lid = ipcid_to_idx(ipcp->id);
431 
432         idr_remove(&ids->ipcs_idr, lid);
433         ids->in_use--;
434         ipcp->deleted = true;
435 }