[PATCH openEuler-26.09 0/3] cache_ext framework
From: Nikita Panov <panov.nikita@huawei.com> Cache_ext framework project integration. Heavily inspired by "cache_ext: Customizing the Page Cache with eBPF" by Tal Zussman, Ioannis Zarkadas and others. This patchset allows users to load custom bpf policies to adjust page cache reclaim algorithm. Policy might be designed to reclaim specific files (filter by name) or an algorithm differ from LRU/MGLRU might be implemented. Additionaly, a refill mechanism was implemented. If the page was reclaimed through the cache_ext, the system has a lot of free mem, and the disk is not busy with IO at the moment, page might be refilled to the page cache. Nikita Panov (3): mm: Cache_ext framework krefilld: initial design and implementation cache_ext: Add examples to tools/cache_ext .../admin-guide/kernel-parameters.txt | 21 + Documentation/admin-guide/sysctl/vm.rst | 41 + arch/arm64/configs/openeuler_defconfig | 1 + fs/drop_caches.c | 6 + fs/inode.c | 13 + fs/super.c | 20 +- include/linux/cache_ext.h | 259 ++++ include/linux/fs.h | 4 + include/linux/memcontrol.h | 26 + include/linux/mempolicy.h | 6 + include/linux/mm_types.h | 33 + include/linux/mmzone.h | 7 + include/linux/page_ext.h | 4 + include/linux/pagemap.h | 3 + include/linux/sched.h | 7 + include/linux/swap.h | 20 + include/linux/sysctl.h | 8 +- kernel/bpf/verifier.c | 129 +- kernel/sysctl.c | 38 +- mm/Kconfig | 11 + mm/Makefile | 1 + mm/cache_ext.c | 1335 +++++++++++++++++ mm/cache_ext_ds.c | 1050 +++++++++++++ mm/filemap.c | 12 +- mm/memcontrol.c | 48 + mm/mempolicy.c | 5 + mm/migrate.c | 16 +- mm/page_ext.c | 7 + mm/swap.c | 1 + mm/truncate.c | 12 +- mm/vmscan.c | 267 +++- mm/vmstat.c | 8 +- mm/workingset.c | 110 +- tools/cache_ext/.gitignore | 2 + tools/cache_ext/Makefile | 229 +++ tools/cache_ext/cache_ext_simple_fifo.bpf.c | 109 ++ tools/cache_ext/cache_ext_simple_fifo.c | 52 + .../cache_ext/include/cache_ext_common.bpf.h | 178 +++ 38 files changed, 4073 insertions(+), 26 deletions(-) create mode 100644 include/linux/cache_ext.h create mode 100644 mm/cache_ext.c create mode 100644 mm/cache_ext_ds.c create mode 100644 tools/cache_ext/.gitignore create mode 100644 tools/cache_ext/Makefile create mode 100644 tools/cache_ext/cache_ext_simple_fifo.bpf.c create mode 100644 tools/cache_ext/cache_ext_simple_fifo.c create mode 100644 tools/cache_ext/include/cache_ext_common.bpf.h -- 2.34.1
From: Nikita Panov <panov.nikita@huawei.com> hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10164 ---------------------------------------- Cache_ext framework project integration. Heavily inspired by "cache_ext: Customizing the Page Cache with eBPF" by Tal Zussman, Ioannis Zarkadas and others. A lot of cleanups and reimplementation's of the original project were performed, fixed a lot of major design issues, including but not limited to ref-counting, memory leaks, data corruption bugs, use-after-free, and many more. Significantly improved scalability and decreased overhead compare to the original implementation. Supported policy loading per-memory cgroup. On policy load, child memory cgroups inherit policy from the parent. For now, we recommend loading a single policy into the root memory cgroup. Migration, drop_caches and standard reclaim - all these features are supported. Page_ext is used for internal structures, be aware about extra memory consumption. Append "cache_ext=on" to the kernel cmdline if you want to enable this feature. Co-developed-by: Alexander Grubnikov <alexander.grubnikov@huawei.com> Signed-off-by: Alexander Grubnikov <alexander.grubnikov@huawei.com> Co-developed-by: Anatoly Stepanov <stepanov.anatoly@huawei.com> Signed-off-by: Anatoly Stepanov <stepanov.anatoly@huawei.com> Co-developed-by: Nikita Panov <panov.nikita@huawei.com> Signed-off-by: Nikita Panov <panov.nikita@huawei.com> --- .../admin-guide/kernel-parameters.txt | 11 + Documentation/admin-guide/sysctl/vm.rst | 11 + arch/arm64/configs/openeuler_defconfig | 1 + include/linux/cache_ext.h | 249 ++++ include/linux/memcontrol.h | 26 + include/linux/mm_types.h | 33 + include/linux/mmzone.h | 5 + include/linux/page_ext.h | 4 + include/linux/sched.h | 6 + include/linux/swap.h | 12 + kernel/bpf/verifier.c | 129 +- kernel/sysctl.c | 10 + mm/Kconfig | 11 + mm/Makefile | 1 + mm/cache_ext.c | 952 +++++++++++++++ mm/cache_ext_ds.c | 1050 +++++++++++++++++ mm/filemap.c | 10 + mm/memcontrol.c | 43 + mm/migrate.c | 16 +- mm/page_ext.c | 7 + mm/swap.c | 1 + mm/truncate.c | 12 +- mm/vmscan.c | 262 +++- mm/vmstat.c | 6 +- mm/workingset.c | 68 +- 25 files changed, 2924 insertions(+), 12 deletions(-) create mode 100644 include/linux/cache_ext.h create mode 100644 mm/cache_ext.c create mode 100644 mm/cache_ext_ds.c diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 17160ad5aefe..ab5ecf034b22 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -538,6 +538,17 @@ possible to determine what the correct size should be. This option provides an override for these situations. + cache_ext= [KNL] + Format: [on|off] + Default: off + This feature enables support of the cache_ext framework. + Cache_ext allows implementing custom policies for page + cache reclaim and loading them per memory cgroup. + Page_ext is used in this framework for internal data + structures. If policy is not loaded, or policy is unable + to reclaim the requested number of pages, fallback to + vanilla reclaim is performed. + carrier_timeout= [NET] Specifies amount of time (in seconds) that the kernel should wait for a network carrier. By default diff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst index 2ad3bf320c55..cdbcf806b7fe 100644 --- a/Documentation/admin-guide/sysctl/vm.rst +++ b/Documentation/admin-guide/sysctl/vm.rst @@ -80,6 +80,7 @@ Currently, these files are in /proc/sys/vm: - cache_reclaim_weight - cache_reclaim_enable - cache_limit_mbytes +- allow_direct_cache_ext admin_reserve_kbytes @@ -1099,3 +1100,13 @@ cache_limit_mbytes This is used to set the upper limit of page cache in megabytes. Page cache will be reclaimed periodically if page cache is over limit. + + +allow_direct_cache_ext +=================== + +When CONFIG_CACHE_EXT is enabled, bpf policies for page cache reclaim might be +implemented and loaded per memory cgroup. This file controls whether direct +reclaim is allowed to be performed from the custom policy or not. If not, +only kswapd will reclaim from the cache_ext policy. Noop if cache_ext=on is +not specified in kernel cmdline. Default value is true. diff --git a/arch/arm64/configs/openeuler_defconfig b/arch/arm64/configs/openeuler_defconfig index 476efcfb46fe..fc503fff4c29 100644 --- a/arch/arm64/configs/openeuler_defconfig +++ b/arch/arm64/configs/openeuler_defconfig @@ -1266,6 +1266,7 @@ CONFIG_RECLAIM_NOTIFY=y # CONFIG_BPF_READAHEAD is not set CONFIG_MEM_SAMPLING=y CONFIG_NUMABALANCING_MEM_SAMPLING=y +CONFIG_CACHE_EXT=y # # Data Access Monitoring diff --git a/include/linux/cache_ext.h b/include/linux/cache_ext.h new file mode 100644 index 000000000000..40e3c4230942 --- /dev/null +++ b/include/linux/cache_ext.h @@ -0,0 +1,249 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_CACHE_EXT_H +#define _LINUX_CACHE_EXT_H + +#include <linux/list.h> +#include <linux/hashtable.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/bitops.h> +#include <linux/page_ext.h> + + +#ifdef CONFIG_CACHE_EXT + +DECLARE_STATIC_KEY_TRUE(cache_ext_disabled); + +static inline bool folio_test_cache_ext(struct folio *folio) +{ + bool result; + struct page_ext *page_ext; + + if (static_branch_unlikely(&cache_ext_disabled)) + return false; + + page_ext = page_ext_get(folio_page(folio, 0)); + + if (unlikely(!page_ext)) + return false; + + result = test_bit(PAGE_EXT_CACHE_EXT, &page_ext->flags); + + page_ext_put(page_ext); + return result; +} + +static inline void folio_clear_cache_ext(struct folio *folio) +{ + struct page_ext *page_ext; + + if (static_branch_unlikely(&cache_ext_disabled)) + return; + + page_ext = page_ext_get(folio_page(folio, 0)); + + if (unlikely(!page_ext)) + return; + + clear_bit(PAGE_EXT_CACHE_EXT, &page_ext->flags); + + page_ext_put(page_ext); +} + +static inline void folio_set_cache_ext(struct folio *folio) +{ + struct page_ext *page_ext; + + if (static_branch_unlikely(&cache_ext_disabled)) + return; + + page_ext = page_ext_get(folio_page(folio, 0)); + + if (unlikely(!page_ext)) + return; + + set_bit(PAGE_EXT_CACHE_EXT, &page_ext->flags); + + page_ext_put(page_ext); +} + +static inline bool folio_test_cache_ext_special_list(struct folio *folio) +{ + bool result; + struct page_ext *page_ext; + + if (static_branch_unlikely(&cache_ext_disabled)) + return false; + + page_ext = page_ext_get(folio_page(folio, 0)); + + if (unlikely(!page_ext)) + return false; + + result = test_bit(PAGE_EXT_CACHE_EXT, &page_ext->flags); + + page_ext_put(page_ext); + return result; +} + +static inline void folio_clear_cache_ext_special_list(struct folio *folio) +{ + struct page_ext *page_ext; + + if (static_branch_unlikely(&cache_ext_disabled)) + return; + + page_ext = page_ext_get(folio_page(folio, 0)); + + if (unlikely(!page_ext)) + return; + + clear_bit(PAGE_EXT_CACHE_EXT_SPECIAL_LIST, &page_ext->flags); + + page_ext_put(page_ext); +} + +static inline void folio_set_cache_ext_special_list(struct folio *folio) +{ + struct page_ext *page_ext; + + if (static_branch_unlikely(&cache_ext_disabled)) + return; + + page_ext = page_ext_get(folio_page(folio, 0)); + + if (unlikely(!page_ext)) + return; + + set_bit(PAGE_EXT_CACHE_EXT_SPECIAL_LIST, &page_ext->flags); + + page_ext_put(page_ext); +} + +typedef u64 (*bpf_callback_t)(u64, u64, u64, u64, u64); + +bool cache_ext_is_callback_calling_kfunc_iterate(u32 btf_id); +bool cache_ext_is_callback_calling_kfunc_sample(u32 btf_id); + +struct cache_ext_list { + struct list_head head; +}; + +/* + * Cookie might be set only + * for non-cache_ext folios. + */ +struct cache_ext_list_node { + union { + struct folio *folio; + s64 cookie; + }; + struct list_head node; +}; + +struct cache_ext_iterate_opts { + u64 continue_list; + u64 continue_mode; + + u64 nr_folios_continue; + u64 nr_folios_isolated; +}; + +struct sampling_options { + __u32 sample_size; + __u32 select_size; +}; + +int bpf_cache_ext_list_splice(u64 list_id, u64 head_id, struct mem_cgroup *memcg, int nid); +int bpf_cache_ext_list_splice_tail(u64 list_id, u64 head_id, struct mem_cgroup *memcg, int nid); + +int bpf_cache_ext_list_add(u64 list, struct folio *folio); +int bpf_cache_ext_list_add_tail(u64 list, struct folio *folio); +int bpf_cache_ext_list_move(u64 list, struct folio *folio, bool tail); +int bpf_cache_ext_list_del(struct folio *folio); +int bpf_cache_ext_list_iterate(struct mem_cgroup *memcg, u64 list_id, + int (*iter_fn)(int idx, + struct cache_ext_list_node *node), + struct cache_ext_isolation_ctx *ctx); +int bpf_cache_ext_list_sample(struct mem_cgroup *memcg, u64 list, + s32 (*score_fn)(struct cache_ext_list_node *a), + struct sampling_options *opts, + struct cache_ext_isolation_ctx *ctx); +int bpf_cache_ext_list_iterate_extended(struct mem_cgroup *memcg, u64 list, + int (*iter_fn)(int idx, struct cache_ext_list_node *node), + struct cache_ext_iterate_opts *opts, + struct cache_ext_isolation_ctx *ctx); +u64 bpf_cache_ext_ds_registry_new_list(struct mem_cgroup *memcg); +bool bpf_cache_ext_filename_strstr(const char *str, u32 str__sz, struct folio *folio); +bool bpf_cache_ext_list_empty(u64 list_id, struct mem_cgroup *memcg, int nid); +int bpf_folio_nid(struct folio *folio); + +#define CACHE_EXT_REGISTRY_MAX_ENTRIES 8 + +struct cache_ext_ds_registry { + /* + * rwlock serializing the following: + * - item add/del + * - per-list add/del/move + */ + rwlock_t lock; + struct cache_ext_list items[CACHE_EXT_REGISTRY_MAX_ENTRIES]; + int nr_entries; +}; + +struct cache_ext_policy { + struct cache_ext_ops *cache_ext_ops; + rwlock_t lock; + struct mem_cgroup *memcg; +}; + +int cache_ext_policy_init(struct mem_cgroup *memcg); +void cache_ext_policy_free(struct mem_cgroup *memcg); + +void cache_ext_folio_added(struct folio *folio, bool increase_ref, s64 cookie); +void cache_ext_folio_accessed(struct folio *folio); +s64 cache_ext_folio_evicted(struct folio *folio); +int cache_ext_isolate_folios(struct cache_ext_isolation_ctx *ctx, struct mem_cgroup *memcg); +int cache_ext_policy_stat_show(struct seq_file *m, void *v); + +int cache_ext_folio_meta_init(struct folio *folio, bool increase_ref); +int cache_ext_folio_meta_fini(struct folio *folio); + +struct cache_ext_list_node *get_cache_ext_list_node(struct folio *folio); +void put_cache_ext_list_node(struct cache_ext_list_node *node); + +void set_cookie_to_folio_page_ext(struct folio *folio, s64 cookie); +s64 get_and_clear_cookie_from_folio_page_ext(struct folio *folio); + +bool cache_ext_kfunc(u32 btf_id); + +extern bool allow_direct_cache_ext; + +#else + +static inline bool folio_test_cache_ext(struct folio *folio) { return false; } +static inline void folio_clear_cache_ext(struct folio *folio) { } +static inline void folio_set_cache_ext(struct folio *folio) { } +static inline bool folio_test_cache_ext_special_list(struct folio *folio) { return false; } +static inline void folio_clear_cache_ext_special_list(struct folio *folio) { } +static inline void folio_set_cache_ext_special_list(struct folio *folio) { } + +static inline bool cache_ext_is_callback_calling_kfunc_iterate(u32 btf_id) { return false; } +static inline bool cache_ext_is_callback_calling_kfunc_sample(u32 btf_id) { return false; } + +static inline int cache_ext_policy_init(struct mem_cgroup *memcg) { return 0; } +static inline void cache_ext_policy_free(struct mem_cgroup *memcg) { } + +static inline void cache_ext_folio_added(struct folio *folio, bool increase_ref, s64 cookie) { } +static inline void cache_ext_folio_accessed(struct folio *folio) { } +static inline int cache_ext_folio_evicted(struct folio *folio) { return -EOPNOTSUPP; } + +static inline void set_cookie_to_folio_page_ext(struct folio *folio, s64 cookie) { } +static inline s64 get_and_clear_cookie_from_folio_page_ext(struct folio *folio) { return 0; } + +static inline bool cache_ext_kfunc(u32 btf_id) { return false; } + +#endif + +#endif diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index 89ef30f20275..c2221d106b00 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -21,6 +21,7 @@ #include <linux/vmstat.h> #include <linux/writeback.h> #include <linux/page-flags.h> +#include <linux/cache_ext.h> #include <linux/kabi.h> #ifndef __GENKSYMS__ @@ -157,6 +158,10 @@ struct mem_cgroup_per_node { struct mem_cgroup *memcg; /* Back pointer, we cannot */ /* use container_of */ +#ifdef CONFIG_CACHE_EXT + struct cache_ext_ds_registry *cache_ext_ds_registry; +#endif + KABI_RESERVE(1) KABI_RESERVE(2) KABI_RESERVE(3) @@ -412,6 +417,11 @@ struct mem_cgroup { struct dynamic_pool *dpool; #endif +#ifdef CONFIG_CACHE_EXT + struct cache_ext_policy *cache_ext_policy; + struct cache_ext_policy *cache_ext_ptr; +#endif + KABI_RESERVE(1) KABI_RESERVE(2) KABI_RESERVE(3) @@ -2107,4 +2117,20 @@ static inline void obj_cgroup_uncharge_zswap(struct obj_cgroup *objcg, } #endif +#ifdef CONFIG_CACHE_EXT +static inline struct cache_ext_ops *get_cache_ext_ops_memcg(struct mem_cgroup *memcg) +{ + if (memcg && memcg->cache_ext_ptr) + return memcg->cache_ext_ptr->cache_ext_ops; + return NULL; +} + +static inline struct cache_ext_ops *get_cache_ext_ops_lruvec(struct lruvec *lruvec) +{ + if (!lruvec) + return NULL; + return get_cache_ext_ops_memcg(lruvec_memcg(lruvec)); +} +#endif + #endif /* _LINUX_MEMCONTROL_H */ diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 20c62bc70558..e2050defc043 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -1500,4 +1500,37 @@ enum { /* See also internal only FOLL flags in mm/internal.h */ }; +#define CACHE_EXT_NR_ISOLATED (32) +struct cache_ext_isolation_ctx { + /* Requested nr */ + unsigned long nr_folios_to_isolate; + int numa_node_id; + /* Actual nr */ + unsigned long nr_folios_isolated; + unsigned long nr_base_pages_isolated; + struct folio *folios[CACHE_EXT_NR_ISOLATED]; + s64 scores[CACHE_EXT_NR_ISOLATED]; + s64 cookies[CACHE_EXT_NR_ISOLATED]; +}; + +#define MAX_STATS_COUNT (4) +#define MAX_STAT_LEN (32) +struct cache_ext_stats { + u64 n; + char names[MAX_STATS_COUNT][MAX_STAT_LEN]; + u64 values[MAX_STATS_COUNT]; +}; + +struct lruvec; +struct cache_ext_ops { + /* Implement bpf_verifier_ops */ + s32 (*init)(struct mem_cgroup *memcg); + int (*isolate_folios)(struct cache_ext_isolation_ctx *ctx, struct mem_cgroup *memcg); + void (*folio_added)(struct folio *folio, s64 cookie); + void (*folio_accessed)(struct folio *folio); + s64 (*folio_evicted)(struct folio *folio); + void (*get_statistics)(struct lruvec *lruvec, struct cache_ext_stats *stats); + struct mem_cgroup *memcg; +}; + #endif /* _LINUX_MM_TYPES_H */ diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index b831463c2e6e..3fbc6b5fbba9 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -219,6 +219,11 @@ enum node_stat_item { PGDEMOTE_KSWAPD, PGDEMOTE_DIRECT, PGDEMOTE_KHUGEPAGED, +#ifdef CONFIG_CACHE_EXT + NR_FILE_CACHE_EXT, + NR_FILE_CACHE_EXT_KSWAPD_RECLAIMED, + NR_FILE_CACHE_EXT_REFAULT, +#endif NR_VM_NODE_STAT_ITEMS }; diff --git a/include/linux/page_ext.h b/include/linux/page_ext.h index be98564191e6..d8c9fb64d5ee 100644 --- a/include/linux/page_ext.h +++ b/include/linux/page_ext.h @@ -40,6 +40,10 @@ enum page_ext_flags { PAGE_EXT_YOUNG, PAGE_EXT_IDLE, #endif +#ifdef CONFIG_CACHE_EXT + PAGE_EXT_CACHE_EXT, + PAGE_EXT_CACHE_EXT_SPECIAL_LIST, +#endif }; /* diff --git a/include/linux/sched.h b/include/linux/sched.h index 3014da820ba6..d7f1c1095cbb 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1862,7 +1862,13 @@ extern struct pid *cad_pid; #define PF_KTHREAD 0x00200000 /* I am a kernel thread */ #define PF_RANDOMIZE 0x00400000 /* Randomize virtual address space */ #define PF__HOLE__00800000 0x00800000 + +#ifdef CONFIG_CACHE_EXT +#define PF_CACHE_EXT_RECLAIM 0x01000000 /* I reclaim from cache_ext right now */ +#else #define PF__HOLE__01000000 0x01000000 +#endif + #define PF__HOLE__02000000 0x02000000 #define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_mask */ #define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */ diff --git a/include/linux/swap.h b/include/linux/swap.h index f11b96cec851..9f1f77f4cf59 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -39,6 +39,18 @@ static inline int current_is_kswapd(void) return current->flags & PF_KSWAPD; } +#ifdef CONFIG_CACHE_EXT +static inline int current_is_cache_ext_reclaim(void) +{ + return current->flags & PF_CACHE_EXT_RECLAIM; +} +#else +static inline int current_is_cache_ext_reclaim(void) +{ + return 0; +} +#endif + /* * MAX_SWAPFILES defines the maximum number of swaptypes: things which can * be swapped to. The swap type and the offset into that swap type are diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 5c24f719437f..1dff96c87ead 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -26,6 +26,7 @@ #include <linux/poison.h> #include <linux/module.h> #include <linux/cpumask.h> +#include <linux/cache_ext.h> #include <net/xdp.h> #ifdef CONFIG_BPF_SCHED @@ -9758,6 +9759,73 @@ static int set_rbtree_add_callback_state(struct bpf_verifier_env *env, return 0; } +#ifdef CONFIG_CACHE_EXT + +BTF_ID_LIST_SINGLE(bpf_cache_ext_list_node_btf_id, struct, cache_ext_list_node) + +static int set_cache_ext_list_iterate_callback_state( + struct bpf_verifier_env *env, + struct bpf_func_state *caller, + struct bpf_func_state *callee, + int insn_idx) +{ + /* int bpf_cache_ext_list_iterate( + * struct mem_cgroup *memcg, + * u64 list, + * int (iter_fn)(int idx, struct cache_ext_list_node *node), + * struct cache_ext_eviction_ctx *ctx) + */ + __mark_reg_not_init(env, &callee->regs[BPF_REG_0]); + + /* Set scalar register */ + __mark_reg_unknown(env, &callee->regs[BPF_REG_1]); + + /* Set node pointer */ + callee->regs[BPF_REG_2].type = PTR_TO_BTF_ID; + __mark_reg_known_zero(&callee->regs[BPF_REG_2]); + callee->regs[BPF_REG_2].btf = btf_vmlinux; + callee->regs[BPF_REG_2].btf_id = bpf_cache_ext_list_node_btf_id[0]; + + /* unused */ + __mark_reg_not_init(env, &callee->regs[BPF_REG_3]); + __mark_reg_not_init(env, &callee->regs[BPF_REG_4]); + __mark_reg_not_init(env, &callee->regs[BPF_REG_5]); + + callee->in_callback_fn = true; + callee->callback_ret_range = retval_range(0, 2); + return 0; +} + +static int set_cache_ext_list_sample_callback_state( + struct bpf_verifier_env *env, + struct bpf_func_state *caller, + struct bpf_func_state *callee, + int insn_idx) +{ + /* int bpf_cache_ext_list_sample(struct mem_cgroup *memcg, u64 list, + * s64(score_fn)(struct cache_ext_list_node *a), + * struct sampling_options *opts, + * struct cache_ext_eviction_ctx *ctx) + */ + __mark_reg_not_init(env, &callee->regs[BPF_REG_0]); + + /* Set node pointer */ + callee->regs[BPF_REG_1].type = PTR_TO_BTF_ID; + __mark_reg_known_zero(&callee->regs[BPF_REG_1]); + callee->regs[BPF_REG_1].btf = btf_vmlinux; + callee->regs[BPF_REG_1].btf_id = bpf_cache_ext_list_node_btf_id[0]; + + /* unused */ + __mark_reg_not_init(env, &callee->regs[BPF_REG_3]); + __mark_reg_not_init(env, &callee->regs[BPF_REG_4]); + __mark_reg_not_init(env, &callee->regs[BPF_REG_5]); + + callee->in_callback_fn = true; + callee->callback_ret_range = retval_range(S32_MIN, S32_MAX); + return 0; +} +#endif + static bool is_rbtree_lock_required_kfunc(u32 btf_id); /* Are we currently verifying the callback for a rbtree helper that must @@ -11238,7 +11306,9 @@ static bool is_bpf_graph_api_kfunc(u32 btf_id) static bool is_sync_callback_calling_kfunc(u32 btf_id) { - return btf_id == special_kfunc_list[KF_bpf_rbtree_add_impl]; + return (btf_id == special_kfunc_list[KF_bpf_rbtree_add_impl] || + cache_ext_is_callback_calling_kfunc_iterate(btf_id) || + cache_ext_is_callback_calling_kfunc_sample(btf_id)); } static bool is_rbtree_lock_required_kfunc(u32 btf_id) @@ -11931,6 +12001,36 @@ static int fetch_kfunc_meta(struct bpf_verifier_env *env, return 0; } +#ifdef CONFIG_CACHE_EXT +static bool in_cache_ext_list_cb(struct bpf_verifier_env *env) +{ + struct bpf_verifier_state *state = env->cur_state; + struct bpf_insn *insn = env->prog->insnsi; + struct bpf_func_state *callee; + int kfunc_btf_id; + u32 frame; + + if (static_branch_unlikely(&cache_ext_disabled)) + return false; + + for (frame = state->curframe; frame; frame--) { + callee = state->frame[frame]; + + if (!callee->in_callback_fn) + continue; + + kfunc_btf_id = insn[callee->callsite].imm; + if (cache_ext_is_callback_calling_kfunc_iterate(kfunc_btf_id) || + cache_ext_is_callback_calling_kfunc_sample(kfunc_btf_id)) + return true; + } + + return false; +} +#else +static bool in_cache_ext_list_cb(struct bpf_verifier_env *env) { return false; } +#endif + static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, int *insn_idx_p) { @@ -11971,6 +12071,12 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, return -EACCES; } + if (in_cache_ext_list_cb(env) && cache_ext_kfunc(meta.func_id)) { + verbose(env, "kfunc %s#%d cannot be called from cache_ext list callback\n", + func_name, meta.func_id); + return -EACCES; + } + /* Check the arguments */ err = check_kfunc_args(env, &meta, insn_idx); if (err < 0) @@ -11986,6 +12092,27 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, } } +#ifdef CONFIG_CACHE_EXT + if (cache_ext_is_callback_calling_kfunc_iterate(meta.func_id)) { + err = push_callback_call(env, insn, insn_idx, meta.subprogno, + set_cache_ext_list_iterate_callback_state); + if (err) { + verbose(env, "kfunc %s#%d failed callback verification\n", + func_name, meta.func_id); + return err; + } + } + if (cache_ext_is_callback_calling_kfunc_sample(meta.func_id)) { + err = push_callback_call(env, insn, insn_idx, meta.subprogno, + set_cache_ext_list_sample_callback_state); + if (err) { + verbose(env, "kfunc %s#%d failed callback verification\n", + func_name, meta.func_id); + return err; + } + } +#endif + rcu_lock = is_kfunc_bpf_rcu_read_lock(&meta); rcu_unlock = is_kfunc_bpf_rcu_read_unlock(&meta); diff --git a/kernel/sysctl.c b/kernel/sysctl.c index e84df0818517..88d8ca3c0fd3 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -63,6 +63,7 @@ #include <linux/mount.h> #include <linux/userfaultfd_k.h> #include <linux/pid.h> +#include <linux/cache_ext.h> #include "../lib/kstrtox.h" @@ -2118,6 +2119,15 @@ static struct ctl_table vm_table[] = { .extra1 = SYSCTL_ONE, .extra2 = SYSCTL_FOUR, }, +#ifdef CONFIG_CACHE_EXT + { + .procname = "allow_direct_cache_ext", + .data = &allow_direct_cache_ext, + .maxlen = sizeof(bool), + .mode = 0644, + .proc_handler = proc_dobool + }, +#endif { .procname = "page_lock_unfairness", .data = &sysctl_page_lock_unfairness, diff --git a/mm/Kconfig b/mm/Kconfig index b99e90efcc2c..62646204f67e 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -1528,6 +1528,17 @@ config GMEM say Y here to enable gmem subsystem +config CACHE_EXT + bool "Cache_ext framework" + depends on BPF_SYSCALL && MEMCG + select PAGE_EXTENSION + default n + help + This feature provides infrastructure for implementing + custom reclaim policies for page cache reclaim in BPF + + say Y here to enable + source "mm/damon/Kconfig" config THP_CONTROL diff --git a/mm/Makefile b/mm/Makefile index e45cdeda47b7..7a026d41588c 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -150,3 +150,4 @@ obj-$(CONFIG_RECLAIM_NOTIFY) += reclaim_notify.o obj-$(CONFIG_DYNAMIC_POOL) += dynamic_pool.o obj-$(CONFIG_MEM_SAMPLING) += mem_sampling.o obj-$(CONFIG_THP_CONTROL) += thp_ctl.o +obj-$(CONFIG_CACHE_EXT) += cache_ext.o cache_ext_ds.o diff --git a/mm/cache_ext.c b/mm/cache_ext.c new file mode 100644 index 000000000000..2b349030209f --- /dev/null +++ b/mm/cache_ext.c @@ -0,0 +1,952 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/bpf_verifier.h> +#include <linux/bpf.h> +#include <linux/btf.h> +#include <linux/kernel.h> +#include <linux/memcontrol.h> +#include <linux/mm_types.h> +#include <linux/types.h> +#include <linux/cgroup.h> +#include <linux/pagevec.h> +#include <linux/mm.h> +#include <linux/mm_inline.h> +#include <linux/page_ext.h> + +#include "internal.h" + +DEFINE_STATIC_KEY_TRUE(cache_ext_disabled); + +static int __init setup_cache_ext(char *str) +{ + int ret = -EINVAL; + + if (!str) + goto out; + if (!strcmp(str, "on")) { + static_branch_disable(&cache_ext_disabled); + pr_info("cache_ext enabled via cmdline\n"); + ret = 0; + } else if (!strcmp(str, "off")) { + static_branch_enable(&cache_ext_disabled); + pr_info("cache_ext disabled via cmdline\n"); + ret = 0; + } +out: + if (ret) + pr_warn("cache_ext= cannot parse, ignored\n"); + return ret; +} +early_param("cache_ext", setup_cache_ext); + +static const struct btf_type *cache_ext_isolation_ctx_type; +static const struct btf_type *cache_ext_stats_ctx_type; + +static int bpf_cache_ext_init(struct btf *btf) +{ + u32 isolation_type_id, stats_type_id; + + isolation_type_id = btf_find_by_name_kind(btf, "cache_ext_isolation_ctx", + BTF_KIND_STRUCT); + if (isolation_type_id < 0) { + pr_err("cache_ext: failed to find struct cache_ext_isolation_ctx\n"); + return -EINVAL; + } + + stats_type_id = btf_find_by_name_kind(btf, "cache_ext_stats", + BTF_KIND_STRUCT); + if (stats_type_id < 0) { + pr_err("cache_ext: failed to find struct cache_ext_stats\n"); + return -EINVAL; + } + + cache_ext_isolation_ctx_type = btf_type_by_id(btf, isolation_type_id); + cache_ext_stats_ctx_type = btf_type_by_id(btf, stats_type_id); + return 0; +} + +static const struct bpf_func_proto * +bpf_cache_ext_get_func_proto(enum bpf_func_id func_id, + const struct bpf_prog *prog) +{ + switch (func_id) { + case BPF_FUNC_get_current_pid_tgid: + return &bpf_get_current_pid_tgid_proto; + default: + return bpf_base_func_proto(func_id); + } +} + +static bool bpf_cache_ext_is_valid_access(int off, int size, + enum bpf_access_type type, + const struct bpf_prog *prog, + struct bpf_insn_access_aux *info) +{ + if (off % size != 0) + return false; + + return btf_ctx_access(off, size, type, prog, info); +} + +static int bpf_cache_ext_btf_struct_access(struct bpf_verifier_log *log, + const struct bpf_reg_state *reg, + int off, int size) +{ + /* + * For each of the allowed types, allow read access. + */ + const struct btf_type *t; + + t = btf_type_by_id(reg->btf, reg->btf_id); + if (t == cache_ext_isolation_ctx_type) { + unsigned long start, end; + + if (off + size > sizeof(struct cache_ext_isolation_ctx)) { + bpf_log(log, "out of bounds access at off %d with size %d\n", off, size); + return -EACCES; + } + start = offsetof(struct cache_ext_isolation_ctx, cookies); + end = start + (CACHE_EXT_NR_ISOLATED * sizeof(s64)); + if (off >= start && off + size <= end) + return SCALAR_VALUE; + + bpf_log(log, "Invalid write in isolation ctx at off %d with size %d\n", off, size); + return -EACCES; + } else if (t == cache_ext_stats_ctx_type) { + if (off + size > sizeof(struct cache_ext_stats)) { + bpf_log(log, "out of bounds access at off %d with size %d\n", off, size); + return -EACCES; + } + return SCALAR_VALUE; + } + + return -EACCES; +} + +static int bpf_cache_ext_init_member(const struct btf_type *t, + const struct btf_member *member, + void *kdata, const void *udata) +{ + return 0; +} + +static int bpf_cache_ext_check_member(const struct btf_type *t, + const struct btf_member *member, + const struct bpf_prog *prog) +{ + const char *member_name; + + member_name = btf_name_by_offset(bpf_get_btf_vmlinux(), member->name_off); + + if (prog->aux->sleepable) { + + if (member_name && strcmp(member_name, "init") == 0) + return 0; + + pr_warn_ratelimited("cache_ext: sleepable BPF program is not allowed for atomic hook '%s'\n", + member_name ? member_name : "unknown"); + return -EINVAL; + } + return 0; +} + +static int bpf_cache_ext_validate(void *kdata) +{ + return 0; +} + +#define MAX_CACHE_EXT_BATCH (256) + +/* + * Inspired by isolate lru folios / move folio + */ +static bool isolate_folio_list(struct list_head *src, struct list_head *dst) +{ + int remaining = MAX_CACHE_EXT_BATCH; + bool result = true; + + LIST_HEAD(return_folios); + + while (!list_empty(src)) { + struct list_head *move_to = &return_folios; + struct folio *folio; + + folio = lru_to_folio(src); + + if (!folio_test_lru(folio)) + goto move; + + if (unlikely(!folio_try_get(folio))) + goto move; + + if (!folio_test_clear_lru(folio)) { + /* + * Another thread is already isolating this folio + */ + folio_put(folio); + goto move; + } + move_to = dst; +move: + list_move(&folio->lru, move_to); + remaining--; + + if (!remaining) { + result = false; + break; + } + } + list_splice_tail(&return_folios, src); + + return result; +} + +static bool return_folios_to_lru(struct list_head *dst, struct list_head *src, + struct list_head *putback_list, struct folio_batch *free_folios) +{ + int remaining = MAX_CACHE_EXT_BATCH; + bool result = true; + + while (!list_empty(src)) { + struct folio *folio = lru_to_folio(src); + + VM_BUG_ON_FOLIO(folio_test_lru(folio), folio); + + list_del(&folio->lru); + if (unlikely(!folio_evictable(folio))) { + list_add(&folio->lru, putback_list); + continue; + } + + folio_set_lru(folio); + + if (unlikely(folio_put_testzero(folio))) { + __folio_clear_lru_flags(folio); + + folio_unqueue_deferred_split(folio); + if (folio_batch_add(free_folios, folio) == 0) { + result = false; + break; + } + + continue; + } + + + list_add(&folio->lru, dst); + remaining--; + + if (!remaining) { + result = false; + break; + } + } + + return result; +} + +static bool add_folio_list_to_policy(struct list_head *src, struct list_head *return_list, + struct lruvec *lruvec, bool activate) +{ + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + struct cache_ext_ops *ops = memcg->cache_ext_ptr ? + memcg->cache_ext_ptr->cache_ext_ops : NULL; + int remaining = MAX_CACHE_EXT_BATCH; + bool result = true; + + if (!ops) + return result; + + while (!list_empty(src)) { + struct folio *folio = lru_to_folio(src); + int nr = folio_nr_pages(folio); + + /* + * Since we relase policy and lru locks during isolation, + * some of the isolated folios might be already added to policy. + * Check flag and skip if they are already in policy + */ + if (folio_test_cache_ext(folio)) + goto skip; + + if (cache_ext_folio_meta_init(folio, true)) + goto skip; + + if (ops->folio_added) + ops->folio_added(folio, 0); + + mod_lruvec_state(lruvec, NR_FILE_CACHE_EXT, nr); + + if (activate && ops->folio_accessed) + ops->folio_accessed(folio); + +skip: + list_move(&folio->lru, return_list); + + remaining--; + + if (!remaining) { + result = false; + break; + } + } + + return result; +} + +static void load_lru_list_to_policy(struct lruvec *lruvec, struct list_head *lru_list, + rwlock_t *policy_lock, unsigned long *policy_lock_flags, + bool activate) +{ + unsigned long flags; + struct folio_batch free_folios; + LIST_HEAD(isolated); + LIST_HEAD(return_list); + LIST_HEAD(putback_list); + + folio_batch_init(&free_folios); + + spin_lock_irqsave(&lruvec->lru_lock, flags); + + while (!isolate_folio_list(lru_list, &isolated)) { + spin_unlock_irqrestore(&lruvec->lru_lock, flags); + write_unlock_irqrestore(policy_lock, *policy_lock_flags); + + cond_resched(); + + write_lock_irqsave(policy_lock, *policy_lock_flags); + spin_lock_irqsave(&lruvec->lru_lock, flags); + } + + spin_unlock_irqrestore(&lruvec->lru_lock, flags); + + while (!add_folio_list_to_policy(&isolated, &return_list, lruvec, activate)) { + write_unlock_irqrestore(policy_lock, *policy_lock_flags); + + cond_resched(); + + write_lock_irqsave(policy_lock, *policy_lock_flags); + } + + spin_lock_irqsave(&lruvec->lru_lock, flags); + + while (!return_folios_to_lru(lru_list, &return_list, + &putback_list, &free_folios)) { + spin_unlock_irqrestore(&lruvec->lru_lock, flags); + write_unlock_irqrestore(policy_lock, *policy_lock_flags); + + while (!list_empty(&putback_list)) { + struct folio *folio = lru_to_folio(&putback_list); + + list_del(&folio->lru); + folio_putback_lru(folio); + } + + mem_cgroup_uncharge_folios(&free_folios); + free_unref_folios(&free_folios); + + cond_resched(); + + write_lock_irqsave(policy_lock, *policy_lock_flags); + spin_lock_irqsave(&lruvec->lru_lock, flags); + } + + spin_unlock_irqrestore(&lruvec->lru_lock, flags); + + WARN_ON(!list_empty(&isolated)); + WARN_ON(!list_empty(&return_list)); + WARN_ON(!list_empty(&putback_list)); +} + +#ifdef CONFIG_LRU_GEN +static void load_mglru_lists_to_policy(struct lruvec *lruvec, rwlock_t *policy_lock, + unsigned long *policy_lock_flags) +{ + int gen, zone, type = LRU_GEN_FILE; + + for (gen = 0; gen < MAX_NR_GENS; gen++) { + bool active = lru_gen_is_active(lruvec, gen); + + for (zone = 0; zone < MAX_NR_ZONES; zone++) { + struct list_head *head = &lruvec->lrugen.folios[gen][type][zone]; + + load_lru_list_to_policy(lruvec, head, + policy_lock, policy_lock_flags, active); + } + } +} +#else +static void load_mglru_lists_to_policy(struct lruvec *lruvec, rwlock_t *policy_lock, + unsigned long *policy_lock_flags) +{ } +#endif + +static void load_lru_lists_to_policy(struct mem_cgroup *memcg, rwlock_t *policy_lock, + unsigned long *policy_lock_flags) +{ + int nid; + + if (!memcg->cache_ext_ptr) + return; + + for_each_node_state(nid, N_MEMORY) { + struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec; + + /* + * cgroup_lock protects from concurrent enabling/disabling mglru + */ + if (lru_gen_enabled()) { + load_mglru_lists_to_policy(lruvec, policy_lock, policy_lock_flags); + } else { + load_lru_list_to_policy(lruvec, &lruvec->lists[LRU_INACTIVE_FILE], + policy_lock, policy_lock_flags, false); + load_lru_list_to_policy(lruvec, &lruvec->lists[LRU_ACTIVE_FILE], + policy_lock, policy_lock_flags, true); + } + + } +} + +static bool unload_folios_from_policy(struct mem_cgroup *memcg) +{ + int nid; + struct cache_ext_ops *ops; + int remaining = MAX_CACHE_EXT_BATCH; + + if (!memcg->cache_ext_ptr) + return true; + + ops = memcg->cache_ext_ptr->cache_ext_ops; + + if (!ops) + return true; + + for_each_node_state(nid, N_MEMORY) { + struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec; + struct cache_ext_ds_registry *registry = + memcg->nodeinfo[nid]->cache_ext_ds_registry; + int i; + + for (i = 0; i < CACHE_EXT_REGISTRY_MAX_ENTRIES; i++) { + struct cache_ext_list *head = ®istry->items[i]; + + while (!list_empty(&head->head)) { + struct list_head *prev = head->head.prev; + struct cache_ext_list_node *node = + container_of(prev, struct cache_ext_list_node, node); + struct folio *folio = node->folio; + int nr = folio_nr_pages(folio); + + if (ops->folio_evicted) + ops->folio_evicted(folio); + + cache_ext_folio_meta_fini(folio); + + mod_lruvec_state(lruvec, NR_FILE_CACHE_EXT, -nr); + + remaining--; + + if (!remaining) + return false; + } + + } + registry->nr_entries = 0; + } + + return true; +} + +static void unload_folios_from_prev_policy(struct mem_cgroup *memcg) +{ + struct mem_cgroup *iter = NULL; + unsigned long flags; + struct cache_ext_policy *policy = memcg->cache_ext_ptr; + + cgroup_lock(); + + while ((iter = mem_cgroup_iter(memcg, iter, NULL))) { + write_lock_irqsave(&iter->cache_ext_policy->lock, flags); + if (iter->cache_ext_ptr != policy) { + write_unlock_irqrestore(&iter->cache_ext_policy->lock, flags); + /* + * This memcg is loaded into something else. Skip + */ + continue; + } + + while (!unload_folios_from_policy(iter)) { + write_unlock_irqrestore(&iter->cache_ext_policy->lock, flags); + cond_resched(); + write_lock_irqsave(&iter->cache_ext_policy->lock, flags); + } + + iter->cache_ext_ptr = NULL; + + write_unlock_irqrestore(&iter->cache_ext_policy->lock, flags); + cond_resched(); + } + + cgroup_unlock(); +} + +static void load_folios_to_new_policy(struct mem_cgroup *memcg, struct cache_ext_policy *new_policy) +{ + struct mem_cgroup *iter = NULL; + unsigned long flags; + + cgroup_lock(); + + while ((iter = mem_cgroup_iter(memcg, iter, NULL))) { + cond_resched(); + write_lock_irqsave(&iter->cache_ext_policy->lock, flags); + if (iter->cache_ext_ptr) { + write_unlock_irqrestore(&iter->cache_ext_policy->lock, flags); + /* + * Somethig is already loaded into this memcg. Skip + */ + continue; + } + iter->cache_ext_ptr = new_policy; + + load_lru_lists_to_policy(iter, &iter->cache_ext_policy->lock, &flags); + + write_unlock_irqrestore(&iter->cache_ext_policy->lock, flags); + } + + cgroup_unlock(); +} + + +DEFINE_MUTEX(cache_ext_reg_mutex); + +static int bpf_cache_ext_reg(void *kdata) +{ + struct cache_ext_ops *ops = kdata; + struct mem_cgroup *memcg = get_mem_cgroup_from_current(); + int ret = 0; + + if (memcg == NULL) { + pr_crit("cache_ext: failed to get memcg for registration!\n"); + return -EINVAL; + } + + ops->memcg = memcg; + + mutex_lock(&cache_ext_reg_mutex); + + if (memcg->cache_ext_policy->cache_ext_ops) { + pr_info("cache_ext: something already loaded\n"); + mutex_unlock(&cache_ext_reg_mutex); + mem_cgroup_put(memcg); + return -EINVAL; + } + + lru_cache_disable(); + + unload_folios_from_prev_policy(memcg); + + pr_info("cache_ext: Calling init\n"); + if (ops->init) { + ret = ops->init(memcg); + if (ret) { + struct mem_cgroup *parent_memcg = parent_mem_cgroup(memcg); + struct cache_ext_policy *parent_policy = NULL; + + pr_err("cache_ext: init failed with error code: %d\n", ret); + + if (parent_memcg) + parent_policy = parent_memcg->cache_ext_ptr; + /* + * Return folios to the previous policy + */ + load_folios_to_new_policy(memcg, parent_policy); + + lru_cache_enable(); + mutex_unlock(&cache_ext_reg_mutex); + mem_cgroup_put(memcg); + return ret; + } + } + + memcg->cache_ext_policy->cache_ext_ops = ops; + + load_folios_to_new_policy(memcg, memcg->cache_ext_policy); + + lru_cache_enable(); + mutex_unlock(&cache_ext_reg_mutex); + pr_info("cache_ext: Policy %016lx loaded into memcg %016lx\n", + (unsigned long)ops, (unsigned long)memcg); + return 0; +} + +static void bpf_cache_ext_unreg(void *kdata) +{ + struct cache_ext_ops *prev_ops = kdata; + struct mem_cgroup *memcg = prev_ops->memcg; + struct mem_cgroup *parent_memcg = parent_mem_cgroup(memcg); + struct cache_ext_policy *parent_policy = NULL; + + pr_info("cache_ext: Unregistering struct ops\n"); + + if (memcg == NULL) { + pr_crit("cache_ext: failed to get memcg for release!\n"); + return; + } + + mutex_lock(&cache_ext_reg_mutex); + lru_cache_disable(); + + if (parent_memcg) + parent_policy = parent_memcg->cache_ext_ptr; + + unload_folios_from_prev_policy(memcg); + + memcg->cache_ext_policy->cache_ext_ops = NULL; + + load_folios_to_new_policy(memcg, parent_policy); + + lru_cache_enable(); + mutex_unlock(&cache_ext_reg_mutex); + + pr_info("cache_ext: Policy %016lx unloaded from memcg %016lx\n", + (unsigned long)prev_ops, (unsigned long)memcg); + + + mem_cgroup_put(memcg); +} + +static const struct bpf_verifier_ops bpf_cache_ext_verifier_ops = { + .get_func_proto = bpf_cache_ext_get_func_proto, + .is_valid_access = bpf_cache_ext_is_valid_access, + .btf_struct_access = bpf_cache_ext_btf_struct_access, +}; + +/* + * cache_ext stubs + */ +static s32 cache_ext_init_stub(struct mem_cgroup *memcg) { return -EOPNOTSUPP; } +static int isolate_folios_stub(struct cache_ext_isolation_ctx *ctx, + struct mem_cgroup *memcg) +{ + return -EOPNOTSUPP; +} +static void folio_added_stub(struct folio *folio, s64 cookie) {} +static void folio_accessed_stub(struct folio *folio) {} +static s64 folio_evicted_stub(struct folio *folio) { return -EOPNOTSUPP; } +static void get_statistics_stub(struct lruvec *lruvec, struct cache_ext_stats *stats) {} + +static struct cache_ext_ops __bpf_cache_ext_ops = { + .init = cache_ext_init_stub, + .isolate_folios = isolate_folios_stub, + .folio_added = folio_added_stub, + .folio_accessed = folio_accessed_stub, + .folio_evicted = folio_evicted_stub, + .get_statistics = get_statistics_stub, +}; + +/* + * Callbacks to define the struct_ops map + */ +struct bpf_struct_ops bpf_cache_ext_ops = { + .verifier_ops = &bpf_cache_ext_verifier_ops, + .init = bpf_cache_ext_init, + .reg = bpf_cache_ext_reg, + .unreg = bpf_cache_ext_unreg, + .init_member = bpf_cache_ext_init_member, + .check_member = bpf_cache_ext_check_member, + .validate = bpf_cache_ext_validate, + .cfi_stubs = &__bpf_cache_ext_ops, + .name = "cache_ext_ops", + .owner = THIS_MODULE, +}; + +static int __init cache_ext_ops_init(void) +{ + int ret; + + if (static_branch_unlikely(&cache_ext_disabled)) + return 0; + + ret = register_bpf_struct_ops(&bpf_cache_ext_ops, cache_ext_ops); + + if (ret) { + pr_err("cache_ext: Failed to register struct_ops (%d)\n", ret); + return ret; + } + + return 0; +} + +late_initcall(cache_ext_ops_init); + + +void cache_ext_folio_added(struct folio *folio, bool increase_ref, s64 cookie) +{ + struct mem_cgroup *memcg = folio_memcg(folio); + struct cache_ext_ops *pcext_ops = NULL; + unsigned long flags; + struct lruvec *lruvec = folio_lruvec(folio); + int ret; + + if (static_branch_unlikely(&cache_ext_disabled)) + return; + + if (cookie < 0) + return; + + if (!memcg) + return; + + if (!memcg->cache_ext_ptr) + return; + + if (!folio_is_file_lru(folio)) + return; + + read_lock_irqsave(&memcg->cache_ext_policy->lock, flags); + + pcext_ops = get_cache_ext_ops_memcg(memcg); + + if (pcext_ops && folio_test_cache_ext(folio)) { + read_unlock_irqrestore(&memcg->cache_ext_policy->lock, flags); + return; + } + + if (pcext_ops) { + ret = cache_ext_folio_meta_init(folio, increase_ref); + if (ret) + goto out; + } + + if (pcext_ops && pcext_ops->folio_added) + pcext_ops->folio_added(folio, cookie); + + if (pcext_ops) + mod_lruvec_state(lruvec, NR_FILE_CACHE_EXT, folio_nr_pages(folio)); + +out: + + read_unlock_irqrestore(&memcg->cache_ext_policy->lock, flags); +} + +void cache_ext_folio_accessed(struct folio *folio) +{ + struct mem_cgroup *memcg = folio_memcg(folio); + struct cache_ext_ops *pcext_ops = NULL; + unsigned long flags; + + if (static_branch_unlikely(&cache_ext_disabled)) + return; + + if (!memcg) + return; + + if (!memcg->cache_ext_ptr) + return; + + if (!folio_test_cache_ext(folio)) + return; + + WARN_ON(!folio_is_file_lru(folio)); + + read_lock_irqsave(&memcg->cache_ext_policy->lock, flags); + + pcext_ops = get_cache_ext_ops_memcg(memcg); + if (pcext_ops && pcext_ops->folio_accessed) + pcext_ops->folio_accessed(folio); + + read_unlock_irqrestore(&memcg->cache_ext_policy->lock, flags); +} + +s64 cache_ext_folio_evicted(struct folio *folio) +{ + s64 ret = -EINVAL; + struct mem_cgroup *memcg = folio_memcg(folio); + struct cache_ext_ops *pcext_ops = NULL; + unsigned long flags; + + if (static_branch_unlikely(&cache_ext_disabled)) + return ret; + + if (!memcg) + return ret; + + if (!folio_test_cache_ext(folio)) + return ret; + + if (!memcg->cache_ext_ptr) + return ret; + + WARN_ON(!folio_is_file_lru(folio)); + + read_lock_irqsave(&memcg->cache_ext_policy->lock, flags); + pcext_ops = get_cache_ext_ops_memcg(memcg); + + if (pcext_ops && pcext_ops->folio_evicted) + ret = pcext_ops->folio_evicted(folio); + + if (pcext_ops) { + int cache_ext_ret = cache_ext_folio_meta_fini(folio); + /* + * Folio was part of the policy, adjust the cookie + */ + if (!cache_ext_ret) { + struct lruvec *lruvec = folio_lruvec(folio); + + ret = max(0, ret); + mod_lruvec_state(lruvec, NR_FILE_CACHE_EXT, -folio_nr_pages(folio)); + } else + ret = cache_ext_ret; + } + + read_unlock_irqrestore(&memcg->cache_ext_policy->lock, flags); + return ret; +} + +/* + * Isolate folios from registered cache_ext lists. + * + * The folios are guaranteed to be removed from any registered cache_ext list. + * If a user keeps extra folio information in eBPF-maps, for example, + * then it's his responsibility to remove the information from corresponding structures. + * + * Expected return value: "rc < 0" - on error, "rc = 0" - otherwise. + */ +int cache_ext_isolate_folios(struct cache_ext_isolation_ctx *ctx, struct mem_cgroup *memcg) +{ + int ret = -EINVAL; + struct cache_ext_ops *pcext_ops = NULL; + unsigned long flags; + + if (static_branch_unlikely(&cache_ext_disabled)) + return ret; + + if (!memcg) + return ret; + + if (!memcg->cache_ext_ptr) + return ret; + + read_lock_irqsave(&memcg->cache_ext_policy->lock, flags); + + pcext_ops = get_cache_ext_ops_memcg(memcg); + + if (pcext_ops && pcext_ops->isolate_folios) + ret = pcext_ops->isolate_folios(ctx, memcg); + + read_unlock_irqrestore(&memcg->cache_ext_policy->lock, flags); + return ret; +} + +static bool need_page_ext_cache_ext(void) +{ + if (static_branch_unlikely(&cache_ext_disabled)) + return false; + + return true; +} + +struct page_ext_operations page_ext_cache_ext_ops = { + .size = sizeof(struct cache_ext_list_node), + .need = need_page_ext_cache_ext, + .need_shared_flags = true, +}; + +struct cache_ext_list_node *get_cache_ext_list_node(struct folio *folio) +{ + struct page_ext *page_ext = page_ext_get(folio_page(folio, 0)); + + if (unlikely(!page_ext)) + return NULL; + + return page_ext_data(page_ext, &page_ext_cache_ext_ops); +} + +void put_cache_ext_list_node(struct cache_ext_list_node *node) +{ + struct page_ext *page_ext = + (struct page_ext *)((void *)node - page_ext_cache_ext_ops.offset); + + page_ext_put(page_ext); +} + +void set_cookie_to_folio_page_ext(struct folio *folio, s64 cookie) +{ + struct cache_ext_list_node *node; + + if (static_branch_unlikely(&cache_ext_disabled)) + return; + + node = get_cache_ext_list_node(folio); + + if (unlikely(!node)) + return; + + VM_BUG_ON(folio_test_cache_ext(folio)); + node->cookie = cookie; + put_cache_ext_list_node(node); +} + +s64 get_and_clear_cookie_from_folio_page_ext(struct folio *folio) +{ + struct cache_ext_list_node *node; + s64 cookie; + + if (static_branch_unlikely(&cache_ext_disabled)) + return 0; + + node = get_cache_ext_list_node(folio); + + if (unlikely(!node)) + return 0; + + cookie = node->cookie; + + + VM_BUG_ON(folio_test_cache_ext(folio)); + node->cookie = 0; + put_cache_ext_list_node(node); + return cookie; +} + +int cache_ext_policy_stat_show(struct seq_file *m, void *v) +{ + struct mem_cgroup *memcg = mem_cgroup_from_seq(m); + int nid; + + if (static_branch_unlikely(&cache_ext_disabled)) + return 0; + + if (!memcg) + return 0; + + if (!memcg->cache_ext_ptr) + return 0; + + for_each_node_state(nid, N_MEMORY) { + struct cache_ext_stats stats = {}; + struct lruvec *lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(nid)); + struct cache_ext_ops *pcext_ops = NULL; + unsigned long flags; + int i; + + read_lock_irqsave(&memcg->cache_ext_policy->lock, flags); + pcext_ops = get_cache_ext_ops_memcg(memcg); + if (!pcext_ops || !pcext_ops->get_statistics) { + read_unlock_irqrestore(&memcg->cache_ext_policy->lock, flags); + break; + } + + pcext_ops->get_statistics(lruvec, &stats); + read_unlock_irqrestore(&memcg->cache_ext_policy->lock, flags); + + seq_printf(m, "Node # %d\n", nid); + for (i = 0; i < stats.n && i < MAX_STATS_COUNT; i++) + seq_printf(m, "%.*s : %llu\n", MAX_STAT_LEN, + stats.names[i], stats.values[i]); + } + + return 0; +} + + diff --git a/mm/cache_ext_ds.c b/mm/cache_ext_ds.c new file mode 100644 index 000000000000..dca000894684 --- /dev/null +++ b/mm/cache_ext_ds.c @@ -0,0 +1,1050 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/memcontrol.h> +#include <linux/atomic.h> +#include <linux/bpf.h> +#include <linux/cache_ext.h> +#include <linux/btf.h> +#include <linux/sort.h> +#include <linux/vmalloc.h> +#include <linux/mm_inline.h> + +static int cache_ext_ds_registry_init(struct mem_cgroup *memcg) +{ + int nid; + int i; + + for_each_node(nid) { + struct cache_ext_ds_registry *registry = + kzalloc_node(sizeof(struct cache_ext_ds_registry), GFP_KERNEL, nid); + + if (!registry) + return -ENOMEM; + + if (memcg->nodeinfo[nid]->cache_ext_ds_registry) { + kfree(registry); + WARN_ON(true); + return -EINVAL; + } + + rwlock_init(®istry->lock); + + for (i = 0; i < CACHE_EXT_REGISTRY_MAX_ENTRIES; i++) + INIT_LIST_HEAD(®istry->items[i].head); + + memcg->nodeinfo[nid]->cache_ext_ds_registry = registry; + } + + return 0; +} + + +static void cache_ext_folio_meta_fini_unlocked(struct cache_ext_ds_registry *registry, + struct cache_ext_list_node *node); + +static void cache_ext_ds_registry_free(struct mem_cgroup *memcg) +{ + int nid; + + for_each_node(nid) { + struct cache_ext_ds_registry *registry = + memcg->nodeinfo[nid]->cache_ext_ds_registry; + + kfree(registry); + memcg->nodeinfo[nid]->cache_ext_ds_registry = NULL; + } +} + +int cache_ext_policy_init(struct mem_cgroup *memcg) +{ + struct cache_ext_policy *policy; + + if (static_branch_unlikely(&cache_ext_disabled)) + return 0; + + policy = kmalloc(sizeof(struct cache_ext_policy), GFP_KERNEL | __GFP_ZERO); + + if (!policy) + return -ENOMEM; + + policy->memcg = memcg; + rwlock_init(&policy->lock); + memcg->cache_ext_policy = policy; + + if (cache_ext_ds_registry_init(memcg)) + return -ENOMEM; + + return 0; +} + +void cache_ext_policy_free(struct mem_cgroup *memcg) +{ + if (static_branch_unlikely(&cache_ext_disabled)) + return; + + if (!memcg->cache_ext_policy) + return; + cache_ext_ds_registry_free(memcg); + + kfree(memcg->cache_ext_policy); +} + +static struct cache_ext_list * +cache_ext_ds_registry_get(struct cache_ext_ds_registry *registry, u64 list_id) +{ + struct cache_ext_list *list; + unsigned long flags; + + if (list_id <= 0 || list_id > CACHE_EXT_REGISTRY_MAX_ENTRIES) + return NULL; + + read_lock_irqsave(®istry->lock, flags); + list = ®istry->items[list_id]; + read_unlock_irqrestore(®istry->lock, flags); + return list; +} + +static struct cache_ext_list * +cache_ext_ds_registry_get_unlocked(struct cache_ext_ds_registry *registry, u64 list_id) +{ + struct cache_ext_list *list; + + if (list_id <= 0 || list_id > CACHE_EXT_REGISTRY_MAX_ENTRIES) + return NULL; + + list = ®istry->items[list_id]; + return list; +} + +static struct cache_ext_list * +cache_ext_ds_registry_get_special_list_unlocked(struct cache_ext_ds_registry *registry) +{ + struct cache_ext_list *list; + + list = ®istry->items[0]; + + return list; +} + +static inline struct cache_ext_ds_registry * +cache_ext_ds_registry_from_memcg(struct mem_cgroup *memcg, int nid) +{ + return memcg->nodeinfo[nid]->cache_ext_ds_registry; +} + +static struct cache_ext_ds_registry * +cache_ext_ds_registry_from_folio(struct folio *folio) +{ + /* + * These kfuncs should be called only on page cache + * folios, which are charged and should not return NULL + */ + VM_BUG_ON_FOLIO(!folio_memcg(folio), folio); + + return cache_ext_ds_registry_from_memcg(folio_memcg(folio), folio_nid(folio)); +} + +/* + * Register list for each NUMA node + * Return rergistered list id or 0 in case of error. + * List id ranging from 1 to CACHE_EXT_REGISTRY_MAX_ENTRIES. + * Since this functions is sleepable, can be called only on policy init + */ +static u64 cache_ext_ds_registry_new_list(struct mem_cgroup *memcg) +{ + int nid; + u64 lock_id = 0; + unsigned long flags; + int fail_node; + + for_each_node(nid) { + struct cache_ext_ds_registry *registry = + cache_ext_ds_registry_from_memcg(memcg, nid); + + write_lock_irqsave(®istry->lock, flags); + if (registry->nr_entries >= CACHE_EXT_REGISTRY_MAX_ENTRIES) { + fail_node = nid; + write_unlock_irqrestore(®istry->lock, flags); + goto cleanup; + } + + registry->nr_entries++; + + if (!lock_id) + lock_id = registry->nr_entries; + write_unlock_irqrestore(®istry->lock, flags); + } + + return lock_id; + +cleanup: + for_each_node(nid) { + struct cache_ext_ds_registry *registry = + cache_ext_ds_registry_from_memcg(memcg, nid); + + if (nid == fail_node) + break; + + write_lock_irqsave(®istry->lock, flags); + registry->nr_entries--; + write_unlock_irqrestore(®istry->lock, flags); + } + return 0; +} + +__bpf_kfunc u64 bpf_cache_ext_ds_registry_new_list(struct mem_cgroup *memcg) +{ + return (u64)cache_ext_ds_registry_new_list(memcg); +} + +static int cache_ext_list_add(struct cache_ext_ds_registry *registry, + struct cache_ext_list *list, struct folio *folio, bool tail) +{ + struct cache_ext_list_node *node = get_cache_ext_list_node(folio); + int ret = 0; + unsigned long flags; + + if (unlikely(!node)) + return -EINVAL; + + if (!folio_test_cache_ext(folio)) { + ret = -EINVAL; + goto ret; + } + + write_lock_irqsave(®istry->lock, flags); + + if (!folio_test_cache_ext(folio)) { + ret = -EINVAL; + goto unlock_ret; + } + + if (!folio_test_cache_ext_special_list(folio)) { + ret = -EINVAL; + goto unlock_ret; + } + + folio_clear_cache_ext_special_list(folio); + + if (tail) + list_move_tail(&node->node, &list->head); + else + list_move(&node->node, &list->head); + +unlock_ret: + write_unlock_irqrestore(®istry->lock, flags); +ret: + put_cache_ext_list_node(node); + return ret; +} + +static int cache_ext_list_splice(struct cache_ext_ds_registry *registry, + struct cache_ext_list *list, struct cache_ext_list *head, bool tail) +{ + unsigned long flags; + + write_lock_irqsave(®istry->lock, flags); + + if (tail) + list_splice_tail_init(&list->head, &head->head); + else + list_splice_init(&list->head, &head->head); + + write_unlock_irqrestore(®istry->lock, flags); + + return 0; +} + + +static int cache_ext_list_move(struct cache_ext_ds_registry *registry, + struct cache_ext_list *list, struct folio *folio, + bool tail) +{ + struct cache_ext_list_node *node = get_cache_ext_list_node(folio); + int ret = 0; + unsigned long flags; + + if (unlikely(!node)) + return -EINVAL; + + if (!folio_test_cache_ext(folio)) { + ret = -EINVAL; + goto ret; + } + + write_lock_irqsave(®istry->lock, flags); + if (!folio_test_cache_ext(folio)) { + ret = -EINVAL; + goto unlock_ret; + } + + if (folio_test_cache_ext_special_list(folio)) { + ret = -EEXIST; + goto unlock_ret; + } + + if (tail) + list_move_tail(&node->node, &list->head); + else + list_move(&node->node, &list->head); + + +unlock_ret: + write_unlock_irqrestore(®istry->lock, flags); +ret: + put_cache_ext_list_node(node); + return ret; +} + +static int cache_ext_list_del(struct cache_ext_ds_registry *registry, + struct folio *folio) +{ + struct cache_ext_list_node *node = get_cache_ext_list_node(folio); + struct cache_ext_list *special_list = + cache_ext_ds_registry_get_special_list_unlocked(registry); + + int ret = 0; + unsigned long flags; + + if (unlikely(!node)) + return -EINVAL; + + if (!folio_test_cache_ext(folio)) { + ret = -EINVAL; + goto ret; + } + + write_lock_irqsave(®istry->lock, flags); + if (!folio_test_cache_ext(folio)) { + ret = -EINVAL; + goto unlock_ret; + } + + if (folio_test_cache_ext_special_list(folio)) { + ret = -EINVAL; + goto unlock_ret; + } + + folio_set_cache_ext_special_list(folio); + list_move(&node->node, &special_list->head); + +unlock_ret: + write_unlock_irqrestore(®istry->lock, flags); +ret: + put_cache_ext_list_node(node); + return ret; +} + +enum cache_ext_iter_callback_ret { + CACHE_EXT_CONTINUE_ITER = 0, + CACHE_EXT_STOP_ITER = 1, + CACHE_EXT_ISOLATE_NODE = 2, +}; + +enum cache_ext_iter_ret { + CACHE_EXT_DONE_ITER = 0, + CACHE_EXT_MAX_ITER_REACHED = 8, + CACHE_EXT_EVICT_ARRAY_FILLED = 9, +}; + +static int cache_ext_list_iterate(struct cache_ext_ds_registry *registry, + u64 list_id, void *iter_fn, + struct cache_ext_isolation_ctx *ctx) +{ + uint64_t ret = CACHE_EXT_DONE_ITER, cb_ret, iter = 0; + struct cache_ext_list_node *node, *node2; + struct cache_ext_list *list; + bpf_callback_t bpf_iter_fn = (bpf_callback_t)iter_fn; + unsigned long flags; + + if (ctx->nr_folios_isolated >= ARRAY_SIZE(ctx->folios)) + return CACHE_EXT_EVICT_ARRAY_FILLED; + + write_lock_irqsave(®istry->lock, flags); + list = cache_ext_ds_registry_get_unlocked(registry, list_id); + + if (!list) + goto exit; + + list_for_each_entry_safe(node, node2, &list->head, node) { + struct folio *folio = node->folio; + + if (!folio_test_lru(folio)) + continue; + + /* + * TODO: Ensure that we don't let the callback use any of the list + * helpers, or we will have a deadlock. + */ + cb_ret = bpf_iter_fn((u64)iter, (u64)node, (u64)0, (u64)0, (u64)0); + iter++; + + if (cb_ret == CACHE_EXT_CONTINUE_ITER) { + continue; + } else if (cb_ret == CACHE_EXT_STOP_ITER) { + ret = CACHE_EXT_DONE_ITER; + break; + } else if (cb_ret == CACHE_EXT_ISOLATE_NODE) { + /* + * Isolate should clear cache_ext flag, but keep single + * folio reference. + * Just keep cache_ext folio ref, so don't need extra + * folio_get() for isolate reference. + */ + cache_ext_folio_meta_fini_unlocked(registry, node); + ctx->folios[ctx->nr_folios_isolated] = folio; + ctx->nr_folios_isolated++; + ctx->nr_base_pages_isolated += folio_nr_pages(folio); + + mod_lruvec_state(folio_lruvec(folio), + NR_FILE_CACHE_EXT, -folio_nr_pages(folio)); + + if (ctx->nr_folios_isolated == ARRAY_SIZE(ctx->folios)) { + ret = CACHE_EXT_EVICT_ARRAY_FILLED; + break; + } + } else { + break; + } + } +exit: + write_unlock_irqrestore(®istry->lock, flags); + return ret; +} + +enum cache_ext_iterate_mode { + CACHE_EXT_ITERATE_SKIP = 0, + CACHE_EXT_ITERATE_HEAD, + CACHE_EXT_ITERATE_TAIL, + CACHE_EXT_ITERATE_MAX, +}; + +enum cache_ext_iterate_list { + CACHE_EXT_ITERATE_SELF = 0, +}; + +static bool cache_ext_validate_iterate_opts(struct cache_ext_iterate_opts *opts) +{ + if (opts->continue_mode >= CACHE_EXT_ITERATE_MAX) + return false; + + if (opts->continue_list != CACHE_EXT_ITERATE_SELF && + opts->continue_mode == CACHE_EXT_ITERATE_SKIP) + return false; + return true; +} + +static int cache_ext_list_iterate_extended(struct cache_ext_ds_registry *registry, + u64 list_id, void *iter_fn, + struct cache_ext_iterate_opts *opts, + struct cache_ext_isolation_ctx *ctx) +{ + uint64_t ret = CACHE_EXT_DONE_ITER, cb_ret, iter = 0; + struct cache_ext_list_node *node, *node2; + bpf_callback_t bpf_iter_fn = (bpf_callback_t)iter_fn; + unsigned long flags; + struct cache_ext_list *list = NULL; + struct cache_ext_list *continue_list = NULL; + + if (!cache_ext_validate_iterate_opts(opts)) + return -EINVAL; + + if (ctx->nr_folios_isolated >= ARRAY_SIZE(ctx->folios)) + return CACHE_EXT_EVICT_ARRAY_FILLED; + + if (opts->continue_list != CACHE_EXT_ITERATE_SELF) { + continue_list = cache_ext_ds_registry_get(registry, opts->continue_list); + if (!continue_list) + return -EINVAL; + } + + write_lock_irqsave(®istry->lock, flags); + + list = cache_ext_ds_registry_get_unlocked(registry, list_id); + + if (!list) { + ret = -EINVAL; + goto out_unlock; + } + + if (!continue_list) + continue_list = list; + + list_for_each_entry_safe(node, node2, &list->head, node) { + struct folio *folio = node->folio; + + if (!folio_test_lru(folio)) + cb_ret = CACHE_EXT_CONTINUE_ITER; + else + cb_ret = bpf_iter_fn((u64)iter, (u64)node, (u64)0, (u64)0, (u64)0); + iter++; + + if (cb_ret == CACHE_EXT_CONTINUE_ITER) { + if (opts->continue_mode == CACHE_EXT_ITERATE_HEAD) + list_move(&node->node, &continue_list->head); + else if (opts->continue_mode == CACHE_EXT_ITERATE_TAIL) + list_move_tail(&node->node, &continue_list->head); + + opts->nr_folios_continue++; + + continue; + } else if (cb_ret == CACHE_EXT_STOP_ITER) { + ret = CACHE_EXT_DONE_ITER; + break; + } else if (cb_ret == CACHE_EXT_ISOLATE_NODE) { + /* + * Isolate should clear cache_ext flag, but keep single + * folio reference. + * Just keep cache_ext folio ref, so don't need extra + * folio_get() for isolate reference. + */ + cache_ext_folio_meta_fini_unlocked(registry, node); + ctx->folios[ctx->nr_folios_isolated] = folio; + ctx->nr_folios_isolated++; + ctx->nr_base_pages_isolated += folio_nr_pages(folio); + opts->nr_folios_isolated++; + + mod_lruvec_state(folio_lruvec(folio), + NR_FILE_CACHE_EXT, -folio_nr_pages(folio)); + + if (ctx->nr_folios_isolated == ARRAY_SIZE(ctx->folios)) { + ret = CACHE_EXT_EVICT_ARRAY_FILLED; + break; + } + } else + break; + } +out_unlock: + write_unlock_irqrestore(®istry->lock, flags); + return ret; +} + +int cache_ext_folio_meta_init(struct folio *folio, bool increase_ref) +{ + struct cache_ext_list_node *node = get_cache_ext_list_node(folio); + struct cache_ext_ds_registry *registry = cache_ext_ds_registry_from_folio(folio); + struct cache_ext_list *special_list; + + unsigned long flags; + + if (unlikely(!node)) + return -EINVAL; + + if (increase_ref) + folio_get(folio); + + INIT_LIST_HEAD(&node->node); + node->folio = folio; + + /* Paired with smp_rmb() after folio_test_cache_ext() + * Required to correctly order node initilaiztion with flag setting. + */ + + write_lock_irqsave(®istry->lock, flags); + + special_list = cache_ext_ds_registry_get_special_list_unlocked(registry); + + folio_set_cache_ext(folio); + folio_set_cache_ext_special_list(folio); + + list_add(&node->node, &special_list->head); + + write_unlock_irqrestore(®istry->lock, flags); + + + put_cache_ext_list_node(node); + + return 0; +} + +static void cache_ext_folio_meta_fini_unlocked(struct cache_ext_ds_registry *registry, + struct cache_ext_list_node *node) +{ + folio_clear_cache_ext(node->folio); + folio_clear_cache_ext_special_list(node->folio); + + list_del(&node->node); + node->folio = NULL; +} + +/* Returns 0 if folio was removed from cache_ext */ +int cache_ext_folio_meta_fini(struct folio *folio) +{ + struct cache_ext_ds_registry *registry = cache_ext_ds_registry_from_folio(folio); + struct cache_ext_list_node *node = get_cache_ext_list_node(folio); + unsigned long flags; + int ret = -EINVAL; + + if (unlikely(!node)) + return -EINVAL; + + write_lock_irqsave(®istry->lock, flags); + if (node->folio) { + cache_ext_folio_meta_fini_unlocked(registry, node); + folio_put(folio); + ret = 0; + } + write_unlock_irqrestore(®istry->lock, flags); + + put_cache_ext_list_node(node); + return ret; +} + +__bpf_kfunc int +bpf_cache_ext_list_splice(u64 list_id, u64 head_id, struct mem_cgroup *memcg, int nid) +{ + struct cache_ext_ds_registry *registry; + struct cache_ext_list *list; + struct cache_ext_list *head; + + if (nid < 0 || nid >= MAX_NUMNODES) + return -EINVAL; + + if (!node_online(nid) || !node_state(nid, N_MEMORY)) + return -EINVAL; + + if (list_id == head_id) + return -EINVAL; + + registry = cache_ext_ds_registry_from_memcg(memcg, nid); + list = cache_ext_ds_registry_get(registry, list_id); + head = cache_ext_ds_registry_get(registry, head_id); + + if (!list || !head) + return -EINVAL; + + return cache_ext_list_splice(registry, list, head, false); +}; + + +__bpf_kfunc int +bpf_cache_ext_list_splice_tail(u64 list_id, u64 head_id, struct mem_cgroup *memcg, int nid) +{ + struct cache_ext_ds_registry *registry; + struct cache_ext_list *list; + struct cache_ext_list *head; + + + if (nid < 0 || nid >= MAX_NUMNODES) + return -EINVAL; + + if (!node_online(nid) || !node_state(nid, N_MEMORY)) + return -EINVAL; + + if (list_id == head_id) + return -EINVAL; + + registry = cache_ext_ds_registry_from_memcg(memcg, nid); + list = cache_ext_ds_registry_get(registry, list_id); + head = cache_ext_ds_registry_get(registry, head_id); + + if (!list || !head) + return -EINVAL; + + return cache_ext_list_splice(registry, list, head, true); +}; + +__bpf_kfunc int bpf_cache_ext_list_add(u64 list_id, struct folio *folio) +{ + struct cache_ext_ds_registry *registry = cache_ext_ds_registry_from_folio(folio); + struct cache_ext_list *list = cache_ext_ds_registry_get(registry, list_id); + + if (!list) + return -EINVAL; + + return cache_ext_list_add(registry, list, folio, false); +}; + +__bpf_kfunc int bpf_cache_ext_list_add_tail(u64 list_id, struct folio *folio) +{ + struct cache_ext_ds_registry *registry = cache_ext_ds_registry_from_folio(folio); + struct cache_ext_list *list = cache_ext_ds_registry_get(registry, list_id); + + if (!list) + return -EINVAL; + + return cache_ext_list_add(registry, list, folio, true); +}; + +__bpf_kfunc int bpf_cache_ext_list_move(u64 list_id, struct folio *folio, bool tail) +{ + struct cache_ext_ds_registry *registry = cache_ext_ds_registry_from_folio(folio); + struct cache_ext_list *list = cache_ext_ds_registry_get(registry, list_id); + + if (!list) + return -EINVAL; + + return cache_ext_list_move(registry, list, folio, tail); +}; + +__bpf_kfunc int bpf_cache_ext_list_del(struct folio *folio) +{ + struct cache_ext_ds_registry *registry = cache_ext_ds_registry_from_folio(folio); + + return cache_ext_list_del(registry, folio); +}; + +__bpf_kfunc int bpf_cache_ext_list_iterate( + struct mem_cgroup *memcg, u64 list_id, + int (*iter_fn)(int idx, struct cache_ext_list_node *node), + struct cache_ext_isolation_ctx *ctx) +{ + struct cache_ext_ds_registry *registry = + cache_ext_ds_registry_from_memcg(memcg, ctx->numa_node_id); + + if (ctx->nr_folios_to_isolate > ARRAY_SIZE(ctx->folios)) + return -EINVAL; + + if (!registry) + return -EINVAL; + + return cache_ext_list_iterate(registry, list_id, (void *)iter_fn, ctx); +}; + +__bpf_kfunc int bpf_cache_ext_list_iterate_extended( + struct mem_cgroup *memcg, u64 list_id, + int (*iter_fn)(int idx, struct cache_ext_list_node *node), + struct cache_ext_iterate_opts *opts, + struct cache_ext_isolation_ctx *ctx) +{ + struct cache_ext_ds_registry *registry; + + if (!opts) + return -EINVAL; + + if (ctx->nr_folios_to_isolate > ARRAY_SIZE(ctx->folios)) + return -EINVAL; + + registry = cache_ext_ds_registry_from_memcg(memcg, ctx->numa_node_id); + if (!registry) + return -EINVAL; + + return cache_ext_list_iterate_extended(registry, list_id, (void *)iter_fn, opts, ctx); +}; + +#define MAX_SAMPLE_FOLIOS 2048 +DEFINE_PER_CPU(struct cache_ext_list_node *, sample_folios[MAX_SAMPLE_FOLIOS]); + +static void __putback_list_nodes(struct cache_ext_list *list, + struct cache_ext_list_node **sample_folios_arr, int size) +{ + for (int i = 0; i < size; i++) { + if (sample_folios_arr[i]->folio != NULL) + list_add_tail(&sample_folios_arr[i]->node, &list->head); + } +} + +static int __bpf_cache_ext_list_sample(struct mem_cgroup *memcg, u64 list_id, + s32 (*score_fn)(struct cache_ext_list_node *a), + struct sampling_options *opts, + struct cache_ext_isolation_ctx *ctx) +{ + unsigned int sample_size = opts->sample_size; + int i = 0; + unsigned long num_folios_to_sample = ctx->nr_folios_to_isolate * sample_size; + unsigned long flags; + struct cache_ext_list_node **sample_folios_arr; + struct cache_ext_ds_registry *registry; + struct cache_ext_list *list_ptr; + int sample_folios_size = 0; + int sample_folios_idx = 0; + + if (num_folios_to_sample > MAX_SAMPLE_FOLIOS) + return -EINVAL; + + sample_folios_arr = this_cpu_ptr(sample_folios); + + registry = cache_ext_ds_registry_from_memcg(memcg, ctx->numa_node_id); + write_lock_irqsave(®istry->lock, flags); + list_ptr = cache_ext_ds_registry_get_unlocked(registry, list_id); + + if (!list_ptr) { + write_unlock_irqrestore(®istry->lock, flags); + return -EINVAL; + } + + while (i < num_folios_to_sample) { + struct cache_ext_list_node *node; + + if (list_empty(&list_ptr->head)) { + if (i == 0) { + __putback_list_nodes(list_ptr, sample_folios_arr, + sample_folios_size); + + write_unlock_irqrestore(®istry->lock, flags); + return -EINVAL; + } + + if (sample_folios_size < ctx->nr_folios_to_isolate) { + ctx->nr_folios_to_isolate = i; + sample_size = 1; + break; + } + + sample_size = sample_folios_size / ctx->nr_folios_to_isolate; + break; + } + node = list_first_entry( + &list_ptr->head, struct cache_ext_list_node, node); + + if (!folio_test_cache_ext(node->folio)) { + list_del_init(&node->node); + continue; + } + + sample_folios_arr[i] = node; + sample_folios_size++; + i++; + list_del_init(&node->node); + } + + ctx->nr_folios_isolated = 0; + for (int i = 0; i < ctx->nr_folios_to_isolate; i++) { + struct cache_ext_list_node *min_node = sample_folios_arr[sample_folios_idx]; + struct folio *folio = NULL; + s64 min_score = score_fn(min_node); + + sample_folios_idx++; + + for (int j = 1; j < sample_size; j++) { + struct cache_ext_list_node *curr_node = + sample_folios_arr[sample_folios_idx]; + s64 curr_score = score_fn(curr_node); + + sample_folios_idx++; + if (curr_score < min_score) { + min_score = curr_score; + min_node = curr_node; + } + } + + folio = min_node->folio; + + cache_ext_folio_meta_fini_unlocked(registry, min_node); + ctx->folios[ctx->nr_folios_isolated] = folio; + ctx->scores[ctx->nr_folios_isolated] = min_score; + ctx->nr_folios_isolated++; + ctx->nr_base_pages_isolated += folio_nr_pages(folio); + + mod_lruvec_state(folio_lruvec(folio), + NR_FILE_CACHE_EXT, -folio_nr_pages(folio)); + } + + __putback_list_nodes(list_ptr, sample_folios_arr, sample_folios_size); + write_unlock_irqrestore(®istry->lock, flags); + + return 0; +} + +__bpf_kfunc int +bpf_cache_ext_list_sample(struct mem_cgroup *memcg, u64 list, + s32(score_fn)(struct cache_ext_list_node *a), + struct sampling_options *opts, + struct cache_ext_isolation_ctx *ctx) +{ + if (opts->sample_size == 0) + return -EINVAL; + + if (ctx->nr_folios_to_isolate > ARRAY_SIZE(ctx->folios)) + return -EINVAL; + + scoped_guard(preempt) { + return __bpf_cache_ext_list_sample(memcg, list, score_fn, opts, ctx); + } +} + +#define MAX_STRSTR_LEN (128) + +__bpf_kfunc bool +bpf_cache_ext_filename_strstr(const char *str, u32 str__sz, struct folio *folio) +{ + struct address_space *mapping; + struct inode *inode; + struct dentry *dentry; + int ret = 0; + + if (!str) + return ret; + + if (str__sz == 0) + return true; + + if (str__sz > MAX_STRSTR_LEN || str[str__sz - 1] != '\0') + return ret; + + if (!folio) + return ret; + + if (!folio_is_file_lru(folio)) + return ret; + + rcu_read_lock(); + + mapping = folio_mapping(folio); + + if (!mapping) + goto out; + + inode = mapping->host; + + if (!inode) + goto out; + + dentry = d_find_alias_rcu(inode); + if (dentry) { + const char *name; + + spin_lock(&dentry->d_lock); + name = dentry->d_name.name; + + if (name && (strnstr(name, str, MAX_STRSTR_LEN))) + ret = 1; + + spin_unlock(&dentry->d_lock); + } +out: + rcu_read_unlock(); + return ret; +} + + +__bpf_kfunc bool bpf_cache_ext_list_empty(u64 list_id, struct mem_cgroup *memcg, int nid) +{ + bool empty = true; + unsigned long flags; + struct cache_ext_ds_registry *registry; + struct cache_ext_list *list; + + if (!node_online(nid) || !node_state(nid, N_MEMORY)) + return empty; + + registry = cache_ext_ds_registry_from_memcg(memcg, nid); + list = cache_ext_ds_registry_get(registry, list_id); + + if (!list) + return empty; + + read_lock_irqsave(®istry->lock, flags); + empty = list_empty(&list->head); + read_unlock_irqrestore(®istry->lock, flags); + + return empty; +} + +__bpf_kfunc int bpf_folio_nid(struct folio *folio) +{ + return folio_nid(folio); +} + +enum cache_ext_list_ops_type { + KF_bpf_cache_ext_list_add = 0, + KF_bpf_cache_ext_list_add_tail, + KF_bpf_cache_ext_list_del, + KF_bpf_cache_ext_list_iterate, + KF_bpf_cache_ext_list_sample, + KF_bpf_cache_ext_list_move, + KF_bpf_cache_ext_list_iterate_extended, + KF_bpf_cache_ext_filename_strstr, + KF_bpf_cache_ext_list_empty, + KF_bpf_folio_nid, + KF_bpf_cache_ext_list_splice, + KF_bpf_cache_ext_list_splice_tail, + __CACHE_EXT_KF_NR +}; + +BTF_KFUNCS_START(cache_ext_list_ops) +BTF_ID_FLAGS(func, bpf_cache_ext_list_add) +BTF_ID_FLAGS(func, bpf_cache_ext_list_add_tail) +BTF_ID_FLAGS(func, bpf_cache_ext_list_del) +BTF_ID_FLAGS(func, bpf_cache_ext_list_iterate) +BTF_ID_FLAGS(func, bpf_cache_ext_list_sample) +BTF_ID_FLAGS(func, bpf_cache_ext_list_move) +BTF_ID_FLAGS(func, bpf_cache_ext_list_iterate_extended) +BTF_ID_FLAGS(func, bpf_cache_ext_filename_strstr) +BTF_ID_FLAGS(func, bpf_cache_ext_list_empty) +BTF_ID_FLAGS(func, bpf_folio_nid) +BTF_ID_FLAGS(func, bpf_cache_ext_list_splice) +BTF_ID_FLAGS(func, bpf_cache_ext_list_splice_tail) +BTF_KFUNCS_END(cache_ext_list_ops) + +BTF_ID_LIST(cache_ext_list_ops_list) +BTF_ID(func, bpf_cache_ext_list_add) +BTF_ID(func, bpf_cache_ext_list_add_tail) +BTF_ID(func, bpf_cache_ext_list_del) +BTF_ID(func, bpf_cache_ext_list_iterate) +BTF_ID(func, bpf_cache_ext_list_sample) +BTF_ID(func, bpf_cache_ext_list_move) +BTF_ID(func, bpf_cache_ext_list_iterate_extended) +BTF_ID(func, bpf_cache_ext_filename_strstr) +BTF_ID(func, bpf_cache_ext_list_empty) +BTF_ID(func, bpf_folio_nid) +BTF_ID(func, bpf_cache_ext_list_splice) +BTF_ID(func, bpf_cache_ext_list_splice_tail) + +bool cache_ext_kfunc(u32 btf_id) +{ + int i; + + if (static_branch_unlikely(&cache_ext_disabled)) + return false; + + for (i = 0; i < __CACHE_EXT_KF_NR; i++) + if (btf_id == cache_ext_list_ops_list[i]) + return true; + + return false; +} + +bool cache_ext_is_callback_calling_kfunc_iterate(u32 btf_id) +{ + if (static_branch_unlikely(&cache_ext_disabled)) + return false; + + return (btf_id == cache_ext_list_ops_list[KF_bpf_cache_ext_list_iterate] || + btf_id == cache_ext_list_ops_list[KF_bpf_cache_ext_list_iterate_extended]); +} + +bool cache_ext_is_callback_calling_kfunc_sample(u32 btf_id) +{ + if (static_branch_unlikely(&cache_ext_disabled)) + return false; + + return (btf_id == cache_ext_list_ops_list[KF_bpf_cache_ext_list_sample]); +} + +static const struct btf_kfunc_id_set cache_ext_kfunc_set_list_ops = { + .owner = THIS_MODULE, + .set = &cache_ext_list_ops, +}; + +BTF_KFUNCS_START(cache_ext_registry_ops) +BTF_ID_FLAGS(func, bpf_cache_ext_ds_registry_new_list, KF_SLEEPABLE) +BTF_KFUNCS_END(cache_ext_registry_ops) + +static const struct btf_kfunc_id_set cache_ext_kfunc_set_registry_ops = { + .owner = THIS_MODULE, + .set = &cache_ext_registry_ops, +}; + +static int __init register_cache_ext_kfuncs(void) +{ + int ret = 0; + + if (static_branch_unlikely(&cache_ext_disabled)) + return 0; + + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, + &cache_ext_kfunc_set_list_ops) || + register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, + &cache_ext_kfunc_set_registry_ops); + + if (ret) { + pr_err("cache_ext: failed to register kfunc sets (%d)\n", ret); + return ret; + } + + return 0; +} +late_initcall(register_cache_ext_kfuncs); diff --git a/mm/filemap.c b/mm/filemap.c index a94d7dd3f0ff..58dd2fde49a6 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -255,6 +255,9 @@ void filemap_remove_folio(struct folio *folio) struct address_space *mapping = folio->mapping; BUG_ON(!folio_test_locked(folio)); + + cache_ext_folio_evicted(folio); + spin_lock(&mapping->host->i_lock); xa_lock_irq(&mapping->i_pages); __filemap_remove_folio(folio, NULL); @@ -328,6 +331,12 @@ void delete_from_page_cache_batch(struct address_space *mapping, if (!folio_batch_count(fbatch)) return; + for (i = 0; i < folio_batch_count(fbatch); i++) { + struct folio *folio = fbatch->folios[i]; + + cache_ext_folio_evicted(folio); + } + spin_lock(&mapping->host->i_lock); xa_lock_irq(&mapping->i_pages); for (i = 0; i < folio_batch_count(fbatch); i++) { @@ -975,6 +984,7 @@ int filemap_add_folio(struct address_space *mapping, struct folio *folio, if (!(gfp & __GFP_WRITE) && shadow) workingset_refault(folio, shadow); folio_add_lru(folio); + cache_ext_folio_added(folio, true, 0); } return ret; } diff --git a/mm/memcontrol.c b/mm/memcontrol.c index a67d363accad..f6be624801e1 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -68,6 +68,7 @@ #include <linux/sched/isolation.h> #include <linux/parser.h> #include <linux/dynamic_pool.h> +#include <linux/cache_ext.h> #ifdef CONFIG_MEMCG_SWAP_QOS #include <linux/blkdev.h> @@ -1625,6 +1626,11 @@ static int memcg_page_state_unit(int item) case WORKINGSET_RESTORE_ANON: case WORKINGSET_RESTORE_FILE: case WORKINGSET_NODERECLAIM: +#ifdef CONFIG_CACHE_EXT + case NR_FILE_CACHE_EXT: + case NR_FILE_CACHE_EXT_KSWAPD_RECLAIMED: + case NR_FILE_CACHE_EXT_REFAULT: +#endif return 1; case NR_KERNEL_STACK_KB: return SZ_1K; @@ -4972,6 +4978,11 @@ static const unsigned int memcg1_stats[] = { MEMCG_SWAP, NR_SWAPCACHE, #endif +#ifdef CONFIG_CACHE_EXT + NR_FILE_CACHE_EXT, + NR_FILE_CACHE_EXT_KSWAPD_RECLAIMED, + NR_FILE_CACHE_EXT_REFAULT, +#endif }; static const char *const memcg1_stat_names[] = { @@ -4990,6 +5001,12 @@ static const char *const memcg1_stat_names[] = { "swap", "swapcached", #endif +#ifdef CONFIG_CACHE_EXT + "file_cache_ext", + "file_cache_ext_reclaimed_kswapd", + "file_cache_ext_refault", + +#endif }; /* Universal VM events cgroup1 shows, original sort order */ @@ -6273,6 +6290,12 @@ static struct cftype mem_cgroup_legacy_files[] = { .name = "stat", .seq_show = memory_stat_show, }, +#ifdef CONFIG_CACHE_EXT + { + .name = "cache_ext_policy_stat", + .seq_show = cache_ext_policy_stat_show, + }, +#endif { .name = "force_empty", .write = mem_cgroup_force_empty_write, @@ -6628,6 +6651,7 @@ static void __mem_cgroup_free(struct mem_cgroup *memcg) if (memcg->orig_objcg) obj_cgroup_put(memcg->orig_objcg); + cache_ext_policy_free(memcg); for_each_node(node) free_mem_cgroup_per_node_info(memcg, node); kfree(memcg->vmstats); @@ -6676,6 +6700,9 @@ static struct mem_cgroup *mem_cgroup_alloc(void) if (alloc_mem_cgroup_per_node_info(memcg, node)) goto fail; + if (cache_ext_policy_init(memcg)) + goto fail; + if (memcg_wb_domain_init(memcg, GFP_KERNEL)) goto fail; @@ -6784,6 +6811,16 @@ static int mem_cgroup_css_online(struct cgroup_subsys_state *css) FLUSH_TIME); lru_gen_online_memcg(memcg); +#ifdef CONFIG_CACHE_EXT + if (parent_mem_cgroup(memcg)) + /* + * cache_ext_ptr setting is protected via cgroup_lock + * on policy registration. We hold the same lock here, + * so there is no need in additinal synchronisation + */ + memcg->cache_ext_ptr = parent_mem_cgroup(memcg)->cache_ext_ptr; +#endif + #ifdef CONFIG_MEMCG_OOM_PRIORITY memcg_oom_prio_init(memcg); #endif @@ -8308,6 +8345,12 @@ static struct cftype memory_files[] = { .name = "stat", .seq_show = memory_stat_show, }, +#ifdef CONFIG_CACHE_EXT + { + .name = "cache_ext_policy_stat", + .seq_show = cache_ext_policy_stat_show, + }, +#endif #ifdef CONFIG_NUMA { .name = "numa_stat", diff --git a/mm/migrate.c b/mm/migrate.c index 4edd29d9a041..44cc91e0587e 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -433,6 +433,7 @@ static int __folio_migrate_mapping(struct address_space *mapping, int dirty; long nr = folio_nr_pages(folio); long entries, i; + s64 cache_ext_cookie = 0; if (!mapping) { /* Take off deferred split queue while frozen and memcg set */ @@ -458,12 +459,19 @@ static int __folio_migrate_mapping(struct address_space *mapping, oldzone = folio_zone(folio); newzone = folio_zone(newfolio); + if (folio_is_file_lru(folio)) + cache_ext_cookie = cache_ext_folio_evicted(folio); + xas_lock_irq(&xas); if (!folio_ref_freeze(folio, expected_count)) { xas_unlock_irq(&xas); + if (folio_is_file_lru(folio) && cache_ext_cookie >= 0) + cache_ext_folio_added(folio, true, cache_ext_cookie); return -EAGAIN; } + set_cookie_to_folio_page_ext(newfolio, cache_ext_cookie); + /* Take off deferred split queue while frozen and memcg set */ folio_unqueue_deferred_split(folio); @@ -566,7 +574,7 @@ int folio_migrate_mapping(struct address_space *mapping, { int expected_count = folio_expected_refs(mapping, folio) + extra_count; - if (folio_ref_count(folio) != expected_count) + if (folio_ref_count(folio) != expected_count + folio_test_cache_ext(folio)) return -EAGAIN; return __folio_migrate_mapping(mapping, newfolio, folio, expected_count); @@ -577,7 +585,7 @@ static int folio_migrate_mc_copy(struct folio *dst, struct folio *src, enum migrate_mode mode, int expected_count) { /* Check whether src does not have extra refs before we do more work */ - if (folio_ref_count(src) != expected_count) + if (folio_ref_count(src) != expected_count + folio_test_cache_ext(src)) return -EAGAIN; if (mode == MIGRATE_SYNC_NO_COPY) @@ -813,7 +821,7 @@ static int __buffer_migrate_folio(struct address_space *mapping, /* Check whether page does not have extra refs before we do more work */ expected_count = folio_expected_refs(mapping, src); - if (folio_ref_count(src) != expected_count) + if (folio_ref_count(src) != expected_count + folio_test_cache_ext(src)) return -EAGAIN; if (!buffer_migrate_lock_buffers(head, mode)) @@ -1347,6 +1355,8 @@ static int migrate_folio_move(free_folio_t put_new_folio, unsigned long private, * isolated from the unevictable LRU: but this case is the easiest. */ folio_add_lru(dst); + if (folio_is_file_lru(dst)) + cache_ext_folio_added(dst, true, get_and_clear_cookie_from_folio_page_ext(dst)); if (old_page_state & PAGE_WAS_MLOCKED) lru_add_drain(); diff --git a/mm/page_ext.c b/mm/page_ext.c index 4548fcc66d74..c29c115232cf 100644 --- a/mm/page_ext.c +++ b/mm/page_ext.c @@ -75,6 +75,10 @@ static struct page_ext_operations page_idle_ops __initdata = { }; #endif +#ifdef CONFIG_CACHE_EXT +extern struct page_ext_operations page_ext_cache_ext_ops; +#endif + static struct page_ext_operations *page_ext_ops[] __initdata = { #ifdef CONFIG_PAGE_OWNER &page_owner_ops, @@ -85,6 +89,9 @@ static struct page_ext_operations *page_ext_ops[] __initdata = { #ifdef CONFIG_PAGE_TABLE_CHECK &page_table_check_ops, #endif +#ifdef CONFIG_CACHE_EXT + &page_ext_cache_ext_ops, +#endif }; unsigned long page_ext_size; diff --git a/mm/swap.c b/mm/swap.c index 3c0e14640951..607c8a521351 100644 --- a/mm/swap.c +++ b/mm/swap.c @@ -449,6 +449,7 @@ static void folio_inc_refs(struct folio *folio) */ void folio_mark_accessed(struct folio *folio) { + cache_ext_folio_accessed(folio); if (lru_gen_enabled()) { folio_inc_refs(folio); return; diff --git a/mm/truncate.c b/mm/truncate.c index 1557a0503f8e..c1488c290f8c 100644 --- a/mm/truncate.c +++ b/mm/truncate.c @@ -21,6 +21,8 @@ #include <linux/task_io_accounting_ops.h> #include <linux/shmem_fs.h> #include <linux/rmap.h> +#include <linux/cache_ext.h> + #include "internal.h" /* @@ -286,7 +288,8 @@ long mapping_evict_folio(struct address_space *mapping, struct folio *folio) return 0; /* The refcount will be elevated if any page in the folio is mapped */ if (folio_ref_count(folio) > - folio_nr_pages(folio) + folio_has_private(folio) + 1) + folio_nr_pages(folio) + folio_has_private(folio) + 1 + + !!folio_test_cache_ext(folio)) return 0; if (!filemap_release_folio(folio, 0)) return 0; @@ -561,12 +564,15 @@ EXPORT_SYMBOL(invalidate_mapping_pages); static int invalidate_complete_folio2(struct address_space *mapping, struct folio *folio) { + s64 cookie; if (folio->mapping != mapping) return 0; if (!filemap_release_folio(folio, GFP_KERNEL)) return 0; + cookie = cache_ext_folio_evicted(folio); + spin_lock(&mapping->host->i_lock); xa_lock_irq(&mapping->i_pages); if (folio_test_dirty(folio)) @@ -584,6 +590,10 @@ static int invalidate_complete_folio2(struct address_space *mapping, failed: xa_unlock_irq(&mapping->i_pages); spin_unlock(&mapping->host->i_lock); + + if (cookie >= 0) + cache_ext_folio_added(folio, true, cookie); + return 0; } diff --git a/mm/vmscan.c b/mm/vmscan.c index 9e4aedbfd85e..f521d2d82a69 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -444,7 +444,7 @@ static inline int is_page_cache_freeable(struct folio *folio) * private data at folio->private. */ return folio_ref_count(folio) - folio_test_private(folio) == - 1 + folio_nr_pages(folio); + 1 + folio_nr_pages(folio) + folio_test_cache_ext(folio); } /* @@ -699,10 +699,18 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio, { int refcount; void *shadow = NULL; + s64 cookie = -EINVAL; BUG_ON(!folio_test_locked(folio)); BUG_ON(mapping != folio_mapping(folio)); + if (folio_is_file_lru(folio)) { + if (folio_test_cache_ext(folio) && + folio_ref_count(folio) > 2 + folio_nr_pages(folio)) + goto out; + cookie = cache_ext_folio_evicted(folio); + } + if (!folio_test_swapcache(folio)) spin_lock(&mapping->host->i_lock); xa_lock_irq(&mapping->i_pages); @@ -788,6 +796,12 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio, xa_unlock_irq(&mapping->i_pages); if (!folio_test_swapcache(folio)) spin_unlock(&mapping->host->i_lock); + /* + * If the folio was successfully evicted above - re-add back + */ + if (folio_is_file_lru(folio) && (cookie >= 0)) + cache_ext_folio_added(folio, true, cookie); +out: return 0; } @@ -2581,6 +2595,187 @@ static bool can_age_anon_pages(struct pglist_data *pgdat, return can_demote(pgdat->node_id, sc); } +#ifdef CONFIG_CACHE_EXT + +bool allow_direct_cache_ext = true; + +static unsigned int reclaim_cache_ext_folio_list(struct list_head *folio_list, + struct pglist_data *pgdat, + bool ignore_references) +{ + struct reclaim_stat dummy_stat; + unsigned int nr_reclaimed; + struct folio *folio; + struct scan_control sc = { + .gfp_mask = GFP_KERNEL, + .may_writepage = 0, + .may_unmap = 1, + .may_swap = 0, + .no_demotion = 0, + }; + + nr_reclaimed = shrink_folio_list(folio_list, pgdat, &sc, &dummy_stat, ignore_references); + while (!list_empty(folio_list)) { + folio = lru_to_folio(folio_list); + list_del(&folio->lru); + + if (folio_is_file_lru(folio)) + cache_ext_folio_added(folio, true, + get_and_clear_cookie_from_folio_page_ext(folio)); + + folio_putback_lru(folio); + } + + return nr_reclaimed; +} + +static bool isolate_cache_ext_folio(struct folio *folio) +{ + struct lruvec *lruvec; + + if (WARN_ON(folio_test_anon(folio))) + return false; + + if (folio_test_unevictable(folio)) + return false; + + if (!folio_test_clear_lru(folio)) + return false; + + folio_clear_referenced(folio); + folio_test_clear_young(folio); + folio_clear_reclaim(folio); + + lruvec = folio_lruvec(folio); + + lruvec_del_folio(lruvec, folio); + folio_clear_active(folio); + + return true; +} + +static void return_isolated_folios_to_policy(struct cache_ext_isolation_ctx *ctx) +{ + for (int i = 0; i < ctx->nr_folios_isolated; i++) { + struct folio *folio = ctx->folios[i]; + + if (!folio) + continue; + + VM_BUG_ON(!pfn_valid(folio_pfn(folio))); + + cache_ext_folio_added(folio, false, ctx->cookies[i]); + } +} + +static unsigned long cache_ext_isolate_and_reclaim(struct lruvec *lruvec, int nr_to_reclaim) +{ + int ret = 0; + LIST_HEAD(free_folios); + unsigned long nr_reclaimed = 0; + struct cache_ext_isolation_ctx ctx; + int nr_to_reclaim_batch = min(CACHE_EXT_NR_ISOLATED, nr_to_reclaim); + int nid = lruvec_pgdat(lruvec)->node_id; + + while (nr_to_reclaim > 0) { + unsigned long nr_reclaimed_batch = 0; + + memset(&ctx, 0, sizeof(ctx)); + ctx.nr_folios_to_isolate = nr_to_reclaim_batch; + ctx.numa_node_id = nid; + + ret = cache_ext_isolate_folios(&ctx, lruvec_memcg(lruvec)); + + /* + * If for some reason, policy return error from isolation, + * but isolated something, we need to return such folios to the policy. + * Only kfuncs can populate ctx.folios, so it is guranteed that they are valid + */ + if (ret < 0) { + return_isolated_folios_to_policy(&ctx); + goto out; + } + + /* + * ctx.folios is populated only via kfuncs, + * policy can not perform direct writes into it. + * So, we have full control over its conten and + * VM_BUG_ON conditions are bugs + */ + spin_lock_irq(&lruvec->lru_lock); + for (int i = 0; i < ctx.nr_folios_isolated; i++) { + struct folio *untrusted_folio = ctx.folios[i]; + + VM_BUG_ON(!untrusted_folio); + VM_BUG_ON(!pfn_valid(folio_pfn(untrusted_folio))); + + set_cookie_to_folio_page_ext(untrusted_folio, ctx.cookies[i]); + + VM_BUG_ON_FOLIO(lruvec != folio_lruvec(untrusted_folio), untrusted_folio); + + if (!isolate_cache_ext_folio(untrusted_folio)) + continue; + + list_add(&untrusted_folio->lru, &free_folios); + + /* + * Keep in this array only folios which must be re-added to cache_ext + * due to isolation failure or lruvec mismatch + */ + ctx.folios[i] = NULL; + } + spin_unlock_irq(&lruvec->lru_lock); + + return_isolated_folios_to_policy(&ctx); + + current->flags |= PF_CACHE_EXT_RECLAIM; + nr_reclaimed_batch = + reclaim_cache_ext_folio_list(&free_folios, NODE_DATA(nid), true); + current->flags &= ~PF_CACHE_EXT_RECLAIM; + + nr_reclaimed += nr_reclaimed_batch; + nr_to_reclaim -= max(CACHE_EXT_NR_ISOLATED, nr_reclaimed_batch); + + cond_resched(); + } +out: + return nr_reclaimed; +} + +static unsigned long +kswapd_reclaim_from_cache_ext_memcg(struct lruvec *lruvec, unsigned long nr_to_reclaim) +{ + struct mem_cgroup *memcg = lruvec_memcg(lruvec); + unsigned long reclaimed; + + if (!memcg) + return 0; + + if (!memcg->cache_ext_ptr) + return 0; + + if (!nr_to_reclaim) + return 0; + + reclaimed = cache_ext_isolate_and_reclaim(lruvec, nr_to_reclaim); + + mod_lruvec_state(lruvec, NR_FILE_CACHE_EXT_KSWAPD_RECLAIMED, reclaimed); + + return reclaimed; +} + +#else + +#define allow_direct_cache_ext (false) + +static unsigned long +kswapd_reclaim_from_cache_ext_memcg(struct lruvec *lruvec, unsigned long nr_to_reclaim) +{ + return 0; +} + +#endif + #ifdef CONFIG_LRU_GEN #ifdef CONFIG_LRU_GEN_ENABLED @@ -4554,6 +4749,44 @@ static int isolate_folios(struct lruvec *lruvec, struct scan_control *sc, int sw return scanned; } +#ifdef CONFIG_CACHE_EXT + +static bool should_lru_gen_reclaim_file(struct lruvec *lruvec, int swappiness) +{ + int type; + int tier = -1; + DEFINE_MIN_SEQ(lruvec); + + /* + * Try to make the obvious choice first. When anon and file are both + * available from the same generation, interpret swappiness 1 as file + * first and 200 as anon first. + */ + if (!swappiness) + type = LRU_GEN_FILE; + else if (min_seq[LRU_GEN_ANON] < min_seq[LRU_GEN_FILE]) + type = LRU_GEN_ANON; + else if (swappiness == 1) + type = LRU_GEN_FILE; + else if (swappiness == 200) + type = LRU_GEN_ANON; + else + type = get_type_to_scan(lruvec, swappiness, &tier); + + return type == LRU_GEN_FILE; +} + +#else + +#define allow_direct_cache_ext (false) + +static bool should_lru_gen_reclaim_file(struct lruvec *lruvec, int swappiness) +{ + return false; +} + +#endif + static int evict_folios(struct lruvec *lruvec, struct scan_control *sc, int swappiness) { int type; @@ -4570,6 +4803,19 @@ static int evict_folios(struct lruvec *lruvec, struct scan_control *sc, int swap struct mem_cgroup *memcg = lruvec_memcg(lruvec); struct pglist_data *pgdat = lruvec_pgdat(lruvec); + if (should_lru_gen_reclaim_file(lruvec, swappiness) && + (allow_direct_cache_ext || current_is_kswapd())) { + unsigned long nr_to_reclaim = min(sc->nr_to_reclaim, MIN_LRU_BATCH); + unsigned long reclaimed = + kswapd_reclaim_from_cache_ext_memcg(lruvec, nr_to_reclaim); + + sc->nr_reclaimed += reclaimed; + + if (reclaimed >= nr_to_reclaim) + return reclaimed; + } + + spin_lock_irq(&lruvec->lru_lock); scanned = isolate_folios(lruvec, sc, swappiness, &type, &list); @@ -5720,6 +5966,20 @@ static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc) nr_to_scan = min(nr[lru], SWAP_CLUSTER_MAX); nr[lru] -= nr_to_scan; + if (lru == LRU_INACTIVE_FILE && + (allow_direct_cache_ext || current_is_kswapd())) { + unsigned long cache_ext_reclaimed = + kswapd_reclaim_from_cache_ext_memcg(lruvec, + nr_to_scan); + + nr_reclaimed += cache_ext_reclaimed; + + if (cache_ext_reclaimed >= nr_to_scan) + continue; + + nr_to_scan -= cache_ext_reclaimed; + } + nr_reclaimed += shrink_list(lru, nr_to_scan, lruvec, sc); } diff --git a/mm/vmstat.c b/mm/vmstat.c index c183908a6623..b6d4a0f33de4 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c @@ -1250,7 +1250,11 @@ const char * const vmstat_text[] = { "pgdemote_kswapd", "pgdemote_direct", "pgdemote_khugepaged", - +#ifdef CONFIG_CACHE_EXT + "nr_file_cache_ext", + "nr_cache_ext_reclaimed_kswapd", + "nr_cache_ext_refaulted", +#endif /* enum writeback_stat_item counters */ "nr_dirty_threshold", "nr_dirty_background_threshold", diff --git a/mm/workingset.c b/mm/workingset.c index 80588168ba41..613705f8a4ec 100644 --- a/mm/workingset.c +++ b/mm/workingset.c @@ -16,6 +16,8 @@ #include <linux/dax.h> #include <linux/fs.h> #include <linux/mm.h> +#include <linux/cache_ext.h> + #include "internal.h" /* @@ -181,9 +183,14 @@ */ #define WORKINGSET_SHIFT 1 +#ifdef CONFIG_CACHE_EXT +#define CACHE_EXT_RECLAIM_SHIFT 1 +#else +#define CACHE_EXT_RECLAIM_SHIFT 0 +#endif #define EVICTION_SHIFT ((BITS_PER_LONG - BITS_PER_XA_VALUE) + \ - WORKINGSET_SHIFT + NODES_SHIFT + \ - MEM_CGROUP_ID_SHIFT) + CACHE_EXT_RECLAIM_SHIFT + WORKINGSET_SHIFT + \ + NODES_SHIFT + MEM_CGROUP_ID_SHIFT) #define EVICTION_MASK (~0UL >> EVICTION_SHIFT) /* @@ -199,20 +206,30 @@ static unsigned int bucket_order __read_mostly; static void *pack_shadow(int memcgid, pg_data_t *pgdat, unsigned long eviction, bool workingset) { + bool cache_ext = false; + + if (current_is_cache_ext_reclaim()) + cache_ext = true; + eviction &= EVICTION_MASK; eviction = (eviction << MEM_CGROUP_ID_SHIFT) | memcgid; eviction = (eviction << NODES_SHIFT) | pgdat->node_id; eviction = (eviction << WORKINGSET_SHIFT) | workingset; + eviction = (eviction << CACHE_EXT_RECLAIM_SHIFT) | cache_ext; return xa_mk_value(eviction); } static void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat, - unsigned long *evictionp, bool *workingsetp) + unsigned long *evictionp, bool *workingsetp, bool *cache_extp) { unsigned long entry = xa_to_value(shadow); int memcgid, nid; bool workingset; + bool cache_ext; + + cache_ext = entry & ((1UL << CACHE_EXT_RECLAIM_SHIFT) - 1); + entry >>= CACHE_EXT_RECLAIM_SHIFT; workingset = entry & ((1UL << WORKINGSET_SHIFT) - 1); entry >>= WORKINGSET_SHIFT; @@ -225,7 +242,41 @@ static void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat, *pgdat = NODE_DATA(nid); *evictionp = entry; *workingsetp = workingset; + + if (cache_extp) + *cache_extp = cache_ext; +} + +#ifdef CONFIG_CACHE_EXT +static void account_cache_ext_refault(void *shadow, long nr) +{ + struct mem_cgroup *eviction_memcg; + struct lruvec *lruvec; + int memcgid; + struct pglist_data *pgdat; + unsigned long eviction; + bool workingset; + bool cache_ext; + + unpack_shadow(shadow, &memcgid, &pgdat, &eviction, &workingset, &cache_ext); + + if (!cache_ext) + return; + + eviction_memcg = mem_cgroup_from_id(memcgid); + /* We need to account this somewhere */ + if (!mem_cgroup_disabled() && !eviction_memcg) + eviction_memcg = root_mem_cgroup; + if (!eviction_memcg->cache_ext_ptr) + return; + + lruvec = &eviction_memcg->nodeinfo[pgdat->node_id]->lruvec; + + mod_lruvec_state(lruvec, NR_FILE_CACHE_EXT_REFAULT, nr); } +#else +static inline void account_cache_ext_refault(void *shadow, long nr) { } +#endif #ifdef CONFIG_LRU_GEN @@ -268,7 +319,7 @@ static bool lru_gen_test_recent(void *shadow, bool file, struct lruvec **lruvec, struct mem_cgroup *memcg; struct pglist_data *pgdat; - unpack_shadow(shadow, &memcg_id, &pgdat, token, workingset); + unpack_shadow(shadow, &memcg_id, &pgdat, token, workingset, NULL); memcg = mem_cgroup_from_id(memcg_id); *lruvec = mem_cgroup_lruvec(memcg, pgdat); @@ -291,6 +342,10 @@ static void lru_gen_refault(struct folio *folio, void *shadow) rcu_read_lock(); recent = lru_gen_test_recent(shadow, type, &lruvec, &token, &workingset); + + if (type) + account_cache_ext_refault(shadow, delta); + if (lruvec != folio_lruvec(folio)) goto unlock; @@ -429,7 +484,7 @@ bool workingset_test_recent(void *shadow, bool file, bool *workingset) if (lru_gen_enabled()) return lru_gen_test_recent(shadow, file, &eviction_lruvec, &eviction, workingset); - unpack_shadow(shadow, &memcgid, &pgdat, &eviction, workingset); + unpack_shadow(shadow, &memcgid, &pgdat, &eviction, workingset, NULL); eviction <<= bucket_order; /* @@ -540,6 +595,9 @@ void workingset_refault(struct folio *folio, void *shadow) mod_lruvec_state(lruvec, WORKINGSET_REFAULT_BASE + file, nr); + if (file) + account_cache_ext_refault(shadow, nr); + if (!workingset_test_recent(shadow, file, &workingset)) goto out; -- 2.34.1
From: Nikita Panov <panov.nikita@huawei.com> hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10164 ---------------------------------------- This mechanism is inteneded to refill page cache pages which were evicted through cache_ext mechanism. Per super block thread is spawned on mount, only for rotational devices. This mechanism is DISABLED by default. /proc/sys/vm/page_cache_refill_threshold (default=0) /proc/sys/vm/refill_hardware_mq_usage_threshold (default=500(5%)) /proc/sys/vm/refill_ms_sleep_duration (default=500(500ms)) Files above are used to control its behaviour, Second and third values are already set to something meaningful for our HDD setup, regarding page_cache_refill_threshold - we recommend to test different values, our test shows 6000 (60%) most promising. Other values we keep as a default Append "cache_ext_refill=on" to the kernel cmdline if you want to enable this feature. Co-developed-by: Alexander Grubnikov <alexander.grubnikov@huawei.com> Signed-off-by: Alexander Grubnikov <alexander.grubnikov@huawei.com> Co-developed-by: Anatoly Stepanov <stepanov.anatoly@huawei.com> Signed-off-by: Anatoly Stepanov <stepanov.anatoly@huawei.com> Co-developed-by: Nikita Panov <panov.nikita@huawei.com> Signed-off-by: Nikita Panov <panov.nikita@huawei.com> --- .../admin-guide/kernel-parameters.txt | 10 + Documentation/admin-guide/sysctl/vm.rst | 30 ++ fs/drop_caches.c | 6 + fs/inode.c | 13 + fs/super.c | 20 +- include/linux/cache_ext.h | 10 + include/linux/fs.h | 4 + include/linux/mempolicy.h | 6 + include/linux/mmzone.h | 2 + include/linux/pagemap.h | 3 + include/linux/sched.h | 3 +- include/linux/swap.h | 8 + include/linux/sysctl.h | 8 +- kernel/sysctl.c | 28 +- mm/Kconfig | 2 +- mm/cache_ext.c | 383 ++++++++++++++++++ mm/filemap.c | 2 +- mm/memcontrol.c | 7 +- mm/mempolicy.c | 5 + mm/vmscan.c | 5 + mm/vmstat.c | 2 + mm/workingset.c | 56 ++- 22 files changed, 589 insertions(+), 24 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index ab5ecf034b22..7cb0af3a33f1 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -549,6 +549,16 @@ to reclaim the requested number of pages, fallback to vanilla reclaim is performed. + cache_ext_refill= + [KNL] + Format: [on|off] + Default: off + Might be enabled only if cache_ext=on. + This feature proactively reads file pages that were + previously reclaimed using the cache_ext policy. + For precise configuration, check + /proc/sys/vm/documentation. + carrier_timeout= [NET] Specifies amount of time (in seconds) that the kernel should wait for a network carrier. By default diff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst index cdbcf806b7fe..d3b8e4390de3 100644 --- a/Documentation/admin-guide/sysctl/vm.rst +++ b/Documentation/admin-guide/sysctl/vm.rst @@ -81,6 +81,9 @@ Currently, these files are in /proc/sys/vm: - cache_reclaim_enable - cache_limit_mbytes - allow_direct_cache_ext +- page_cache_refill_threshold +- refill_hardware_mq_usage_threshold +- refill_ms_sleep_duration admin_reserve_kbytes @@ -1110,3 +1113,30 @@ implemented and loaded per memory cgroup. This file controls whether direct reclaim is allowed to be performed from the custom policy or not. If not, only kswapd will reclaim from the cache_ext policy. Noop if cache_ext=on is not specified in kernel cmdline. Default value is true. + + +page_cache_refill_threshold +=================== + +When CONFIG_CACHE_EXT is enabled, it sets the allowed memory threshold for the +refill feature. The refill mechanism will proactively read file pages that +were evicted using the cache_ext policy. Refill will work only if the overall +system memory usage is below this threshold. 10000 for 100% (always try to +refill), 5000 for 50%, and 0 for 0% (default) - refill is disabled. Noop if +cache_ext_refill=on is not specified in kernel cmdline. + + +refill_hardware_mq_usage_threshold +=================== + +When CONFIG_CACHE_EXT is enabled and page_cache_refill_threshold is not 0, +this entry sets a limit on block device mq usage. If usage is above the set +value, a refill will not be performed. Noop if cache_ext_refill=on is not +specified in the kernel cmdline. Default value is 500 (5%). + + +refill_ms_sleep_duration +=================== + +Sleep interval for refill threads between refill attempts in ms. 100 ms is the +minimal allowed value to prevent busy loops. diff --git a/fs/drop_caches.c b/fs/drop_caches.c index b9575957a7c2..b7a518f2bd9b 100644 --- a/fs/drop_caches.c +++ b/fs/drop_caches.c @@ -34,6 +34,12 @@ static void drop_pagecache_sb(struct super_block *sb, void *unused) continue; } __iget(inode); + +#ifdef CONFIG_CACHE_EXT + if (test_bit(AS_CACHE_EXT, &inode->i_mapping->flags)) + clear_bit(AS_CACHE_EXT, &inode->i_mapping->flags); +#endif + spin_unlock(&inode->i_lock); spin_unlock(&sb->s_inode_list_lock); diff --git a/fs/inode.c b/fs/inode.c index 878a9c671c91..3fe99641a9d7 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -20,6 +20,7 @@ #include <linux/ratelimit.h> #include <linux/list_lru.h> #include <linux/iversion.h> +#include <linux/cache_ext.h> #include <trace/events/writeback.h> #include "internal.h" @@ -880,6 +881,18 @@ static enum lru_status inode_lru_isolate(struct list_head *item, if (!spin_trylock(&inode->i_lock)) return LRU_SKIP; +#ifdef CONFIG_CACHE_EXT + /* + * Skip cache_ext inodes in direct or kswapd reclaim, because they are candidates + * for refill. + */ + if (!static_branch_unlikely(&cache_ext_refill_disabled) && + (current->flags & PF_MEMALLOC) && + test_bit(AS_CACHE_EXT, &inode->i_mapping->flags)) { + spin_unlock(&inode->i_lock); + return LRU_SKIP; + } +#endif /* * Inodes can get referenced, redirtied, or repopulated while * they're already on the LRU, and this can make them diff --git a/fs/super.c b/fs/super.c index 6f5e2881fd10..a35e16f711bf 100644 --- a/fs/super.c +++ b/fs/super.c @@ -36,13 +36,14 @@ #include <linux/lockdep.h> #include <linux/user_namespace.h> #include <linux/fs_context.h> +#include <linux/cache_ext.h> #include <uapi/linux/mount.h> #include "internal.h" static int thaw_super_locked(struct super_block *sb, enum freeze_holder who); static LIST_HEAD(super_blocks); -static DEFINE_SPINLOCK(sb_lock); +DEFINE_SPINLOCK(sb_lock); static char *sb_writers_name[SB_FREEZE_LEVELS] = { "sb_writers", @@ -76,7 +77,7 @@ static inline void super_unlock_excl(struct super_block *sb) super_unlock(sb, true); } -static inline void super_unlock_shared(struct super_block *sb) +void super_unlock_shared(struct super_block *sb) { super_unlock(sb, false); } @@ -141,7 +142,7 @@ static __must_check bool super_lock(struct super_block *sb, bool excl) } /* wait and acquire read-side of @sb->s_umount */ -static inline bool super_lock_shared(struct super_block *sb) +bool super_lock_shared(struct super_block *sb) { return super_lock(sb, false); } @@ -285,6 +286,14 @@ static void destroy_super_work(struct work_struct *work) for (i = 0; i < SB_FREEZE_LEVELS; i++) percpu_free_rwsem(&s->s_writers.rw_sem[i]); + +#ifdef CONFIG_CACHE_EXT + if (s->krefilld) { + kthread_stop(s->krefilld); + s->krefilld = NULL; + } +#endif + kfree(s); } @@ -1537,6 +1546,11 @@ int setup_bdev_super(struct super_block *sb, int sb_flags, bdev_release(bdev_handle); return -EBUSY; } + + if (!bdev_nonrot(bdev)) + if (krefilld_run(sb, bdev->bd_disk->disk_name)) + pr_warn("Failed to start krefilld for %s\n", bdev->bd_disk->disk_name); + spin_lock(&sb_lock); sb->s_bdev_handle = bdev_handle; sb->s_bdev = bdev; diff --git a/include/linux/cache_ext.h b/include/linux/cache_ext.h index 40e3c4230942..0e51dcec01f1 100644 --- a/include/linux/cache_ext.h +++ b/include/linux/cache_ext.h @@ -11,9 +11,12 @@ #include <linux/page_ext.h> +struct super_block; + #ifdef CONFIG_CACHE_EXT DECLARE_STATIC_KEY_TRUE(cache_ext_disabled); +DECLARE_STATIC_KEY_TRUE(cache_ext_refill_disabled); static inline bool folio_test_cache_ext(struct folio *folio) { @@ -219,6 +222,11 @@ s64 get_and_clear_cookie_from_folio_page_ext(struct folio *folio); bool cache_ext_kfunc(u32 btf_id); extern bool allow_direct_cache_ext; +extern unsigned long refill_threshold; +extern unsigned long hardware_mq_usage_threshold; +extern unsigned long refill_ms_sleep_duration; + +int krefilld_run(struct super_block *sb, const char *name); #else @@ -244,6 +252,8 @@ static inline s64 get_and_clear_cookie_from_folio_page_ext(struct folio *folio) static inline bool cache_ext_kfunc(u32 btf_id) { return false; } +static inline int krefilld_run(struct super_block *sb, const char *name) { return 0; } + #endif #endif diff --git a/include/linux/fs.h b/include/linux/fs.h index 37ff2e2a36fa..69f1bb960528 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1494,6 +1494,10 @@ struct super_block { spinlock_t s_inode_wblist_lock; struct list_head s_inodes_wb; /* writeback inodes */ +#ifdef CONFIG_CACHE_EXT + struct task_struct *krefilld; +#endif + KABI_USE(1, struct shrinker_v2 *s_shrink) KABI_RESERVE(2) diff --git a/include/linux/mempolicy.h b/include/linux/mempolicy.h index 5cb841943283..43c609506dd8 100644 --- a/include/linux/mempolicy.h +++ b/include/linux/mempolicy.h @@ -139,6 +139,7 @@ struct mempolicy *get_vma_policy(struct vm_area_struct *vma, bool vma_policy_mof(struct vm_area_struct *vma); extern void numa_default_policy(void); +extern long numa_set_interleave_policy(nodemask_t *nodes); extern void numa_policy_init(void); extern void mpol_rebind_task(struct task_struct *tsk, const nodemask_t *new); extern void mpol_rebind_mm(struct mm_struct *mm, nodemask_t *new); @@ -253,6 +254,11 @@ static inline void numa_default_policy(void) { } +static inline long numa_set_interleave_policy(nodemask_t *nodes) +{ + return 0; +} + static inline void mpol_rebind_task(struct task_struct *tsk, const nodemask_t *new) { diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 3fbc6b5fbba9..596a5a2e6aac 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -223,6 +223,8 @@ enum node_stat_item { NR_FILE_CACHE_EXT, NR_FILE_CACHE_EXT_KSWAPD_RECLAIMED, NR_FILE_CACHE_EXT_REFAULT, + NR_FILE_CACHE_EXT_REFILLED, + REFILL_REFAULT_FILE, #endif NR_VM_NODE_STAT_ITEMS }; diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h index 624712273947..6ed3ba3ecfef 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -211,6 +211,9 @@ enum mapping_flags { AS_FOLIO_ORDER_BITS = 5, AS_FOLIO_ORDER_MIN = 16, AS_FOLIO_ORDER_MAX = AS_FOLIO_ORDER_MIN + AS_FOLIO_ORDER_BITS, +#ifdef CONFIG_CACHE_EXT + AS_CACHE_EXT = AS_FOLIO_ORDER_MAX + AS_FOLIO_ORDER_BITS, +#endif }; #define AS_FOLIO_ORDER_BITS_MASK ((1u << AS_FOLIO_ORDER_BITS) - 1) diff --git a/include/linux/sched.h b/include/linux/sched.h index d7f1c1095cbb..61055ce6bb40 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1865,11 +1865,12 @@ extern struct pid *cad_pid; #ifdef CONFIG_CACHE_EXT #define PF_CACHE_EXT_RECLAIM 0x01000000 /* I reclaim from cache_ext right now */ +#define PF_REFILL_THREAD 0x02000000 /* I am refill thread */ #else #define PF__HOLE__01000000 0x01000000 +#define PF__HOLE__02000000 0x02000000 #endif -#define PF__HOLE__02000000 0x02000000 #define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_mask */ #define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */ #define PF_MEMALLOC_PIN 0x10000000 /* Allocation context constrained to zones which allow long term pinning. */ diff --git a/include/linux/swap.h b/include/linux/swap.h index 9f1f77f4cf59..05cdf1047e9c 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -44,11 +44,19 @@ static inline int current_is_cache_ext_reclaim(void) { return current->flags & PF_CACHE_EXT_RECLAIM; } +static inline int current_is_krefilld(void) +{ + return current->flags & PF_REFILL_THREAD; +} #else static inline int current_is_cache_ext_reclaim(void) { return 0; } +static inline int current_is_krefilld(void) +{ + return 0; +} #endif /* diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 61b40ea81f4d..02b512fc43db 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -55,9 +55,11 @@ struct ctl_dir; extern const int sysctl_vals[]; -#define SYSCTL_LONG_ZERO ((void *)&sysctl_long_vals[0]) -#define SYSCTL_LONG_ONE ((void *)&sysctl_long_vals[1]) -#define SYSCTL_LONG_MAX ((void *)&sysctl_long_vals[2]) +#define SYSCTL_LONG_ZERO ((void *)&sysctl_long_vals[0]) +#define SYSCTL_LONG_ONE ((void *)&sysctl_long_vals[1]) +#define SYSCTL_LONG_MAX ((void *)&sysctl_long_vals[2]) +#define SYSCTL_LONG_ONE_HUNDRED ((void *)&sysctl_long_vals[3]) +#define SYSCTL_LONG_TEN_THOUSAND ((void *)&sysctl_long_vals[4]) extern const unsigned long sysctl_long_vals[]; diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 88d8ca3c0fd3..9c11d522631c 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -86,7 +86,7 @@ const int sysctl_vals[] = { 0, 1, 2, 3, 4, 100, 200, 1000, 3000, INT_MAX, 65535, -1 }; EXPORT_SYMBOL(sysctl_vals); -const unsigned long sysctl_long_vals[] = { 0, 1, LONG_MAX }; +const unsigned long sysctl_long_vals[] = { 0, 1, LONG_MAX, 100, 10000 }; EXPORT_SYMBOL_GPL(sysctl_long_vals); #if defined(CONFIG_SYSCTL) @@ -2127,6 +2127,32 @@ static struct ctl_table vm_table[] = { .mode = 0644, .proc_handler = proc_dobool }, + { + .procname = "page_cache_refill_threshold", + .data = &refill_threshold, + .maxlen = sizeof(unsigned long), + .mode = 0644, + .proc_handler = proc_doulongvec_minmax, + .extra1 = SYSCTL_LONG_ZERO, + .extra2 = SYSCTL_LONG_TEN_THOUSAND, + }, + { + .procname = "refill_hardware_mq_usage_threshold", + .data = &hardware_mq_usage_threshold, + .maxlen = sizeof(unsigned long), + .mode = 0644, + .proc_handler = proc_doulongvec_minmax, + .extra1 = SYSCTL_LONG_ZERO, + .extra2 = SYSCTL_LONG_TEN_THOUSAND, + }, + { + .procname = "refill_ms_sleep_duration", + .data = &refill_ms_sleep_duration, + .maxlen = sizeof(unsigned long), + .mode = 0644, + .proc_handler = proc_doulongvec_minmax, + .extra1 = SYSCTL_LONG_ONE_HUNDRED + }, #endif { .procname = "page_lock_unfairness", diff --git a/mm/Kconfig b/mm/Kconfig index 62646204f67e..310362c9c8fb 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -1530,7 +1530,7 @@ config GMEM config CACHE_EXT bool "Cache_ext framework" - depends on BPF_SYSCALL && MEMCG + depends on BPF_SYSCALL && MEMCG && BLOCK select PAGE_EXTENSION default n help diff --git a/mm/cache_ext.c b/mm/cache_ext.c index 2b349030209f..9b9f61139bb7 100644 --- a/mm/cache_ext.c +++ b/mm/cache_ext.c @@ -11,10 +11,17 @@ #include <linux/mm.h> #include <linux/mm_inline.h> #include <linux/page_ext.h> +#include <linux/blk-mq.h> +#include <linux/mempolicy.h> +#include <linux/fs.h> +#include <linux/backing-dev.h> +#include <linux/freezer.h> +#include <linux/delay.h> #include "internal.h" DEFINE_STATIC_KEY_TRUE(cache_ext_disabled); +DEFINE_STATIC_KEY_TRUE(cache_ext_refill_disabled); static int __init setup_cache_ext(char *str) { @@ -38,6 +45,35 @@ static int __init setup_cache_ext(char *str) } early_param("cache_ext", setup_cache_ext); +static int __init setup_cache_ext_refill(char *str) +{ + int ret = 0; + + if (!str) + goto out; + + if (!strcmp(str, "on")) { + + if (static_branch_unlikely(&cache_ext_disabled)) { + pr_warn("cache_ext_refill= cannot enable without cache_ext\n"); + return 0; + } + + static_branch_disable(&cache_ext_refill_disabled); + pr_info("cache_ext_refill enabled via cmdline\n"); + ret = 1; + } else if (!strcmp(str, "off")) { + static_branch_enable(&cache_ext_refill_disabled); + pr_info("cache_ext_refill disabled via cmdline\n"); + ret = 1; + } +out: + if (!ret) + pr_warn("cache_ext_refill= cannot parse, ignored\n"); + return ret; +} +__setup("cache_ext_refill=", setup_cache_ext_refill); + static const struct btf_type *cache_ext_isolation_ctx_type; static const struct btf_type *cache_ext_stats_ctx_type; @@ -949,4 +985,351 @@ int cache_ext_policy_stat_show(struct seq_file *m, void *v) return 0; } +unsigned long refill_threshold; /* 0%, disabled by default */ +unsigned long hardware_mq_usage_threshold = 500; +unsigned long refill_ms_sleep_duration = 500; + +static void krefilld_file_sleep(void) +{ + msleep_interruptible(READ_ONCE(refill_ms_sleep_duration)); +} + +void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat, + unsigned long *evictionp, bool *workingsetp, bool *cache_ext_p); + +static bool below_refill_threshold(void) +{ + unsigned long total_pages = 0, free_pages = 0; + unsigned long refill_threshold_local = READ_ONCE(refill_threshold); + struct zone *zone; + + free_pages = global_zone_page_state(NR_FREE_PAGES); + + for_each_populated_zone(zone) { + total_pages += zone_managed_pages(zone); + } + + if (!total_pages) + return false; + + return (((total_pages - free_pages) * 10000) / total_pages) < refill_threshold_local; +} + +static bool is_candidate_for_refill(struct folio *folio) +{ + int memcg_id; + struct pglist_data *pgdat; + unsigned long token; + bool workingset; + bool cache_ext; + + if (WARN_ON(!xa_is_value(folio))) + return false; + + unpack_shadow(folio, &memcg_id, &pgdat, &token, &workingset, &cache_ext); + + return cache_ext; +} + +struct folio *find_get_entry(struct xa_state *xas, pgoff_t max, xa_mark_t mark); + +static unsigned int find_exceptional_entry(struct address_space *mapping, pgoff_t *start, + pgoff_t end, pgoff_t *index) +{ + XA_STATE(xas, &mapping->i_pages, *start); + struct folio *folio; + unsigned long res = 0; + + rcu_read_lock(); + while ((folio = find_get_entry(&xas, end, XA_PRESENT))) { + unsigned long base; + unsigned long nr; + + if (!xa_is_value(folio)) { + nr = folio_nr_pages(folio); + base = folio->index; + folio_put(folio); + /* Omit large folio which begins before the start */ + if (base < *start) + break; + /* Omit large folio which extends beyond the end */ + if (base + nr - 1 > end) + break; + + *start = base + nr; + } else { + nr = 1 << xas_get_order(&xas); + base = xas.xa_index & ~(nr - 1); + /* Omit order>0 value which begins before the start */ + if (base < *start) + continue; + /* Omit order>0 value which extends beyond the end */ + if (base + nr - 1 > end) + break; + + *start = base + nr; + + if (!is_candidate_for_refill(folio)) + continue; + + *index = xas.xa_index; + res = 1; + break; + } + } + rcu_read_unlock(); + + return res; +} + +static unsigned long refill_page_cache_mapping_pages(struct address_space *mapping, + pgoff_t start, pgoff_t end, bool *has_refill_candidates) +{ + pgoff_t read_index; + pgoff_t index = start; + unsigned long count = 0; + *has_refill_candidates = false; + + while (find_exceptional_entry(mapping, &index, end, &read_index)) { + unsigned long read_size = inode_to_bdi(mapping->host)->ra_pages; + struct file_ra_state ra = { + .start = -1, + .size = read_size, + .prev_pos = read_index << PAGE_SHIFT, + .ra_pages = read_size, + }; + DEFINE_READAHEAD(ractl, NULL, &ra, mapping, read_index); + + *has_refill_candidates = true; + + page_cache_sync_ra(&ractl, read_size); + + count += ra.ra_pages; + + cond_resched(); + } + + return count; +} + +static bool below_hardware_queue_threshold(struct super_block *sb) +{ + struct block_device *bdev; + struct request_queue *q; + struct blk_mq_hw_ctx *hctx; + unsigned long ret; + unsigned int active_hw_requests = 0; + unsigned long hw_capacity = 0; + unsigned long i; + unsigned long hardware_usage_local = READ_ONCE(hardware_mq_usage_threshold); + bool result = true; + + if (!sb->s_bdev) + return false; + + bdev = sb->s_bdev; + + if (!bdev->bd_disk || !bdev->bd_disk->queue) + return false; + + q = bdev->bd_disk->queue; + + ret = blk_queue_enter(q, 0); + if (ret) + return false; + + queue_for_each_hw_ctx(q, hctx, i) { + if (!hctx || !hctx->tags) + continue; + + hw_capacity = hctx->tags->nr_tags; + active_hw_requests = sbitmap_weight(&hctx->tags->bitmap_tags.sb); + + if (!hw_capacity) { + result = false; + break; + } + + if (((active_hw_requests * 10000) / hw_capacity) > hardware_usage_local) { + result = false; + break; + } + } + + blk_queue_exit(q); + + return result; +} + +static void refill_pagecache_sb(struct super_block *sb) +{ + struct inode *inode, *toput_inode = NULL; + unsigned long total_page_cache_refilled = 0; + + if (!below_refill_threshold()) + return; + + spin_lock(&sb->s_inode_list_lock); + + list_for_each_entry(inode, &sb->s_inodes, i_sb_list) { + bool has_refill_candidates = false; + + if (!spin_trylock(&inode->i_lock)) + continue; + /* + * We must skip inodes in unusual state. + */ + if (inode->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) { + spin_unlock(&inode->i_lock); + continue; + } + + if (!test_bit(AS_CACHE_EXT, &inode->i_mapping->flags)) { + spin_unlock(&inode->i_lock); + continue; + } + + if (!inode->i_mapping->a_ops->readahead) { + clear_bit(AS_CACHE_EXT, &inode->i_mapping->flags); + spin_unlock(&inode->i_lock); + continue; + } + + __iget(inode); + spin_unlock(&inode->i_lock); + spin_unlock(&sb->s_inode_list_lock); + + total_page_cache_refilled += + refill_page_cache_mapping_pages(inode->i_mapping, 0, -1, + &has_refill_candidates); + + if (!has_refill_candidates) + clear_bit(AS_CACHE_EXT, &inode->i_mapping->flags); + + mod_memcg_state(root_mem_cgroup, NR_FILE_CACHE_EXT_REFILLED, + total_page_cache_refilled); + + total_page_cache_refilled = 0; + + iput(toput_inode); + toput_inode = inode; + + cond_resched(); + + if (!below_refill_threshold() || !below_hardware_queue_threshold(sb)) + goto out; + + spin_lock(&sb->s_inode_list_lock); + + + } + spin_unlock(&sb->s_inode_list_lock); +out: + iput(toput_inode); + +} + +extern bool super_lock_shared(struct super_block *sb); +extern void super_unlock_shared(struct super_block *sb); +extern spinlock_t sb_lock; +extern void put_super(struct super_block *sb); + +static int krefilld(void *p) +{ + struct super_block *sb = (struct super_block *)p; + struct task_struct *tsk = current; + nodemask_t target_nodes; + unsigned long ret = 0; + + nodes_clear(target_nodes); + nodes_or(target_nodes, target_nodes, node_states[N_MEMORY]); + + if (WARN_ON(numa_set_interleave_policy(&target_nodes))) + goto out; + + if (WARN_ON(set_task_ioprio(current, IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)))) + goto out; + + tsk->flags |= PF_REFILL_THREAD; + + set_freezable(); + for ( ; ; ) { + bool born; + + krefilld_file_sleep(); + + ret = try_to_freeze(); + + if (kthread_should_stop()) + break; + + if (ret) + continue; + + if (!below_refill_threshold()) + continue; + + spin_lock(&sb_lock); + if (sb->s_count == 0) { + spin_unlock(&sb_lock); + continue; + } + + if (kthread_should_stop()) { + spin_unlock(&sb_lock); + break; + } + + sb->s_count++; + spin_unlock(&sb_lock); + + + born = super_lock_shared(sb); + + if (!below_hardware_queue_threshold(sb)) + goto skip; + + if (born && sb->s_root) + refill_pagecache_sb(sb); + +skip: + super_unlock_shared(sb); + + put_super(sb); + } + + tsk->flags &= ~(PF_REFILL_THREAD); +out: + /* + * Why? + * To prevent kthread_stop on already stopped kthreads and avoid UAF + */ + for ( ; ; ) { + set_current_state(TASK_INTERRUPTIBLE); + + if (kthread_should_stop()) + break; + schedule(); + } + + set_current_state(TASK_RUNNING); + return 0; + +} + +int krefilld_run(struct super_block *sb, const char *name) +{ + struct task_struct *tsk; + if (static_branch_unlikely(&cache_ext_refill_disabled)) + return 0; + + tsk = kthread_create(krefilld, sb, "krefilld_%s", name); + + if (IS_ERR(tsk)) + return PTR_ERR(tsk); + + sb->krefilld = tsk; + wake_up_process(tsk); + return 0; +} diff --git a/mm/filemap.c b/mm/filemap.c index 58dd2fde49a6..c4155d24a218 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -1995,7 +1995,7 @@ struct folio *__filemap_get_folio(struct address_space *mapping, pgoff_t index, } EXPORT_SYMBOL(__filemap_get_folio); -static inline struct folio *find_get_entry(struct xa_state *xas, pgoff_t max, +struct folio *find_get_entry(struct xa_state *xas, pgoff_t max, xa_mark_t mark) { struct folio *folio; diff --git a/mm/memcontrol.c b/mm/memcontrol.c index f6be624801e1..d88ac91860a1 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -1630,6 +1630,8 @@ static int memcg_page_state_unit(int item) case NR_FILE_CACHE_EXT: case NR_FILE_CACHE_EXT_KSWAPD_RECLAIMED: case NR_FILE_CACHE_EXT_REFAULT: + case NR_FILE_CACHE_EXT_REFILLED: + case REFILL_REFAULT_FILE: #endif return 1; case NR_KERNEL_STACK_KB: @@ -4982,6 +4984,8 @@ static const unsigned int memcg1_stats[] = { NR_FILE_CACHE_EXT, NR_FILE_CACHE_EXT_KSWAPD_RECLAIMED, NR_FILE_CACHE_EXT_REFAULT, + NR_FILE_CACHE_EXT_REFILLED, + REFILL_REFAULT_FILE, #endif }; @@ -5005,7 +5009,8 @@ static const char *const memcg1_stat_names[] = { "file_cache_ext", "file_cache_ext_reclaimed_kswapd", "file_cache_ext_refault", - + "file_cache_ext_refilled", + "refill_refault_file", #endif }; diff --git a/mm/mempolicy.c b/mm/mempolicy.c index b960310a70f9..7b6b7b011960 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -3317,6 +3317,11 @@ void numa_default_policy(void) do_set_mempolicy(MPOL_DEFAULT, 0, NULL); } +long numa_set_interleave_policy(nodemask_t *nodes) +{ + return do_set_mempolicy(MPOL_INTERLEAVE, 0, nodes); +} + /* * Parse and format mempolicy from/to strings */ diff --git a/mm/vmscan.c b/mm/vmscan.c index f521d2d82a69..f697d60a4dcb 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -782,6 +782,11 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio, shadow = workingset_eviction(folio, target_memcg); __filemap_remove_folio(folio, shadow); xa_unlock_irq(&mapping->i_pages); +#ifdef CONFIG_CACHE_EXT + if (current_is_cache_ext_reclaim()) + /* Mark inode for future refill*/ + set_bit(AS_CACHE_EXT, &mapping->flags); +#endif if (mapping_shrinkable(mapping)) inode_add_lru(mapping->host); spin_unlock(&mapping->host->i_lock); diff --git a/mm/vmstat.c b/mm/vmstat.c index b6d4a0f33de4..bde8f3f69e51 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c @@ -1254,6 +1254,8 @@ const char * const vmstat_text[] = { "nr_file_cache_ext", "nr_cache_ext_reclaimed_kswapd", "nr_cache_ext_refaulted", + "nr_cache_ext_refilled", + "refill_refault_file", #endif /* enum writeback_stat_item counters */ "nr_dirty_threshold", diff --git a/mm/workingset.c b/mm/workingset.c index 613705f8a4ec..2fc06a4cbcfe 100644 --- a/mm/workingset.c +++ b/mm/workingset.c @@ -220,7 +220,7 @@ static void *pack_shadow(int memcgid, pg_data_t *pgdat, unsigned long eviction, return xa_mk_value(eviction); } -static void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat, +void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat, unsigned long *evictionp, bool *workingsetp, bool *cache_extp) { unsigned long entry = xa_to_value(shadow); @@ -274,8 +274,24 @@ static void account_cache_ext_refault(void *shadow, long nr) mod_lruvec_state(lruvec, NR_FILE_CACHE_EXT_REFAULT, nr); } + +static void account_refault(struct lruvec *lruvec, int type, int nr, void *shadow) +{ + if (current_is_krefilld()) { + mod_lruvec_state(lruvec, REFILL_REFAULT_FILE, nr); + } else { + mod_lruvec_state(lruvec, WORKINGSET_REFAULT_BASE + type, nr); + if (type) + account_cache_ext_refault(shadow, nr); + } + +} + #else -static inline void account_cache_ext_refault(void *shadow, long nr) { } +static void account_refault(struct lruvec *lruvec, int type, int nr, void *shadow) +{ + mod_lruvec_state(lruvec, WORKINGSET_REFAULT_BASE + type, nr); +} #endif #ifdef CONFIG_LRU_GEN @@ -343,13 +359,10 @@ static void lru_gen_refault(struct folio *folio, void *shadow) recent = lru_gen_test_recent(shadow, type, &lruvec, &token, &workingset); - if (type) - account_cache_ext_refault(shadow, delta); - if (lruvec != folio_lruvec(folio)) goto unlock; - mod_lruvec_state(lruvec, WORKINGSET_REFAULT_BASE + type, delta); + account_refault(lruvec, type, delta, shadow); if (!recent) goto unlock; @@ -593,10 +606,7 @@ void workingset_refault(struct folio *folio, void *shadow) pgdat = folio_pgdat(folio); lruvec = mem_cgroup_lruvec(memcg, pgdat); - mod_lruvec_state(lruvec, WORKINGSET_REFAULT_BASE + file, nr); - - if (file) - account_cache_ext_refault(shadow, nr); + account_refault(lruvec, file, nr, shadow); if (!workingset_test_recent(shadow, file, &workingset)) goto out; @@ -660,10 +670,10 @@ struct list_lru shadow_nodes; void workingset_update_node(struct xa_node *node) { struct address_space *mapping; - + bool cache_ext_node = false; /* * Track non-empty nodes that contain only shadow entries; - * unlink those that contain pages or are being freed. + * unlink those that contain pages or are being freed or might be refilled. * * Avoid acquiring the list_lru lock when the nodes are * already where they should be. The list_empty() test is safe @@ -672,11 +682,31 @@ void workingset_update_node(struct xa_node *node) mapping = container_of(node->array, struct address_space, i_pages); lockdep_assert_held(&mapping->i_pages.xa_lock); +#ifdef CONFIG_CACHE_EXT + if (static_branch_unlikely(&cache_ext_refill_disabled)) + cache_ext_node = false; + else + cache_ext_node = test_bit(AS_CACHE_EXT, &mapping->flags); +#endif + if (node->count && node->count == node->nr_values) { - if (list_empty(&node->private_list)) { + + /* + * Do not add cache_ext nodes to shadow list + */ + if (list_empty(&node->private_list) && !cache_ext_node) { list_lru_add(&shadow_nodes, &node->private_list); __inc_lruvec_kmem_state(node, WORKINGSET_NODES); } + + /* + * If cache_ext node in shadow list - remove it + */ + if (!list_empty(&node->private_list) && cache_ext_node) { + list_lru_del(&shadow_nodes, &node->private_list); + __dec_lruvec_kmem_state(node, WORKINGSET_NODES); + } + } else { if (!list_empty(&node->private_list)) { list_lru_del(&shadow_nodes, &node->private_list); -- 2.34.1
From: Nikita Panov <panov.nikita@huawei.com> hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10164 ---------------------------------------- Co-developed-by: Alexander Grubnikov <alexander.grubnikov@huawei.com> Signed-off-by: Alexander Grubnikov <alexander.grubnikov@huawei.com> Co-developed-by: Anatoly Stepanov <stepanov.anatoly@huawei.com> Signed-off-by: Anatoly Stepanov <stepanov.anatoly@huawei.com> Co-developed-by: Nikita Panov <panov.nikita@huawei.com> Signed-off-by: Nikita Panov <panov.nikita@huawei.com> --- tools/cache_ext/.gitignore | 2 + tools/cache_ext/Makefile | 229 ++++++++++++++++++ tools/cache_ext/cache_ext_simple_fifo.bpf.c | 109 +++++++++ tools/cache_ext/cache_ext_simple_fifo.c | 52 ++++ .../cache_ext/include/cache_ext_common.bpf.h | 178 ++++++++++++++ 5 files changed, 570 insertions(+) create mode 100644 tools/cache_ext/.gitignore create mode 100644 tools/cache_ext/Makefile create mode 100644 tools/cache_ext/cache_ext_simple_fifo.bpf.c create mode 100644 tools/cache_ext/cache_ext_simple_fifo.c create mode 100644 tools/cache_ext/include/cache_ext_common.bpf.h diff --git a/tools/cache_ext/.gitignore b/tools/cache_ext/.gitignore new file mode 100644 index 000000000000..d6264fe1c8cd --- /dev/null +++ b/tools/cache_ext/.gitignore @@ -0,0 +1,2 @@ +tools/ +build/ diff --git a/tools/cache_ext/Makefile b/tools/cache_ext/Makefile new file mode 100644 index 000000000000..29db925673c0 --- /dev/null +++ b/tools/cache_ext/Makefile @@ -0,0 +1,229 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copied from tools/sched_ext/Makefile +include ../build/Build.include +include ../scripts/Makefile.arch +include ../scripts/Makefile.include + +all: all_targets + +ifneq ($(LLVM),) +ifneq ($(filter %/,$(LLVM)),) +LLVM_PREFIX := $(LLVM) +else ifneq ($(filter -%,$(LLVM)),) +LLVM_SUFFIX := $(LLVM) +endif + +CLANG_TARGET_FLAGS_arm := arm-linux-gnueabi +CLANG_TARGET_FLAGS_arm64 := aarch64-linux-gnu +CLANG_TARGET_FLAGS_hexagon := hexagon-linux-musl +CLANG_TARGET_FLAGS_m68k := m68k-linux-gnu +CLANG_TARGET_FLAGS_mips := mipsel-linux-gnu +CLANG_TARGET_FLAGS_powerpc := powerpc64le-linux-gnu +CLANG_TARGET_FLAGS_riscv := riscv64-linux-gnu +CLANG_TARGET_FLAGS_s390 := s390x-linux-gnu +CLANG_TARGET_FLAGS_x86 := x86_64-linux-gnu +CLANG_TARGET_FLAGS := $(CLANG_TARGET_FLAGS_$(ARCH)) + +ifeq ($(CROSS_COMPILE),) +ifeq ($(CLANG_TARGET_FLAGS),) +$(error Specify CROSS_COMPILE or add '--target=' option to lib.mk) +else +CLANG_FLAGS += --target=$(CLANG_TARGET_FLAGS) +endif # CLANG_TARGET_FLAGS +else +CLANG_FLAGS += --target=$(notdir $(CROSS_COMPILE:%-=%)) +endif # CROSS_COMPILE + +CC := $(LLVM_PREFIX)clang$(LLVM_SUFFIX) $(CLANG_FLAGS) -fintegrated-as +else +CC := $(CROSS_COMPILE)gcc +endif # LLVM + +CURDIR := $(abspath .) +TOOLSDIR := $(abspath ..) +LIBDIR := $(TOOLSDIR)/lib +BPFDIR := $(LIBDIR)/bpf +TOOLSINCDIR := $(TOOLSDIR)/include +BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool +APIDIR := $(TOOLSINCDIR)/uapi +GENDIR := $(abspath ../../include/generated) +GENHDR := $(GENDIR)/autoconf.h + +ifeq ($(O),) +OUTPUT_DIR := $(CURDIR)/build +else +OUTPUT_DIR := $(O)/build +endif # O +OBJ_DIR := $(OUTPUT_DIR)/obj +INCLUDE_DIR := $(OUTPUT_DIR)/include +BPFOBJ_DIR := $(OBJ_DIR)/libbpf +CACHE_EXT_OBJ_DIR := $(OBJ_DIR)/cache_ext +BINDIR := $(OUTPUT_DIR)/bin +BPFOBJ := $(BPFOBJ_DIR)/libbpf.a +ifneq ($(CROSS_COMPILE),) +HOST_BUILD_DIR := $(OBJ_DIR)/host +HOST_OUTPUT_DIR := host-tools +HOST_INCLUDE_DIR := $(HOST_OUTPUT_DIR)/include +else +HOST_BUILD_DIR := $(OBJ_DIR) +HOST_OUTPUT_DIR := $(OUTPUT_DIR) +HOST_INCLUDE_DIR := $(INCLUDE_DIR) +endif +HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a +RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids +DEFAULT_BPFTOOL := $(HOST_OUTPUT_DIR)/sbin/bpftool + +VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \ + $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \ + ../../vmlinux \ + /sys/kernel/btf/vmlinux \ + /boot/vmlinux-$(shell uname -r) +VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS)))) +ifeq ($(VMLINUX_BTF),) +$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)") +endif + +BPFTOOL ?= $(DEFAULT_BPFTOOL) + +ifneq ($(wildcard $(GENHDR)),) + GENFLAGS := -DHAVE_GENHDR +endif + +CFLAGS += -g -O2 -rdynamic -pthread -Wall -Werror $(GENFLAGS) \ + -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR) \ + -I$(TOOLSINCDIR) -I$(APIDIR) -I$(CURDIR)/include + +# Silence some warnings when compiled with clang +ifneq ($(LLVM),) +CFLAGS += -Wno-unused-command-line-argument +endif + +LDFLAGS = -lelf -lz -lpthread -static + +IS_LITTLE_ENDIAN = $(shell $(CC) -dM -E - </dev/null | \ + grep 'define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__') + +# Get Clang's default includes on this system, as opposed to those seen by +# '-target bpf'. This fixes "missing" files on some architectures/distros, +# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +define get_sys_includes +$(shell $(1) -v -E - </dev/null 2>&1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \ +$(shell $(1) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}') +endef + +BPF_CFLAGS = -g -D__TARGET_ARCH_$(SRCARCH) \ + $(if $(IS_LITTLE_ENDIAN),-mlittle-endian,-mbig-endian) \ + -I$(CURDIR)/include -I$(CURDIR)/include/bpf-compat \ + -I$(INCLUDE_DIR) -I$(APIDIR) \ + -I../../include \ + $(call get_sys_includes,$(CLANG)) \ + -Wall -Wno-compare-distinct-pointer-types \ + -O2 -mcpu=v3 + +# sort removes libbpf duplicates when not cross-building +MAKE_DIRS := $(sort $(OBJ_DIR)/libbpf $(HOST_BUILD_DIR)/libbpf \ + $(HOST_BUILD_DIR)/bpftool $(HOST_BUILD_DIR)/resolve_btfids \ + $(INCLUDE_DIR) $(CACHE_EXT_OBJ_DIR) $(BINDIR)) + +$(MAKE_DIRS): + $(call msg,MKDIR,,$@) + $(Q)mkdir -p $@ + +$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \ + $(APIDIR)/linux/bpf.h \ + | $(OBJ_DIR)/libbpf + $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(OBJ_DIR)/libbpf/ \ + EXTRA_CFLAGS='-g -O0 -fPIC' \ + DESTDIR=$(OUTPUT_DIR) prefix= all install_headers + +$(DEFAULT_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) \ + $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/bpftool + $(Q)$(MAKE) $(submake_extras) -C $(BPFTOOLDIR) \ + ARCH= CROSS_COMPILE= CC=$(HOSTCC) LD=$(HOSTLD) \ + EXTRA_CFLAGS='-g -O0' \ + OUTPUT=$(HOST_BUILD_DIR)/bpftool/ \ + LIBBPF_OUTPUT=$(HOST_BUILD_DIR)/libbpf/ \ + LIBBPF_DESTDIR=$(HOST_OUTPUT_DIR)/ \ + prefix= DESTDIR=$(HOST_OUTPUT_DIR)/ install-bin + +$(INCLUDE_DIR)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR) +ifeq ($(VMLINUX_H),) + $(call msg,GEN,,$@) + $(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@ +else + $(call msg,CP,,$@) + $(Q)cp "$(VMLINUX_H)" $@ +endif + +$(CACHE_EXT_OBJ_DIR)/%.bpf.o: %.bpf.c $(INCLUDE_DIR)/vmlinux.h include/*.h \ + | $(BPFOBJ) $(CACHE_EXT_OBJ_DIR) + $(call msg,CLNG-BPF,,$(notdir $@)) + $(Q)$(CLANG) $(BPF_CFLAGS) -target bpf -c $< -o $@ + +$(INCLUDE_DIR)/%.bpf.skel.h: $(CACHE_EXT_OBJ_DIR)/%.bpf.o $(INCLUDE_DIR)/vmlinux.h $(BPFTOOL) + $(eval cache_ext_policy=$(notdir $@)) + $(call msg,GEN-SKEL,,$(cache_ext_policy)) + $(Q)$(BPFTOOL) gen object $(<:.o=.linked1.o) $< + $(Q)$(BPFTOOL) gen object $(<:.o=.linked2.o) $(<:.o=.linked1.o) + $(Q)$(BPFTOOL) gen object $(<:.o=.linked3.o) $(<:.o=.linked2.o) + $(Q)diff $(<:.o=.linked2.o) $(<:.o=.linked3.o) + $(Q)$(BPFTOOL) gen skeleton $(<:.o=.linked3.o) name $(subst .bpf.skel.h,,$(cache_ext_policy)) > $@ + $(Q)$(BPFTOOL) gen subskeleton $(<:.o=.linked3.o) name $(subst .bpf.skel.h,,$(cache_ext_policy)) > $(@:.skel.h=.subskel.h) + +CACHE_EXT_COMMON_DEPS := include/cache_ext_common.bpf.h | $(BINDIR) + +c-cache-ext-policy-targets = cache_ext_simple_fifo + +$(addprefix $(BINDIR)/,$(c-cache-ext-policy-targets)): \ + $(BINDIR)/%: \ + $(filter-out %.bpf.c,%.c) \ + $(INCLUDE_DIR)/%.bpf.skel.h \ + $(CACHE_EXT_COMMON_DEPS) + $(eval cache_ext_policy=$(notdir $@)) + $(CC) $(CFLAGS) -c $(cache_ext_policy).c -o $(CACHE_EXT_OBJ_DIR)/$(cache_ext_policy).o + $(CC) -o $@ $(CACHE_EXT_OBJ_DIR)/$(cache_ext_policy).o $(HOST_BPFOBJ) $(LDFLAGS) + +$(c-cache-ext-policy-targets): %: $(BINDIR)/% + +install: all + $(Q)mkdir -p $(DESTDIR)/usr/local/bin/ + $(Q)cp $(BINDIR)/* $(DESTDIR)/usr/local/bin/ + +clean: + rm -rf $(OUTPUT_DIR) $(HOST_OUTPUT_DIR) + rm -f *.o *.bpf.o *.bpf.skel.h *.bpf.subskel.h + rm -f $(c-cache-ext-policy-targets) + +help: + @echo 'Building targets' + @echo '================' + @echo '' + @echo ' all - Compile all policies' + @echo '' + @echo 'Alternatively, you may compile individual policies:' + @echo '' + @printf ' %s\n' $(c-cache-ext-policy-targets) + @echo '' + @echo 'For any policy build target, you may specify an alternative' + @echo 'build output path with the O= environment variable. For example:' + @echo '' + @echo ' O=/tmp/cache_ext make all' + @echo '' + @echo 'will compile all policies, and emit the build artifacts to' + @echo '/tmp/cache_ext/build.' + @echo '' + + +all_targets: $(c-cache-ext-policy-targets) + +.PHONY: all all_targets $(c-cache-ext-policy-targets) clean help + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.bpf.skel.h, .bpf.o, etc) targets +.SECONDARY: diff --git a/tools/cache_ext/cache_ext_simple_fifo.bpf.c b/tools/cache_ext/cache_ext_simple_fifo.bpf.c new file mode 100644 index 000000000000..5aa7786a4bee --- /dev/null +++ b/tools/cache_ext/cache_ext_simple_fifo.bpf.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_core_read.h> + +#include "cache_ext_common.bpf.h" + +char _license[] SEC("license") = "GPL"; + +u64 simple_fifo_list; + +unsigned long isolated_stats[4] = {}; +const char stat_str[16] = "isolated: "; + +#define MAX_SCAN_SIZE (1024) + +SEC("struct_ops.s/simple_fifo_init") +s32 BPF_PROG(simple_fifo_init, struct mem_cgroup *memcg) +{ + simple_fifo_list = bpf_cache_ext_ds_registry_new_list(memcg); + + if (simple_fifo_list == 0) + return -EINVAL; + + return 0; +} + +static int bpf_simple_fifo_evict_cb(int idx, struct cache_ext_list_node *a) +{ + bool can_be_reclaimed = folio_test_lru(a->folio) && !folio_test_dirty(a->folio) && + !folio_test_writeback(a->folio); + + if (!can_be_reclaimed) { + if (idx < MAX_SCAN_SIZE) + return CACHE_EXT_CONTINUE_ITER; + else + return CACHE_EXT_STOP_ITER; + } + + return CACHE_EXT_ISOLATE_NODE; +} + +static void update_stats(int node, unsigned long nr) +{ + if (node >= 0 && node <= 3) + __sync_fetch_and_add(&isolated_stats[node], nr); +} + +SEC("struct_ops/simple_fifo_isolate_folios") +int BPF_PROG(simple_fifo_isolate_folios, struct cache_ext_isolation_ctx *eviction_ctx, + struct mem_cgroup *memcg) +{ + unsigned long isolated; + + + bpf_cache_ext_list_iterate(memcg, simple_fifo_list, bpf_simple_fifo_evict_cb, eviction_ctx); + + isolated = eviction_ctx->nr_base_pages_isolated; + + update_stats(eviction_ctx->numa_node_id, isolated); + + return 0; +} + +SEC("struct_ops/simple_fifo_folio_accessed") +void BPF_PROG(simple_fifo_folio_accessed, struct folio *folio) +{ + bpf_cache_ext_list_move(simple_fifo_list, folio, true); +} + +SEC("struct_ops/simple_fifo_folio_evicted") +s64 BPF_PROG(simple_fifo_folio_evicted, struct folio *folio) +{ + return bpf_cache_ext_list_del(folio); +} + +SEC("struct_ops/simple_fifo_folio_added") +void BPF_PROG(simple_fifo_folio_added, struct folio *folio, s64 cookie) +{ + bpf_cache_ext_list_add_tail(simple_fifo_list, folio); +} + +SEC("struct_ops/simple_fifo_get_statistics") +void BPF_PROG(simple_fifo_get_statistics, struct lruvec *lruvec, + struct cache_ext_stats *stats) +{ + int node = BPF_CORE_READ(lruvec, pgdat, node_id); + char *stat = (char *)stats + offsetof(struct cache_ext_stats, names); + + if (node < 0 || node >= 4) + return; + + stats->n = 1; + + __builtin_memcpy(stat, stat_str, sizeof(stat_str)); + stats->values[0] = READ_ONCE(isolated_stats[node]); + +} + +SEC(".struct_ops.link") +struct cache_ext_ops simple_fifo_ops = { + .init = simple_fifo_init, + .isolate_folios = simple_fifo_isolate_folios, + .folio_accessed = simple_fifo_folio_accessed, + .folio_evicted = simple_fifo_folio_evicted, + .folio_added = simple_fifo_folio_added, + .get_statistics = simple_fifo_get_statistics, +}; diff --git a/tools/cache_ext/cache_ext_simple_fifo.c b/tools/cache_ext/cache_ext_simple_fifo.c new file mode 100644 index 000000000000..85a2e2fa463b --- /dev/null +++ b/tools/cache_ext/cache_ext_simple_fifo.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <argp.h> +#include <bpf/bpf.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdint.h> +#include <inttypes.h> + +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; + +#include "cache_ext_simple_fifo.bpf.skel.h" + +int main(int argc, char **argv) +{ + struct cache_ext_simple_fifo *skel = NULL; + struct bpf_link *link = NULL; + int ret = 1; + + libbpf_set_strict_mode(LIBBPF_STRICT_ALL); + + + skel = cache_ext_simple_fifo__open(); + if (!skel) { + perror("Failed to open BPF skeleton"); + goto cleanup; + } + + if (cache_ext_simple_fifo__load(skel)) { + perror("Failed to load BPF skeleton"); + goto cleanup; + } + + link = bpf_map__attach_struct_ops(skel->maps.simple_fifo_ops); + if (link == NULL) { + perror("Failed to attach cache_ext_ops to cgroup"); + goto cleanup; + } + + system("sleep infinity"); + ret = 0; +cleanup: + bpf_link__destroy(link); + return ret; +} diff --git a/tools/cache_ext/include/cache_ext_common.bpf.h b/tools/cache_ext/include/cache_ext_common.bpf.h new file mode 100644 index 000000000000..ab2ff78c3162 --- /dev/null +++ b/tools/cache_ext/include/cache_ext_common.bpf.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _CACHE_EXT_COMMON_BPF_H +#define _CACHE_EXT_COMMON_BPF_H + +#define U32_MAX ((u32)~0U) +#define U64_MAX ((u64)~0ULL) +#define S64_MAX ((s64)(U64_MAX >> 1)) + +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) < (b) ? (a) : (b)) + +#define barrier() \ + do { \ + asm volatile("" ::: "memory"); \ + } while (0) + +typedef __u8 __attribute__((__may_alias__)) __u8_alias_t; +typedef __u16 __attribute__((__may_alias__)) __u16_alias_t; +typedef __u32 __attribute__((__may_alias__)) __u32_alias_t; +typedef __u64 __attribute__((__may_alias__)) __u64_alias_t; + +#define EINVAL 22 + + +static __always_inline void __read_once_size(const volatile void *p, void *res, int size) +{ + switch (size) { + case 1: + *(__u8_alias_t *) res = *(volatile __u8_alias_t *) p; + break; + case 2: + *(__u16_alias_t *) res = *(volatile __u16_alias_t *) p; + break; + case 4: + *(__u32_alias_t *) res = *(volatile __u32_alias_t *) p; + break; + case 8: + *(__u64_alias_t *) res = *(volatile __u64_alias_t *) p; + break; + default: + barrier(); + __builtin_memcpy((void *)res, (const void *)p, size); + barrier(); + } +} + +#define READ_ONCE(x) \ +({ \ + union { typeof(x) __val; char __c[1]; } __u = \ + { .__c = { 0 } }; \ + __read_once_size(&(x), __u.__c, sizeof(x)); \ + __u.__val; \ +}) + + +static __always_inline void __write_once_size(volatile void *p, void *res, int size) +{ + switch (size) { + case 1: + *(volatile __u8_alias_t *) p = *(__u8_alias_t *) res; + break; + case 2: + *(volatile __u16_alias_t *) p = *(__u16_alias_t *) res; + break; + case 4: + *(volatile __u32_alias_t *) p = *(__u32_alias_t *) res; + break; + case 8: + *(volatile __u64_alias_t *) p = *(__u64_alias_t *) res; + break; + default: + barrier(); + __builtin_memcpy((void *)p, (const void *)res, size); + barrier(); + } +} + +#define WRITE_ONCE(x, val) \ +({ \ + union { typeof(x) __val; char __c[1]; } __u = \ + { .__val = (val) }; \ + __write_once_size(&(x), __u.__c, sizeof(x)); \ + __u.__val; \ +}) + +int bpf_cache_ext_list_add(u64 list, struct folio *folio) __ksym; +int bpf_cache_ext_list_add_tail(u64 list, struct folio *folio) __ksym; +int bpf_cache_ext_list_del(struct folio *folio) __ksym; +int bpf_cache_ext_list_move(u64 list, struct folio *folio, bool tail) __ksym; +int bpf_cache_ext_list_iterate(struct mem_cgroup *memcg, u64 list, + int(iter_fn)(int idx, struct cache_ext_list_node *node), + struct cache_ext_isolation_ctx *ctx) __ksym; +int bpf_cache_ext_list_iterate_extended(struct mem_cgroup *memcg, u64 list, + int(iter_fn)(int idx, struct cache_ext_list_node *node), + struct cache_ext_iterate_opts *opts, + struct cache_ext_isolation_ctx *ctx) __ksym; +int bpf_cache_ext_list_sample(struct mem_cgroup *memcg, u64 list, + s32(score_fn)(struct cache_ext_list_node *a), + struct sampling_options *opts, + struct cache_ext_isolation_ctx *ctx) __ksym; +u64 bpf_cache_ext_ds_registry_new_list(struct mem_cgroup *memcg) __ksym; +bool bpf_cache_ext_filename_strstr(const char *str, struct folio *folio) __ksym; +bool bpf_cache_ext_list_empty(u64 list_id, struct mem_cgroup *memcg, int nid) __ksym; +int bpf_folio_nid(struct folio *folio) __ksym; +int bpf_cache_ext_list_splice(u64 list_id, u64 head_id, + struct mem_cgroup *memcg, int nid) __ksym; +int bpf_cache_ext_list_splice_tail(u64 list_id, u64 head_id, + struct mem_cgroup *memcg, int nid) __ksym; + +#define BITS_PER_LONG 64 +#define BIT_WORD(nr) ((nr) / BITS_PER_LONG) + +static __always_inline bool +generic_test_bit(unsigned long nr, const volatile unsigned long *addr) +{ + /* + * Unlike the bitops with the '__' prefix above, this one *is* atomic, + * so `volatile` must always stay here with no cast-aways. See + * `Documentation/atomic_bitops.txt` for the details. + */ + return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1))); +} + +static inline unsigned long *folio_flags(struct folio *folio, unsigned int n) +{ + struct page *page = &folio->page; + + return &page[n].flags; +} + + +static inline bool folio_test_uptodate(struct folio *folio) +{ + bool ret = generic_test_bit(PG_uptodate, folio_flags(folio, 0)); + + return ret; +} + +static inline bool folio_test_lru(struct folio *folio) +{ + bool ret = generic_test_bit(PG_lru, folio_flags(folio, 0)); + + return ret; +} + +static inline bool folio_test_dirty(struct folio *folio) +{ + bool ret = generic_test_bit(PG_dirty, folio_flags(folio, 0)); + + return ret; +} + +static inline bool folio_test_reclaim(struct folio *folio) +{ + bool ret = generic_test_bit(PG_reclaim, folio_flags(folio, 0)); + + return ret; +} + +static inline bool folio_test_writeback(struct folio *folio) +{ + bool ret = generic_test_bit(PG_writeback, folio_flags(folio, 0)); + + return ret; +} + +static inline bool folio_test_locked(struct folio *folio) +{ + bool ret = generic_test_bit(PG_locked, folio_flags(folio, 0)); + + return ret; +} + +#endif /* _CACHE_EXT_COMMON_BPF_H */ -- 2.34.1
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://atomgit.com/openeuler/kernel/merge_requests/30861 邮件列表地址:https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/PMP... FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://atomgit.com/openeuler/kernel/merge_requests/30861 Mailing list address: https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/PMP...
participants (2)
-
Nikita Panov -
patchwork bot