IPCセマフォの取り消し(1)
ユーザプロセスがIPCセマフォを取得したまま終了(とりわけ異常終了)した場合、これらのセマフォを操作している他のプロセス動作に影響することになります。そのためIPCセマフォには取り消し処理機能がインプリメントされています。
プロセス毎にIPC識別子のセマフォ組に対する操作を、struct sem_undo構造体に記録しています。そのメンバーsemadjの初期値は0です。セマフォを更新する毎にsemadjも更新していきます。ただしセマフォ取得(セマフォカウンタ値を減算)では、その分反対に加算します。そうする事で取り消し処理において、0でないsemadjのセマフォカウンタ値に対して、semadjを加算する事でセマフォの取り消しができてしまいます。
semtimedop()で操作するセマフォ組内に、1つでもSEM_UNDOフラグが設定してあると、find_alloc_undo()でstruct sem_undo unを取得し、これをqueue.undo = un、そしてsma->sem_pendingへリストしていますが、実はこのsem_undo unは操作したプロセスのタスク構造体にもリストされていき、取り消し処理を行う際、簡単にsem_undo unを参照することができるようになっています。またstruct sem_undo unにはメンバーsemidに、IPC識別子を設定されており、これから取り消しするstruct sem_arrayが取得できるようになっています。
lookup_undo()ではulpにリストされているsem_undo unで、IPC識別子semidと一致するstruct sem_undo *unを見つけ出します。もし見つかればstruct sem_undo *unを作成する必要はありません。goto outで復帰します。でなければ新規作成いたします。
新規作成はそのIPC識別子に対するセマフォ組が何個あるかに拠ります。そのためsem_lock_check()で識別子に対するstruct sem_array *smaを求め、semadjのサイズがsma->sem_nsems分となるようにkzalloc()でstruct sem_undo *newを確保し、newのメンバーに各設定をした後、list_add_rcu()でnewをulp->list_procすなわちcurrent->sysvsem.undo_listに、またIPC識別子管理下でのsma->list_idそれぞれにリストしています。
プロセス毎にIPC識別子のセマフォ組に対する操作を、struct sem_undo構造体に記録しています。そのメンバーsemadjの初期値は0です。セマフォを更新する毎にsemadjも更新していきます。ただしセマフォ取得(セマフォカウンタ値を減算)では、その分反対に加算します。そうする事で取り消し処理において、0でないsemadjのセマフォカウンタ値に対して、semadjを加算する事でセマフォの取り消しができてしまいます。
semtimedop()で操作するセマフォ組内に、1つでもSEM_UNDOフラグが設定してあると、find_alloc_undo()でstruct sem_undo unを取得し、これをqueue.undo = un、そしてsma->sem_pendingへリストしていますが、実はこのsem_undo unは操作したプロセスのタスク構造体にもリストされていき、取り消し処理を行う際、簡単にsem_undo unを参照することができるようになっています。またstruct sem_undo unにはメンバーsemidに、IPC識別子を設定されており、これから取り消しするstruct sem_arrayが取得できるようになっています。
SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, unsigned, nsops, const struct timespec __user *, timeout) { struct sem_array *sma; struct sem_undo *un, *new; : for (sop = sops; sop < sops + nsops; sop++) { if (sop->sem_flg & SEM_UNDO) undos = 1; : } if (undos) { un = find_alloc_undo(ns, semid); if (IS_ERR(un)) { error = PTR_ERR(un); goto out_free; } } else un = NULL; : error = try_atomic_semop (sma, sops, nsops, un, task_tgid_vnr(current)); if (error <= 0) { if (alter && error == 0) update_queue (sma); goto out_unlock_free; } : queue.sops = sops; queue.nsops = nsops; queue.undo = un; queue.pid = task_tgid_vnr(current); queue.alter = alter; if (alter) list_add_tail(&queue.list, &sma->sem_pending); else list_add(&queue.list, &sma->sem_pending); queue.status = -EINTR; queue.sleeper = current; current->state = TASK_INTERRUPTIBLE; sem_unlock(sma); if (timeout) jiffies_left = schedule_timeout(jiffies_left); else schedule(); : }find_alloc_undo()で取り消し構造体sem_undo unを取得します。get_undo_list()はcurrent->sysvsem.undo_listをulpにセットして返すだけで、ここにはsem_undo unがリストされています。
lookup_undo()ではulpにリストされているsem_undo unで、IPC識別子semidと一致するstruct sem_undo *unを見つけ出します。もし見つかればstruct sem_undo *unを作成する必要はありません。goto outで復帰します。でなければ新規作成いたします。
新規作成はそのIPC識別子に対するセマフォ組が何個あるかに拠ります。そのためsem_lock_check()で識別子に対するstruct sem_array *smaを求め、semadjのサイズがsma->sem_nsems分となるようにkzalloc()でstruct sem_undo *newを確保し、newのメンバーに各設定をした後、list_add_rcu()でnewをulp->list_procすなわちcurrent->sysvsem.undo_listに、またIPC識別子管理下でのsma->list_idそれぞれにリストしています。
static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid) { struct sem_array *sma; struct sem_undo_list *ulp; struct sem_undo *un, *new; int nsems; int error; error = get_undo_list(&ulp); if (error) return ERR_PTR(error); rcu_read_lock(); spin_lock(&ulp->lock); un = lookup_undo(ulp, semid); spin_unlock(&ulp->lock); if (likely(un!=NULL)) goto out; rcu_read_unlock(); sma = sem_lock_check(ns, semid); if (IS_ERR(sma)) return ERR_PTR(PTR_ERR(sma)); nsems = sma->sem_nsems; sem_getref_and_unlock(sma); new = kzalloc(sizeof(struct sem_undo) + sizeof(short)*nsems, GFP_KERNEL); : : new->semadj = (short *) &new[1]; new->ulp = ulp; new->semid = semid; assert_spin_locked(&ulp->lock); list_add_rcu(&new->list_proc, &ulp->list_proc); assert_spin_locked(&sma->sem_perm.lock); list_add(&new->list_id, &sma->list_id); un = new; success: spin_unlock(&ulp->lock); rcu_read_lock(); sem_unlock(sma); out: return un; }lookup_undo()はプロセス構造体から参照されたulp->list_procリストの、IPC識別子semidと一致するstruct sem_undoを返すわけですが、NULLなら、そのIPC識別子のセマフォは、初めて取り消し処理を行う事を意味するわけです。
static struct sem_undo *lookup_undo(struct sem_undo_list *ulp, int semid) { struct sem_undo *walk; list_for_each_entry_rcu(walk, &ulp->list_proc, list_proc) { if (walk->semid == semid) return walk; } return NULL; }get_undo_list()は操作するプロセスの取り消し処理リストcurrent->sysvsem.undo_listを返します。もしこのリストが無い(初めてセマフォ取り消し処理でセマフォ操作を行った)場合、ヘッダーとなるstruct sem_undo_listを確保し、それをcurrent->sysvsem.undo_listとしる処理です。
static inline int get_undo_list(struct sem_undo_list **undo_listp) { struct sem_undo_list *undo_list; undo_list = current->sysvsem.undo_list; if (!undo_list) { undo_list = kzalloc(sizeof(*undo_list), GFP_KERNEL); if (undo_list == NULL) return -ENOMEM; spin_lock_init(&undo_list->lock); atomic_set(&undo_list->refcnt, 1); INIT_LIST_HEAD(&undo_list->list_proc); current->sysvsem.undo_list = undo_list; } *undo_listp = undo_list; return 0; }
備考
struct sem_undoのメモリを確保する処理で、new = kzalloc(sizeof(struct sem_undo) + sizeof(short)*nsems, GFP_KERNEL)とし、new->semadj = (short *) &new[1]としていて、これはちょっとしたプログラムテクニックだなあ。と感心してしまいました。要はstruct sem_undoをヘッダーとし、 その下にsizeof(short)*nsemsのsizeof(short)を付加した形でメモリを確保し、new->semadjに(short *) &new[1]と次のstruct sem_undo配列、これはsizeof(short)*nsemsをセットする事で実現しています。struct sem_undo { struct list_head list_proc; struct rcu_head rcu; struct sem_undo_list *ulp; struct list_head list_id; int semid; short * semadj; }