Commit d5cedb89 authored by Dave Olien's avatar Dave Olien Committed by Linus Torvalds

[PATCH] 2.5.9 SEM_UNDO patch

As we discussed some time ago, here is a patch for the SEM_UNDO change
that can be applied to linux-2.5.9.
parent 40bbbd47
...@@ -45,6 +45,7 @@ struct exec_domain; ...@@ -45,6 +45,7 @@ struct exec_domain;
#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */ #define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */
#define CLONE_THREAD 0x00010000 /* Same thread group? */ #define CLONE_THREAD 0x00010000 /* Same thread group? */
#define CLONE_NEWNS 0x00020000 /* New namespace group? */ #define CLONE_NEWNS 0x00020000 /* New namespace group? */
#define CLONE_SYSVSEM 0x00040000 /* share system V SEM_UNDO semantics */
#define CLONE_SIGNAL (CLONE_SIGHAND | CLONE_THREAD) #define CLONE_SIGNAL (CLONE_SIGHAND | CLONE_THREAD)
...@@ -315,8 +316,7 @@ struct task_struct { ...@@ -315,8 +316,7 @@ struct task_struct {
struct tty_struct *tty; /* NULL if no tty */ struct tty_struct *tty; /* NULL if no tty */
unsigned int locks; /* How many file locks are being held */ unsigned int locks; /* How many file locks are being held */
/* ipc stuff */ /* ipc stuff */
struct sem_undo *semundo; struct sysv_sem sysvsem;
struct sem_queue *semsleeping;
/* CPU-specific state of this task */ /* CPU-specific state of this task */
struct thread_struct thread; struct thread_struct thread;
/* filesystem information */ /* filesystem information */
......
...@@ -121,6 +121,21 @@ struct sem_undo { ...@@ -121,6 +121,21 @@ struct sem_undo {
short * semadj; /* array of adjustments, one per semaphore */ short * semadj; /* array of adjustments, one per semaphore */
}; };
/* sem_undo_list controls shared access to the list of sem_undo structures
* that may be shared among all a CLONE_SYSVSEM task group.
*/
struct sem_undo_list {
atomic_t refcnt;
spinlock_t lock;
volatile unsigned long add_count;
struct sem_undo *proc_list;
};
struct sysv_sem {
struct sem_undo_list *undo_list;
struct sem_queue *sleep_list;
};
asmlinkage long sys_semget (key_t key, int nsems, int semflg); asmlinkage long sys_semget (key_t key, int nsems, int semflg);
asmlinkage long sys_semop (int semid, struct sembuf *sops, unsigned nsops); asmlinkage long sys_semop (int semid, struct sembuf *sops, unsigned nsops);
asmlinkage long sys_semctl (int semid, int semnum, int cmd, union semun arg); asmlinkage long sys_semctl (int semid, int semnum, int cmd, union semun arg);
......
...@@ -789,12 +789,75 @@ asmlinkage long sys_semctl (int semid, int semnum, int cmd, union semun arg) ...@@ -789,12 +789,75 @@ asmlinkage long sys_semctl (int semid, int semnum, int cmd, union semun arg)
} }
} }
static struct sem_undo* freeundos(struct sem_array *sma, struct sem_undo* un) static inline void lock_semundo(void)
{
struct sem_undo_list *undo_list;
undo_list = current->sysvsem.undo_list;
if ((undo_list != NULL) && (atomic_read(&undo_list->refcnt) != 1))
spin_lock(&undo_list->lock);
}
/* This code has an interaction with copy_semundo().
* Consider; two tasks are sharing the undo_list. task1
* acquires the undo_list lock in lock_semundo(). If task2 now
* exits before task1 releases the lock (by calling
* unlock_semundo()), then task1 will never call spin_unlock().
* This leave the sem_undo_list in a locked state. If task1 now creats task3
* and once again shares the sem_undo_list, the sem_undo_list will still be
* locked, and future SEM_UNDO operations will deadlock. This case is
* dealt with in copy_semundo() by having it reinitialize the spin lock when
* the refcnt goes from 1 to 2.
*/
static inline void unlock_semundo(void)
{
struct sem_undo_list *undo_list;
undo_list = current->sysvsem.undo_list;
if ((undo_list != NULL) && (atomic_read(&undo_list->refcnt) != 1))
spin_unlock(&undo_list->lock);
}
/* If the task doesn't already have a undo_list, then allocate one
* here. We guarantee there is only one thread using this undo list,
* and current is THE ONE
*
* If this allocation and assignment succeeds, but later
* portions of this code fail, there is no need to free the sem_undo_list.
* Just let it stay associated with the task, and it'll be freed later
* at exit time.
*
* This can block, so callers must hold no locks.
*/
static inline int get_undo_list(struct sem_undo_list **undo_listp)
{
struct sem_undo_list *undo_list;
int size;
undo_list = current->sysvsem.undo_list;
if (!undo_list) {
size = sizeof(struct sem_undo_list);
undo_list = (struct sem_undo_list *) kmalloc(size, GFP_KERNEL);
if (undo_list == NULL)
return -ENOMEM;
memset(undo_list, 0, size);
/* don't initialize unodhd->lock here. It's done
* in copy_semundo() instead.
*/
atomic_set(&undo_list->refcnt, 1);
current->sysvsem.undo_list = undo_list;
}
*undo_listp = undo_list;
return 0;
}
static struct sem_undo* freeundos(struct sem_undo* un)
{ {
struct sem_undo* u; struct sem_undo* u;
struct sem_undo** up; struct sem_undo** up;
for(up = &current->semundo;(u=*up);up=&u->proc_next) { for(up = &current->sysvsem.undo_list->proc_list;(u=*up);up=&u->proc_next) {
if(un==u) { if(un==u) {
un=u->proc_next; un=u->proc_next;
*up=un; *up=un;
...@@ -806,33 +869,87 @@ static struct sem_undo* freeundos(struct sem_array *sma, struct sem_undo* un) ...@@ -806,33 +869,87 @@ static struct sem_undo* freeundos(struct sem_array *sma, struct sem_undo* un)
return un->proc_next; return un->proc_next;
} }
/* returns without sem_lock on error! */ static inline struct sem_undo *find_undo(int semid)
{
struct sem_undo *un;
un = NULL;
if (current->sysvsem.undo_list != NULL) {
un = current->sysvsem.undo_list->proc_list;
}
while(un != NULL) {
if(un->semid==semid)
break;
if(un->semid==-1)
un=freeundos(un);
else
un=un->proc_next;
}
return un;
}
/* returns without sem_lock and semundo list locks on error! */
static int alloc_undo(struct sem_array *sma, struct sem_undo** unp, int semid, int alter) static int alloc_undo(struct sem_array *sma, struct sem_undo** unp, int semid, int alter)
{ {
int size, nsems, error; int size, nsems, error;
struct sem_undo *un; struct sem_undo *un, *new_un;
struct sem_undo_list *undo_list;
unsigned long saved_add_count;
nsems = sma->sem_nsems; nsems = sma->sem_nsems;
size = sizeof(struct sem_undo) + sizeof(short)*nsems; saved_add_count = 0;
if (current->sysvsem.undo_list != NULL)
saved_add_count = current->sysvsem.undo_list->add_count;
sem_unlock(semid); sem_unlock(semid);
unlock_semundo();
error = get_undo_list(&undo_list);
if (error)
return error;
size = sizeof(struct sem_undo) + sizeof(short)*nsems;
un = (struct sem_undo *) kmalloc(size, GFP_KERNEL); un = (struct sem_undo *) kmalloc(size, GFP_KERNEL);
if (!un) if (!un)
return -ENOMEM; return -ENOMEM;
memset(un, 0, size); memset(un, 0, size);
lock_semundo();
error = sem_revalidate(semid, sma, nsems, alter ? S_IWUGO : S_IRUGO); error = sem_revalidate(semid, sma, nsems, alter ? S_IWUGO : S_IRUGO);
if(error) { if(error) {
unlock_semundo();
kfree(un); kfree(un);
return error; return error;
} }
un->semadj = (short *) &un[1];
un->semid = semid; /* alloc_undo has just
un->proc_next = current->semundo; * released all locks and reacquired them.
current->semundo = un; * But, another thread may have
un->id_next = sma->undo; * added the semundo we were looking for
sma->undo = un; * during that time.
* So, we check for it again.
* only initialize and add the new one
* if we don't discover one.
*/
new_un = NULL;
if (current->sysvsem.undo_list->add_count != saved_add_count)
new_un = find_undo(semid);
if (new_un != NULL) {
if (sma->undo != new_un)
BUG();
kfree(un);
un = new_un;
} else {
current->sysvsem.undo_list->add_count++;
un->semadj = (short *) &un[1];
un->semid = semid;
un->proc_next = undo_list->proc_list;
undo_list->proc_list = un;
un->id_next = sma->undo;
sma->undo = un;
}
*unp = un; *unp = un;
return 0; return 0;
} }
...@@ -847,6 +964,7 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops) ...@@ -847,6 +964,7 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops)
int undos = 0, decrease = 0, alter = 0; int undos = 0, decrease = 0, alter = 0;
struct sem_queue queue; struct sem_queue queue;
if (nsops < 1 || semid < 0) if (nsops < 1 || semid < 0)
return -EINVAL; return -EINVAL;
if (nsops > sc_semopm) if (nsops > sc_semopm)
...@@ -860,17 +978,18 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops) ...@@ -860,17 +978,18 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops)
error=-EFAULT; error=-EFAULT;
goto out_free; goto out_free;
} }
lock_semundo();
sma = sem_lock(semid); sma = sem_lock(semid);
error=-EINVAL; error=-EINVAL;
if(sma==NULL) if(sma==NULL)
goto out_free; goto out_semundo_free;
error = -EIDRM; error = -EIDRM;
if (sem_checkid(sma,semid)) if (sem_checkid(sma,semid))
goto out_unlock_free; goto out_unlock_semundo_free;
error = -EFBIG; error = -EFBIG;
for (sop = sops; sop < sops + nsops; sop++) { for (sop = sops; sop < sops + nsops; sop++) {
if (sop->sem_num >= sma->sem_nsems) if (sop->sem_num >= sma->sem_nsems)
goto out_unlock_free; goto out_unlock_semundo_free;
if (sop->sem_flg & SEM_UNDO) if (sop->sem_flg & SEM_UNDO)
undos++; undos++;
if (sop->sem_op < 0) if (sop->sem_op < 0)
...@@ -882,24 +1001,18 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops) ...@@ -882,24 +1001,18 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops)
error = -EACCES; error = -EACCES;
if (ipcperms(&sma->sem_perm, alter ? S_IWUGO : S_IRUGO)) if (ipcperms(&sma->sem_perm, alter ? S_IWUGO : S_IRUGO))
goto out_unlock_free; goto out_unlock_semundo_free;
if (undos) { if (undos) {
/* Make sure we have an undo structure /* Make sure we have an undo structure
* for this process and this semaphore set. * for this process and this semaphore set.
*/ */
un=current->semundo;
while(un != NULL) { un = find_undo(semid);
if(un->semid==semid)
break;
if(un->semid==-1)
un=freeundos(sma,un);
else
un=un->proc_next;
}
if (!un) { if (!un) {
error = alloc_undo(sma,&un,semid,alter); error = alloc_undo(sma,&un,semid,alter);
if(error) if (error)
goto out_free; goto out_free;
} }
} else } else
un = NULL; un = NULL;
...@@ -923,7 +1036,7 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops) ...@@ -923,7 +1036,7 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops)
append_to_queue(sma ,&queue); append_to_queue(sma ,&queue);
else else
prepend_to_queue(sma ,&queue); prepend_to_queue(sma ,&queue);
current->semsleeping = &queue; current->sysvsem.sleep_list = &queue;
for (;;) { for (;;) {
struct sem_array* tmp; struct sem_array* tmp;
...@@ -931,16 +1044,18 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops) ...@@ -931,16 +1044,18 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops)
queue.sleeper = current; queue.sleeper = current;
current->state = TASK_INTERRUPTIBLE; current->state = TASK_INTERRUPTIBLE;
sem_unlock(semid); sem_unlock(semid);
unlock_semundo();
schedule(); schedule();
lock_semundo();
tmp = sem_lock(semid); tmp = sem_lock(semid);
if(tmp==NULL) { if(tmp==NULL) {
if(queue.prev != NULL) if(queue.prev != NULL)
BUG(); BUG();
current->semsleeping = NULL; current->sysvsem.sleep_list = NULL;
error = -EIDRM; error = -EIDRM;
goto out_free; goto out_semundo_free;
} }
/* /*
* If queue.status == 1 we where woken up and * If queue.status == 1 we where woken up and
...@@ -960,23 +1075,67 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops) ...@@ -960,23 +1075,67 @@ asmlinkage long sys_semop (int semid, struct sembuf *tsops, unsigned nsops)
if (queue.prev) /* got Interrupt */ if (queue.prev) /* got Interrupt */
break; break;
/* Everything done by update_queue */ /* Everything done by update_queue */
current->semsleeping = NULL; current->sysvsem.sleep_list = NULL;
goto out_unlock_free; goto out_unlock_semundo_free;
} }
} }
current->semsleeping = NULL; current->sysvsem.sleep_list = NULL;
remove_from_queue(sma,&queue); remove_from_queue(sma,&queue);
update: update:
if (alter) if (alter)
update_queue (sma); update_queue (sma);
out_unlock_free: out_unlock_semundo_free:
sem_unlock(semid); sem_unlock(semid);
out_semundo_free:
unlock_semundo();
out_free: out_free:
if(sops != fast_sops) if(sops != fast_sops)
kfree(sops); kfree(sops);
return error; return error;
} }
/* If CLONE_SYSVSEM is set, establish sharing of SEM_UNDO state between
* parent and child tasks.
*
* See the notes above unlock_semundo() regarding the spin_lock_init()
* in this code. Initialize the undo_list->lock here instead of get_undo_list()
* because of the reasoning in the comment above unlock_semundo.
*/
int copy_semundo(unsigned long clone_flags, struct task_struct *tsk)
{
struct sem_undo_list *undo_list;
int error;
if (clone_flags & CLONE_SYSVSEM) {
error = get_undo_list(&undo_list);
if (error)
return error;
if (atomic_read(&undo_list->refcnt) == 1)
spin_lock_init(&undo_list->lock);
atomic_inc(&undo_list->refcnt);
tsk->sysvsem.undo_list = undo_list;
} else
tsk->sysvsem.undo_list = NULL;
return 0;
}
static inline void __exit_semundo(struct task_struct *tsk)
{
struct sem_undo_list *undo_list;
undo_list = tsk->sysvsem.undo_list;
if (!atomic_dec_and_test(&undo_list->refcnt))
kfree(undo_list);
}
void exit_semundo(struct task_struct *tsk)
{
if (tsk->sysvsem.undo_list != NULL)
__exit_semundo(tsk);
}
/* /*
* add semadj values to semaphores, free undo structures. * add semadj values to semaphores, free undo structures.
* undo structures are not freed when semaphore arrays are destroyed * undo structures are not freed when semaphore arrays are destroyed
...@@ -994,6 +1153,7 @@ void sem_exit (void) ...@@ -994,6 +1153,7 @@ void sem_exit (void)
struct sem_queue *q; struct sem_queue *q;
struct sem_undo *u, *un = NULL, **up, **unp; struct sem_undo *u, *un = NULL, **up, **unp;
struct sem_array *sma; struct sem_array *sma;
struct sem_undo_list *undo_list;
int nsems, i; int nsems, i;
lock_kernel(); lock_kernel();
...@@ -1001,10 +1161,10 @@ void sem_exit (void) ...@@ -1001,10 +1161,10 @@ void sem_exit (void)
/* If the current process was sleeping for a semaphore, /* If the current process was sleeping for a semaphore,
* remove it from the queue. * remove it from the queue.
*/ */
if ((q = current->semsleeping)) { if ((q = current->sysvsem.sleep_list)) {
int semid = q->id; int semid = q->id;
sma = sem_lock(semid); sma = sem_lock(semid);
current->semsleeping = NULL; current->sysvsem.sleep_list = NULL;
if (q->prev) { if (q->prev) {
if(sma==NULL) if(sma==NULL)
...@@ -1015,7 +1175,14 @@ void sem_exit (void) ...@@ -1015,7 +1175,14 @@ void sem_exit (void)
sem_unlock(semid); sem_unlock(semid);
} }
for (up = &current->semundo; (u = *up); *up = u->proc_next, kfree(u)) { undo_list = current->sysvsem.undo_list;
if ((undo_list == NULL) || (atomic_read(&undo_list->refcnt) != 1))
return;
/* There's no need to hold the semundo list lock, as current
* is the last task exiting for this undo list.
*/
for (up = &undo_list->proc_list; (u = *up); *up = u->proc_next, kfree(u)) {
int semid = u->semid; int semid = u->semid;
if(semid == -1) if(semid == -1)
continue; continue;
...@@ -1053,7 +1220,7 @@ void sem_exit (void) ...@@ -1053,7 +1220,7 @@ void sem_exit (void)
next_entry: next_entry:
sem_unlock(semid); sem_unlock(semid);
} }
current->semundo = NULL; __exit_semundo(current);
unlock_kernel(); unlock_kernel();
} }
......
...@@ -340,6 +340,17 @@ int ipc_parse_version (int *cmd) ...@@ -340,6 +340,17 @@ int ipc_parse_version (int *cmd)
* Dummy functions when SYSV IPC isn't configured * Dummy functions when SYSV IPC isn't configured
*/ */
int copy_semundo(unsigned long clone_flags, struct task_struct *tsk)
{
return 0;
}
void exit_semundo(struct task_struct *tsk)
{
return;
}
void sem_exit (void) void sem_exit (void)
{ {
return; return;
......
...@@ -34,6 +34,9 @@ ...@@ -34,6 +34,9 @@
static kmem_cache_t *task_struct_cachep; static kmem_cache_t *task_struct_cachep;
extern int copy_semundo(unsigned long clone_flags, struct task_struct *tsk);
extern void exit_semundo(struct task_struct *tsk);
/* The idle threads do not count.. */ /* The idle threads do not count.. */
int nr_threads; int nr_threads;
...@@ -710,8 +713,10 @@ int do_fork(unsigned long clone_flags, unsigned long stack_start, ...@@ -710,8 +713,10 @@ int do_fork(unsigned long clone_flags, unsigned long stack_start,
retval = -ENOMEM; retval = -ENOMEM;
/* copy all the process information */ /* copy all the process information */
if (copy_files(clone_flags, p)) if (copy_semundo(clone_flags, p))
goto bad_fork_cleanup; goto bad_fork_cleanup;
if (copy_files(clone_flags, p))
goto bad_fork_cleanup_semundo;
if (copy_fs(clone_flags, p)) if (copy_fs(clone_flags, p))
goto bad_fork_cleanup_files; goto bad_fork_cleanup_files;
if (copy_sighand(clone_flags, p)) if (copy_sighand(clone_flags, p))
...@@ -723,7 +728,6 @@ int do_fork(unsigned long clone_flags, unsigned long stack_start, ...@@ -723,7 +728,6 @@ int do_fork(unsigned long clone_flags, unsigned long stack_start,
retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
if (retval) if (retval)
goto bad_fork_cleanup_namespace; goto bad_fork_cleanup_namespace;
p->semundo = NULL;
/* Our parent execution domain becomes current domain /* Our parent execution domain becomes current domain
These must match for thread signalling to apply */ These must match for thread signalling to apply */
...@@ -815,6 +819,8 @@ int do_fork(unsigned long clone_flags, unsigned long stack_start, ...@@ -815,6 +819,8 @@ int do_fork(unsigned long clone_flags, unsigned long stack_start,
exit_fs(p); /* blocking */ exit_fs(p); /* blocking */
bad_fork_cleanup_files: bad_fork_cleanup_files:
exit_files(p); /* blocking */ exit_files(p); /* blocking */
bad_fork_cleanup_semundo:
exit_semundo(p);
bad_fork_cleanup: bad_fork_cleanup:
put_exec_domain(p->thread_info->exec_domain); put_exec_domain(p->thread_info->exec_domain);
if (p->binfmt && p->binfmt->module) if (p->binfmt && p->binfmt->module)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment