diff options
| author | Kirill A. Shutemov <kirill@shutemov.name> | 2014-12-10 18:44:36 -0500 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-12-10 20:41:08 -0500 |
| commit | c164e038eee805147e95789dddb88ae3b3aca11c (patch) | |
| tree | 6e3c5c99920142d46a9acb3d334e58c78d005467 /fs/proc | |
| parent | 2314b42db67be30b747122d65c6cd2c85da34538 (diff) | |
mm: fix huge zero page accounting in smaps report
As a small zero page, huge zero page should not be accounted in smaps
report as normal page.
For small pages we rely on vm_normal_page() to filter out zero page, but
vm_normal_page() is not designed to handle pmds. We only get here due
hackish cast pmd to pte in smaps_pte_range() -- pte and pmd format is not
necessary compatible on each and every architecture.
Let's add separate codepath to handle pmds. follow_trans_huge_pmd() will
detect huge zero page for us.
We would need pmd_dirty() helper to do this properly. The patch adds it
to THP-enabled architectures which don't yet have one.
[akpm@linux-foundation.org: use do_div to fix 32-bit build]
Signed-off-by: "Kirill A. Shutemov" <kirill@shutemov.name>
Reported-by: Fengguang Wu <fengguang.wu@intel.com>
Tested-by: Fengwei Yin <yfw.kernel@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/proc')
| -rw-r--r-- | fs/proc/task_mmu.c | 104 |
1 files changed, 68 insertions, 36 deletions
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index f6734c6b66a6..246eae84b13b 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c | |||
| @@ -447,58 +447,91 @@ struct mem_size_stats { | |||
| 447 | u64 pss; | 447 | u64 pss; |
| 448 | }; | 448 | }; |
| 449 | 449 | ||
| 450 | static void smaps_account(struct mem_size_stats *mss, struct page *page, | ||
| 451 | unsigned long size, bool young, bool dirty) | ||
| 452 | { | ||
| 453 | int mapcount; | ||
| 454 | |||
| 455 | if (PageAnon(page)) | ||
| 456 | mss->anonymous += size; | ||
| 450 | 457 | ||
| 451 | static void smaps_pte_entry(pte_t ptent, unsigned long addr, | 458 | mss->resident += size; |
| 452 | unsigned long ptent_size, struct mm_walk *walk) | 459 | /* Accumulate the size in pages that have been accessed. */ |
| 460 | if (young || PageReferenced(page)) | ||
| 461 | mss->referenced += size; | ||
| 462 | mapcount = page_mapcount(page); | ||
| 463 | if (mapcount >= 2) { | ||
| 464 | u64 pss_delta; | ||
| 465 | |||
| 466 | if (dirty || PageDirty(page)) | ||
| 467 | mss->shared_dirty += size; | ||
| 468 | else | ||
| 469 | mss->shared_clean += size; | ||
| 470 | pss_delta = (u64)size << PSS_SHIFT; | ||
| 471 | do_div(pss_delta, mapcount); | ||
| 472 | mss->pss += pss_delta; | ||
| 473 | } else { | ||
| 474 | if (dirty || PageDirty(page)) | ||
| 475 | mss->private_dirty += size; | ||
| 476 | else | ||
| 477 | mss->private_clean += size; | ||
| 478 | mss->pss += (u64)size << PSS_SHIFT; | ||
| 479 | } | ||
| 480 | } | ||
| 481 | |||
| 482 | static void smaps_pte_entry(pte_t *pte, unsigned long addr, | ||
| 483 | struct mm_walk *walk) | ||
| 453 | { | 484 | { |
| 454 | struct mem_size_stats *mss = walk->private; | 485 | struct mem_size_stats *mss = walk->private; |
| 455 | struct vm_area_struct *vma = mss->vma; | 486 | struct vm_area_struct *vma = mss->vma; |
| 456 | pgoff_t pgoff = linear_page_index(vma, addr); | 487 | pgoff_t pgoff = linear_page_index(vma, addr); |
| 457 | struct page *page = NULL; | 488 | struct page *page = NULL; |
| 458 | int mapcount; | ||
| 459 | 489 | ||
| 460 | if (pte_present(ptent)) { | 490 | if (pte_present(*pte)) { |
| 461 | page = vm_normal_page(vma, addr, ptent); | 491 | page = vm_normal_page(vma, addr, *pte); |
| 462 | } else if (is_swap_pte(ptent)) { | 492 | } else if (is_swap_pte(*pte)) { |
| 463 | swp_entry_t swpent = pte_to_swp_entry(ptent); | 493 | swp_entry_t swpent = pte_to_swp_entry(*pte); |
| 464 | 494 | ||
| 465 | if (!non_swap_entry(swpent)) | 495 | if (!non_swap_entry(swpent)) |
| 466 | mss->swap += ptent_size; | 496 | mss->swap += PAGE_SIZE; |
| 467 | else if (is_migration_entry(swpent)) | 497 | else if (is_migration_entry(swpent)) |
| 468 | page = migration_entry_to_page(swpent); | 498 | page = migration_entry_to_page(swpent); |
| 469 | } else if (pte_file(ptent)) { | 499 | } else if (pte_file(*pte)) { |
| 470 | if (pte_to_pgoff(ptent) != pgoff) | 500 | if (pte_to_pgoff(*pte) != pgoff) |
| 471 | mss->nonlinear += ptent_size; | 501 | mss->nonlinear += PAGE_SIZE; |
| 472 | } | 502 | } |
| 473 | 503 | ||
| 474 | if (!page) | 504 | if (!page) |
| 475 | return; | 505 | return; |
| 476 | 506 | ||
| 477 | if (PageAnon(page)) | ||
| 478 | mss->anonymous += ptent_size; | ||
| 479 | |||
| 480 | if (page->index != pgoff) | 507 | if (page->index != pgoff) |
| 481 | mss->nonlinear += ptent_size; | 508 | mss->nonlinear += PAGE_SIZE; |
| 482 | 509 | ||
| 483 | mss->resident += ptent_size; | 510 | smaps_account(mss, page, PAGE_SIZE, pte_young(*pte), pte_dirty(*pte)); |
| 484 | /* Accumulate the size in pages that have been accessed. */ | 511 | } |
| 485 | if (pte_young(ptent) || PageReferenced(page)) | 512 | |
| 486 | mss->referenced += ptent_size; | 513 | #ifdef CONFIG_TRANSPARENT_HUGEPAGE |
| 487 | mapcount = page_mapcount(page); | 514 | static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr, |
| 488 | if (mapcount >= 2) { | 515 | struct mm_walk *walk) |
| 489 | if (pte_dirty(ptent) || PageDirty(page)) | 516 | { |
| 490 | mss->shared_dirty += ptent_size; | 517 | struct mem_size_stats *mss = walk->private; |
| 491 | else | 518 | struct vm_area_struct *vma = mss->vma; |
| 492 | mss->shared_clean += ptent_size; | 519 | struct page *page; |
| 493 | mss->pss += (ptent_size << PSS_SHIFT) / mapcount; | 520 | |
| 494 | } else { | 521 | /* FOLL_DUMP will return -EFAULT on huge zero page */ |
| 495 | if (pte_dirty(ptent) || PageDirty(page)) | 522 | page = follow_trans_huge_pmd(vma, addr, pmd, FOLL_DUMP); |
| 496 | mss->private_dirty += ptent_size; | 523 | if (IS_ERR_OR_NULL(page)) |
| 497 | else | 524 | return; |
| 498 | mss->private_clean += ptent_size; | 525 | mss->anonymous_thp += HPAGE_PMD_SIZE; |
| 499 | mss->pss += (ptent_size << PSS_SHIFT); | 526 | smaps_account(mss, page, HPAGE_PMD_SIZE, |
| 500 | } | 527 | pmd_young(*pmd), pmd_dirty(*pmd)); |
| 501 | } | 528 | } |
| 529 | #else | ||
| 530 | static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr, | ||
| 531 | struct mm_walk *walk) | ||
| 532 | { | ||
| 533 | } | ||
| 534 | #endif | ||
| 502 | 535 | ||
| 503 | static int smaps_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end, | 536 | static int smaps_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end, |
| 504 | struct mm_walk *walk) | 537 | struct mm_walk *walk) |
| @@ -509,9 +542,8 @@ static int smaps_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end, | |||
| 509 | spinlock_t *ptl; | 542 | spinlock_t *ptl; |
| 510 | 543 | ||
| 511 | if (pmd_trans_huge_lock(pmd, vma, &ptl) == 1) { | 544 | if (pmd_trans_huge_lock(pmd, vma, &ptl) == 1) { |
| 512 | smaps_pte_entry(*(pte_t *)pmd, addr, HPAGE_PMD_SIZE, walk); | 545 | smaps_pmd_entry(pmd, addr, walk); |
| 513 | spin_unlock(ptl); | 546 | spin_unlock(ptl); |
| 514 | mss->anonymous_thp += HPAGE_PMD_SIZE; | ||
| 515 | return 0; | 547 | return 0; |
| 516 | } | 548 | } |
| 517 | 549 | ||
| @@ -524,7 +556,7 @@ static int smaps_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end, | |||
| 524 | */ | 556 | */ |
| 525 | pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl); | 557 | pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl); |
| 526 | for (; addr != end; pte++, addr += PAGE_SIZE) | 558 | for (; addr != end; pte++, addr += PAGE_SIZE) |
| 527 | smaps_pte_entry(*pte, addr, PAGE_SIZE, walk); | 559 | smaps_pte_entry(pte, addr, walk); |
| 528 | pte_unmap_unlock(pte - 1, ptl); | 560 | pte_unmap_unlock(pte - 1, ptl); |
| 529 | cond_resched(); | 561 | cond_resched(); |
| 530 | return 0; | 562 | return 0; |
