netlink: use call_rcu for netlink_change_ngroups
For the network namespace work in generic netlink I need to be able to call this function under rcu_read_lock(), otherwise the locking becomes a nightmare and more locks would be needed. Instead, just embed a struct rcu_head (actually a struct listeners_rcu_head that also carries the pointer to the memory block) into the listeners memory so we can use call_rcu() instead of synchronising and then freeing. No rcu_barrier() is needed since this code cannot be modular. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
487420df79
commit
6c04bb18dd
|
@ -83,6 +83,11 @@ struct netlink_sock {
|
|||
struct module *module;
|
||||
};
|
||||
|
||||
struct listeners_rcu_head {
|
||||
struct rcu_head rcu_head;
|
||||
void *ptr;
|
||||
};
|
||||
|
||||
#define NETLINK_KERNEL_SOCKET 0x1
|
||||
#define NETLINK_RECV_PKTINFO 0x2
|
||||
#define NETLINK_BROADCAST_SEND_ERROR 0x4
|
||||
|
@ -1453,7 +1458,8 @@ netlink_kernel_create(struct net *net, int unit, unsigned int groups,
|
|||
if (groups < 32)
|
||||
groups = 32;
|
||||
|
||||
listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL);
|
||||
listeners = kzalloc(NLGRPSZ(groups) + sizeof(struct listeners_rcu_head),
|
||||
GFP_KERNEL);
|
||||
if (!listeners)
|
||||
goto out_sock_release;
|
||||
|
||||
|
@ -1501,6 +1507,14 @@ netlink_kernel_release(struct sock *sk)
|
|||
EXPORT_SYMBOL(netlink_kernel_release);
|
||||
|
||||
|
||||
static void netlink_free_old_listeners(struct rcu_head *rcu_head)
|
||||
{
|
||||
struct listeners_rcu_head *lrh;
|
||||
|
||||
lrh = container_of(rcu_head, struct listeners_rcu_head, rcu_head);
|
||||
kfree(lrh->ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* netlink_change_ngroups - change number of multicast groups
|
||||
*
|
||||
|
@ -1516,6 +1530,7 @@ EXPORT_SYMBOL(netlink_kernel_release);
|
|||
int netlink_change_ngroups(struct sock *sk, unsigned int groups)
|
||||
{
|
||||
unsigned long *listeners, *old = NULL;
|
||||
struct listeners_rcu_head *old_rcu_head;
|
||||
struct netlink_table *tbl = &nl_table[sk->sk_protocol];
|
||||
int err = 0;
|
||||
|
||||
|
@ -1524,7 +1539,9 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups)
|
|||
|
||||
netlink_table_grab();
|
||||
if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) {
|
||||
listeners = kzalloc(NLGRPSZ(groups), GFP_ATOMIC);
|
||||
listeners = kzalloc(NLGRPSZ(groups) +
|
||||
sizeof(struct listeners_rcu_head),
|
||||
GFP_ATOMIC);
|
||||
if (!listeners) {
|
||||
err = -ENOMEM;
|
||||
goto out_ungrab;
|
||||
|
@ -1532,13 +1549,22 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups)
|
|||
old = tbl->listeners;
|
||||
memcpy(listeners, old, NLGRPSZ(tbl->groups));
|
||||
rcu_assign_pointer(tbl->listeners, listeners);
|
||||
/*
|
||||
* Free the old memory after an RCU grace period so we
|
||||
* don't leak it. We use call_rcu() here in order to be
|
||||
* able to call this function from atomic contexts. The
|
||||
* allocation of this memory will have reserved enough
|
||||
* space for struct listeners_rcu_head at the end.
|
||||
*/
|
||||
old_rcu_head = (void *)(tbl->listeners +
|
||||
NLGRPLONGS(tbl->groups));
|
||||
old_rcu_head->ptr = old;
|
||||
call_rcu(&old_rcu_head->rcu_head, netlink_free_old_listeners);
|
||||
}
|
||||
tbl->groups = groups;
|
||||
|
||||
out_ungrab:
|
||||
netlink_table_ungrab();
|
||||
synchronize_rcu();
|
||||
kfree(old);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue