ユーザプロセスが異常終了を含めて終了する場合、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()でこの取り消しによって起床できるプロセスが無いかチェックし、必要なら起床させる事になります。
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);
}
備考
取り消し処理において、if (semaphore->semval < 0)ならsemaphore->semval = 0、if (semaphore->semval > SEMVMX)ならsemaphore->semval = SEMVMXとキャップしています。このあたりコメントにあるようにSUS(POSIXを拡張した物らしい。)では定義されてなく、システムによって実装が異なるようです。まあ雑知識として・・・。まあカーネルリーディングそのものが、雑知識と言えなくはないですが。