IPCセマフォの取り消し(2)
ユーザプロセスが異常終了を含めて終了する場合、do_exit()がコールされ、プロセスのリソースの解放等の終了処理が行われます。その中で exit_sem()をコールする事で、IPCセマフォの取り消し処理が行われます。これはIPCセマフォの取り消し(1)のタスク構造体のtsk->sysvsem.undo_listにリストされているstruct sem_undoを参照する事で行っています。
まずulp = tsk->sysvsem.undo_listで終了プロセスのundoリストをulpに設定したのち、atomic_dec_and_test()で、他のプロセスがこのundoリストを参照しているかチェックします。fork()でCLONE_SYSVSEMフラグを設定するとundoリストは継承されるからです。
次のforループが実際の取り消し処理になります。list_entryマクロでulp->list_proc(tsk->sysvsem.undo_listにリストされているノード)、struct sem_undo *unを取得します。取得したunlist_procがulp->list_procと同じなら、リストを一巡したという事でsemid = -1とし、ループを抜けることになります。そうでないなら、semid = un->semidとし、これが取り消し処理するIPC識別子です。このIPC識別子でIPCセマフォそのものを管理しているIPCネームスペースから、sem_lock_check()でstruct sem_array *smaを取得します。この構造体がポイントするstruct sem * semaphoreのセマフォカウンターを更新することになります。
なお、この識別子でもって再度lookup_undo()でtsk->sysvsem.undo_listからstruct sem_undo *unを取得していますが、理由はわかりません。(コメントありますが・・・)
list_del()でun->list_id/un->list_procをノードとするリストを削除します。list_procはタスク構造体、list_idはstruct sem_arrayでリストされている物です。
次のforループで、使用しちるセマフォ組数sma->sem_nsemsで、各struct sem * semaphoreを取得し、semaphore->semval += un->semadj[i]とすることで、そのプロセスが行ったセマフォ処理の取り消し処理を行い、1つのIPC識別子に対する取り消し処理が終了する毎に、update_queue()でこの取り消しによって起床できるプロセスが無いかチェックし、必要なら起床させる事になります。
まずulp = tsk->sysvsem.undo_listで終了プロセスのundoリストをulpに設定したのち、atomic_dec_and_test()で、他のプロセスがこのundoリストを参照しているかチェックします。fork()でCLONE_SYSVSEMフラグを設定するとundoリストは継承されるからです。
次のforループが実際の取り消し処理になります。list_entryマクロでulp->list_proc(tsk->sysvsem.undo_listにリストされているノード)、struct sem_undo *unを取得します。取得したunlist_procがulp->list_procと同じなら、リストを一巡したという事でsemid = -1とし、ループを抜けることになります。そうでないなら、semid = un->semidとし、これが取り消し処理するIPC識別子です。このIPC識別子でIPCセマフォそのものを管理しているIPCネームスペースから、sem_lock_check()でstruct sem_array *smaを取得します。この構造体がポイントするstruct sem * semaphoreのセマフォカウンターを更新することになります。
なお、この識別子でもって再度lookup_undo()でtsk->sysvsem.undo_listからstruct sem_undo *unを取得していますが、理由はわかりません。(コメントありますが・・・)
list_del()でun->list_id/un->list_procをノードとするリストを削除します。list_procはタスク構造体、list_idはstruct sem_arrayでリストされている物です。
次のforループで、使用しちるセマフォ組数sma->sem_nsemsで、各struct sem * semaphoreを取得し、semaphore->semval += un->semadj[i]とすることで、そのプロセスが行ったセマフォ処理の取り消し処理を行い、1つのIPC識別子に対する取り消し処理が終了する毎に、update_queue()でこの取り消しによって起床できるプロセスが無いかチェックし、必要なら起床させる事になります。
void exit_sem(struct task_struct *tsk) { struct sem_undo_list *ulp; ulp = tsk->sysvsem.undo_list; if (!ulp) return; tsk->sysvsem.undo_list = NULL; if (!atomic_dec_and_test(&ulp->refcnt)) return; for (;;) { struct sem_array *sma; struct sem_undo *un; int semid; int i; rcu_read_lock(); un = list_entry(rcu_dereference(ulp->list_proc.next), struct sem_undo, list_proc); if (&un->list_proc == &ulp->list_proc) semid = -1; else semid = un->semid; rcu_read_unlock(); if (semid == -1) break; sma = sem_lock_check(tsk->nsproxy->ipc_ns, un->semid); /* exit_sem raced with IPC_RMID, nothing to do */ if (IS_ERR(sma)) continue; un = lookup_undo(ulp, semid); if (un == NULL) { /* exit_sem raced with IPC_RMID+semget() that created * exactly the same semid. Nothing to do. */ sem_unlock(sma); continue; } /* remove un from the linked lists */ assert_spin_locked(&sma->sem_perm.lock); list_del(&un->list_id); spin_lock(&ulp->lock); list_del_rcu(&un->list_proc); spin_unlock(&ulp->lock); /* perform adjustments registered in un */ for (i = 0; i < sma->sem_nsems; i++) { struct sem * semaphore = &sma->sem_base[i]; if (un->semadj[i]) { semaphore->semval += un->semadj[i]; /* * Range checks of the new semaphore value, * not defined by sus: * - Some unices ignore the undo entirely * (e.g. HP UX 11i 11.22, Tru64 V5.1) * - some cap the value (e.g. FreeBSD caps * at 0, but doesn't enforce SEMVMX) * * Linux caps the semaphore value, both at 0 * and at SEMVMX. * * Manfred <manfred@colorfullife.com> */ if (semaphore->semval < 0) semaphore->semval = 0; if (semaphore->semval > SEMVMX) semaphore->semval = SEMVMX; semaphore->sempid = task_tgid_vnr(current); } } sma->sem_otime = get_seconds(); /* maybe some queued-up processes were waiting for this */ update_queue(sma); sem_unlock(sma); call_rcu(&un->rcu, free_un); } kfree(ulp); }