we have to rely on pagevec_release() to release extra pins and play with
goto. This version does in this way. The patch is bigger than Hugh's due
to extra comments to make the flow clear.
mm/shmem.c | 120 ++++++++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 83 insertions(+), 37 deletions(-)
diff --git a/mm/shmem.c b/mm/shmem.c
index 220be9f..1ae0c7f 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -806,12 +806,15 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend,
long nr_swaps_freed = 0;
pgoff_t index;
int i;
+ bool split = false;
+ struct page *page = NULL;
if (lend == -1)
end = -1; /* unsigned, so actually very big */
pagevec_init(&pvec);
index = start;
+retry:
while (index < end) {
pvec.nr = find_get_entries(mapping, index,
min(end - index, (pgoff_t)PAGEVEC_SIZE),
@@ -819,7 +822,8 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend,
if (!pvec.nr)
break;
for (i = 0; i < pagevec_count(&pvec); i++) {
- struct page *page = pvec.pages[i];
+ split = false;
+ page = pvec.pages[i];
index = indices[i];
if (index >= end)
@@ -838,23 +842,24 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend,
if (!trylock_page(page))
continue;
- if (PageTransTail(page)) {
- /* Middle of THP: zero out the page */
- clear_highpage(page);
- unlock_page(page);
- continue;
- } else if (PageTransHuge(page)) {
- if (index == round_down(end, HPAGE_PMD_NR)) {
+ if (PageTransCompound(page) && !unfalloc) {
+ if (PageHead(page) &&
+ index != round_down(end, HPAGE_PMD_NR)) {
/*
- * Range ends in the middle of THP:
- * zero out the page
+ * Fall through when punching whole
+ * THP.
*/
- clear_highpage(page);
- unlock_page(page);
- continue;
+ index += HPAGE_PMD_NR - 1;
+ i += HPAGE_PMD_NR - 1;
+ } else {
+ /*
+ * Split THP for any partial hole
+ * punch.
+ */
+ get_page(page);
+ split = true;
+ goto split;
}
- index += HPAGE_PMD_NR - 1;
- i += HPAGE_PMD_NR - 1;
}
if (!unfalloc || !PageUptodate(page)) {
@@ -866,9 +871,29 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend,
}
unlock_page(page);
}
+split:
pagevec_remove_exceptionals(&pvec);
pagevec_release(&pvec);
cond_resched();
+
+ if (split) {
+ /*
+ * The pagevec_release() released all extra pins
+ * from pagevec lookup. And we hold an extra pin
+ * and still have the page locked under us.
+ */
+ if (!split_huge_page(page)) {
+ unlock_page(page);
+ put_page(page);
+ /* Re-lookup page cache from current index */
+ goto retry;
+ }
+
+ /* Fail to split THP, move to next index */
+ unlock_page(page);
+ put_page(page);
+ }
+
index++;
}
@@ -901,6 +926,7 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend,
return;
index = start;
+again:
while (index < end) {
cond_resched();
@@ -916,7 +942,8 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend,
continue;
}
for (i = 0; i < pagevec_count(&pvec); i++) {
- struct page *page = pvec.pages[i];
+ split = false;
+ page = pvec.pages[i];
index = indices[i];
if (index >= end)
@@ -936,30 +963,24 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend,
lock_page(page);
- if (PageTransTail(page)) {
- /* Middle of THP: zero out the page */
- clear_highpage(page);
- unlock_page(page);
- /*
- * Partial thp truncate due 'start' in middle
- * of THP: don't need to look on these pages
- * again on !pvec.nr restart.
- */
- if (index != round_down(end, HPAGE_PMD_NR))
- start++;
- continue;
- } else if (PageTransHuge(page)) {
- if (index == round_down(end, HPAGE_PMD_NR)) {
+ if (PageTransCompound(page) && !unfalloc) {
+ if (PageHead(page) &&
+ index != round_down(end, HPAGE_PMD_NR)) {
/*
- * Range ends in the middle of THP:
- * zero out the page
+ * Fall through when punching whole
+ * THP.
*/
- clear_highpage(page);
- unlock_page(page);
- continue;
+ index += HPAGE_PMD_NR - 1;
+ i += HPAGE_PMD_NR - 1;
+ } else {
+ /*
+ * Split THP for any partial hole
+ * punch.
+ */
+ get_page(page);
+ split = true;
+ goto rescan_split;
}
- index += HPAGE_PMD_NR - 1;
- i += HPAGE_PMD_NR - 1;
}
if (!unfalloc || !PageUptodate(page)) {
@@ -976,8 +997,33 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend,
}
unlock_page(page);
}
+rescan_split:
pagevec_remove_exceptionals(&pvec);
pagevec_release(&pvec);
+
+ if (split) {
+ /*
+ * The pagevec_release() released all extra pins
+ * from pagevec lookup. And we hold an extra pin
+ * and still have the page locked under us.
+ */
+ if (!split_huge_page(page)) {
+ unlock_page(page);
+ put_page(page);
+ /* Re-lookup page cache from current index */
+ goto again;
+ }
+
+ /*
+ * Split fail, clear the page then move to next
+ * index.
+ */
+ clear_highpage(page);
+
+ unlock_page(page);
+ put_page(page);
+ }
+
index++;
}
--
1.8.3.1