/* * linux/ipc/sem.c * Copyright (C) 1992 Krishna Balasubramanian * Copyright (C) 1995 Eric Schenk, Bruno Haible * * IMPLEMENTATION NOTES ON CODE REWRITE (Eric Schenk, January 1995): * This code underwent a massive rewrite in order to solve some problems * with the original code. In particular the original code failed to * wake up processes that were waiting for semval to go to 0 if the * value went to 0 and was then incremented rapidly enough. In solving * this problem I have also modified the implementation so that it * processes pending operations in a FIFO manner, thus give a guarantee * that processes waiting for a lock on the semaphore won't starve * unless another locking process fails to unlock. * In addition the following two changes in behavior have been introduced: * - The original implementation of semop returned the value * last semaphore element examined on success. This does not * match the manual page specifications, and effectively * allows the user to read the semaphore even if they do not * have read permissions. The implementation now returns 0 * on success as stated in the manual page. * - There is some confusion over whether the set of undo adjustments * to be performed at exit should be done in an atomic manner. * That is, if we are attempting to decrement the semval should we queue * up and wait until we can do so legally? * The original implementation attempted to do this. * The current implementation does not do so. This is because I don't * think it is the right thing (TM) to do, and because I couldn't * see a clean way to get the old behavior with the new design. * The POSIX standard and SVID should be consulted to determine * what behavior is mandated. * * Further notes on refinement (Christoph Rohland, December 1998): * - The POSIX standard says, that the undo adjustments simply should * redo. So the current implementation is o.K. * - The previous code had two flaws: * 1) It actively gave the semaphore to the next waiting process * sleeping on the semaphore. Since this process did not have the * cpu this led to many unnecessary context switches and bad * performance. Now we only check which process should be able to * get the semaphore and if this process wants to reduce some * semaphore value we simply wake it up without doing the * operation. So it has to try to get it later. Thus e.g. the * running process may reacquire the semaphore during the current * time slice. If it only waits for zero or increases the semaphore, * we do the operation in advance and wake it up. * 2) It did not wake up all zero waiting processes. We try to do * better but only get the semops right which only wait for zero or * increase. If there are decrement operations in the operations * array we do the same as before. * * With the incarnation of O(1) scheduler, it becomes unnecessary to perform * check/retry algorithm for waking up blocked processes as the new scheduler * is better at handling thread switch than the old one. * * /proc/sysvipc/sem support (c) 1999 Dragos Acostachioaie <dragos@iname.com> * * SMP-threaded, sysctl's added * (c) 1999 Manfred Spraul <manfred@colorfullife.com> * Enforced range limit on SEM_UNDO * (c) 2001 Red Hat Inc <alan@redhat.com> * Lockless wakeup * (c) 2003 Manfred Spraul <manfred@colorfullife.com> * * support for audit of ipc object properties and permission changes * Dustin Kirkland <dustin.kirkland@us.ibm.com> * * namespaces support * OpenVZ, SWsoft Inc. * Pavel Emelianov <xemul@openvz.org> */ #include <linux/slab.h>#include <linux/spinlock.h>#include <linux/init.h>#include <linux/proc_fs.h>#include <linux/time.h>#include <linux/smp_lock.h>#include <linux/security.h>#include <linux/syscalls.h>#include <linux/audit.h>#include <linux/capability.h>#include <linux/seq_file.h>#include <linux/mutex.h>#include <linux/nsproxy.h> #include <asm/uaccess.h>#include "util.h" #define sem_ids(ns) (*((ns)->ids[IPC_SEM_IDS])) #define sem_lock(ns, id) ((struct sem_array*)ipc_lock(&sem_ids(ns), id))#define sem_unlock(sma) ipc_unlock(&(sma)->sem_perm)#define sem_rmid(ns, id) ((struct sem_array*)ipc_rmid(&sem_ids(ns), id))#define sem_checkid(ns, sma, semid) \ ipc_checkid(&sem_ids(ns),&sma->sem_perm,semid)#define sem_buildid(ns, id, seq) \ ipc_buildid(&sem_ids(ns), id, seq) static struct ipc_ids init_sem_ids; static int newary(struct ipc_namespace *, key_t, int, int);static void freeary(struct ipc_namespace *ns, struct sem_array *sma, int id);#ifdef CONFIG_PROC_FSstatic int sysvipc_sem_proc_show(struct seq_file *s, void *it);#endif #define SEMMSL_FAST 256 /* 512 bytes on stack */#define SEMOPM_FAST 64 /* ~ 372 bytes on stack */ /* * linked list protection: * sem_undo.id_next, * sem_array.sem_pending{,last}, * sem_array.sem_undo: sem_lock() for read/write * sem_undo.proc_next: only "current" is allowed to read/write that field. * */ #define sc_semmsl sem_ctls[0]#define sc_semmns sem_ctls[1]#define sc_semopm sem_ctls[2]#define sc_semmni sem_ctls[3] static void __ipc_init __sem_init_ns(struct ipc_namespace *ns, struct ipc_ids *ids){ ns->ids[IPC_SEM_IDS] = ids; ns->sc_semmsl = SEMMSL; ns->sc_semmns = SEMMNS; ns->sc_semopm = SEMOPM; ns->sc_semmni = SEMMNI; ns->used_sems = 0; ipc_init_ids(ids, ns->sc_semmni);} #ifdef CONFIG_IPC_NSint sem_init_ns(struct ipc_namespace *ns){ struct ipc_ids *ids; ids = kmalloc(sizeof(struct ipc_ids), GFP_KERNEL); if (ids == NULL) return -ENOMEM; __sem_init_ns(ns, ids); return 0;} void sem_exit_ns(struct ipc_namespace *ns){ int i; struct sem_array *sma; mutex_lock(&sem_ids(ns).mutex); for (i = 0; i <= sem_ids(ns).max_id; i++) { sma = sem_lock(ns, i); if (sma == NULL) continue; freeary(ns, sma, i); } mutex_unlock(&sem_ids(ns).mutex); ipc_fini_ids(ns->ids[IPC_SEM_IDS]); kfree(ns->ids[IPC_SEM_IDS]); ns->ids[IPC_SEM_IDS] = NULL;}#endif void __init sem_init (void){ __sem_init_ns(&init_ipc_ns, &init_sem_ids); ipc_init_proc_interface("sysvipc/sem", " key semid perms nsems uid gid cuid cgid otime ctime\n", IPC_SEM_IDS, sysvipc_sem_proc_show);} /* * Lockless wakeup algorithm: * Without the check/retry algorithm a lockless wakeup is possible: * - queue.status is initialized to -EINTR before blocking. * - wakeup is performed by * * unlinking the queue entry from sma->sem_pending * * setting queue.status to IN_WAKEUP * This is the notification for the blocked thread that a * result value is imminent. * * call wake_up_process * * set queue.status to the final value. * - the previously blocked thread checks queue.status: * * if it's IN_WAKEUP, then it must wait until the value changes * * if it's not -EINTR, then the operation was completed by * update_queue. semtimedop can return queue.status without * performing any operation on the sem array. * * otherwise it must acquire the spinlock and check what's up. * * The two-stage algorithm is necessary to protect against the following * races: * - if queue.status is set after wake_up_process, then the woken up idle * thread could race forward and try (and fail) to acquire sma->lock * before update_queue had a chance to set queue.status * - if queue.status is written before wake_up_process and if the * blocked process is woken up by a signal between writing * queue.status and the wake_up_process, then the woken up * process could return from semtimedop and die by calling * sys_exit before wake_up_process is called. Then wake_up_process * will oops, because the task structure is already invalid. * (yes, this happened on s390 with sysv msg). * */#define IN_WAKEUP 1 static int newary (struct ipc_namespace *ns, key_t key, int nsems, int semflg){ int id; int retval; struct sem_array *sma; int size; if (!nsems) return -EINVAL; if (ns->used_sems + nsems > ns->sc_semmns) return -ENOSPC; size = sizeof (*sma) + nsems * sizeof (struct sem); sma = ipc_rcu_alloc(size); if (!sma) { return -ENOMEM; } memset (sma, 0, size); sma->sem_perm.mode = (semflg & S_IRWXUGO); sma->sem_perm.key = key; sma->sem_perm.security = NULL; retval = security_sem_alloc(sma); if (retval) { ipc_rcu_putref(sma); return retval; } id = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni); if(id == -1) { security_sem_free(sma); ipc_rcu_putref(sma); return -ENOSPC; } ns->used_sems += nsems; sma->sem_id = sem_buildid(ns, id, sma->sem_perm.seq); sma->sem_base = (struct sem *) &sma[1]; /* sma->sem_pending = NULL; */ sma->sem_pending_last = &sma->sem_pending; /* sma->undo = NULL; */ sma->sem_nsems = nsems; sma->sem_ctime = get_seconds(); sem_unlock(sma); return sma->sem_id;} asmlinkage long sys_semget (key_t key, int nsems, int semflg){ int id, err = -EINVAL; struct sem_array *sma; struct ipc_namespace *ns; ns = current->nsproxy->ipc_ns; if (nsems < 0 || nsems > ns->sc_semmsl) return -EINVAL; mutex_lock(&sem_ids(ns).mutex); if (key == IPC_PRIVATE) { err = newary(ns, key, nsems, semflg); } else if ((id = ipc_findkey(&sem_ids(ns), key)) == -1) { /* key not used */ if (!(semflg & IPC_CREAT)) err = -ENOENT; else err = newary(ns, key, nsems, semflg); } else if (semflg & IPC_CREAT && semflg & IPC_EXCL) { err = -EEXIST; } else { sma = sem_lock(ns, id); BUG_ON(sma==NULL); if (nsems > sma->sem_nsems) err = -EINVAL; else if (ipcperms(&sma->sem_perm, semflg)) err = -EACCES; else { int semid = sem_buildid(ns, id, sma->sem_perm.seq); err = security_sem_associate(sma, semflg); if (!err) err = semid; } sem_unlock(sma); } mutex_unlock(&sem_ids(ns).mutex); return err;} /* Manage the doubly linked list sma->sem_pending as a FIFO: * insert new queue elements at the tail sma->sem_pending_last. */static inline void append_to_queue (struct sem_array * sma, struct sem_queue * q){ *(q->prev = sma->sem_pending_last) = q; *(sma->sem_pending_last = &q->next) = NULL;} static inline void prepend_to_queue (struct sem_array * sma, struct sem_queue * q){ q->next = sma->sem_pending; *(q->prev = &sma->sem_pending) = q; if (q->next) q->next->prev = &q->next; else /* sma->sem_pending_last == &sma->sem_pending */ sma->sem_pending_last = &q->next;} static inline void remove_from_queue (struct sem_array * sma, struct sem_queue * q){ *(q->prev) = q->next; if (q->next) q->next->prev = q->prev; else /* sma->sem_pending_last == &q->next */ sma->sem_pending_last = q->prev; q->prev = NULL; /* mark as removed */} /* * Determine whether a sequence of semaphore operations would succeed * all at once. Return 0 if yes, 1 if need to sleep, else return error code. */ 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; /* * Exceeding the undo range is an error. */ 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;} /* Go through the pending queue for the indicated semaphore * looking for tasks that can be completed. */static void update_queue (struct sem_array * sma){ int error; struct sem_queue * q; q = sma->sem_pending; while(q) { error = try_atomic_semop(sma, q->sops, q->nsops, q->undo, q->pid); /* Does q->sleeper still need to sleep? */ if (error <= 0) { struct sem_queue *n; remove_from_queue(sma,q); q->status = IN_WAKEUP; /* * Continue scanning. The next operation * that must be checked depends on the type of the * completed operation: * - if the operation modified the array, then * restart from the head of the queue and * check for threads that might be waiting * for semaphore values to become 0. * - if the operation didn't modify the array, * then just continue. */ if (q->alter) n = sma->sem_pending; else n = q->next; wake_up_process(q->sleeper); /* hands-off: q will disappear immediately after * writing q->status. */ smp_wmb(); q->status = error; q = n; } else { q = q->next; } }} /* The following counts are associated to each semaphore: * semncnt number of tasks waiting on semval being nonzero * semzcnt number of tasks waiting on semval being zero * This model assumes that a task waits on exactly one semaphore. * Since semaphore operations are to be performed atomically, tasks actually * wait on a whole sequence of semaphores simultaneously. * The counts we return here are a rough approximation, but still * warrant that semncnt+semzcnt>0 if the task is on the pending queue. */static int count_semncnt (struct sem_array * sma, ushort semnum){ int semncnt; struct sem_queue * q; semncnt = 0; for (q = sma->sem_pending; q; q = q->next) { struct sembuf * sops = q->sops; int nsops = q->nsops; int i; for (i = 0; i < nsops; i++) if (sops[i].sem_num == semnum && (sops[i].sem_op < 0) && !(sops[i].sem_flg & IPC_NOWAIT)) semncnt++; } return semncnt;}static int count_semzcnt (struct sem_array * sma, ushort semnum){ int semzcnt; struct sem_queue * q; semzcnt = 0; for (q = sma->sem_pending; q; q = q->next) { struct sembuf * sops = q->sops; int nsops = q->nsops; int i; for (i = 0; i < nsops; i++) if (sops[i].sem_num == semnum && (sops[i].sem_op == 0) && !(sops[i].sem_flg & IPC_NOWAIT)) semzcnt++; } return semzcnt;} /* Free a semaphore set. freeary() is called with sem_ids.mutex locked and * the spinlock for this semaphore set hold. sem_ids.mutex remains locked * on exit. */static void freeary (struct ipc_namespace *ns, struct sem_array *sma, int id){ struct sem_undo *un; struct sem_queue *q;Lines 1–500 of 1418. Load more
/* * linux/ipc/sem.c * Copyright (C) 1992 Krishna Balasubramanian * Copyright (C) 1995 Eric Schenk, Bruno Haible * * IMPLEMENTATION NOTES ON CODE REWRITE (Eric Schenk, January 1995): * This code underwent a massive rewrite in order to solve some problems * with the original code. In particular the original code failed to * wake up processes that were waiting for semval to go to 0 if the * value went to 0 and was then incremented rapidly enough. In solving * this problem I have also modified the implementation so that it * processes pending operations in a FIFO manner, thus give a guarantee * that processes waiting for a lock on the semaphore won't starve * unless another locking process fails to unlock. * In addition the following two changes in behavior have been introduced: * - The original implementation of semop returned the value * last semaphore element examined on success. This does not * match the manual page specifications, and effectively * allows the user to read the semaphore even if they do not * have read permissions. The implementation now returns 0 * on success as stated in the manual page. * - There is some confusion over whether the set of undo adjustments * to be performed at exit should be done in an atomic manner. * That is, if we are attempting to decrement the semval should we queue * up and wait until we can do so legally? * The original implementation attempted to do this. * The current implementation does not do so. This is because I don't * think it is the right thing (TM) to do, and because I couldn't * see a clean way to get the old behavior with the new design. * The POSIX standard and SVID should be consulted to determine * what behavior is mandated. * * Further notes on refinement (Christoph Rohland, December 1998): * - The POSIX standard says, that the undo adjustments simply should * redo. So the current implementation is o.K. * - The previous code had two flaws: * 1) It actively gave the semaphore to the next waiting process * sleeping on the semaphore. Since this process did not have the * cpu this led to many unnecessary context switches and bad * performance. Now we only check which process should be able to * get the semaphore and if this process wants to reduce some * semaphore value we simply wake it up without doing the * operation. So it has to try to get it later. Thus e.g. the * running process may reacquire the semaphore during the current * time slice. If it only waits for zero or increases the semaphore, * we do the operation in advance and wake it up. * 2) It did not wake up all zero waiting processes. We try to do * better but only get the semops right which only wait for zero or * increase. If there are decrement operations in the operations * array we do the same as before. * * With the incarnation of O(1) scheduler, it becomes unnecessary to perform * check/retry algorithm for waking up blocked processes as the new scheduler * is better at handling thread switch than the old one. * * /proc/sysvipc/sem support (c) 1999 Dragos Acostachioaie <dragos@iname.com> * * SMP-threaded, sysctl's added * (c) 1999 Manfred Spraul <manfred@colorfullife.com> * Enforced range limit on SEM_UNDO * (c) 2001 Red Hat Inc <alan@redhat.com> * Lockless wakeup * (c) 2003 Manfred Spraul <manfred@colorfullife.com> * * support for audit of ipc object properties and permission changes * Dustin Kirkland <dustin.kirkland@us.ibm.com> * * namespaces support * OpenVZ, SWsoft Inc. * Pavel Emelianov <xemul@openvz.org> */ #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/init.h> #include <linux/proc_fs.h> #include <linux/time.h> #include <linux/smp_lock.h> #include <linux/security.h> #include <linux/syscalls.h> #include <linux/audit.h> #include <linux/capability.h> #include <linux/seq_file.h> #include <linux/mutex.h> #include <linux/nsproxy.h> #include <asm/uaccess.h> #include "util.h" #define sem_ids(ns) (*((ns)->ids[IPC_SEM_IDS])) #define sem_lock(ns, id) ((struct sem_array*)ipc_lock(&sem_ids(ns), id)) #define sem_unlock(sma) ipc_unlock(&(sma)->sem_perm) #define sem_rmid(ns, id) ((struct sem_array*)ipc_rmid(&sem_ids(ns), id)) #define sem_checkid(ns, sma, semid) \ ipc_checkid(&sem_ids(ns),&sma->sem_perm,semid) #define sem_buildid(ns, id, seq) \ ipc_buildid(&sem_ids(ns), id, seq) static struct ipc_ids init_sem_ids; static int newary(struct ipc_namespace *, key_t, int, int); static void freeary(struct ipc_namespace *ns, struct sem_array *sma, int id); #ifdef CONFIG_PROC_FS static int sysvipc_sem_proc_show(struct seq_file *s, void *it); #endif #define SEMMSL_FAST 256 /* 512 bytes on stack */ #define SEMOPM_FAST 64 /* ~ 372 bytes on stack */ /* * linked list protection: * sem_undo.id_next, * sem_array.sem_pending{,last}, * sem_array.sem_undo: sem_lock() for read/write * sem_undo.proc_next: only "current" is allowed to read/write that field. * */ #define sc_semmsl sem_ctls[0] #define sc_semmns sem_ctls[1] #define sc_semopm sem_ctls[2] #define sc_semmni sem_ctls[3] static void __ipc_init __sem_init_ns(struct ipc_namespace *ns, struct ipc_ids *ids) { ns->ids[IPC_SEM_IDS] = ids; ns->sc_semmsl = SEMMSL; ns->sc_semmns = SEMMNS; ns->sc_semopm = SEMOPM; ns->sc_semmni = SEMMNI; ns->used_sems = 0; ipc_init_ids(ids, ns->sc_semmni); } #ifdef CONFIG_IPC_NS int sem_init_ns(struct ipc_namespace *ns) { struct ipc_ids *ids; ids = kmalloc(sizeof(struct ipc_ids), GFP_KERNEL); if (ids == NULL) return -ENOMEM; __sem_init_ns(ns, ids); return 0; } void sem_exit_ns(struct ipc_namespace *ns) { int i; struct sem_array *sma; mutex_lock(&sem_ids(ns).mutex); for (i = 0; i <= sem_ids(ns).max_id; i++) { sma = sem_lock(ns, i); if (sma == NULL) continue; freeary(ns, sma, i); } mutex_unlock(&sem_ids(ns).mutex); ipc_fini_ids(ns->ids[IPC_SEM_IDS]); kfree(ns->ids[IPC_SEM_IDS]); ns->ids[IPC_SEM_IDS] = NULL; } #endif void __init sem_init (void) { __sem_init_ns(&init_ipc_ns, &init_sem_ids); ipc_init_proc_interface("sysvipc/sem", " key semid perms nsems uid gid cuid cgid otime ctime\n", IPC_SEM_IDS, sysvipc_sem_proc_show); } /* * Lockless wakeup algorithm: * Without the check/retry algorithm a lockless wakeup is possible: * - queue.status is initialized to -EINTR before blocking. * - wakeup is performed by * * unlinking the queue entry from sma->sem_pending * * setting queue.status to IN_WAKEUP * This is the notification for the blocked thread that a * result value is imminent. * * call wake_up_process * * set queue.status to the final value. * - the previously blocked thread checks queue.status: * * if it's IN_WAKEUP, then it must wait until the value changes * * if it's not -EINTR, then the operation was completed by * update_queue. semtimedop can return queue.status without * performing any operation on the sem array. * * otherwise it must acquire the spinlock and check what's up. * * The two-stage algorithm is necessary to protect against the following * races: * - if queue.status is set after wake_up_process, then the woken up idle * thread could race forward and try (and fail) to acquire sma->lock * before update_queue had a chance to set queue.status * - if queue.status is written before wake_up_process and if the * blocked process is woken up by a signal between writing * queue.status and the wake_up_process, then the woken up * process could return from semtimedop and die by calling * sys_exit before wake_up_process is called. Then wake_up_process * will oops, because the task structure is already invalid. * (yes, this happened on s390 with sysv msg). * */ #define IN_WAKEUP 1 static int newary (struct ipc_namespace *ns, key_t key, int nsems, int semflg) { int id; int retval; struct sem_array *sma; int size; if (!nsems) return -EINVAL; if (ns->used_sems + nsems > ns->sc_semmns) return -ENOSPC; size = sizeof (*sma) + nsems * sizeof (struct sem); sma = ipc_rcu_alloc(size); if (!sma) { return -ENOMEM; } memset (sma, 0, size); sma->sem_perm.mode = (semflg & S_IRWXUGO); sma->sem_perm.key = key; sma->sem_perm.security = NULL; retval = security_sem_alloc(sma); if (retval) { ipc_rcu_putref(sma); return retval; } id = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni); if(id == -1) { security_sem_free(sma); ipc_rcu_putref(sma); return -ENOSPC; } ns->used_sems += nsems; sma->sem_id = sem_buildid(ns, id, sma->sem_perm.seq); sma->sem_base = (struct sem *) &sma[1]; /* sma->sem_pending = NULL; */ sma->sem_pending_last = &sma->sem_pending; /* sma->undo = NULL; */ sma->sem_nsems = nsems; sma->sem_ctime = get_seconds(); sem_unlock(sma); return sma->sem_id; } asmlinkage long sys_semget (key_t key, int nsems, int semflg) { int id, err = -EINVAL; struct sem_array *sma; struct ipc_namespace *ns; ns = current->nsproxy->ipc_ns; if (nsems < 0 || nsems > ns->sc_semmsl) return -EINVAL; mutex_lock(&sem_ids(ns).mutex); if (key == IPC_PRIVATE) { err = newary(ns, key, nsems, semflg); } else if ((id = ipc_findkey(&sem_ids(ns), key)) == -1) { /* key not used */ if (!(semflg & IPC_CREAT)) err = -ENOENT; else err = newary(ns, key, nsems, semflg); } else if (semflg & IPC_CREAT && semflg & IPC_EXCL) { err = -EEXIST; } else { sma = sem_lock(ns, id); BUG_ON(sma==NULL); if (nsems > sma->sem_nsems) err = -EINVAL; else if (ipcperms(&sma->sem_perm, semflg)) err = -EACCES; else { int semid = sem_buildid(ns, id, sma->sem_perm.seq); err = security_sem_associate(sma, semflg); if (!err) err = semid; } sem_unlock(sma); } mutex_unlock(&sem_ids(ns).mutex); return err; } /* Manage the doubly linked list sma->sem_pending as a FIFO: * insert new queue elements at the tail sma->sem_pending_last. */ static inline void append_to_queue (struct sem_array * sma, struct sem_queue * q) { *(q->prev = sma->sem_pending_last) = q; *(sma->sem_pending_last = &q->next) = NULL; } static inline void prepend_to_queue (struct sem_array * sma, struct sem_queue * q) { q->next = sma->sem_pending; *(q->prev = &sma->sem_pending) = q; if (q->next) q->next->prev = &q->next; else /* sma->sem_pending_last == &sma->sem_pending */ sma->sem_pending_last = &q->next; } static inline void remove_from_queue (struct sem_array * sma, struct sem_queue * q) { *(q->prev) = q->next; if (q->next) q->next->prev = q->prev; else /* sma->sem_pending_last == &q->next */ sma->sem_pending_last = q->prev; q->prev = NULL; /* mark as removed */ } /* * Determine whether a sequence of semaphore operations would succeed * all at once. Return 0 if yes, 1 if need to sleep, else return error code. */ 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; /* * Exceeding the undo range is an error. */ 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; } /* Go through the pending queue for the indicated semaphore * looking for tasks that can be completed. */ static void update_queue (struct sem_array * sma) { int error; struct sem_queue * q; q = sma->sem_pending; while(q) { error = try_atomic_semop(sma, q->sops, q->nsops, q->undo, q->pid); /* Does q->sleeper still need to sleep? */ if (error <= 0) { struct sem_queue *n; remove_from_queue(sma,q); q->status = IN_WAKEUP; /* * Continue scanning. The next operation * that must be checked depends on the type of the * completed operation: * - if the operation modified the array, then * restart from the head of the queue and * check for threads that might be waiting * for semaphore values to become 0. * - if the operation didn't modify the array, * then just continue. */ if (q->alter) n = sma->sem_pending; else n = q->next; wake_up_process(q->sleeper); /* hands-off: q will disappear immediately after * writing q->status. */ smp_wmb(); q->status = error; q = n; } else { q = q->next; } } } /* The following counts are associated to each semaphore: * semncnt number of tasks waiting on semval being nonzero * semzcnt number of tasks waiting on semval being zero * This model assumes that a task waits on exactly one semaphore. * Since semaphore operations are to be performed atomically, tasks actually * wait on a whole sequence of semaphores simultaneously. * The counts we return here are a rough approximation, but still * warrant that semncnt+semzcnt>0 if the task is on the pending queue. */ static int count_semncnt (struct sem_array * sma, ushort semnum) { int semncnt; struct sem_queue * q; semncnt = 0; for (q = sma->sem_pending; q; q = q->next) { struct sembuf * sops = q->sops; int nsops = q->nsops; int i; for (i = 0; i < nsops; i++) if (sops[i].sem_num == semnum && (sops[i].sem_op < 0) && !(sops[i].sem_flg & IPC_NOWAIT)) semncnt++; } return semncnt; } static int count_semzcnt (struct sem_array * sma, ushort semnum) { int semzcnt; struct sem_queue * q; semzcnt = 0; for (q = sma->sem_pending; q; q = q->next) { struct sembuf * sops = q->sops; int nsops = q->nsops; int i; for (i = 0; i < nsops; i++) if (sops[i].sem_num == semnum && (sops[i].sem_op == 0) && !(sops[i].sem_flg & IPC_NOWAIT)) semzcnt++; } return semzcnt; } /* Free a semaphore set. freeary() is called with sem_ids.mutex locked and * the spinlock for this semaphore set hold. sem_ids.mutex remains locked * on exit. */ static void freeary (struct ipc_namespace *ns, struct sem_array *sma, int id) { struct sem_undo *un; struct sem_queue *q;