poll/wait/md: allow module to safely support 'poll' on /proc files
PATCH 1/1 poll/wait/md: allow module to safely support 'poll' on /proc files
The md driver currently supports 'poll' on /proc/mdstat. This is unsafe as if the md-mod module is removed while a 'poll' or 'select' is outstanding on /proc/mdstat, an oops occurs when the syscall completes.poll_freewait() will call remove_wait_queue() on a wait_queue_head_t which was local to the module which no-longer exists.
This problem is particular to /proc. Most filesystems do not allow the module to be unloaded while any files are open on it. /proc only blocks module unloading while a file_operations call is currently active into the module, not while the file is open. kernfs has this property too but kernfs allocates a wait_queue_head_t in its internal data structures so the module doesn't need to provide one.
(A previous patch to add a similar allocation to procfs was not accepted).
This patch takes a different approach and allows a module to disconnect the wait_queue_head_t that was passed to poll_wait() from all the clients which are waiting on it. Thus after calling
proc_remove_entry("mdstat", NULL);
we simply call
wait_queue_purge(&md_event_waiters);
and then know that it is safe to remove the module.
rcu infrastructure is used to avoid races.poll_freewait() checks if the purge has happened under rcu_read_lock() to ensure that it never touches any freed memory. wait_queue_purge() uses synchronize_rcu() to ensure no poll_freewait() could still be looking at the wait_queue_head_t.
drivers/md/md.c +1 -0
@@ -8681,6 +8681,7 @@ static __exit void md_exit(void)unregister_reboot_notifier (&md_notifier);unregister_sysctl_table (raid_table_header);remove_proc_entry ("mdstat" ,NULL ); +wait_queue_purge (&md_event_waiters);for_each_mddev (mddev, tmp) {export_array (mddev); mddev->hold_active =0 ;
fs/eventpoll.c +1 -1
@@ -521,7 +521,7 @@ static void ep_remove_wait_queue(struct eppoll_entry *pwq)/* If it is cleared by POLLFREE, it should be rcu-safe */ whead =rcu_dereference (pwq->whead);if (whead) -remove_wait_queue (whead, &pwq->wait); +remove_wait_queue_purgeable (whead, &pwq->wait);rcu_read_unlock (); }
fs/select.c +1 -1
@@ -129,7 +129,7 @@ EXPORT_SYMBOL(poll_initwait);static void free_poll_entry (struct poll_table_entry *entry) { -remove_wait_queue (entry->wait_address, &entry->wait); +remove_wait_queue_purgeable (entry->wait_address, &entry->wait);fput (entry->filp); }
include/linux/wait.h +2 -0
@@ -106,6 +106,8 @@ static inline int waitqueue_active(wait_queue_head_t *q)extern void add_wait_queue (wait_queue_head_t *q,wait_queue_t *wait);extern void add_wait_queue_exclusive (wait_queue_head_t *q,wait_queue_t *wait);extern void remove_wait_queue (wait_queue_head_t *q,wait_queue_t *wait); +extern void wait_queue_purge (wait_queue_head_t *q); +extern void remove_wait_queue_purgeable (wait_queue_head_t *q,wait_queue_t *wait);static inline void __add_wait_queue (wait_queue_head_t *head,wait_queue_t *new) {
kernel/sched/wait.c +45 -0
51 lines not on this page · raw message
Quoted 8 lines · On Wed, 12 Mar 2014 13:36:38 +1100 NeilBrown <[email protected]> wrote:
The md driver currently supports 'poll' on
/proc/mdstat. This is unsafe as if the md-mod module is removed while a 'poll' or 'select' is outstanding on/proc/mdstat, an oops occurs when the syscall completes.poll_freewait()will callremove_wait_queue()on await_queue_head_twhich was local to the module which no-longer exists.
Quoted 9 lines
This problem is particular to /proc. Most filesystems do not allow the module to be unloaded while any files are open on it. /proc only blocks module unloading while a
file_operationscall is currently active into the module, not while the file is open. kernfs has this property too but kernfs allocates await_queue_head_tin its internal data structures so the module doesn't need to provide one.
(A previous patch to add a similar allocation to procfs was not accepted).
By who, me? I was hoping we could somehow keep the implementation contained within md. I don't think I actually looked at it to any significant extent!
Was hoping that viro would pipe up.
Quoted 27 lines
This patch takes a different approach and allows a module to disconnect the
wait_queue_head_tthat was passed topoll_wait()from all the clients which are waiting on it. Thus after calling
proc_remove_entry("mdstat", NULL);
we simply call
wait_queue_purge(&md_event_waiters);and then know that it is safe to remove the module.
rcu infrastructure is used to avoid races.
poll_freewait()checks if the purge has happened underrcu_read_lock()to ensure that it never touches any freed memory.wait_queue_purge()usessynchronize_rcu()to ensure nopoll_freewait()could still be looking at thewait_queue_head_t....
kernel/sched/wait.c +10
-0+/** +* wait_queue_purge - remove all waiter from a wait_queue +* @q: The queue to be purged +* +* Unlink all pending waiters from the queue. +* This can be used prior to freeing a queue providing all waiters are +* prepared for queue purging. +* Waiters must call remove_wait_queue_puregeable() rather than +* remove_wait_queue(). +*
kernel/sched/wait.c void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) · line 74
Quoted 10 lines
kernel/sched/wait.c +10
-0+*/ +void wait_queue_purge (wait_queue_head_t *q) +{ +spin_lock (&q->lock); +while (!list_empty (&q->task_list)) +list_del_init (q->task_list.next); +spin_unlock (&q->lock); +synchronize_rcu (); +} +EXPORT_SYMBOL (wait_queue_purge);
I don't get this. If a task is waiting on that wait_queue_head_t, how does it get woken?
Quoted 7 lines
kernel/sched/wait.c +7
-0+/** +* remove_wait_queue_puregeable - remove_wait_queue if wait_queue_purge might be used. +* @q: the queue, which may already be purged, to remove from +* @wait: the waiter to remove +* +* Remove a waiter from a queue if it hasn't already been purged. +* If the queue has already been purged then task_list will be empty.
kernel/sched/wait.c void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) · line 92
Quoted 10 lines
kernel/sched/wait.c +10
-0+* If it isn't then it is still safe to lock the queue and remove +* the task. +*/ +void remove_wait_queue_purgeable (wait_queue_head_t *q,wait_queue_t *wait) +{ +unsigned long flags; + +rcu_read_lock (); +if (!list_empty (&wait->task_list)) { +spin_lock_irqsave (&q->lock, flags);
Mixture of spin_lock_irqsave() here and spin_lock() in wait_queue_purge() looks odd.
Quoted 6 lines
kernel/sched/wait.c +6
-0+list_del_init (&wait->task_list); +spin_unlock_irqrestore (&q->lock, flags); + } +rcu_read_unlock (); +} +EXPORT_SYMBOL (remove_wait_queue_purgeable);
Quoted 20 lines · On Tue, 11 Mar 2014 20:03:31 -0700 Andrew Morton <[email protected]>…
Quoted 8 lines · On Wed, 12 Mar 2014 13:36:38 +1100 NeilBrown <[email protected]> wrote:
The md driver currently supports 'poll' on
/proc/mdstat. This is unsafe as if the md-mod module is removed while a 'poll' or 'select' is outstanding on/proc/mdstat, an oops occurs when the syscall completes.poll_freewait()will callremove_wait_queue()on await_queue_head_twhich was local to the module which no-longer exists.Quoted 9 lines
This problem is particular to /proc. Most filesystems do not allow the module to be unloaded while any files are open on it. /proc only blocks module unloading while a
file_operationscall is currently active into the module, not while the file is open. kernfs has this property too but kernfs allocates await_queue_head_tin its internal data structures so the module doesn't need to provide one.
(A previous patch to add a similar allocation to procfs was not accepted).
Quoted 5 lines
By who, me? I was hoping we could somehow keep the implementation contained within md. I don't think I actually looked at it to any significant extent!
Was hoping that viro would pipe up.
Was not accepted by anybody. Everybody is guilty :-) You were at least nice enough to comment (thanks).
I think I like this version better so it might not be a problem that the previous version was not accepted. Depends on what the scheduler guys think though....
Quoted 33 lines
Quoted 33 lines
This patch takes a different approach and allows a module to disconnect the
wait_queue_head_tthat was passed topoll_wait()from all the clients which are waiting on it. Thus after calling
proc_remove_entry("mdstat", NULL);
we simply call
wait_queue_purge(&md_event_waiters);and then know that it is safe to remove the module.
rcu infrastructure is used to avoid races.
poll_freewait()checks if the purge has happened underrcu_read_lock()to ensure that it never touches any freed memory.wait_queue_purge()usessynchronize_rcu()to ensure nopoll_freewait()could still be looking at thewait_queue_head_t....
kernel/sched/wait.c +15
-0+/** +* wait_queue_purge - remove all waiter from a wait_queue +* @q: The queue to be purged +* +* Unlink all pending waiters from the queue. +* This can be used prior to freeing a queue providing all waiters are +* prepared for queue purging. +* Waiters must call remove_wait_queue_puregeable() rather than +* remove_wait_queue(). +* +*/ +void wait_queue_purge (wait_queue_head_t *q) +{ +spin_lock (&q->lock); +while (!list_empty (&q->task_list))
Quoted 8 lines
kernel/sched/wait.c void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) · line 74
Quoted 5 lines
kernel/sched/wait.c +5
-0+list_del_init (q->task_list.next); +spin_unlock (&q->lock); +synchronize_rcu (); +} +EXPORT_SYMBOL (wait_queue_purge);I don't get this. If a task is waiting on that
wait_queue_head_t, how does it get woken?
This is only expected to be used by tasks which have some other means of being woken up. Possibly a timeout passed to 'select' or 'poll', possibly a signal.
Quoted 13 lines
Quoted 13 lines
kernel/sched/wait.c +12
-0+/** +* remove_wait_queue_puregeable - remove_wait_queue if wait_queue_purge might be used. +* @q: the queue, which may already be purged, to remove from +* @wait: the waiter to remove +* +* Remove a waiter from a queue if it hasn't already been purged. +* If the queue has already been purged then task_list will be empty. +* If it isn't then it is still safe to lock the queue and remove +* the task. +*/ +void remove_wait_queue_purgeable (wait_queue_head_t *q,wait_queue_t *wait) +{
Quoted 8 lines
kernel/sched/wait.c void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) · line 92
Quoted 5 lines
kernel/sched/wait.c +5
-0+unsigned long flags; + +rcu_read_lock (); +if (!list_empty (&wait->task_list)) { +spin_lock_irqsave (&q->lock, flags);Mixture of
spin_lock_irqsave()here andspin_lock()inwait_queue_purge()looks odd.
True - I was copying remove_wait_queue(). Maybe I should have just called remove_wait_queue() directly (we don't actually need that _init on the list_del).
Quoted 7 lines
kernel/sched/wait.c void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) · line 98
Quoted 7 lines
kernel/sched/wait.c +6
-0+list_del_init (&wait->task_list); +spin_unlock_irqrestore (&q->lock, flags); + } +rcu_read_unlock (); +} +EXPORT_SYMBOL (remove_wait_queue_purgeable);
Thanks,
NeilBrown
Quoted 27 lines · On Wed, 12 Mar 2014 14:10:25 +1100 NeilBrown <[email protected]> wrote:
Quoted 24 lines · On Tue, 11 Mar 2014 20:03:31 -0700 Andrew Morton <[email protected]>…
Quoted 8 lines · On Wed, 12 Mar 2014 13:36:38 +1100 NeilBrown <[email protected]> wrote:
The md driver currently supports 'poll' on
/proc/mdstat. This is unsafe as if the md-mod module is removed while a 'poll' or 'select' is outstanding on/proc/mdstat, an oops occurs when the syscall completes.poll_freewait()will callremove_wait_queue()on await_queue_head_twhich was local to the module which no-longer exists.Quoted 9 lines
This problem is particular to /proc. Most filesystems do not allow the module to be unloaded while any files are open on it. /proc only blocks module unloading while a
file_operationscall is currently active into the module, not while the file is open. kernfs has this property too but kernfs allocates await_queue_head_tin its internal data structures so the module doesn't need to provide one.
(A previous patch to add a similar allocation to procfs was not accepted).By who, me? I was hoping we could somehow keep the implementation contained within md. I don't think I actually looked at it to any significant extent!
Quoted 8 lines
Quoted 1 line
Was hoping that viro would pipe up.
Was not accepted by anybody. Everybody is guilty :-) You were at least nice enough to comment (thanks).
I think I like this version better so it might not be a problem that the previous version was not accepted. Depends on what the scheduler guys think though....
OK..
Quoted 26 lines
Quoted 17 lines
Quoted 17 lines
...
kernel/sched/wait.c +15
-0+/** +* wait_queue_purge - remove all waiter from a wait_queue +* @q: The queue to be purged +* +* Unlink all pending waiters from the queue. +* This can be used prior to freeing a queue providing all waiters are +* prepared for queue purging. +* Waiters must call remove_wait_queue_puregeable() rather than +* remove_wait_queue(). +* +*/ +void wait_queue_purge (wait_queue_head_t *q) +{ +spin_lock (&q->lock); +while (!list_empty (&q->task_list))Quoted 8 lines
kernel/sched/wait.c void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) · line 74
Quoted 5 lines
kernel/sched/wait.c +5
-0+list_del_init (q->task_list.next); +spin_unlock (&q->lock); +synchronize_rcu (); +} +EXPORT_SYMBOL (wait_queue_purge);I don't get this. If a task is waiting on that
wait_queue_head_t, how does it get woken?
Quoted 3 lines
This is only expected to be used by tasks which have some other means of being woken up. Possibly a timeout passed to 'select' or 'poll', possibly a signal.
Oh. So the caller is supposed to take the tasks off the queue via wait_queue_purge(), then to wake them up (which implies the caller has a second way of looking the tasks up).
And the tasks themselves ... do they need to know what happened? If they run remove_wait_queue() then will still take wait_queue_head_t.lock, so the calling task need to somehow keep the wait_queue_head_t alive until everyone has woken and cleared off.
Complex! Could we please get that all fleshed out in the changelog and kerneldoc?
Quoted 22 lines
Quoted 13 lines
Quoted 13 lines
kernel/sched/wait.c +12
-0+/** +* remove_wait_queue_puregeable - remove_wait_queue if wait_queue_purge might be used. +* @q: the queue, which may already be purged, to remove from +* @wait: the waiter to remove +* +* Remove a waiter from a queue if it hasn't already been purged. +* If the queue has already been purged then task_list will be empty. +* If it isn't then it is still safe to lock the queue and remove +* the task. +*/ +void remove_wait_queue_purgeable (wait_queue_head_t *q,wait_queue_t *wait) +{Quoted 8 lines
kernel/sched/wait.c void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) · line 92
Quoted 5 lines
kernel/sched/wait.c +5
-0+unsigned long flags; + +rcu_read_lock (); +if (!list_empty (&wait->task_list)) { +spin_lock_irqsave (&q->lock, flags);Mixture of
spin_lock_irqsave()here andspin_lock()inwait_queue_purge()looks odd.
Quoted 3 lines
True - I was copying
remove_wait_queue(). Maybe I should have just calledremove_wait_queue()directly (we don't actually need that_initon thelist_del).
lockdep should have complained about the spin_lock().
Quoted 66 lines · On Tue, 11 Mar 2014 21:19:24 -0700 Andrew Morton <[email protected]>…
Quoted 27 lines · On Wed, 12 Mar 2014 14:10:25 +1100 NeilBrown <[email protected]> wrote:
Quoted 24 lines · On Tue, 11 Mar 2014 20:03:31 -0700 Andrew Morton <[email protected]>…
Quoted 8 lines · On Wed, 12 Mar 2014 13:36:38 +1100 NeilBrown <[email protected]> wrote:
The md driver currently supports 'poll' on
/proc/mdstat. This is unsafe as if the md-mod module is removed while a 'poll' or 'select' is outstanding on/proc/mdstat, an oops occurs when the syscall completes.poll_freewait()will callremove_wait_queue()on await_queue_head_twhich was local to the module which no-longer exists.Quoted 9 lines
This problem is particular to /proc. Most filesystems do not allow the module to be unloaded while any files are open on it. /proc only blocks module unloading while a
file_operationscall is currently active into the module, not while the file is open. kernfs has this property too but kernfs allocates await_queue_head_tin its internal data structures so the module doesn't need to provide one.
(A previous patch to add a similar allocation to procfs was not accepted).By who, me? I was hoping we could somehow keep the implementation contained within md. I don't think I actually looked at it to any significant extent!
Quoted 8 lines
Quoted 1 line
Was hoping that viro would pipe up.
Was not accepted by anybody. Everybody is guilty :-) You were at least nice enough to comment (thanks).
I think I like this version better so it might not be a problem that the previous version was not accepted. Depends on what the scheduler guys think though....
OK..
Quoted 26 lines
Quoted 25 lines
Quoted 12 lines
...
kernel/sched/wait.c +10
-0+/** +* wait_queue_purge - remove all waiter from a wait_queue +* @q: The queue to be purged +* +* Unlink all pending waiters from the queue. +* This can be used prior to freeing a queue providing all waiters are +* prepared for queue purging. +* Waiters must call remove_wait_queue_puregeable() rather than +* remove_wait_queue(). +* kernel/sched/wait.c void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) · line 74
Quoted 10 lines
kernel/sched/wait.c +10
-0+*/ +void wait_queue_purge (wait_queue_head_t *q) +{ +spin_lock (&q->lock); +while (!list_empty (&q->task_list)) +list_del_init (q->task_list.next); +spin_unlock (&q->lock); +synchronize_rcu (); +} +EXPORT_SYMBOL (wait_queue_purge);I don't get this. If a task is waiting on that
wait_queue_head_t, how does it get woken?
Quoted 7 lines
Quoted 3 lines
This is only expected to be used by tasks which have some other means of being woken up. Possibly a timeout passed to 'select' or 'poll', possibly a signal.
Oh. So the caller is supposed to take the tasks off the queue via
wait_queue_purge(), then to wake them up (which implies the caller has a second way of looking the tasks up).
No. The caller of wait_queue_purge() doesn't care beyond purging the queue. The process that are waiting will be in 'select' or 'poll' or similar and there is no guarantee that anything will ever happen to wake them up. They can just keep waiting for all I care.
I most cases they will have set their own timeout (or should have done).
I could wake_up() the wait_queue before purging it. Then the callers would wake up, attempt IO, probably get EINVAL or something because the /proc file has been de-registered and hopefully move on with life.
But that is really independent of the implementation of wait_queue_purge().
It is entirely possible for a process to be waiting on multiple events (And in the select/poll case it is very common) and if one event wants to say "this event is never ever going to happen so I'm detaching the queue", then there is no particular reason to do anything but detach the queue.
Quoted 5 lines
And the tasks themselves ... do they need to know what happened? If they run
remove_wait_queue()then will still take wait_queue_head_t.lock, so the calling task need to somehow keep thewait_queue_head_talive until everyone has woken and cleared off.
The tasks themselves mustn't run remove_wait_queue(). They must run remove_wait_queue_purgeable(). This check if the remove has already happened and avoids taking the spinlock (which no longer exists) in that case.
You cannot just call wait_queue_purge() on any old queue - only one that is known to be potentially purged and waiters know to use remove_wait_queue_purge().
Currently that is only wait_queues that are passed to poll_wait() inside a ->poll() method.
Quoted 3 lines
Complex! Could we please get that all fleshed out in the changelog and kerneldoc?
I thought the important parts (the waiter having to call remove_wait_queue_purgeable()) already was in the kerneldoc.
Thanks,
NeilBrown
Quoted 28 lines
Quoted 23 lines
Quoted 14 lines
Quoted 14 lines
kernel/sched/wait.c +12
-0+/** +* remove_wait_queue_puregeable - remove_wait_queue if wait_queue_purge might be used. +* @q: the queue, which may already be purged, to remove from +* @wait: the waiter to remove +* +* Remove a waiter from a queue if it hasn't already been purged. +* If the queue has already been purged then task_list will be empty. +* If it isn't then it is still safe to lock the queue and remove +* the task. +*/ +void remove_wait_queue_purgeable (wait_queue_head_t *q,wait_queue_t *wait) +{Quoted 8 lines
kernel/sched/wait.c void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) · line 92
Quoted 5 lines
kernel/sched/wait.c +5
-0+unsigned long flags; + +rcu_read_lock (); +if (!list_empty (&wait->task_list)) { +spin_lock_irqsave (&q->lock, flags);Mixture of
spin_lock_irqsave()here andspin_lock()inwait_queue_purge()looks odd.Quoted 3 lines
True - I was copying
remove_wait_queue(). Maybe I should have just calledremove_wait_queue()directly (we don't actually need that_initon thelist_del).lockdep should have complained about the
spin_lock().