diff options
Diffstat (limited to 'ipc/util.c')
| -rw-r--r-- | ipc/util.c | 119 |
1 files changed, 106 insertions, 13 deletions
diff --git a/ipc/util.c b/ipc/util.c index 73b978baa1ec..4b7f324d9125 100644 --- a/ipc/util.c +++ b/ipc/util.c @@ -8,6 +8,8 @@ * Chris Evans, <chris@ferret.lmh.ox.ac.uk> * Nov 1999 - ipc helper functions, unified SMP locking * Manfred Spraul <manfreds@colorfullife.com> + * Oct 2002 - One lock per IPC id. RCU ipc_free for lock-free grow_ary(). + * Mingming Cao <cmm@us.ibm.com> */ #include <linux/config.h> @@ -20,6 +22,7 @@ #include <linux/slab.h> #include <linux/highuid.h> #include <linux/security.h> +#include <linux/workqueue.h> #if defined(CONFIG_SYSVIPC) @@ -69,13 +72,12 @@ void __init ipc_init_ids(struct ipc_ids* ids, int size) ids->seq_max = seq_limit; } - ids->entries = ipc_alloc(sizeof(struct ipc_id)*size); + ids->entries = ipc_rcu_alloc(sizeof(struct ipc_id)*size); if(ids->entries == NULL) { printk(KERN_ERR "ipc_init_ids() failed, ipc service disabled.\n"); ids->size = 0; } - ids->ary = SPIN_LOCK_UNLOCKED; for(i=0;i<ids->size;i++) ids->entries[i].p = NULL; } @@ -84,7 +86,8 @@ void __init ipc_init_ids(struct ipc_ids* ids, int size) * ipc_findkey - find a key in an ipc identifier set * @ids: Identifier set * @key: The key to find - * + * + * Requires ipc_ids.sem locked. * Returns the identifier if found or -1 if not. */ @@ -92,8 +95,9 @@ int ipc_findkey(struct ipc_ids* ids, key_t key) { int id; struct kern_ipc_perm* p; + int max_id = ids->max_id; - for (id = 0; id <= ids->max_id; id++) { + for (id = 0; id <= max_id; id++) { p = ids->entries[id].p; if(p==NULL) continue; @@ -103,6 +107,9 @@ int ipc_findkey(struct ipc_ids* ids, key_t key) return -1; } +/* + * Requires ipc_ids.sem locked + */ static int grow_ary(struct ipc_ids* ids, int newsize) { struct ipc_id* new; @@ -114,21 +121,21 @@ static int grow_ary(struct ipc_ids* ids, int newsize) if(newsize <= ids->size) return newsize; - new = ipc_alloc(sizeof(struct ipc_id)*newsize); + new = ipc_rcu_alloc(sizeof(struct ipc_id)*newsize); if(new == NULL) return ids->size; memcpy(new, ids->entries, sizeof(struct ipc_id)*ids->size); for(i=ids->size;i<newsize;i++) { new[i].p = NULL; } - spin_lock(&ids->ary); - old = ids->entries; - ids->entries = new; i = ids->size; + + ids->entries = new; + wmb(); ids->size = newsize; - spin_unlock(&ids->ary); - ipc_free(old, sizeof(struct ipc_id)*i); + + ipc_rcu_free(old, sizeof(struct ipc_id)*i); return ids->size; } @@ -166,7 +173,10 @@ found: if(ids->seq > ids->seq_max) ids->seq = 0; - spin_lock(&ids->ary); + new->lock = SPIN_LOCK_UNLOCKED; + new->deleted = 0; + rcu_read_lock(); + spin_lock(&new->lock); ids->entries[id].p = new; return id; } @@ -180,6 +190,8 @@ found: * fed an invalid identifier. The entry is removed and internal * variables recomputed. The object associated with the identifier * is returned. + * ipc_ids.sem and the spinlock for this ID is hold before this function + * is called, and remain locked on the exit. */ struct kern_ipc_perm* ipc_rmid(struct ipc_ids* ids, int id) @@ -188,6 +200,7 @@ struct kern_ipc_perm* ipc_rmid(struct ipc_ids* ids, int id) int lid = id % SEQ_MULTIPLIER; if(lid >= ids->size) BUG(); + p = ids->entries[lid].p; ids->entries[lid].p = NULL; if(p==NULL) @@ -202,6 +215,7 @@ struct kern_ipc_perm* ipc_rmid(struct ipc_ids* ids, int id) } while (ids->entries[lid].p == NULL); ids->max_id = lid; } + p->deleted = 1; return p; } @@ -224,14 +238,14 @@ void* ipc_alloc(int size) } /** - * ipc_free - free ipc space + * ipc_free - free ipc space * @ptr: pointer returned by ipc_alloc * @size: size of block * * Free a block created with ipc_alloc. The caller must know the size * used in the allocation call. */ - + void ipc_free(void* ptr, int size) { if(size > PAGE_SIZE) @@ -240,6 +254,85 @@ void ipc_free(void* ptr, int size) kfree(ptr); } +struct ipc_rcu_kmalloc +{ + struct rcu_head rcu; + /* "void *" makes sure alignment of following data is sane. */ + void *data[0]; +}; + +struct ipc_rcu_vmalloc +{ + struct rcu_head rcu; + struct work_struct work; + /* "void *" makes sure alignment of following data is sane. */ + void *data[0]; +}; + +static inline int rcu_use_vmalloc(int size) +{ + /* Too big for a single page? */ + if (sizeof(struct ipc_rcu_kmalloc) + size > PAGE_SIZE) + return 1; + return 0; +} + +/** + * ipc_rcu_alloc - allocate ipc and rcu space + * @size: size desired + * + * Allocate memory for the rcu header structure + the object. + * Returns the pointer to the object. + * NULL is returned if the allocation fails. + */ + +void* ipc_rcu_alloc(int size) +{ + void* out; + /* + * We prepend the allocation with the rcu struct, and + * workqueue if necessary (for vmalloc). + */ + if (rcu_use_vmalloc(size)) { + out = vmalloc(sizeof(struct ipc_rcu_vmalloc) + size); + if (out) out += sizeof(struct ipc_rcu_vmalloc); + } else { + out = kmalloc(sizeof(struct ipc_rcu_kmalloc)+size, GFP_KERNEL); + if (out) out += sizeof(struct ipc_rcu_kmalloc); + } + + return out; +} + +/** + * ipc_schedule_free - free ipc + rcu space + * + * Since RCU callback function is called in bh, + * we need to defer the vfree to schedule_work + */ +static void ipc_schedule_free(void* arg) +{ + struct ipc_rcu_vmalloc *free = arg; + + INIT_WORK(&free->work, vfree, free); + schedule_work(&free->work); +} + +void ipc_rcu_free(void* ptr, int size) +{ + if (rcu_use_vmalloc(size)) { + struct ipc_rcu_vmalloc *free; + free = ptr - sizeof(*free); + call_rcu(&free->rcu, ipc_schedule_free, free); + } else { + struct ipc_rcu_kmalloc *free; + free = ptr - sizeof(*free); + /* kfree takes a "const void *" so gcc warns. So we cast. */ + call_rcu(&free->rcu, (void (*)(void *))kfree, free); + } + +} + /** * ipcperms - check IPC permissions * @ipcp: IPC permission set |
