If an anonymous mapping is not allowed to fault thp memory and then madvise(MADV_HUGEPAGE) is used after fault, khugepaged will never collapse this memory into thp memory. This occurs because the madvise(2) handler for thp, hugepage_madvise(), clears VM_NOHUGEPAGE on the stack and it isn't stored in vma->vm_flags until the final action of madvise_behavior(). This causes the khugepaged_enter_vma_merge() to be a no-op in hugepage_madvise() when the vma had previously had VM_NOHUGEPAGE set. Fix this by passing the correct vma flags to the khugepaged mm slot handler. There's no chance khugepaged can run on this vma until after madvise_behavior() returns since we hold mm->mmap_sem. It would be possible to clear VM_NOHUGEPAGE directly from vma->vm_flags in hugepage_advise(), but I didn't want to introduce special case behavior into madvise_behavior(). I think it's best to just let it always set vma->vm_flags itself.
Showing the changes to include/linux (1 of 3 files). Show all
Unified · Split
| @@ -6,7 +6,8 @@ · 5 unmodified lines | |||
| 6 | #ifdef CONFIG_TRANSPARENT_HUGEPAGE | 6 | #ifdef CONFIG_TRANSPARENT_HUGEPAGE |
| 7 | extern int __khugepaged_enter(struct mm_struct *mm); | 7 | extern int __khugepaged_enter(struct mm_struct *mm); |
| 8 | extern void __khugepaged_exit(struct mm_struct *mm); | 8 | extern void __khugepaged_exit(struct mm_struct *mm); |
| 9 | extern int khugepaged_enter_vma_merge(struct vm_area_struct *vma); | 9 | extern int khugepaged_enter_vma_merge(struct vm_area_struct *vma, |
| 10 | unsigned long vm_flags); | ||
| 10 | 11 | ||
| 11 | #define khugepaged_enabled() \ | 12 | #define khugepaged_enabled() \ |
| 12 | (transparent_hugepage_flags & \ | 13 | (transparent_hugepage_flags & \ |
| @@ -35,13 +36,13 @@ static inline void khugepaged_exit(struct mm_struct *mm) · 22 unmodified lines | |||
| 35 | __khugepaged_exit(mm); | 36 | __khugepaged_exit(mm); |
| 36 | } | 37 | } |
| 37 | 38 | ||
| 38 | static inline int khugepaged_enter(struct vm_area_struct *vma) | 39 | static inline int khugepaged_enter(struct vm_area_struct *vma, |
| 40 | unsigned long vm_flags) | ||
| 39 | { | 41 | { |
| 40 | if (!test_bit(MMF_VM_HUGEPAGE, &vma->vm_mm->flags)) | 42 | if (!test_bit(MMF_VM_HUGEPAGE, &vma->vm_mm->flags)) |
| 41 | if ((khugepaged_always() || | 43 | if ((khugepaged_always() || |
| 42 | (khugepaged_req_madv() && | 44 | (khugepaged_req_madv() && (vm_flags & VM_HUGEPAGE))) && |
| 43 | vma->vm_flags & VM_HUGEPAGE)) && | 45 | !(vm_flags & VM_NOHUGEPAGE)) |
| 44 | !(vma->vm_flags & VM_NOHUGEPAGE)) | ||
| 45 | if (__khugepaged_enter(vma->vm_mm)) | 46 | if (__khugepaged_enter(vma->vm_mm)) |
| 46 | return -ENOMEM; | 47 | return -ENOMEM; |
| 47 | return 0; | 48 | return 0; |
| @@ -54,11 +55,13 @@ static inline int khugepaged_fork(struct mm_struct *mm, struct mm_struct *oldmm) · 6 unmodified lines | |||
| 54 | static inline void khugepaged_exit(struct mm_struct *mm) | 55 | static inline void khugepaged_exit(struct mm_struct *mm) |
| 55 | { | 56 | { |
| 56 | } | 57 | } |
| 57 | static inline int khugepaged_enter(struct vm_area_struct *vma) | 58 | static inline int khugepaged_enter(struct vm_area_struct *vma, |
| 59 | unsigned long vm_flags) | ||
| 58 | { | 60 | { |
| 59 | return 0; | 61 | return 0; |
| 60 | } | 62 | } |
| 61 | static inline int khugepaged_enter_vma_merge(struct vm_area_struct *vma) | 63 | static inline int khugepaged_enter_vma_merge(struct vm_area_struct *vma, |
| 64 | unsigned long vm_flags) | ||
| 62 | { | 65 | { |
| 63 | return 0; | 66 | return 0; |
| 64 | } | 67 | } |