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 | 6 | #ifdef CONFIG_TRANSPARENT_HUGEPAGE |
| 7 | 7 | extern int __khugepaged_enter(struct mm_struct *mm); |
| 8 | 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 | 12 | #define khugepaged_enabled() \ |
| 12 | 13 | (transparent_hugepage_flags & \ |
| @@ -35,13 +36,13 @@ static inline void khugepaged_exit(struct mm_struct *mm) · 22 unmodified lines | ||
| 35 | 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 | 42 | if (!test_bit(MMF_VM_HUGEPAGE, &vma->vm_mm->flags)) |
| 41 | 43 | if ((khugepaged_always() || |
| 42 | − (khugepaged_req_madv() && | |
| 43 | − vma->vm_flags & VM_HUGEPAGE)) && | |
| 44 | − !(vma->vm_flags & VM_NOHUGEPAGE)) | |
| 44 | + (khugepaged_req_madv() && (vm_flags & VM_HUGEPAGE))) && | |
| 45 | + !(vm_flags & VM_NOHUGEPAGE)) | |
| 45 | 46 | if (__khugepaged_enter(vma->vm_mm)) |
| 46 | 47 | return -ENOMEM; |
| 47 | 48 | return 0; |
| @@ -54,11 +55,13 @@ static inline int khugepaged_fork(struct mm_struct *mm, struct mm_struct *oldmm) · 6 unmodified lines | ||
| 54 | 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 | 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 | 66 | return 0; |
| 64 | 67 | } |