From: Hugh Dickins The 4level mods have caused 2level swapoff to miss entries and hang. There's probably a one-line fix for that, but the error is really caused by previous awkwardness - each mask applied on two levels, an "address" that's an offset plus an "offset" that's an address. Simplify the four levels to behave in the same address/next/end way and the bug vanishes. Signed-off-by: Hugh Dickins Signed-off-by: Andrew Morton --- 25-akpm/mm/swapfile.c | 96 ++++++++++++++++++++------------------------------ 1 files changed, 39 insertions(+), 57 deletions(-) diff -puN mm/swapfile.c~4level-swapoff-hang-fix mm/swapfile.c --- 25/mm/swapfile.c~4level-swapoff-hang-fix 2005-01-08 12:01:29.531805136 -0800 +++ 25-akpm/mm/swapfile.c 2005-01-08 12:01:29.536804376 -0800 @@ -442,12 +442,11 @@ unuse_pte(struct vm_area_struct *vma, un } /* vma->vm_mm->page_table_lock is held */ -static unsigned long unuse_pmd(struct vm_area_struct * vma, pmd_t *dir, - unsigned long address, unsigned long size, unsigned long offset, +static unsigned long unuse_pmd(struct vm_area_struct *vma, pmd_t *dir, + unsigned long address, unsigned long end, swp_entry_t entry, struct page *page) { - pte_t * pte; - unsigned long end; + pte_t *pte; pte_t swp_pte = swp_entry_to_pte(entry); if (pmd_none(*dir)) @@ -458,18 +457,13 @@ static unsigned long unuse_pmd(struct vm return 0; } pte = pte_offset_map(dir, address); - offset += address & PMD_MASK; - address &= ~PMD_MASK; - end = address + size; - if (end > PMD_SIZE) - end = PMD_SIZE; do { /* * swapoff spends a _lot_ of time in this loop! * Test inline before going to call unuse_pte. */ if (unlikely(pte_same(*pte, swp_pte))) { - unuse_pte(vma, offset + address, pte, entry, page); + unuse_pte(vma, address, pte, entry, page); pte_unmap(pte); /* @@ -479,22 +473,22 @@ static unsigned long unuse_pmd(struct vm activate_page(page); /* add 1 since address may be 0 */ - return 1 + offset + address; + return 1 + address; } address += PAGE_SIZE; pte++; - } while (address && (address < end)); + } while (address < end); pte_unmap(pte - 1); return 0; } /* vma->vm_mm->page_table_lock is held */ -static unsigned long unuse_pud(struct vm_area_struct * vma, pud_t *pud, - unsigned long address, unsigned long size, unsigned long offset, +static unsigned long unuse_pud(struct vm_area_struct *vma, pud_t *pud, + unsigned long address, unsigned long end, swp_entry_t entry, struct page *page) { - pmd_t * pmd; - unsigned long end; + pmd_t *pmd; + unsigned long next; unsigned long foundaddr; if (pud_none(*pud)) @@ -505,33 +499,27 @@ static unsigned long unuse_pud(struct vm return 0; } pmd = pmd_offset(pud, address); - offset += address & PUD_MASK; - address &= ~PUD_MASK; - end = address + size; - if (end > PUD_SIZE) - end = PUD_SIZE; - if (address >= end) - BUG(); do { - foundaddr = unuse_pmd(vma, pmd, address, end - address, - offset, entry, page); + next = (address + PMD_SIZE) & PMD_MASK; + if (next > end || !next) + next = end; + foundaddr = unuse_pmd(vma, pmd, address, next, entry, page); if (foundaddr) return foundaddr; - address = (address + PMD_SIZE) & PMD_MASK; + address = next; pmd++; - } while (address && (address < end)); + } while (address < end); return 0; } /* vma->vm_mm->page_table_lock is held */ -static unsigned long unuse_pgd(struct vm_area_struct * vma, pgd_t *pgd, - unsigned long address, unsigned long size, +static unsigned long unuse_pgd(struct vm_area_struct *vma, pgd_t *pgd, + unsigned long address, unsigned long end, swp_entry_t entry, struct page *page) { - pud_t * pud; - unsigned long offset; + pud_t *pud; + unsigned long next; unsigned long foundaddr; - unsigned long end; if (pgd_none(*pgd)) return 0; @@ -541,54 +529,48 @@ static unsigned long unuse_pgd(struct vm return 0; } pud = pud_offset(pgd, address); - offset = address & PGDIR_MASK; - address &= ~PGDIR_MASK; - end = address + size; - if (end > PGDIR_SIZE) - end = PGDIR_SIZE; - BUG_ON (address >= end); do { - foundaddr = unuse_pud(vma, pud, address, end - address, - offset, entry, page); + next = (address + PUD_SIZE) & PUD_MASK; + if (next > end || !next) + next = end; + foundaddr = unuse_pud(vma, pud, address, next, entry, page); if (foundaddr) return foundaddr; - address = (address + PUD_SIZE) & PUD_MASK; + address = next; pud++; - } while (address && (address < end)); + } while (address < end); return 0; } /* vma->vm_mm->page_table_lock is held */ -static unsigned long unuse_vma(struct vm_area_struct * vma, +static unsigned long unuse_vma(struct vm_area_struct *vma, swp_entry_t entry, struct page *page) { pgd_t *pgd; - unsigned long start, end, next; + unsigned long address, next, end; unsigned long foundaddr; - int i; if (page->mapping) { - start = page_address_in_vma(page, vma); - if (start == -EFAULT) + address = page_address_in_vma(page, vma); + if (address == -EFAULT) return 0; else - end = start + PAGE_SIZE; + end = address + PAGE_SIZE; } else { - start = vma->vm_start; + address = vma->vm_start; end = vma->vm_end; } - pgd = pgd_offset(vma->vm_mm, start); - for (i = pgd_index(start); i <= pgd_index(end-1); i++) { - next = (start + PGDIR_SIZE) & PGDIR_MASK; - if (next > end || next <= start) + pgd = pgd_offset(vma->vm_mm, address); + do { + next = (address + PGDIR_SIZE) & PGDIR_MASK; + if (next > end || !next) next = end; - foundaddr = unuse_pgd(vma, pgd, start, next - start, entry, page); + foundaddr = unuse_pgd(vma, pgd, address, next, entry, page); if (foundaddr) return foundaddr; - start = next; - i++; + address = next; pgd++; - } + } while (address < end); return 0; } _