From 47154277c9e84e7403a28280971abe85634178ae Mon Sep 17 00:00:00 2001 From: wonghiu45 Date: Wed, 10 May 2023 13:50:53 +0800 Subject: [PATCH] mm:add purgeable ashmem Signed-off-by: wonghiu45 --- drivers/staging/android/ashmem.c | 203 +++++++++++++++++++++++++- drivers/staging/android/ashmem.h | 17 +++ drivers/staging/android/uapi/ashmem.h | 9 ++ include/linux/memcheck.h | 5 + mm/Kconfig | 6 + mm/Makefile | 1 + mm/purgeable_ashmem_trigger.c | 122 ++++++++++++++++ mm/vmscan.c | 8 +- 8 files changed, 364 insertions(+), 7 deletions(-) create mode 100644 mm/purgeable_ashmem_trigger.c diff --git a/drivers/staging/android/ashmem.c b/drivers/staging/android/ashmem.c index 764e9d9c0830..e1a96e7e4433 100644 --- a/drivers/staging/android/ashmem.c +++ b/drivers/staging/android/ashmem.c @@ -31,6 +31,13 @@ #define ASHMEM_NAME_PREFIX_LEN (sizeof(ASHMEM_NAME_PREFIX) - 1) #define ASHMEM_FULL_NAME_LEN (ASHMEM_NAME_LEN + ASHMEM_NAME_PREFIX_LEN) +#ifdef CONFIG_PURGEABLE_ASHMEM +#define PURGEABLE_ASHMEM_INIT_REFCOUNT 1 +#define PURGEABLE_ASHMEM_UNPIN_REFCOUNT 0 +#define PURGEABLE_ASHMEM_PIN_OFFSET 0 +#define PURGEABLE_ASHMEM_PIN_LEN 0 +#endif + /** * struct ashmem_area - The anonymous shared memory area * @name: The optional name in /proc/pid/maps @@ -50,6 +57,13 @@ struct ashmem_area { struct file *file; size_t size; unsigned long prot_mask; +#ifdef CONFIG_PURGEABLE_ASHMEM + bool is_purgeable; + bool purged; + unsigned int id; + unsigned int create_time; + int ref_count; +#endif }; /** @@ -155,6 +169,12 @@ static inline bool range_before_page(struct ashmem_range *range, return range->pgend < page; } +#ifdef CONFIG_PURGEABLE_ASHMEM +static inline bool is_purgeable_ashmem(const struct ashmem_area *asma) +{ + return (asma && asma->is_purgeable); +} +#endif #define PROT_MASK (PROT_EXEC | PROT_READ | PROT_WRITE) /** @@ -275,7 +295,13 @@ static int ashmem_open(struct inode *inode, struct file *file) memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN); asma->prot_mask = PROT_MASK; file->private_data = asma; - +#ifdef CONFIG_PURGEABLE_ASHMEM + asma->ref_count = PURGEABLE_ASHMEM_INIT_REFCOUNT; + asma->is_purgeable = false; + asma->purged = false; + asma->id = current->pid; + asma->create_time = ktime_get(); +#endif return 0; } @@ -506,6 +532,10 @@ ashmem_shrink_scan(struct shrinker *shrink, struct shrink_control *sc) get_file(f); atomic_inc(&ashmem_shrink_inflight); range->purged = ASHMEM_WAS_PURGED; +#ifdef CONFIG_PURGEABLE_ASHMEM + if (is_purgeable_ashmem(range->asma)) + range->asma->purged = true; +#endif lru_del(range); freed += range_size(range); @@ -647,7 +677,13 @@ static int ashmem_pin(struct ashmem_area *asma, size_t pgstart, size_t pgend, { struct ashmem_range *range, *next; int ret = ASHMEM_NOT_PURGED; - +#ifdef CONFIG_PURGEABLE_ASHMEM + if (is_purgeable_ashmem(asma)) { + asma->ref_count++; + if (asma->ref_count > 1) + return PM_SUCCESS; + } +#endif list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) { /* moved past last applicable page; we can short circuit */ if (range_before_page(range, pgstart)) @@ -715,7 +751,17 @@ static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend, { struct ashmem_range *range, *next; unsigned int purged = ASHMEM_NOT_PURGED; - +#ifdef CONFIG_PURGEABLE_ASHMEM + if (is_purgeable_ashmem(asma)) { + if (asma->ref_count > PURGEABLE_ASHMEM_UNPIN_REFCOUNT && + !(--asma->ref_count == PURGEABLE_ASHMEM_UNPIN_REFCOUNT)) + return PM_SUCCESS; + if (asma->ref_count < PURGEABLE_ASHMEM_UNPIN_REFCOUNT) { + asma->ref_count = PURGEABLE_ASHMEM_UNPIN_REFCOUNT; + return PM_FAIL; + } + } +#endif restart: list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) { /* short circuit: this is our insertion point */ @@ -752,7 +798,10 @@ static int ashmem_get_pin_status(struct ashmem_area *asma, size_t pgstart, { struct ashmem_range *range; int ret = ASHMEM_IS_PINNED; - +#ifdef CONFIG_PURGEABLE_ASHMEM + if (is_purgeable_ashmem(asma)) + return asma->ref_count; +#endif list_for_each_entry(range, &asma->unpinned_list, unpinned) { if (range_before_page(range, pgstart)) break; @@ -784,7 +833,15 @@ static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd, mutex_lock(&ashmem_mutex); wait_event(ashmem_shrink_wait, !atomic_read(&ashmem_shrink_inflight)); - +#ifdef CONFIG_PURGEABLE_ASHMEM + if (is_purgeable_ashmem(asma)) { + if (pin.offset != PURGEABLE_ASHMEM_PIN_OFFSET || + pin.len != PURGEABLE_ASHMEM_PIN_LEN) { + ret = -EINVAL; + goto out_unlock; + } + } +#endif if (!asma->file) goto out_unlock; @@ -823,7 +880,120 @@ static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd, return ret; } +#ifdef CONFIG_PURGEABLE_ASHMEM +void ashmem_shrinkall() +{ + struct shrink_control sc = { + .gfp_mask = GFP_KERNEL, + .nr_to_scan = LONG_MAX, + }; + + ashmem_shrink_scan(&ashmem_shrinker, &sc); + return; +} + +void ashmem_shrink_by_id(const unsigned int ashmem_id, const unsigned int create_time) +{ + struct ashmem_range *range, *next; + bool found = false; + + if (!mutex_trylock(&ashmem_mutex)) + return; + + list_for_each_entry_safe(range, next, &ashmem_lru_list, lru) { + if (!is_purgeable_ashmem(range->asma)) + continue; + if (range->asma->id != ashmem_id || + range->asma->create_time != create_time) + continue; + found = true; + range->asma->purged = true; + break; + } + if (!found) { + goto out_unlock; + } + loff_t start = range->pgstart * PAGE_SIZE; + loff_t end = (range->pgend + 1) * PAGE_SIZE; + struct file *f = range->asma->file; + + if (f == NULL) + goto out_unlock; + get_file(f); + atomic_inc(&ashmem_shrink_inflight); + range->purged = ASHMEM_WAS_PURGED; + + lru_del(range); + mutex_unlock(&ashmem_mutex); + f->f_op->fallocate(f, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, + start, end - start); + fput(f); + if (atomic_dec_and_test(&ashmem_shrink_inflight)) + wake_up_all(&ashmem_shrink_wait); + return; + +out_unlock: + mutex_unlock(&ashmem_mutex); + return; +} + +static bool is_ashmem_unpin(struct ashmem_area *asma) +{ + struct ashmem_range *range, *next; + int count = 0; + mutex_lock(&ashmem_mutex); + if (asma == NULL) { + mutex_unlock(&ashmem_mutex); + return false; + } + list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) + count++; + mutex_unlock(&ashmem_mutex); + return count > 0 ? true : false; +} + +static long purgeable_ashmem_cmd(struct ashmem_area *asma, unsigned int cmd) +{ + int ret = -EINVAL; + if (!is_purgeable_ashmem(asma)) + return ret; + mutex_lock(&ashmem_mutex); + switch (cmd) { + case ASHMEM_GET_PURGEABLE: + ret = asma->is_purgeable; + break; + case PURGEABLE_ASHMEM_IS_PURGED: + ret = asma->purged; + break; + case PURGEABLE_ASHMEM_REBUILD_SUCCESS: + asma->purged = false; + ret = PM_SUCCESS; + break; + } + mutex_unlock(&ashmem_mutex); + return ret; +} +bool get_purgeable_ashmem_metadata(struct file *f, + struct purgeable_ashmem_metadata *pmdata) +{ + struct ashmem_area *asma = f->private_data; + mutex_lock(&ashmem_mutex); + if (!asma) { + mutex_unlock(&ashmem_mutex); + return false; + } + pmdata->name = asma->name; + pmdata->size = asma->size; + pmdata->refc = asma->ref_count; + pmdata->purged = asma->purged; + pmdata->is_purgeable = asma->is_purgeable; + pmdata->id = asma->id; + pmdata->create_time = asma->create_time; + mutex_unlock(&ashmem_mutex); + return true; +} +#endif static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ashmem_area *asma = file->private_data; @@ -870,8 +1040,26 @@ static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ashmem_shrink_scan(&ashmem_shrinker, &sc); } break; +#ifdef CONFIG_PURGEABLE_ASHMEM + case ASHMEM_SET_PURGEABLE: + if (is_ashmem_unpin(asma)) { + ret = PM_FAIL; + break; + } + mutex_lock(&ashmem_mutex); + if (asma) { + asma->is_purgeable = true; + ret = PM_SUCCESS; + } + mutex_unlock(&ashmem_mutex); + break; + case ASHMEM_GET_PURGEABLE: /* fall through */ + case PURGEABLE_ASHMEM_IS_PURGED: /* fall through */ + case PURGEABLE_ASHMEM_REBUILD_SUCCESS: + ret = purgeable_ashmem_cmd(asma, cmd); + break; +#endif } - return ret; } @@ -985,6 +1173,9 @@ static int __init ashmem_init(void) goto out_demisc; } init_ashmem_process_info(); +#ifdef CONFIG_PURGEABLE_ASHMEM + init_purgeable_ashmem_trigger(); +#endif pr_info("initialized\n"); return 0; diff --git a/drivers/staging/android/ashmem.h b/drivers/staging/android/ashmem.h index 601441d87f1f..3591e43729f5 100644 --- a/drivers/staging/android/ashmem.h +++ b/drivers/staging/android/ashmem.h @@ -26,4 +26,21 @@ size_t get_ashmem_size_by_file(struct file *f); char *get_ashmem_name_by_file(struct file *f); void ashmem_mutex_lock(void); void ashmem_mutex_unlock(void); + +#ifdef CONFIG_PURGEABLE_ASHMEM +struct purgeable_ashmem_metadata { + char *name; + size_t size; + int refc; + bool purged; + bool is_purgeable; + unsigned int id; + unsigned int create_time; +}; +void ashmem_shrinkall(void); +void ashmem_shrink_by_id(const unsigned int ashmem_id, + const unsigned int create_time); +bool get_purgeable_ashmem_metadata(struct file *f, + struct purgeable_ashmem_metadata *pmdata); +#endif #endif /* _LINUX_ASHMEM_H */ diff --git a/drivers/staging/android/uapi/ashmem.h b/drivers/staging/android/uapi/ashmem.h index 5442e0019dcd..2d4673b609d6 100644 --- a/drivers/staging/android/uapi/ashmem.h +++ b/drivers/staging/android/uapi/ashmem.h @@ -42,4 +42,13 @@ struct ashmem_pin { #define ASHMEM_GET_PIN_STATUS _IO(__ASHMEMIOC, 9) #define ASHMEM_PURGE_ALL_CACHES _IO(__ASHMEMIOC, 10) +#ifdef CONFIG_PURGEABLE_ASHMEM +#define PM_SUCCESS 0 +#define PM_FAIL 1 + +#define ASHMEM_SET_PURGEABLE _IO(__ASHMEMIOC, 11) +#define ASHMEM_GET_PURGEABLE _IO(__ASHMEMIOC, 12) +#define PURGEABLE_ASHMEM_IS_PURGED _IO(__ASHMEMIOC, 13) +#define PURGEABLE_ASHMEM_REBUILD_SUCCESS _IO(__ASHMEMIOC, 14) +#endif #endif /* _UAPI_LINUX_ASHMEM_H */ diff --git a/include/linux/memcheck.h b/include/linux/memcheck.h index 7f5fe3a854db..087cd760ece4 100644 --- a/include/linux/memcheck.h +++ b/include/linux/memcheck.h @@ -13,5 +13,10 @@ void init_ashmem_process_info(void); static inline void init_ashmem_process_info(void) {} #endif +#ifdef CONFIG_PURGEABLE_ASHMEM +void init_purgeable_ashmem_trigger(void); +#else +static inline void init_purgeable_ashmem_trigger(void) {} +#endif #endif /* _MEMCHECK_H */ diff --git a/mm/Kconfig b/mm/Kconfig index 66bcc5ac920e..8d2df75f1860 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -981,4 +981,10 @@ config MEM_PURGEABLE_DEBUG help Debug info for purgeable memory +config PURGEABLE_ASHMEM + bool "Purgeable memory feature for ashmem" + default n + depends on MEM_PURGEABLE + help + Support purgeable ashmem for process endmenu diff --git a/mm/Makefile b/mm/Makefile index b0d224f52b2c..b009914a2bac 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -129,4 +129,5 @@ obj-$(CONFIG_HYPERHOLD_MEMCG) += memcg_control.o obj-$(CONFIG_HYPERHOLD_ZSWAPD) += zswapd.o zswapd_control.o obj-$(CONFIG_RECLAIM_ACCT) += reclaim_acct.o reclaimacct_show.o obj-$(CONFIG_MEM_PURGEABLE) += purgeable.o +obj-$(CONFIG_PURGEABLE_ASHMEM) += purgeable_ashmem_trigger.o obj-$(CONFIG_MEMORY_MONITOR) += memory_monitor.o diff --git a/mm/purgeable_ashmem_trigger.c b/mm/purgeable_ashmem_trigger.c new file mode 100644 index 000000000000..2d41bb0f5fef --- /dev/null +++ b/mm/purgeable_ashmem_trigger.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mm/purgeable_ashmem_trigger.c + * + * Copyright (c) 2023 Huawei Technologies Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include "securec.h" +#include "../drivers/staging/android/ashmem.h" + +#define PURGEABLE_ASHMEM_SHRINKALL_ARG 0 + +struct purgeable_ashmem_trigger_args { + struct seq_file *seq; + struct task_struct *tsk; +}; + +static int purgeable_ashmem_trigger_cb(const void *data, + struct file *f, unsigned int fd) +{ + const struct purgeable_ashmem_trigger_args *args = data; + struct task_struct *tsk = args->tsk; + struct purgeable_ashmem_metadata pmdata; + + if (!is_ashmem_file(f)) + return 0; + if (!get_purgeable_ashmem_metadata(f, &pmdata)) + return 0; + if (pmdata.is_purgeable) { + pmdata.name = pmdata.name == NULL ? "" : pmdata.name; + seq_printf(args->seq, + "%s,%u,%u,%ld,%s,%zu,%u,%u,%d,%d\n", + tsk->comm, tsk->pid, fd, (long)tsk->signal->oom_score_adj, + pmdata.name, pmdata.size, pmdata.id, pmdata.create_time, + pmdata.refc, pmdata.purged); + } + return 0; +} + +static ssize_t purgeable_ashmem_trigger_write(struct file *file, + const char __user *buffer, size_t count, loff_t *ppos) +{ + char *buf; + unsigned long ashmem_id = 0; + unsigned long create_time = 0; + const unsigned int params_num = 2; + const struct cred *cred = current_cred(); // it never be NULL + + if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) && + !uid_eq(cred->euid, GLOBAL_ROOT_UID)) { + pr_err("no permission to shrink purgeable ashmem!\n"); + return -EINVAL; + } + buf = memdup_user_nul(buffer, count); + buf = strstrip(buf); + if (sscanf_s(buf, "%u %u", &ashmem_id, &create_time) != params_num) + return -EINVAL; + if (ashmem_id == PURGEABLE_ASHMEM_SHRINKALL_ARG && + create_time == PURGEABLE_ASHMEM_SHRINKALL_ARG) + ashmem_shrinkall(); + else + ashmem_shrink_by_id(ashmem_id, create_time); + return count; +} + +static int purgeable_ashmem_trigger_show(struct seq_file *s, void *d) +{ + struct task_struct *tsk = NULL; + struct purgeable_ashmem_trigger_args cb_args; + + seq_puts(s, "Process purgeable ashmem detail info:\n"); + seq_puts(s, "----------------------------------------------------\n"); + seq_printf(s, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n", + "process_name", "pid", "adj", "fd", + "ashmem_name", "size", "id", "time", "ref_count", "purged"); + + rcu_read_lock(); + for_each_process(tsk) { + if (tsk->flags & PF_KTHREAD) + continue; + cb_args.seq = s; + cb_args.tsk = tsk; + + task_lock(tsk); + iterate_fd(tsk->files, 0, + purgeable_ashmem_trigger_cb, (void *)&cb_args); + task_unlock(tsk); + } + rcu_read_unlock(); + seq_puts(s, "----------------------------------------------------\n"); + return 0; +} + +static int purgeable_ashmem_trigger_open(struct inode *inode, + struct file *file) +{ + return single_open(file, purgeable_ashmem_trigger_show, + inode->i_private); +} + +static const struct proc_ops purgeable_ashmem_trigger_fops = { + .proc_open = purgeable_ashmem_trigger_open, + .proc_write = purgeable_ashmem_trigger_write, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +void init_purgeable_ashmem_trigger(void) +{ + struct proc_dir_entry *entry = NULL; + + entry = proc_create_data("purgeable_ashmem_trigger", 0666, + NULL, &purgeable_ashmem_trigger_fops, NULL); + if (!entry) + pr_err("Failed to create purgeable ashmem trigger\n"); +} diff --git a/mm/vmscan.c b/mm/vmscan.c index eed31ad36f5c..36b2342b8ef4 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -4392,7 +4392,13 @@ static int purgeable(struct ctl_table *table, int write, void *buffer, .reclaim_idx = MAX_NR_ZONES - 1, }; int nid = 0; + const struct cred *cred = current_cred(); // it never be NULL + if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) && + !uid_eq(cred->euid, GLOBAL_ROOT_UID)) { + pr_err("no permission to shrink purgeable heap!\n"); + return -EINVAL; + } for_each_node_state(nid, N_MEMORY) purgeable_node(NODE_DATA(nid), &sc); return 0; @@ -4401,7 +4407,7 @@ static int purgeable(struct ctl_table *table, int write, void *buffer, static struct ctl_table ker_tab[] = { { .procname = "purgeable", - .mode = 0200, + .mode = 0666, .proc_handler = purgeable, }, {}, -- Gitee