summaryrefslogtreecommitdiff
path: root/ipc/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/util.c')
-rw-r--r--ipc/util.c119
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