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 /net/netlink/af_netlink.c | |
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>
Diffstat (limited to 'net/netlink/af_netlink.c')
-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 | ||