diff --git a/fs/proc/base.c b/fs/proc/base.c index a3e72c567844..fdc8e819015e 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -3237,6 +3237,9 @@ static const struct pid_entry tgid_base_stuff[] = { REG("mounts", S_IRUGO, proc_mounts_operations), REG("mountinfo", S_IRUGO, proc_mountinfo_operations), REG("mountstats", S_IRUSR, proc_mountstats_operations), +#ifdef CONFIG_PROCESS_RECLAIM + REG("reclaim", 0200, proc_reclaim_operations), +#endif #ifdef CONFIG_PROC_PAGE_MONITOR REG("clear_refs", S_IWUSR, proc_clear_refs_operations), REG("smaps", S_IRUGO, proc_pid_smaps_operations), diff --git a/fs/proc/internal.h b/fs/proc/internal.h index 95846552ef24..91ee4eb4a275 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -205,6 +205,7 @@ struct pde_opener { extern const struct inode_operations proc_link_inode_operations; extern const struct inode_operations proc_pid_link_inode_operations; extern const struct super_operations proc_sops; +extern const struct file_operations proc_reclaim_operations; void proc_init_kmemcache(void); void set_proc_pid_nlink(void); diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index 1ce1cb08a5d3..5ab6450ba19c 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -1708,6 +1709,124 @@ const struct file_operations proc_pagemap_operations = { }; #endif /* CONFIG_PROC_PAGE_MONITOR */ +#ifdef CONFIG_PROCESS_RECLAIM +static int reclaim_pte_range(pmd_t *pmd, unsigned long addr, + unsigned long end, struct mm_walk *walk) +{ + struct vm_area_struct *vma = walk->private; + pte_t *pte, ptent; + spinlock_t *ptl; + struct page *page; + LIST_HEAD(page_list); + int isolated; + + split_huge_pmd(vma, addr, pmd); + if (pmd_trans_unstable(pmd)) + return 0; +cont: + isolated = 0; + pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl); + for (; addr != end; pte++, addr += PAGE_SIZE) { + ptent = *pte; + if (!pte_present(ptent)) + continue; + + page = vm_normal_page(vma, addr, ptent); + if (!page) + continue; + + if (isolate_lru_page(page)) + continue; + + list_add(&page->lru, &page_list); + inc_node_page_state(page, NR_ISOLATED_ANON + + page_is_file_cache(page)); + isolated++; + if (isolated >= SWAP_CLUSTER_MAX) + break; + } + pte_unmap_unlock(pte - 1, ptl); + reclaim_pages_from_list(&page_list); + if (addr != end) + goto cont; + + cond_resched(); + return 0; +} + +enum reclaim_type { + RECLAIM_FILE, + RECLAIM_ANON, + RECLAIM_ALL, + RECLAIM_RANGE, +}; + +static ssize_t reclaim_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct task_struct *task; + char buffer[PROC_NUMBUF]; + struct mm_struct *mm; + struct vm_area_struct *vma; + enum reclaim_type type; + char *type_buf; + + memset(buffer, 0, sizeof(buffer)); + if (count > sizeof(buffer) - 1) + count = sizeof(buffer) - 1; + + if (copy_from_user(buffer, buf, count)) + return -EFAULT; + + type_buf = strstrip(buffer); + if (!strcmp(type_buf, "file")) + type = RECLAIM_FILE; + else if (!strcmp(type_buf, "anon")) + type = RECLAIM_ANON; + else if (!strcmp(type_buf, "all")) + type = RECLAIM_ALL; + else + return -EINVAL; + + task = get_proc_task(file->f_path.dentry->d_inode); + if (!task) + return -ESRCH; + + mm = get_task_mm(task); + if (mm) { + const struct mm_walk_ops reclaim_walk_ops = { + .pmd_entry = reclaim_pte_range, + }; + + down_read(&mm->mmap_sem); + for (vma = mm->mmap; vma; vma = vma->vm_next) { + + if (is_vm_hugetlb_page(vma)) + continue; + + if (type == RECLAIM_ANON && vma->vm_file) + continue; + if (type == RECLAIM_FILE && !vma->vm_file) + continue; + + walk_page_range(mm, vma->vm_start, vma->vm_end, + &reclaim_walk_ops, vma); + } + flush_tlb_mm(mm); + up_read(&mm->mmap_sem); + mmput(mm); + } + put_task_struct(task); + + return count; +} + +const struct file_operations proc_reclaim_operations = { + .write = reclaim_write, + .llseek = noop_llseek, +}; +#endif + #ifdef CONFIG_NUMA struct numa_maps { diff --git a/include/linux/rmap.h b/include/linux/rmap.h index 988d176472df..caae95805da5 100644 --- a/include/linux/rmap.h +++ b/include/linux/rmap.h @@ -12,6 +12,10 @@ #include #include +extern int isolate_lru_page(struct page *page); +extern void putback_lru_page(struct page *page); +extern unsigned long reclaim_pages_from_list(struct list_head *page_list); + /* * The anon_vma heads a list of private "related" vmas, to scan if * an anonymous page pointing to this anon_vma needs to be unmapped: diff --git a/mm/Kconfig b/mm/Kconfig index 9fe6287ae035..35ecdc808844 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -832,3 +832,17 @@ config OOM_TASK_PRIORITY_ADJ_LIMIT before considering tasks with a lower oom_score_adj value. endmenu + +config PROCESS_RECLAIM + bool "Enable process reclaim" + depends on PROC_FS + depends on QGKI + default y + help + It allows to reclaim pages of the process by /proc/pid/reclaim. + + (echo file > /proc/PID/reclaim) reclaims file-backed pages only. + (echo anon > /proc/PID/reclaim) reclaims anonymous pages only. + (echo all > /proc/PID/reclaim) reclaims all pages. + + Any other value is ignored. diff --git a/mm/vmscan.c b/mm/vmscan.c index e974f288f569..6b5a5fa76f35 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -1565,6 +1565,62 @@ unsigned long reclaim_clean_pages_from_list(struct zone *zone, return ret; } +#ifdef CONFIG_PROCESS_RECLAIM +static unsigned long shrink_page(struct page *page, + struct zone *zone, + struct scan_control *sc, + enum ttu_flags ttu_flags, + bool force_reclaim, + struct list_head *ret_pages) +{ + int reclaimed; + LIST_HEAD(page_list); + + list_add(&page->lru, &page_list); + + reclaimed = shrink_page_list(&page_list, zone->zone_pgdat, sc, + ttu_flags, NULL, force_reclaim); + if (!reclaimed) + list_splice(&page_list, ret_pages); + + return reclaimed; +} + +unsigned long reclaim_pages_from_list(struct list_head *page_list) +{ + struct scan_control sc = { + .gfp_mask = GFP_KERNEL, + .priority = DEF_PRIORITY, + .may_writepage = 1, + .may_unmap = 1, + .may_swap = 1, + }; + + LIST_HEAD(ret_pages); + struct page *page; + unsigned long nr_reclaimed = 0; + + while (!list_empty(page_list)) { + page = lru_to_page(page_list); + list_del(&page->lru); + + ClearPageActive(page); + nr_reclaimed += shrink_page(page, page_zone(page), &sc, + TTU_IGNORE_ACCESS, true, &ret_pages); + } + + while (!list_empty(&ret_pages)) { + page = lru_to_page(&ret_pages); + list_del(&page->lru); + dec_node_page_state(page, NR_ISOLATED_ANON + + page_is_file_cache(page)); + putback_lru_page(page); + } + + return nr_reclaimed; +} +#endif + /* * Attempt to remove the specified page from its LRU. Only take this page * if it is of the appropriate PageActive status. Pages which are being