Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

mm/huge_memory: change folio_split_supported() to folio_check_splittable()

Patch series "Improve folio split related functions", v4.

This patchset improves several folio split related functions to avoid
future misuse. The changes are:

1. Consolidated folio splittable checks by moving truncated folio check,
huge zero folio check, and writeback folio check into
folio_split_supported(). Changed the function return type. Renamed it
to folio_check_splittable() for clarification.

2. Replaced can_split_folio() with open coded folio_expected_ref_count()
and folio_ref_count() and introduced folio_cache_ref_count().

3. Changed min_order_for_split() to always return an order.

4. Fixed folio split stats counting.

Motivation
==========
This is based on Wei's observation[1] and solves several potential
issues:
1. Dereferencing NULL folio->mapping in try_folio_split_to_order() if it
is called on truncated folios.
2. Not handling of negative return value of min_order_for_split() in
mm/memory-failure.c

There is no bug in the current code.


This patch (of 4):

folio_split_supported() used in try_folio_split_to_order() requires
folio->mapping to be non NULL, but current try_folio_split_to_order() does
not check it. There is no issue in the current code, since
try_folio_split_to_order() is only used in truncate_inode_partial_folio(),
where folio->mapping is not NULL.

To prevent future misuse, move folio->mapping NULL check (i.e., folio is
truncated) into folio_split_supported(). Since folio->mapping NULL check
returns -EBUSY and folio_split_supported() == false means -EINVAL, change
folio_split_supported() return type from bool to int and return error
numbers accordingly. Rename folio_split_supported() to
folio_check_splittable() to match the return type change.

While at it, move is_huge_zero_folio() check and folio_test_writeback()
check into folio_check_splittable() and add kernel-doc.

Remove all warnings inside folio_check_splittable() and give warnings
in __folio_split() instead, so that bool warns parameter can be removed.

Link: https://lkml.kernel.org/r/20251126210618.1971206-1-ziy@nvidia.com
Link: https://lkml.kernel.org/r/20251126210618.1971206-2-ziy@nvidia.com
Signed-off-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Wei Yang <richard.weiyang@gmail.com>
Acked-by: Balbir Singh <balbirs@nvidia.com>
Acked-by: David Hildenbrand (Red Hat) <david@kernel.org>
Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Barry Song <baohua@kernel.org>
Cc: Dev Jain <dev.jain@arm.com>
Cc: Lance Yang <lance.yang@linux.dev>
Cc: Liam Howlett <liam.howlett@oracle.com>
Cc: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Cc: Miaohe Lin <linmiaohe@huawei.com>
Cc: Naoya Horiguchi <nao.horiguchi@gmail.com>
Cc: Nico Pache <npache@redhat.com>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Zi Yan and committed by
Andrew Morton
bdd0d69a 1cba2eba

+46 -36
+3 -3
include/linux/huge_mm.h
··· 375 375 int folio_split_unmapped(struct folio *folio, unsigned int new_order); 376 376 int min_order_for_split(struct folio *folio); 377 377 int split_folio_to_list(struct folio *folio, struct list_head *list); 378 - bool folio_split_supported(struct folio *folio, unsigned int new_order, 379 - enum split_type split_type, bool warns); 378 + int folio_check_splittable(struct folio *folio, unsigned int new_order, 379 + enum split_type split_type); 380 380 int folio_split(struct folio *folio, unsigned int new_order, struct page *page, 381 381 struct list_head *list); 382 382 ··· 407 407 static inline int try_folio_split_to_order(struct folio *folio, 408 408 struct page *page, unsigned int new_order) 409 409 { 410 - if (!folio_split_supported(folio, new_order, SPLIT_TYPE_NON_UNIFORM, /* warns= */ false)) 410 + if (folio_check_splittable(folio, new_order, SPLIT_TYPE_NON_UNIFORM)) 411 411 return split_huge_page_to_order(&folio->page, new_order); 412 412 return folio_split(folio, new_order, page, NULL); 413 413 }
+43 -33
mm/huge_memory.c
··· 3688 3688 return 0; 3689 3689 } 3690 3690 3691 - bool folio_split_supported(struct folio *folio, unsigned int new_order, 3692 - enum split_type split_type, bool warns) 3691 + /** 3692 + * folio_check_splittable() - check if a folio can be split to a given order 3693 + * @folio: folio to be split 3694 + * @new_order: the smallest order of the after split folios (since buddy 3695 + * allocator like split generates folios with orders from @folio's 3696 + * order - 1 to new_order). 3697 + * @split_type: uniform or non-uniform split 3698 + * 3699 + * folio_check_splittable() checks if @folio can be split to @new_order using 3700 + * @split_type method. The truncated folio check must come first. 3701 + * 3702 + * Context: folio must be locked. 3703 + * 3704 + * Return: 0 - @folio can be split to @new_order, otherwise an error number is 3705 + * returned. 3706 + */ 3707 + int folio_check_splittable(struct folio *folio, unsigned int new_order, 3708 + enum split_type split_type) 3693 3709 { 3710 + VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio); 3711 + /* 3712 + * Folios that just got truncated cannot get split. Signal to the 3713 + * caller that there was a race. 3714 + * 3715 + * TODO: this will also currently refuse folios without a mapping in the 3716 + * swapcache (shmem or to-be-anon folios). 3717 + */ 3718 + if (!folio->mapping && !folio_test_anon(folio)) 3719 + return -EBUSY; 3720 + 3694 3721 if (folio_test_anon(folio)) { 3695 3722 /* order-1 is not supported for anonymous THP. */ 3696 - VM_WARN_ONCE(warns && new_order == 1, 3697 - "Cannot split to order-1 folio"); 3698 3723 if (new_order == 1) 3699 - return false; 3724 + return -EINVAL; 3700 3725 } else if (split_type == SPLIT_TYPE_NON_UNIFORM || new_order) { 3701 3726 if (IS_ENABLED(CONFIG_READ_ONLY_THP_FOR_FS) && 3702 3727 !mapping_large_folio_support(folio->mapping)) { ··· 3742 3717 * case, the mapping does not actually support large 3743 3718 * folios properly. 3744 3719 */ 3745 - VM_WARN_ONCE(warns, 3746 - "Cannot split file folio to non-0 order"); 3747 - return false; 3720 + return -EINVAL; 3748 3721 } 3749 3722 } 3750 3723 ··· 3755 3732 * here. 3756 3733 */ 3757 3734 if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && folio_test_swapcache(folio)) { 3758 - VM_WARN_ONCE(warns, 3759 - "Cannot split swapcache folio to non-0 order"); 3760 - return false; 3735 + return -EINVAL; 3761 3736 } 3762 3737 3763 - return true; 3738 + if (is_huge_zero_folio(folio)) 3739 + return -EINVAL; 3740 + 3741 + if (folio_test_writeback(folio)) 3742 + return -EBUSY; 3743 + 3744 + return 0; 3764 3745 } 3765 3746 3766 3747 static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order, ··· 3949 3922 int remap_flags = 0; 3950 3923 int extra_pins, ret; 3951 3924 pgoff_t end = 0; 3952 - bool is_hzp; 3953 3925 3954 3926 VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio); 3955 3927 VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio); ··· 3956 3930 if (folio != page_folio(split_at) || folio != page_folio(lock_at)) 3957 3931 return -EINVAL; 3958 3932 3959 - /* 3960 - * Folios that just got truncated cannot get split. Signal to the 3961 - * caller that there was a race. 3962 - * 3963 - * TODO: this will also currently refuse shmem folios that are in the 3964 - * swapcache. 3965 - */ 3966 - if (!is_anon && !folio->mapping) 3967 - return -EBUSY; 3968 - 3969 3933 if (new_order >= old_order) 3970 3934 return -EINVAL; 3971 3935 3972 - if (!folio_split_supported(folio, new_order, split_type, /* warn = */ true)) 3973 - return -EINVAL; 3974 - 3975 - is_hzp = is_huge_zero_folio(folio); 3976 - if (is_hzp) { 3977 - pr_warn_ratelimited("Called split_huge_page for huge zero page\n"); 3978 - return -EBUSY; 3936 + ret = folio_check_splittable(folio, new_order, split_type); 3937 + if (ret) { 3938 + VM_WARN_ONCE(ret == -EINVAL, "Tried to split an unsplittable folio"); 3939 + return ret; 3979 3940 } 3980 - 3981 - if (folio_test_writeback(folio)) 3982 - return -EBUSY; 3983 3941 3984 3942 if (is_anon) { 3985 3943 /*