diff options
| author | Johannes Berg <johannes@sipsolutions.net> | 2009-07-10 05:51:32 -0400 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2009-07-12 17:03:24 -0400 |
| commit | 6c04bb18ddd633b7feac2c8fe2ae0bf61d20ca7a (patch) | |
| tree | d80582a2c3c248acef1193291a1888ffd588e9fd | |
| parent | 487420df79f1d9f5b9de74c9bef378609c475a39 (diff) | |
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>
| -rw-r--r-- | net/netlink/af_netlink.c | 34 |
1 files changed, 30 insertions, 4 deletions
diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index d7d1b822e824..d46da6cb92e4 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c | |||
| @@ -83,6 +83,11 @@ struct netlink_sock { | |||
| 83 | struct module *module; | 83 | struct module *module; |
| 84 | }; | 84 | }; |
| 85 | 85 | ||
| 86 | struct listeners_rcu_head { | ||
| 87 | struct rcu_head rcu_head; | ||
| 88 | void *ptr; | ||
| 89 | }; | ||
| 90 | |||
| 86 | #define NETLINK_KERNEL_SOCKET 0x1 | 91 | #define NETLINK_KERNEL_SOCKET 0x1 |
| 87 | #define NETLINK_RECV_PKTINFO 0x2 | 92 | #define NETLINK_RECV_PKTINFO 0x2 |
| 88 | #define NETLINK_BROADCAST_SEND_ERROR 0x4 | 93 | #define NETLINK_BROADCAST_SEND_ERROR 0x4 |
| @@ -1453,7 +1458,8 @@ netlink_kernel_create(struct net *net, int unit, unsigned int groups, | |||
| 1453 | if (groups < 32) | 1458 | if (groups < 32) |
| 1454 | groups = 32; | 1459 | groups = 32; |
| 1455 | 1460 | ||
| 1456 | listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL); | 1461 | listeners = kzalloc(NLGRPSZ(groups) + sizeof(struct listeners_rcu_head), |
| 1462 | GFP_KERNEL); | ||
| 1457 | if (!listeners) | 1463 | if (!listeners) |
| 1458 | goto out_sock_release; | 1464 | goto out_sock_release; |
| 1459 | 1465 | ||
| @@ -1501,6 +1507,14 @@ netlink_kernel_release(struct sock *sk) | |||
| 1501 | EXPORT_SYMBOL(netlink_kernel_release); | 1507 | EXPORT_SYMBOL(netlink_kernel_release); |
| 1502 | 1508 | ||
| 1503 | 1509 | ||
| 1510 | static void netlink_free_old_listeners(struct rcu_head *rcu_head) | ||
| 1511 | { | ||
| 1512 | struct listeners_rcu_head *lrh; | ||
| 1513 | |||
| 1514 | lrh = container_of(rcu_head, struct listeners_rcu_head, rcu_head); | ||
| 1515 | kfree(lrh->ptr); | ||
| 1516 | } | ||
| 1517 | |||
| 1504 | /** | 1518 | /** |
| 1505 | * netlink_change_ngroups - change number of multicast groups | 1519 | * netlink_change_ngroups - change number of multicast groups |
| 1506 | * | 1520 | * |
| @@ -1516,6 +1530,7 @@ EXPORT_SYMBOL(netlink_kernel_release); | |||
| 1516 | int netlink_change_ngroups(struct sock *sk, unsigned int groups) | 1530 | int netlink_change_ngroups(struct sock *sk, unsigned int groups) |
| 1517 | { | 1531 | { |
| 1518 | unsigned long *listeners, *old = NULL; | 1532 | unsigned long *listeners, *old = NULL; |
| 1533 | struct listeners_rcu_head *old_rcu_head; | ||
| 1519 | struct netlink_table *tbl = &nl_table[sk->sk_protocol]; | 1534 | struct netlink_table *tbl = &nl_table[sk->sk_protocol]; |
| 1520 | int err = 0; | 1535 | int err = 0; |
| 1521 | 1536 | ||
| @@ -1524,7 +1539,9 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups) | |||
| 1524 | 1539 | ||
| 1525 | netlink_table_grab(); | 1540 | netlink_table_grab(); |
| 1526 | if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) { | 1541 | if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) { |
| 1527 | listeners = kzalloc(NLGRPSZ(groups), GFP_ATOMIC); | 1542 | listeners = kzalloc(NLGRPSZ(groups) + |
| 1543 | sizeof(struct listeners_rcu_head), | ||
| 1544 | GFP_ATOMIC); | ||
| 1528 | if (!listeners) { | 1545 | if (!listeners) { |
| 1529 | err = -ENOMEM; | 1546 | err = -ENOMEM; |
| 1530 | goto out_ungrab; | 1547 | goto out_ungrab; |
| @@ -1532,13 +1549,22 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups) | |||
| 1532 | old = tbl->listeners; | 1549 | old = tbl->listeners; |
| 1533 | memcpy(listeners, old, NLGRPSZ(tbl->groups)); | 1550 | memcpy(listeners, old, NLGRPSZ(tbl->groups)); |
| 1534 | rcu_assign_pointer(tbl->listeners, listeners); | 1551 | rcu_assign_pointer(tbl->listeners, listeners); |
| 1552 | /* | ||
| 1553 | * Free the old memory after an RCU grace period so we | ||
| 1554 | * don't leak it. We use call_rcu() here in order to be | ||
| 1555 | * able to call this function from atomic contexts. The | ||
| 1556 | * allocation of this memory will have reserved enough | ||
| 1557 | * space for struct listeners_rcu_head at the end. | ||
| 1558 | */ | ||
| 1559 | old_rcu_head = (void *)(tbl->listeners + | ||
| 1560 | NLGRPLONGS(tbl->groups)); | ||
| 1561 | old_rcu_head->ptr = old; | ||
| 1562 | call_rcu(&old_rcu_head->rcu_head, netlink_free_old_listeners); | ||
| 1535 | } | 1563 | } |
| 1536 | tbl->groups = groups; | 1564 | tbl->groups = groups; |
| 1537 | 1565 | ||
| 1538 | out_ungrab: | 1566 | out_ungrab: |
| 1539 | netlink_table_ungrab(); | 1567 | netlink_table_ungrab(); |
| 1540 | synchronize_rcu(); | ||
| 1541 | kfree(old); | ||
| 1542 | return err; | 1568 | return err; |
| 1543 | } | 1569 | } |
| 1544 | 1570 | ||
