diff options
| author | Hugh Dickins <hughd@google.com> | 2019-04-18 20:50:13 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-04-19 12:46:04 -0400 |
| commit | af53d3e9e04024885de5b4fda51e5fa362ae2bd8 (patch) | |
| tree | ab2a56b0bafc76d99dcd2924f22f0fed76f4ae2e | |
| parent | 64165b1affc5bc16231ac971e66aae7d68d57f2c (diff) | |
mm: swapoff: shmem_unuse() stop eviction without igrab()
The igrab() in shmem_unuse() looks good, but we forgot that it gives no
protection against concurrent unmounting: a point made by Konstantin
Khlebnikov eight years ago, and then fixed in 2.6.39 by 778dd893ae78
("tmpfs: fix race between umount and swapoff"). The current 5.1-rc
swapoff is liable to hit "VFS: Busy inodes after unmount of tmpfs.
Self-destruct in 5 seconds. Have a nice day..." followed by GPF.
Once again, give up on using igrab(); but don't go back to making such
heavy-handed use of shmem_swaplist_mutex as last time: that would spoil
the new design, and I expect could deadlock inside shmem_swapin_page().
Instead, shmem_unuse() just raise a "stop_eviction" count in the shmem-
specific inode, and shmem_evict_inode() wait for that to go down to 0.
Call it "stop_eviction" rather than "swapoff_busy" because it can be put
to use for others later (huge tmpfs patches expect to use it).
That simplifies shmem_unuse(), protecting it from both unlink and
unmount; and in practice lets it locate all the swap in its first try.
But do not rely on that: there's still a theoretical case, when
shmem_writepage() might have been preempted after its get_swap_page(),
before making the swap entry visible to swapoff.
[hughd@google.com: remove incorrect list_del()]
Link: http://lkml.kernel.org/r/alpine.LSU.2.11.1904091133570.1898@eggly.anvils
Link: http://lkml.kernel.org/r/alpine.LSU.2.11.1904081259400.1523@eggly.anvils
Fixes: b56a2d8af914 ("mm: rid swapoff of quadratic complexity")
Signed-off-by: Hugh Dickins <hughd@google.com>
Cc: "Alex Xu (Hello71)" <alex_y_xu@yahoo.ca>
Cc: Huang Ying <ying.huang@intel.com>
Cc: Kelley Nielsen <kelleynnn@gmail.com>
Cc: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
Cc: Rik van Riel <riel@surriel.com>
Cc: Vineeth Pillai <vpillai@digitalocean.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
| -rw-r--r-- | include/linux/shmem_fs.h | 1 | ||||
| -rw-r--r-- | mm/shmem.c | 40 | ||||
| -rw-r--r-- | mm/swapfile.c | 11 |
3 files changed, 24 insertions, 28 deletions
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h index f3fb1edb3526..20d815a33145 100644 --- a/include/linux/shmem_fs.h +++ b/include/linux/shmem_fs.h | |||
| @@ -21,6 +21,7 @@ struct shmem_inode_info { | |||
| 21 | struct list_head swaplist; /* chain of maybes on swap */ | 21 | struct list_head swaplist; /* chain of maybes on swap */ |
| 22 | struct shared_policy policy; /* NUMA memory alloc policy */ | 22 | struct shared_policy policy; /* NUMA memory alloc policy */ |
| 23 | struct simple_xattrs xattrs; /* list of xattrs */ | 23 | struct simple_xattrs xattrs; /* list of xattrs */ |
| 24 | atomic_t stop_eviction; /* hold when working on inode */ | ||
| 24 | struct inode vfs_inode; | 25 | struct inode vfs_inode; |
| 25 | }; | 26 | }; |
| 26 | 27 | ||
diff --git a/mm/shmem.c b/mm/shmem.c index 859e8628071f..2275a0ff7c30 100644 --- a/mm/shmem.c +++ b/mm/shmem.c | |||
| @@ -1081,9 +1081,14 @@ static void shmem_evict_inode(struct inode *inode) | |||
| 1081 | } | 1081 | } |
| 1082 | spin_unlock(&sbinfo->shrinklist_lock); | 1082 | spin_unlock(&sbinfo->shrinklist_lock); |
| 1083 | } | 1083 | } |
| 1084 | if (!list_empty(&info->swaplist)) { | 1084 | while (!list_empty(&info->swaplist)) { |
| 1085 | /* Wait while shmem_unuse() is scanning this inode... */ | ||
| 1086 | wait_var_event(&info->stop_eviction, | ||
| 1087 | !atomic_read(&info->stop_eviction)); | ||
| 1085 | mutex_lock(&shmem_swaplist_mutex); | 1088 | mutex_lock(&shmem_swaplist_mutex); |
| 1086 | list_del_init(&info->swaplist); | 1089 | /* ...but beware of the race if we peeked too early */ |
| 1090 | if (!atomic_read(&info->stop_eviction)) | ||
| 1091 | list_del_init(&info->swaplist); | ||
| 1087 | mutex_unlock(&shmem_swaplist_mutex); | 1092 | mutex_unlock(&shmem_swaplist_mutex); |
| 1088 | } | 1093 | } |
| 1089 | } | 1094 | } |
| @@ -1227,36 +1232,27 @@ int shmem_unuse(unsigned int type, bool frontswap, | |||
| 1227 | unsigned long *fs_pages_to_unuse) | 1232 | unsigned long *fs_pages_to_unuse) |
| 1228 | { | 1233 | { |
| 1229 | struct shmem_inode_info *info, *next; | 1234 | struct shmem_inode_info *info, *next; |
| 1230 | struct inode *inode; | ||
| 1231 | struct inode *prev_inode = NULL; | ||
| 1232 | int error = 0; | 1235 | int error = 0; |
| 1233 | 1236 | ||
| 1234 | if (list_empty(&shmem_swaplist)) | 1237 | if (list_empty(&shmem_swaplist)) |
| 1235 | return 0; | 1238 | return 0; |
| 1236 | 1239 | ||
| 1237 | mutex_lock(&shmem_swaplist_mutex); | 1240 | mutex_lock(&shmem_swaplist_mutex); |
| 1238 | |||
| 1239 | /* | ||
| 1240 | * The extra refcount on the inode is necessary to safely dereference | ||
| 1241 | * p->next after re-acquiring the lock. New shmem inodes with swap | ||
| 1242 | * get added to the end of the list and we will scan them all. | ||
| 1243 | */ | ||
| 1244 | list_for_each_entry_safe(info, next, &shmem_swaplist, swaplist) { | 1241 | list_for_each_entry_safe(info, next, &shmem_swaplist, swaplist) { |
| 1245 | if (!info->swapped) { | 1242 | if (!info->swapped) { |
| 1246 | list_del_init(&info->swaplist); | 1243 | list_del_init(&info->swaplist); |
| 1247 | continue; | 1244 | continue; |
| 1248 | } | 1245 | } |
| 1249 | 1246 | /* | |
| 1250 | inode = igrab(&info->vfs_inode); | 1247 | * Drop the swaplist mutex while searching the inode for swap; |
| 1251 | if (!inode) | 1248 | * but before doing so, make sure shmem_evict_inode() will not |
| 1252 | continue; | 1249 | * remove placeholder inode from swaplist, nor let it be freed |
| 1253 | 1250 | * (igrab() would protect from unlink, but not from unmount). | |
| 1251 | */ | ||
| 1252 | atomic_inc(&info->stop_eviction); | ||
| 1254 | mutex_unlock(&shmem_swaplist_mutex); | 1253 | mutex_unlock(&shmem_swaplist_mutex); |
| 1255 | if (prev_inode) | ||
| 1256 | iput(prev_inode); | ||
| 1257 | prev_inode = inode; | ||
| 1258 | 1254 | ||
| 1259 | error = shmem_unuse_inode(inode, type, frontswap, | 1255 | error = shmem_unuse_inode(&info->vfs_inode, type, frontswap, |
| 1260 | fs_pages_to_unuse); | 1256 | fs_pages_to_unuse); |
| 1261 | cond_resched(); | 1257 | cond_resched(); |
| 1262 | 1258 | ||
| @@ -1264,14 +1260,13 @@ int shmem_unuse(unsigned int type, bool frontswap, | |||
| 1264 | next = list_next_entry(info, swaplist); | 1260 | next = list_next_entry(info, swaplist); |
| 1265 | if (!info->swapped) | 1261 | if (!info->swapped) |
| 1266 | list_del_init(&info->swaplist); | 1262 | list_del_init(&info->swaplist); |
| 1263 | if (atomic_dec_and_test(&info->stop_eviction)) | ||
| 1264 | wake_up_var(&info->stop_eviction); | ||
| 1267 | if (error) | 1265 | if (error) |
| 1268 | break; | 1266 | break; |
| 1269 | } | 1267 | } |
| 1270 | mutex_unlock(&shmem_swaplist_mutex); | 1268 | mutex_unlock(&shmem_swaplist_mutex); |
| 1271 | 1269 | ||
| 1272 | if (prev_inode) | ||
| 1273 | iput(prev_inode); | ||
| 1274 | |||
| 1275 | return error; | 1270 | return error; |
| 1276 | } | 1271 | } |
| 1277 | 1272 | ||
| @@ -2238,6 +2233,7 @@ static struct inode *shmem_get_inode(struct super_block *sb, const struct inode | |||
| 2238 | info = SHMEM_I(inode); | 2233 | info = SHMEM_I(inode); |
| 2239 | memset(info, 0, (char *)inode - (char *)info); | 2234 | memset(info, 0, (char *)inode - (char *)info); |
| 2240 | spin_lock_init(&info->lock); | 2235 | spin_lock_init(&info->lock); |
| 2236 | atomic_set(&info->stop_eviction, 0); | ||
| 2241 | info->seals = F_SEAL_SEAL; | 2237 | info->seals = F_SEAL_SEAL; |
| 2242 | info->flags = flags & VM_NORESERVE; | 2238 | info->flags = flags & VM_NORESERVE; |
| 2243 | INIT_LIST_HEAD(&info->shrinklist); | 2239 | INIT_LIST_HEAD(&info->shrinklist); |
diff --git a/mm/swapfile.c b/mm/swapfile.c index 71383625a582..cf63b5f01adf 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c | |||
| @@ -2116,12 +2116,11 @@ retry: | |||
| 2116 | * Under global memory pressure, swap entries can be reinserted back | 2116 | * Under global memory pressure, swap entries can be reinserted back |
| 2117 | * into process space after the mmlist loop above passes over them. | 2117 | * into process space after the mmlist loop above passes over them. |
| 2118 | * | 2118 | * |
| 2119 | * Limit the number of retries? No: when shmem_unuse()'s igrab() fails, | 2119 | * Limit the number of retries? No: when mmget_not_zero() above fails, |
| 2120 | * a shmem inode using swap is being evicted; and when mmget_not_zero() | 2120 | * that mm is likely to be freeing swap from exit_mmap(), which proceeds |
| 2121 | * above fails, that mm is likely to be freeing swap from exit_mmap(). | 2121 | * at its own independent pace; and even shmem_writepage() could have |
| 2122 | * Both proceed at their own independent pace: we could move them to | 2122 | * been preempted after get_swap_page(), temporarily hiding that swap. |
| 2123 | * separate lists, and wait for those lists to be emptied; but it's | 2123 | * It's easy and robust (though cpu-intensive) just to keep retrying. |
| 2124 | * easier and more robust (though cpu-intensive) just to keep retrying. | ||
| 2125 | */ | 2124 | */ |
| 2126 | if (si->inuse_pages) { | 2125 | if (si->inuse_pages) { |
| 2127 | if (!signal_pending(current)) | 2126 | if (!signal_pending(current)) |
