IPCセマフォ(2)
IPCセマフォ(1)でのsemtimedop()/semop()のセマフォ処理の実態は、下記のtry_atomic_sem()/update_queue()となります。
セマフォ取得はtry_atomic_semop()で行っています。最初のforループで、次のwhileループは取り消し処理です。
最初のforループはセマフォ取得/解放の処理で、引数で渡されたstruct sembuf * sopsの個数分int nsopsのループで、その時のstruct sem はsop->sem_numで指定されます。currはstruct sem、sem_opはセマフォのオペレーション、そしてresultはセマフォカウンタ値です。
f (!sem_op && result)はsem_op=0でセマフォカウンタ値が0でないなら、goto would_blockです。IPCセマフォのsem_op=0の仕様です。
そうでないなら、result += sem_opでセマフォカウンタ値にsem_opを足しこんで、その結果がマイナスならセマフォ取得できません。goto would_blockです。
セマフォオペレーション結果がSEMVMX(32767)を超えたらgoto out_of_rangです。
SEM_UNDOでのセマフォ操作なら、un->semadj[sop->sem_num] - sem_opの値がSEMAEM(32767)の絶対値の範囲外ならgoto out_of_rangです。ここではまだ取り消し処理は行っていません。そのチェックのみです。
上記チェックがOKなら、curr->semval = resultでstruct semにセマフォ操作を反映させたセマフォカウンタ値を設定します。
forループを終了したというのは、全てのセマフォ操作はOKだという事です。whileループで更新したstruct semに更新したプロセスIDをセットすると共に、un->semadj[sop->sem_num] -= sop->sem_opで取り消し処理を施しています。セマフォ操作ではresult += sem_opとsem_opを加算していますが、取り消し処理では-= sop->sem_opと減算しています。すなわち取り消し処理ではun->semadj[]が0になるように、semvalの加算/減算すればいいと言うことです。
out_of_range:は-ERANGEとして、undo:へジャンプし、ここに飛んでくる前に処理してしまったstruct semを元に戻しています。
would_block:はIPC_NOWAITが設定されていれば、EAGAINを、そうでなければ1を返します。そしてundo:へジャンプています。
セマフォ操作ができたプロセスを起床する条件としてif (error <= 0)となっていますが、ここではマイナス(エラー)としているのは、-EAGAIN(sem_op=0)を想定しての事です。なお他のエラーERANGEはありません。すでにtry_atomic_semop()で処理した結果として、sma->sem_pendingにリストされているわけですから。
try_atomic_semop()がOKなら、そのプロセスをsma->sem_pendingリストから削除して、wake_up_process()で起床させていますが、sma->sem_pendingリストから削除でif (q->alter)によりその削除の仕方が異なっています。
if (q->alter)の時、すなわち新たにセマフォ値の更新が発生した事を意味します。従ってsma->sem_pendingリストの最初からチェックしなおす必要があります。そのため次にチェックするプロセスは、先頭のsma->sem_pending.nextから取得しています。そうでないなら、このプロセスはq->list.nextから取得されるようになっています。
セマフォ取得はtry_atomic_semop()で行っています。最初のforループで、次のwhileループは取り消し処理です。
最初のforループはセマフォ取得/解放の処理で、引数で渡されたstruct sembuf * sopsの個数分int nsopsのループで、その時のstruct sem はsop->sem_numで指定されます。currはstruct sem、sem_opはセマフォのオペレーション、そしてresultはセマフォカウンタ値です。
f (!sem_op && result)はsem_op=0でセマフォカウンタ値が0でないなら、goto would_blockです。IPCセマフォのsem_op=0の仕様です。
そうでないなら、result += sem_opでセマフォカウンタ値にsem_opを足しこんで、その結果がマイナスならセマフォ取得できません。goto would_blockです。
セマフォオペレーション結果がSEMVMX(32767)を超えたらgoto out_of_rangです。
SEM_UNDOでのセマフォ操作なら、un->semadj[sop->sem_num] - sem_opの値がSEMAEM(32767)の絶対値の範囲外ならgoto out_of_rangです。ここではまだ取り消し処理は行っていません。そのチェックのみです。
上記チェックがOKなら、curr->semval = resultでstruct semにセマフォ操作を反映させたセマフォカウンタ値を設定します。
forループを終了したというのは、全てのセマフォ操作はOKだという事です。whileループで更新したstruct semに更新したプロセスIDをセットすると共に、un->semadj[sop->sem_num] -= sop->sem_opで取り消し処理を施しています。セマフォ操作ではresult += sem_opとsem_opを加算していますが、取り消し処理では-= sop->sem_opと減算しています。すなわち取り消し処理ではun->semadj[]が0になるように、semvalの加算/減算すればいいと言うことです。
out_of_range:は-ERANGEとして、undo:へジャンプし、ここに飛んでくる前に処理してしまったstruct semを元に戻しています。
would_block:はIPC_NOWAITが設定されていれば、EAGAINを、そうでなければ1を返します。そしてundo:へジャンプています。
static int try_atomic_semop (struct sem_array * sma, struct sembuf * sops, int nsops, struct sem_undo *un, int pid) { int result, sem_op; struct sembuf *sop; struct sem * curr; for (sop = sops; sop < sops + nsops; sop++) { curr = sma->sem_base + sop->sem_num; sem_op = sop->sem_op; result = curr->semval; if (!sem_op && result) goto would_block; result += sem_op; if (result < 0) goto would_block; if (result > SEMVMX) goto out_of_range; if (sop->sem_flg & SEM_UNDO) { int undo = un->semadj[sop->sem_num] - sem_op; if (undo < (-SEMAEM - 1) || undo > SEMAEM) goto out_of_range; } curr->semval = result; } sop--; while (sop >= sops) { sma->sem_base[sop->sem_num].sempid = pid; if (sop->sem_flg & SEM_UNDO) un->semadj[sop->sem_num] -= sop->sem_op; sop--; } sma->sem_otime = get_seconds(); return 0; out_of_range: result = -ERANGE; goto undo; would_block: if (sop->sem_flg & IPC_NOWAIT) result = -EAGAIN; else result = 1; undo: sop--; while (sop >= sops) { sma->sem_base[sop->sem_num].semval -= sop->sem_op; sop--; } return result; }update_queue()はカレントプロセスのセマフォ更新により、それまでにセマフォ操作でウエイトしているプロセスが起床できるかどうかをチェックします。このプロセスはsma->sem_pendingにリストされていて、この全てのプロセスについて1つづつtry_atomic_semop()をコールすること行っています。
セマフォ操作ができたプロセスを起床する条件としてif (error <= 0)となっていますが、ここではマイナス(エラー)としているのは、-EAGAIN(sem_op=0)を想定しての事です。なお他のエラーERANGEはありません。すでにtry_atomic_semop()で処理した結果として、sma->sem_pendingにリストされているわけですから。
try_atomic_semop()がOKなら、そのプロセスをsma->sem_pendingリストから削除して、wake_up_process()で起床させていますが、sma->sem_pendingリストから削除でif (q->alter)によりその削除の仕方が異なっています。
if (q->alter)の時、すなわち新たにセマフォ値の更新が発生した事を意味します。従ってsma->sem_pendingリストの最初からチェックしなおす必要があります。そのため次にチェックするプロセスは、先頭のsma->sem_pending.nextから取得しています。そうでないなら、このプロセスはq->list.nextから取得されるようになっています。
static void update_queue (struct sem_array * sma) { int error; struct sem_queue * q; q = list_entry(sma->sem_pending.next, struct sem_queue, list); while (&q->list != &sma->sem_pending) { error = try_atomic_semop(sma, q->sops, q->nsops, q->undo, q->pid); if (error <= 0) { struct sem_queue *n; if (q->alter) { list_del(&q->list); n = list_entry(sma->sem_pending.next, struct sem_queue, list); } else { n = list_entry(q->list.next, struct sem_queue, list); list_del(&q->list); } q->status = IN_WAKEUP; wake_up_process(q->sleeper); smp_wmb(); q->status = error; q = n; } else { q = list_entry(q->list.next, struct sem_queue, list); } } }