1418 lines · 34.6 KB · C
RawDownload
/* * 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.