summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/linux/swap.h2
-rw-r--r--mm/swapfile.c42
2 files changed, 37 insertions, 7 deletions
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 93f0eca7f916..db3b5de7c92f 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -107,6 +107,8 @@ enum {
SWP_USED = (1 << 0), /* is slot in swap_info[] used? */
SWP_WRITEOK = (1 << 1), /* ok to write to this swap? */
SWP_ACTIVE = (SWP_USED | SWP_WRITEOK),
+ /* add others here before... */
+ SWP_SCANNING = (1 << 8), /* refcount in scan_swap_map */
};
#define SWAP_CLUSTER_MAX 32
diff --git a/mm/swapfile.c b/mm/swapfile.c
index c70248aab536..fdee145afc6f 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -98,10 +98,12 @@ static inline unsigned long scan_swap_map(struct swap_info_struct *si)
* But we do now try to find an empty cluster. -Andrea
*/
+ si->flags += SWP_SCANNING;
if (unlikely(!si->cluster_nr)) {
si->cluster_nr = SWAPFILE_CLUSTER - 1;
if (si->pages - si->inuse_pages < SWAPFILE_CLUSTER)
goto lowest;
+ swap_device_unlock(si);
offset = si->lowest_bit;
last_in_cluster = offset + SWAPFILE_CLUSTER - 1;
@@ -111,10 +113,12 @@ static inline unsigned long scan_swap_map(struct swap_info_struct *si)
if (si->swap_map[offset])
last_in_cluster = offset + SWAPFILE_CLUSTER;
else if (offset == last_in_cluster) {
+ swap_device_lock(si);
si->cluster_next = offset-SWAPFILE_CLUSTER-1;
goto cluster;
}
}
+ swap_device_lock(si);
goto lowest;
}
@@ -123,10 +127,12 @@ cluster:
offset = si->cluster_next;
if (offset > si->highest_bit)
lowest: offset = si->lowest_bit;
+checks: if (!(si->flags & SWP_WRITEOK))
+ goto no_page;
if (!si->highest_bit)
goto no_page;
if (!si->swap_map[offset]) {
-got_page: if (offset == si->lowest_bit)
+ if (offset == si->lowest_bit)
si->lowest_bit++;
if (offset == si->highest_bit)
si->highest_bit--;
@@ -137,16 +143,22 @@ got_page: if (offset == si->lowest_bit)
}
si->swap_map[offset] = 1;
si->cluster_next = offset + 1;
+ si->flags -= SWP_SCANNING;
return offset;
}
+ swap_device_unlock(si);
while (++offset <= si->highest_bit) {
- if (!si->swap_map[offset])
- goto got_page;
+ if (!si->swap_map[offset]) {
+ swap_device_lock(si);
+ goto checks;
+ }
}
+ swap_device_lock(si);
goto lowest;
no_page:
+ si->flags -= SWP_SCANNING;
return 0;
}
@@ -1111,10 +1123,6 @@ asmlinkage long sys_swapoff(const char __user * specialfile)
err = try_to_unuse(type);
current->flags &= ~PF_SWAPOFF;
- /* wait for any unplug function to finish */
- down_write(&swap_unplug_sem);
- up_write(&swap_unplug_sem);
-
if (err) {
/* re-insert swap space back into swap_list */
swap_list_lock();
@@ -1128,10 +1136,28 @@ asmlinkage long sys_swapoff(const char __user * specialfile)
swap_info[prev].next = p - swap_info;
nr_swap_pages += p->pages;
total_swap_pages += p->pages;
+ swap_device_lock(p);
p->flags |= SWP_WRITEOK;
+ swap_device_unlock(p);
swap_list_unlock();
goto out_dput;
}
+
+ /* wait for any unplug function to finish */
+ down_write(&swap_unplug_sem);
+ up_write(&swap_unplug_sem);
+
+ /* wait for anyone still in scan_swap_map */
+ swap_device_lock(p);
+ p->highest_bit = 0; /* cuts scans short */
+ while (p->flags >= SWP_SCANNING) {
+ swap_device_unlock(p);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ swap_device_lock(p);
+ }
+ swap_device_unlock(p);
+
destroy_swap_extents(p);
down(&swapon_sem);
swap_list_lock();
@@ -1431,6 +1457,8 @@ asmlinkage long sys_swapon(const char __user * specialfile, int swap_flags)
}
p->lowest_bit = 1;
+ p->cluster_next = 1;
+
/*
* Find out how many pages are allowed for a single swap
* device. There are two limiting factors: 1) the number of