diff options
| author | Oleg Nesterov <oleg@redhat.com> | 2014-01-11 13:19:32 -0500 |
|---|---|---|
| committer | Al Viro <viro@zeniv.linux.org.uk> | 2014-01-25 03:14:36 -0500 |
| commit | a8d4b8345e0ee48b732126d980efaf0dc373e2b0 (patch) | |
| tree | 4bdfc52ef4ad176f54e4ce7b81091c0fbef0fd04 | |
| parent | 2ccdc413196b43a02bb68b46be5b68850904e9ea (diff) | |
introduce __fcheck_files() to fix rcu_dereference_check_fdtable(), kill rcu_my_thread_group_empty()
rcu_dereference_check_fdtable() looks very wrong,
1. rcu_my_thread_group_empty() was added by 844b9a8707f1 "vfs: fix
RCU-lockdep false positive due to /proc" but it doesn't really
fix the problem. A CLONE_THREAD (without CLONE_FILES) task can
hit the same race with get_files_struct().
And otoh rcu_my_thread_group_empty() can suppress the correct
warning if the caller is the CLONE_FILES (without CLONE_THREAD)
task.
2. files->count == 1 check is not really right too. Even if this
files_struct is not shared it is not safe to access it lockless
unless the caller is the owner.
Otoh, this check is sub-optimal. files->count == 0 always means
it is safe to use it lockless even if files != current->files,
but put_files_struct() has to take rcu_read_lock(). See the next
patch.
This patch removes the buggy checks and turns fcheck_files() into
__fcheck_files() which uses rcu_dereference_raw(), the "unshared"
callers, fget_light() and fget_raw_light(), can use it to avoid
the warning from RCU-lockdep.
fcheck_files() is trivially reimplemented as rcu_lockdep_assert()
plus __fcheck_files().
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
| -rw-r--r-- | fs/file.c | 4 | ||||
| -rw-r--r-- | include/linux/fdtable.h | 35 | ||||
| -rw-r--r-- | include/linux/rcupdate.h | 2 | ||||
| -rw-r--r-- | kernel/rcu/update.c | 11 |
4 files changed, 23 insertions, 29 deletions
| @@ -707,7 +707,7 @@ struct file *fget_light(unsigned int fd, int *fput_needed) | |||
| 707 | 707 | ||
| 708 | *fput_needed = 0; | 708 | *fput_needed = 0; |
| 709 | if (atomic_read(&files->count) == 1) { | 709 | if (atomic_read(&files->count) == 1) { |
| 710 | file = fcheck_files(files, fd); | 710 | file = __fcheck_files(files, fd); |
| 711 | if (file && (file->f_mode & FMODE_PATH)) | 711 | if (file && (file->f_mode & FMODE_PATH)) |
| 712 | file = NULL; | 712 | file = NULL; |
| 713 | } else { | 713 | } else { |
| @@ -735,7 +735,7 @@ struct file *fget_raw_light(unsigned int fd, int *fput_needed) | |||
| 735 | 735 | ||
| 736 | *fput_needed = 0; | 736 | *fput_needed = 0; |
| 737 | if (atomic_read(&files->count) == 1) { | 737 | if (atomic_read(&files->count) == 1) { |
| 738 | file = fcheck_files(files, fd); | 738 | file = __fcheck_files(files, fd); |
| 739 | } else { | 739 | } else { |
| 740 | rcu_read_lock(); | 740 | rcu_read_lock(); |
| 741 | file = fcheck_files(files, fd); | 741 | file = fcheck_files(files, fd); |
diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h index 085197bd8812..70e8e21c0a30 100644 --- a/include/linux/fdtable.h +++ b/include/linux/fdtable.h | |||
| @@ -59,29 +59,36 @@ struct files_struct { | |||
| 59 | struct file __rcu * fd_array[NR_OPEN_DEFAULT]; | 59 | struct file __rcu * fd_array[NR_OPEN_DEFAULT]; |
| 60 | }; | 60 | }; |
| 61 | 61 | ||
| 62 | #define rcu_dereference_check_fdtable(files, fdtfd) \ | ||
| 63 | (rcu_dereference_check((fdtfd), \ | ||
| 64 | lockdep_is_held(&(files)->file_lock) || \ | ||
| 65 | atomic_read(&(files)->count) == 1 || \ | ||
| 66 | rcu_my_thread_group_empty())) | ||
| 67 | |||
| 68 | #define files_fdtable(files) \ | ||
| 69 | (rcu_dereference_check_fdtable((files), (files)->fdt)) | ||
| 70 | |||
| 71 | struct file_operations; | 62 | struct file_operations; |
| 72 | struct vfsmount; | 63 | struct vfsmount; |
| 73 | struct dentry; | 64 | struct dentry; |
| 74 | 65 | ||
| 75 | extern void __init files_defer_init(void); | 66 | extern void __init files_defer_init(void); |
| 76 | 67 | ||
| 77 | static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd) | 68 | #define rcu_dereference_check_fdtable(files, fdtfd) \ |
| 69 | rcu_dereference_check((fdtfd), lockdep_is_held(&(files)->file_lock)) | ||
| 70 | |||
| 71 | #define files_fdtable(files) \ | ||
| 72 | rcu_dereference_check_fdtable((files), (files)->fdt) | ||
| 73 | |||
| 74 | /* | ||
| 75 | * The caller must ensure that fd table isn't shared or hold rcu or file lock | ||
| 76 | */ | ||
| 77 | static inline struct file *__fcheck_files(struct files_struct *files, unsigned int fd) | ||
| 78 | { | 78 | { |
| 79 | struct file * file = NULL; | 79 | struct fdtable *fdt = rcu_dereference_raw(files->fdt); |
| 80 | struct fdtable *fdt = files_fdtable(files); | ||
| 81 | 80 | ||
| 82 | if (fd < fdt->max_fds) | 81 | if (fd < fdt->max_fds) |
| 83 | file = rcu_dereference_check_fdtable(files, fdt->fd[fd]); | 82 | return rcu_dereference_raw(fdt->fd[fd]); |
| 84 | return file; | 83 | return NULL; |
| 84 | } | ||
| 85 | |||
| 86 | static inline struct file *fcheck_files(struct files_struct *files, unsigned int fd) | ||
| 87 | { | ||
| 88 | rcu_lockdep_assert(rcu_read_lock_held() || | ||
| 89 | lockdep_is_held(&files->file_lock), | ||
| 90 | "suspicious rcu_dereference_check() usage"); | ||
| 91 | return __fcheck_files(files, fd); | ||
| 85 | } | 92 | } |
| 86 | 93 | ||
| 87 | /* | 94 | /* |
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h index 39cbb889e20d..a2482cf90b6b 100644 --- a/include/linux/rcupdate.h +++ b/include/linux/rcupdate.h | |||
| @@ -448,8 +448,6 @@ static inline int rcu_read_lock_sched_held(void) | |||
| 448 | 448 | ||
| 449 | #ifdef CONFIG_PROVE_RCU | 449 | #ifdef CONFIG_PROVE_RCU |
| 450 | 450 | ||
| 451 | extern int rcu_my_thread_group_empty(void); | ||
| 452 | |||
| 453 | /** | 451 | /** |
| 454 | * rcu_lockdep_assert - emit lockdep splat if specified condition not met | 452 | * rcu_lockdep_assert - emit lockdep splat if specified condition not met |
| 455 | * @c: condition to check | 453 | * @c: condition to check |
diff --git a/kernel/rcu/update.c b/kernel/rcu/update.c index 6cb3dff89e2b..a3596c8ec9e4 100644 --- a/kernel/rcu/update.c +++ b/kernel/rcu/update.c | |||
| @@ -195,17 +195,6 @@ void wait_rcu_gp(call_rcu_func_t crf) | |||
| 195 | } | 195 | } |
| 196 | EXPORT_SYMBOL_GPL(wait_rcu_gp); | 196 | EXPORT_SYMBOL_GPL(wait_rcu_gp); |
| 197 | 197 | ||
| 198 | #ifdef CONFIG_PROVE_RCU | ||
| 199 | /* | ||
| 200 | * wrapper function to avoid #include problems. | ||
| 201 | */ | ||
| 202 | int rcu_my_thread_group_empty(void) | ||
| 203 | { | ||
| 204 | return thread_group_empty(current); | ||
| 205 | } | ||
| 206 | EXPORT_SYMBOL_GPL(rcu_my_thread_group_empty); | ||
| 207 | #endif /* #ifdef CONFIG_PROVE_RCU */ | ||
| 208 | |||
| 209 | #ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD | 198 | #ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD |
| 210 | static inline void debug_init_rcu_head(struct rcu_head *head) | 199 | static inline void debug_init_rcu_head(struct rcu_head *head) |
| 211 | { | 200 | { |
