ipc_schedule_free() can do vfree() directly now

Commit 32fcfd4071 ("make vfree() safe to call from interrupt
contexts") made it safe to do vfree directly from the RCU callback,
which allows us to simplify ipc/util.c a lot by getting rid of the
differences between vmalloc/kmalloc memory.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Al Viro 2013-04-29 12:42:09 -04:00 committed by Linus Torvalds
parent 4ada8db38a
commit 600fe9751a

View File

@ -466,51 +466,13 @@ void ipc_free(void* ptr, int size)
kfree(ptr);
}
/*
* rcu allocations:
* There are three headers that are prepended to the actual allocation:
* - during use: ipc_rcu_hdr.
* - during the rcu grace period: ipc_rcu_grace.
* - [only if vmalloc]: ipc_rcu_sched.
* Their lifetime doesn't overlap, thus the headers share the same memory.
* Unlike a normal union, they are right-aligned, thus some container_of
* forward/backward casting is necessary:
*/
struct ipc_rcu_hdr
{
atomic_t refcount;
int is_vmalloc;
void *data[0];
};
struct ipc_rcu_grace
{
struct ipc_rcu {
struct rcu_head rcu;
atomic_t refcount;
/* "void *" makes sure alignment of following data is sane. */
void *data[0];
};
struct ipc_rcu_sched
{
struct work_struct work;
/* "void *" makes sure alignment of following data is sane. */
void *data[0];
};
#define HDRLEN_KMALLOC (sizeof(struct ipc_rcu_grace) > sizeof(struct ipc_rcu_hdr) ? \
sizeof(struct ipc_rcu_grace) : sizeof(struct ipc_rcu_hdr))
#define HDRLEN_VMALLOC (sizeof(struct ipc_rcu_sched) > HDRLEN_KMALLOC ? \
sizeof(struct ipc_rcu_sched) : HDRLEN_KMALLOC)
static inline int rcu_use_vmalloc(int size)
{
/* Too big for a single page? */
if (HDRLEN_KMALLOC + size > PAGE_SIZE)
return 1;
return 0;
}
/**
* ipc_rcu_alloc - allocate ipc and rcu space
* @size: size desired
@ -520,74 +482,41 @@ static inline int rcu_use_vmalloc(int size)
*/
void *ipc_rcu_alloc(int size)
{
void *out;
/*
* We prepend the allocation with the rcu struct, and
* workqueue if necessary (for vmalloc).
* We prepend the allocation with the rcu struct
*/
if (rcu_use_vmalloc(size)) {
out = vmalloc(HDRLEN_VMALLOC + size);
if (!out)
goto done;
out += HDRLEN_VMALLOC;
container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 1;
} else {
out = kmalloc(HDRLEN_KMALLOC + size, GFP_KERNEL);
if (!out)
goto done;
out += HDRLEN_KMALLOC;
container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 0;
}
/* set reference counter no matter what kind of allocation was done */
atomic_set(&container_of(out, struct ipc_rcu_hdr, data)->refcount, 1);
done:
return out;
struct ipc_rcu *out = ipc_alloc(sizeof(struct ipc_rcu) + size);
if (unlikely(!out))
return NULL;
atomic_set(&out->refcount, 1);
return out->data;
}
int ipc_rcu_getref(void *ptr)
{
return atomic_inc_not_zero(&container_of(ptr, struct ipc_rcu_hdr, data)->refcount);
}
static void ipc_do_vfree(struct work_struct *work)
{
vfree(container_of(work, struct ipc_rcu_sched, work));
return atomic_inc_not_zero(&container_of(ptr, struct ipc_rcu, data)->refcount);
}
/**
* ipc_schedule_free - free ipc + rcu space
* @head: RCU callback structure for queued work
*
* Since RCU callback function is called in bh,
* we need to defer the vfree to schedule_work().
*/
static void ipc_schedule_free(struct rcu_head *head)
{
struct ipc_rcu_grace *grace;
struct ipc_rcu_sched *sched;
grace = container_of(head, struct ipc_rcu_grace, rcu);
sched = container_of(&(grace->data[0]), struct ipc_rcu_sched,
data[0]);
INIT_WORK(&sched->work, ipc_do_vfree);
schedule_work(&sched->work);
vfree(container_of(head, struct ipc_rcu, rcu));
}
void ipc_rcu_putref(void *ptr)
{
if (!atomic_dec_and_test(&container_of(ptr, struct ipc_rcu_hdr, data)->refcount))
struct ipc_rcu *p = container_of(ptr, struct ipc_rcu, data);
if (!atomic_dec_and_test(&p->refcount))
return;
if (container_of(ptr, struct ipc_rcu_hdr, data)->is_vmalloc) {
call_rcu(&container_of(ptr, struct ipc_rcu_grace, data)->rcu,
ipc_schedule_free);
if (is_vmalloc_addr(ptr)) {
call_rcu(&p->rcu, ipc_schedule_free);
} else {
kfree_rcu(container_of(ptr, struct ipc_rcu_grace, data), rcu);
kfree_rcu(p, rcu);
}
}