読者です 読者をやめる 読者になる 読者になる

Linux SystemV IPC: semctl()の実装を読む

linux kernel

semctl()を読むのは良いけどフラグによって操作が違うのでshmctl()の時と同じくIPC_RMIDの場合を読みます。

まずはsemctl()からでshmctl()と同じくIPC_SETとIPC_RMIDは同じ関数が使われます。

1569 SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg)
1570 {
〜略〜
1596         case IPC_RMID:
1597         case IPC_SET:
1598                 return semctl_down(ns, semid, cmd, version, p);

ではsemctl_down()を見ていきます。と思ったんですが、shmctl()の場合とほぼ同じなので確実に違う部分だけ読みます。
それはどこかというと、セマフォでコマンドがIPC_RMIDの場合のfreeary()のところです。

1543         case IPC_RMID:
1544                 sem_lock(sma, NULL, -1);
1545                 /* freeary unlocks the ipc object and rcu */
1546                 freeary(ns, ipcp);
1547                 goto out_up;

freeary()を見ていきます。最初は変数宣言とセマフォを管理しているstruct sem_arrayの取得です。

1069 static void freeary(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp)
1070 {
1071         struct sem_undo *un, *tu;
1072         struct sem_queue *q, *tq;
1073         struct sem_array *sma = container_of(ipcp, struct sem_array, sem_perm);
1074         struct list_head tasks;
1075         int i;
1076 

まずはロックを取り、次にリストを辿っていきます。ここでたどるのはundo処理をする必要があるもののリストです。

1077         /* Free the existing undo structures for this semaphore set.  */
1078         ipc_assert_locked_object(&sma->sem_perm);
1079         list_for_each_entry_safe(un, tu, &sma->list_id, list_id) {
1080                 list_del(&un->list_id);
1081                 spin_lock(&un->ulp->lock);
1082                 un->semid = -1;
1083                 list_del_rcu(&un->list_proc);
1084                 spin_unlock(&un->ulp->lock);
1085                 kfree_rcu(un, rcu);
1086         }
1087

次はコメントに書いてありますね。wake_up_sem_queue_prepare()の処理は、qのメンバ変数statusをIN_WAKEUPにしてtasksリストにつなぎます。

1088         /* Wake up all pending processes and let them fail with EIDRM. */
1089         INIT_LIST_HEAD(&tasks);
1090         list_for_each_entry_safe(q, tq, &sma->pending_const, list) {
1091                 unlink_queue(sma, q);
1092                 wake_up_sem_queue_prepare(&tasks, q, -EIDRM);
1093         }
1094 

こちらも上と同じですが手繰るリストが違うだけです。

1095         list_for_each_entry_safe(q, tq, &sma->pending_alter, list) {
1096                 unlink_queue(sma, q);
1097                 wake_up_sem_queue_prepare(&tasks, q, -EIDRM);
1098         }

次はセマフォの数だけループで、セマフォごとに持っているリストを辿ってwakeupしていきます。

1099         for (i = 0; i < sma->sem_nsems; i++) {
1100                 struct sem *sem = sma->sem_base + i;
1101                 list_for_each_entry_safe(q, tq, &sem->pending_const, list) {
1102                         unlink_queue(sma, q);
1103                         wake_up_sem_queue_prepare(&tasks, q, -EIDRM);
1104                 }
1105                 list_for_each_entry_safe(q, tq, &sem->pending_alter, list) {
1106                         unlink_queue(sma, q);
1107                         wake_up_sem_queue_prepare(&tasks, q, -EIDRM);
1108                 }
1109         }
1110 

sem_rmid()ipc_rmid()を呼ぶだけです。ipc_rmid()はこちらに。

1111         /* Remove the semaphore set from the IDR */
1112         sem_rmid(ns, sma);

sem_unlock()は後ほど。ざっくりというとshmのpending_alterリストの内容を別のリストに移して、このリストは空にします。

1113         sem_unlock(sma, -1);
1114         rcu_read_unlock();
1115 

wake_up_sem_queue_do()も後ほど。

1116         wake_up_sem_queue_do(&tasks);

カレントプロセスのipc namespaceで使用していたセマフォ数を減算してロックを解除して終了。

1117         ns->used_sems -= sma->sem_nsems;
1118         ipc_rcu_putref(sma, sem_rcu_free);
1119 }

sem_unlock()はIPC_RMIDの場合、locknumは-1なのでunmerge_queues()が呼ばれます。

364 static inline void sem_unlock(struct sem_array *sma, int locknum)
365 {
366         if (locknum == -1) {
367                 unmerge_queues(sma);
368                 ipc_unlock_object(&sma->sem_perm);
369         } else {
370                 struct sem *sem = sma->sem_base + locknum;
371                 spin_unlock(&sem->lock);
372         }
373 }

unmerge_queues()を見ましょう。まず、shmのcomplex_countが0でない場合は何もしません。

206 static void unmerge_queues(struct sem_array *sma)
207 {
208         struct sem_queue *q, *tq;
209 
210         /* complex operations still around? */
211         if (sma->complex_count)
212                 return;

次にpending_alterリストを辿っていきます。そして、qをcurr->pending_alterにaddしてループが終わったらsmaのpending_alterリストを空にします。

213         /*
214          * We will switch back to simple mode.
215          * Move all pending operation back into the per-semaphore
216          * queues.
217          */
218         list_for_each_entry_safe(q, tq, &sma->pending_alter, list) {
219                 struct sem *curr;
220                 curr = &sma->sem_base[q->sops[0].sem_num];
221 
222                 list_add_tail(&q->list, &curr->pending_alter);
223         }
224         INIT_LIST_HEAD(&sma->pending_alter);
225 }

最後にwake_up_sem_queue_do()を見ましょう。これに引数で渡ってくるリストはundo処理をするリストでwake_up_sem_queue_prepare()で作ったものですね。

697 static void wake_up_sem_queue_do(struct list_head *pt)
698 {
699         struct sem_queue *q, *t;
700         int did_something;
701 

リストが空か?のチェック。list_empty()はリストが空なら1が返ります。

702         did_something = !list_empty(pt);

リストをたどりつつwake_up_process()でプロセスを起こしていきます。

703         list_for_each_entry_safe(q, t, pt, list) {
704                 wake_up_process(q->sleeper);
705                 /* q can disappear immediately after writing q->status. */
706                 smp_wmb();
707                 q->status = q->pid;
708         }

リストが空でなかった場合はpreempt_enable()でpreemptを有効にします。

709         if (did_something)
710                 preempt_enable();
711 }

The Art of Readable Code (Theory in Practice)

The Art of Readable Code (Theory in Practice)