Commit 3f9fabe7 authored by Paul E. McKenney's avatar Paul E. McKenney Committed by Linus Torvalds

[PATCH] RCU: eliminating explicit memory barriers from SysV IPC

This patch uses the rcu_assign_pointer() API to eliminate a number of explicit
memory barriers from the SysV IPC code that uses RCU.  It also restructures
the ipc_ids structure so that the array size is stored in the same memory
block as the array itself (see the new struct ipc_id_ary).  This prevents the
race that the earlier code was subject to, where a reader could see a mismatch
between the size and the actual array.  With the size stored with the array,
the possibility of mismatch is eliminated -- with out the need for careful
ordering and explicit memory barriers.  This has been tested successfully on
i386 and ppc64.

Signed-off-by: <paulmck@us.ibm.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent fa56b76f
...@@ -381,7 +381,7 @@ asmlinkage long sys_msgctl (int msqid, int cmd, struct msqid_ds __user *buf) ...@@ -381,7 +381,7 @@ asmlinkage long sys_msgctl (int msqid, int cmd, struct msqid_ds __user *buf)
int success_return; int success_return;
if (!buf) if (!buf)
return -EFAULT; return -EFAULT;
if(cmd == MSG_STAT && msqid >= msg_ids.size) if(cmd == MSG_STAT && msqid >= msg_ids.entries->size)
return -EINVAL; return -EINVAL;
memset(&tbuf,0,sizeof(tbuf)); memset(&tbuf,0,sizeof(tbuf));
......
...@@ -524,7 +524,7 @@ static int semctl_nolock(int semid, int semnum, int cmd, int version, union semu ...@@ -524,7 +524,7 @@ static int semctl_nolock(int semid, int semnum, int cmd, int version, union semu
struct semid64_ds tbuf; struct semid64_ds tbuf;
int id; int id;
if(semid >= sem_ids.size) if(semid >= sem_ids.entries->size)
return -EINVAL; return -EINVAL;
memset(&tbuf,0,sizeof(tbuf)); memset(&tbuf,0,sizeof(tbuf));
......
...@@ -62,7 +62,6 @@ void __init ipc_init_ids(struct ipc_ids* ids, int size) ...@@ -62,7 +62,6 @@ void __init ipc_init_ids(struct ipc_ids* ids, int size)
if(size > IPCMNI) if(size > IPCMNI)
size = IPCMNI; size = IPCMNI;
ids->size = size;
ids->in_use = 0; ids->in_use = 0;
ids->max_id = -1; ids->max_id = -1;
ids->seq = 0; ids->seq = 0;
...@@ -74,14 +73,17 @@ void __init ipc_init_ids(struct ipc_ids* ids, int size) ...@@ -74,14 +73,17 @@ void __init ipc_init_ids(struct ipc_ids* ids, int size)
ids->seq_max = seq_limit; ids->seq_max = seq_limit;
} }
ids->entries = ipc_rcu_alloc(sizeof(struct ipc_id)*size); ids->entries = ipc_rcu_alloc(sizeof(struct kern_ipc_perm *)*size +
sizeof(struct ipc_id_ary));
if(ids->entries == NULL) { if(ids->entries == NULL) {
printk(KERN_ERR "ipc_init_ids() failed, ipc service disabled.\n"); printk(KERN_ERR "ipc_init_ids() failed, ipc service disabled.\n");
ids->size = 0; size = 0;
ids->entries = &ids->nullentry;
} }
for(i=0;i<ids->size;i++) ids->entries->size = size;
ids->entries[i].p = NULL; for(i=0;i<size;i++)
ids->entries->p[i] = NULL;
} }
/** /**
...@@ -104,7 +106,7 @@ int ipc_findkey(struct ipc_ids* ids, key_t key) ...@@ -104,7 +106,7 @@ int ipc_findkey(struct ipc_ids* ids, key_t key)
* since ipc_ids.sem is held * since ipc_ids.sem is held
*/ */
for (id = 0; id <= max_id; id++) { for (id = 0; id <= max_id; id++) {
p = ids->entries[id].p; p = ids->entries->p[id];
if(p==NULL) if(p==NULL)
continue; continue;
if (key == p->key) if (key == p->key)
...@@ -118,36 +120,36 @@ int ipc_findkey(struct ipc_ids* ids, key_t key) ...@@ -118,36 +120,36 @@ int ipc_findkey(struct ipc_ids* ids, key_t key)
*/ */
static int grow_ary(struct ipc_ids* ids, int newsize) static int grow_ary(struct ipc_ids* ids, int newsize)
{ {
struct ipc_id* new; struct ipc_id_ary* new;
struct ipc_id* old; struct ipc_id_ary* old;
int i; int i;
int size = ids->entries->size;
if(newsize > IPCMNI) if(newsize > IPCMNI)
newsize = IPCMNI; newsize = IPCMNI;
if(newsize <= ids->size) if(newsize <= size)
return newsize; return newsize;
new = ipc_rcu_alloc(sizeof(struct ipc_id)*newsize); new = ipc_rcu_alloc(sizeof(struct kern_ipc_perm *)*newsize +
sizeof(struct ipc_id_ary));
if(new == NULL) if(new == NULL)
return ids->size; return size;
memcpy(new, ids->entries, sizeof(struct ipc_id)*ids->size); new->size = newsize;
for(i=ids->size;i<newsize;i++) { memcpy(new->p, ids->entries->p, sizeof(struct kern_ipc_perm *)*size +
new[i].p = NULL; sizeof(struct ipc_id_ary));
for(i=size;i<newsize;i++) {
new->p[i] = NULL;
} }
old = ids->entries; old = ids->entries;
/* /*
* before setting the ids->entries to the new array, there must be a * Use rcu_assign_pointer() to make sure the memcpyed contents
* smp_wmb() to make sure the memcpyed contents of the new array are * of the new array are visible before the new array becomes visible.
* visible before the new array becomes visible.
*/ */
smp_wmb(); /* prevent seeing new array uninitialized. */ rcu_assign_pointer(ids->entries, new);
ids->entries = new;
smp_wmb(); /* prevent indexing into old array based on new size. */
ids->size = newsize;
ipc_rcu_putref(old); ipc_rcu_putref(old);
return ids->size; return newsize;
} }
/** /**
...@@ -175,7 +177,7 @@ int ipc_addid(struct ipc_ids* ids, struct kern_ipc_perm* new, int size) ...@@ -175,7 +177,7 @@ int ipc_addid(struct ipc_ids* ids, struct kern_ipc_perm* new, int size)
* ipc_ids.sem is held * ipc_ids.sem is held
*/ */
for (id = 0; id < size; id++) { for (id = 0; id < size; id++) {
if(ids->entries[id].p == NULL) if(ids->entries->p[id] == NULL)
goto found; goto found;
} }
return -1; return -1;
...@@ -195,7 +197,7 @@ int ipc_addid(struct ipc_ids* ids, struct kern_ipc_perm* new, int size) ...@@ -195,7 +197,7 @@ int ipc_addid(struct ipc_ids* ids, struct kern_ipc_perm* new, int size)
new->deleted = 0; new->deleted = 0;
rcu_read_lock(); rcu_read_lock();
spin_lock(&new->lock); spin_lock(&new->lock);
ids->entries[id].p = new; ids->entries->p[id] = new;
return id; return id;
} }
...@@ -216,15 +218,15 @@ struct kern_ipc_perm* ipc_rmid(struct ipc_ids* ids, int id) ...@@ -216,15 +218,15 @@ struct kern_ipc_perm* ipc_rmid(struct ipc_ids* ids, int id)
{ {
struct kern_ipc_perm* p; struct kern_ipc_perm* p;
int lid = id % SEQ_MULTIPLIER; int lid = id % SEQ_MULTIPLIER;
if(lid >= ids->size) if(lid >= ids->entries->size)
BUG(); BUG();
/* /*
* do not need a rcu_dereference()() here to force ordering * do not need a rcu_dereference()() here to force ordering
* on Alpha, since the ipc_ids.sem is held. * on Alpha, since the ipc_ids.sem is held.
*/ */
p = ids->entries[lid].p; p = ids->entries->p[lid];
ids->entries[lid].p = NULL; ids->entries->p[lid] = NULL;
if(p==NULL) if(p==NULL)
BUG(); BUG();
ids->in_use--; ids->in_use--;
...@@ -234,7 +236,7 @@ struct kern_ipc_perm* ipc_rmid(struct ipc_ids* ids, int id) ...@@ -234,7 +236,7 @@ struct kern_ipc_perm* ipc_rmid(struct ipc_ids* ids, int id)
lid--; lid--;
if(lid == -1) if(lid == -1)
break; break;
} while (ids->entries[lid].p == NULL); } while (ids->entries->p[lid] == NULL);
ids->max_id = lid; ids->max_id = lid;
} }
p->deleted = 1; p->deleted = 1;
...@@ -493,9 +495,9 @@ struct kern_ipc_perm* ipc_get(struct ipc_ids* ids, int id) ...@@ -493,9 +495,9 @@ struct kern_ipc_perm* ipc_get(struct ipc_ids* ids, int id)
{ {
struct kern_ipc_perm* out; struct kern_ipc_perm* out;
int lid = id % SEQ_MULTIPLIER; int lid = id % SEQ_MULTIPLIER;
if(lid >= ids->size) if(lid >= ids->entries->size)
return NULL; return NULL;
out = ids->entries[lid].p; out = ids->entries->p[lid];
return out; return out;
} }
...@@ -503,25 +505,15 @@ struct kern_ipc_perm* ipc_lock(struct ipc_ids* ids, int id) ...@@ -503,25 +505,15 @@ struct kern_ipc_perm* ipc_lock(struct ipc_ids* ids, int id)
{ {
struct kern_ipc_perm* out; struct kern_ipc_perm* out;
int lid = id % SEQ_MULTIPLIER; int lid = id % SEQ_MULTIPLIER;
struct ipc_id* entries; struct ipc_id_ary* entries;
rcu_read_lock(); rcu_read_lock();
if(lid >= ids->size) { entries = rcu_dereference(ids->entries);
if(lid >= entries->size) {
rcu_read_unlock(); rcu_read_unlock();
return NULL; return NULL;
} }
out = entries->p[lid];
/*
* Note: The following two read barriers are corresponding
* to the two write barriers in grow_ary(). They guarantee
* the writes are seen in the same order on the read side.
* smp_rmb() has effect on all CPUs. rcu_dereference()
* is used if there are data dependency between two reads, and
* has effect only on Alpha.
*/
smp_rmb(); /* prevent indexing old array with new size */
entries = rcu_dereference(ids->entries);
out = entries[lid].p;
if(out == NULL) { if(out == NULL) {
rcu_read_unlock(); rcu_read_unlock();
return NULL; return NULL;
......
...@@ -15,18 +15,19 @@ void sem_init (void); ...@@ -15,18 +15,19 @@ void sem_init (void);
void msg_init (void); void msg_init (void);
void shm_init (void); void shm_init (void);
struct ipc_ids { struct ipc_id_ary {
int size; int size;
struct kern_ipc_perm *p[0];
};
struct ipc_ids {
int in_use; int in_use;
int max_id; int max_id;
unsigned short seq; unsigned short seq;
unsigned short seq_max; unsigned short seq_max;
struct semaphore sem; struct semaphore sem;
struct ipc_id* entries; struct ipc_id_ary nullentry;
}; struct ipc_id_ary* entries;
struct ipc_id {
struct kern_ipc_perm* p;
}; };
void __init ipc_init_ids(struct ipc_ids* ids, int size); void __init ipc_init_ids(struct ipc_ids* ids, int size);
......
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