| From 60deeeb55765656a3a134b7e8e60dadaeb9f48cb Mon Sep 17 00:00:00 2001 |
| From: Luigi Semenzato <semenzato@chromium.org> |
| Date: Thu, 3 Apr 2014 17:23:06 -0700 |
| Subject: [PATCH] CHROMIUM: Add /dev/low-mem device for low-memory |
| notification. |
| |
| This patch adds a simple low-memory notification mechanism. I took a recent |
| proposal by Minchan Kim and adapted it to our (Chromium OS) needs. |
| |
| A process requests a low-memory notification by polling /dev/low-mem |
| (this name could change). The kernel checks for low-memory conditions in |
| the allocator fast path and wakes up the poll queue when such conditions |
| occur. Low-memory conditions are also checked when a process enters |
| the poll function, to ensure that any freeing done by the process is |
| noticed. |
| |
| Low memory condition is defined as the ratio of "free memory" to total memory |
| being lower than a given margin. The margin is 10% by default and can be |
| changed via /sys/kernel/mm/low_mem/margin (this name could change). |
| |
| Free memory is computed as an approximation as described in the code. |
| |
| This patch also contains a test program in tools/mm/low-mem-test.c. |
| |
| BUG=chromium-os:20086 |
| TEST=see enclosed test program. Test team: verify that /dev/low-mem exists. |
| |
| Signed-off-by: Luigi Semenzato <semenzato@chromium.org> |
| |
| Change-Id: Iec1eb499d1482e818b491bacf1d48f53a5d9e191 |
| Reviewed-on: https://gerrit.chromium.org/gerrit/14635 |
| Tested-by: Luigi Semenzato <semenzato@chromium.org> |
| Reviewed-by: Mandeep Singh Baines <msb@chromium.org> |
| Reviewed-by: Olof Johansson <olofj@chromium.org> |
| Commit-Ready: Luigi Semenzato <semenzato@chromium.org> |
| |
| Conflicts: |
| mm/page_alloc.c |
| |
| [anush: fixes for 3.3] |
| Signed-off-by: Anush Elangovan <anush@chromium.org> |
| |
| [benzh: 3.14 rebase. "oldmem" memdev has beem removed. Fixed mm/Kconfig whitespace] |
| Signed-off-by: Ben Zhang <benzh@chromium.org> |
| |
| Conflicts: |
| mm/Kconfig |
| mm/Makefile |
| mm/page_alloc.c |
| [filbranden: 3.18 rebase. Solved conflicts, rename strict_strtoul -> kstrtoul] |
| Signed-off-by: Filipe Brandenburger <filbranden@chromium.org> |
| |
| Conflicts: |
| drivers/char/mem.c |
| mm/Kconfig |
| mm/Makefile |
| mm/page_alloc.c |
| |
| [rebase44(groeck): Resolved conflicts; |
| Adjusted for upstream data structure change in drivers/char/mem.c] |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| |
| Conflicts: |
| mm/Kconfig |
| mm/Makefile |
| mm/page_alloc.c |
| |
| [rebase412(groeck): Resolved conflicts] |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| |
| Conflicts: |
| mm/Kconfig |
| |
| [rebase414(teravest): Resolved trivial conflict] |
| Signed-off-by: Justin TerAvest <teravest@chromium.org> |
| |
| [rebase419(groeck): Squash |
| CHROMIUM: fix low-memory notification patch (change Iec1eb499) |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| |
| [rebase54(yuzhao): Squash |
| cd6dae3cd463 ("CHROMIUM: low-mem: improve detection of low-memory conditions.") |
| 8fb71ef7b6ec ("CHROMIUM: low-mem: Fix incorrect logging in debug code for low-mem-notify.") |
| 12a973b9646d ("CHROMIUM: low-mem: fix sign of free memory calculation.") |
| 405e19006b0f ("CHROMIUM: change low-mem to chromeos-low-mem in /sys/kernel/mm.") |
| 1fa6b9516423 ("CHROMIUM: low-mem notifier: lower default, change units, enable disabling") |
| 8249734249c7 ("CHROMIUM: low-mem: add "on" command and fix error case") |
| fa92fb263868 ("CHROMIUM: low-mem: work with swap enabled") |
| d950f3448614 ("CHROMIUM: low_mem: Use get_nr_swap_pages()") |
| 7d8e3cf4dc72 ("CHROMIUM: Consider min_free_kbytes in low memory notification.") |
| 705c56164a14 ("CHROMIUM: low-mem: Add "available" sysfs file.") |
| 950d9e4560ca ("CHROMIUM: low_mem: make ram_vs_swap_weight configurable") |
| 825100e54c02 ("CHROMIUM: low_mem: fix file_size underflow") |
| e0509f4160bc ("CHROMIUM: mm: low-mem-notify: fix LRU counter usage for 4.14") |
| da545b500aef ("CHROMIUM: low_mem: use enum node_stat_item directly to get memory status") |
| a1f90f2a37a3 ("CHROMIUM: low_mem: notify low memory when anonymous is low") |
| 785186ff7513 ("CHROMIUM: low_mem: log low memory notification with rate limit") |
| b7ec7fb624df ("CHROMIUM: low-mem: Fix return type of low_mem_notify_poll") |
| 3923e87e395b ("CHROMIUM: low-mem: handle multiple levels and provide edge notifications") |
| f0caba2ba832 ("CHROMIUM: low_mem: exclude totalreserve_pages from available memory") |
| d06b5269009a ("CHROMIUM: mm: low_mem_notify before unreserving highatomic") |
| d91758439ffe ("FIXUP: CHROMIUM: low-mem: handle multiple levels and provide edge notifications") |
| ] |
| Signed-off-by: Yu Zhao <yuzhao@google.com> |
| [rebase510(groeck): Context conflicts] |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| |
| Change-Id: Iecee1c530f75cc10741c8f67fbc5ba4bbf707963 |
| --- |
| drivers/char/mem.c | 4 + |
| include/linux/low-mem-notify.h | 150 +++++++++++++++++ |
| mm/Kconfig | 11 ++ |
| mm/Makefile | 1 + |
| mm/low-mem-notify.c | 293 +++++++++++++++++++++++++++++++++ |
| mm/page_alloc.c | 4 + |
| tools/mm/low-mem-test.c | 178 ++++++++++++++++++++ |
| 7 files changed, 641 insertions(+) |
| create mode 100644 include/linux/low-mem-notify.h |
| create mode 100644 mm/low-mem-notify.c |
| create mode 100644 tools/mm/low-mem-test.c |
| |
| diff --git a/drivers/char/mem.c b/drivers/char/mem.c |
| --- a/drivers/char/mem.c |
| +++ b/drivers/char/mem.c |
| @@ -30,6 +30,7 @@ |
| #include <linux/uio.h> |
| #include <linux/uaccess.h> |
| #include <linux/security.h> |
| +#include <linux/low-mem-notify.h> |
| |
| #ifdef CONFIG_IA64 |
| # include <linux/efi.h> |
| @@ -711,6 +712,9 @@ static const struct memdev { |
| #ifdef CONFIG_PRINTK |
| [11] = { "kmsg", 0644, &kmsg_fops, 0 }, |
| #endif |
| +#ifdef CONFIG_LOW_MEM_NOTIFY |
| + [12] = { "chromeos-low-mem", 0666, &low_mem_notify_fops, 0 }, |
| +#endif |
| }; |
| |
| static int memory_open(struct inode *inode, struct file *filp) |
| diff --git a/include/linux/low-mem-notify.h b/include/linux/low-mem-notify.h |
| new file mode 100644 |
| --- /dev/null |
| +++ b/include/linux/low-mem-notify.h |
| @@ -0,0 +1,150 @@ |
| +#ifndef _LINUX_LOW_MEM_NOTIFY_H |
| +#define _LINUX_LOW_MEM_NOTIFY_H |
| + |
| +#include <linux/mm.h> |
| +#include <linux/ratelimit.h> |
| +#include <linux/stddef.h> |
| +#include <linux/swap.h> |
| + |
| +/* We support up to this many different thresholds. */ |
| +#define LOW_MEM_THRESHOLD_MAX 5 |
| + |
| +extern unsigned long low_mem_thresholds[]; |
| +extern unsigned int low_mem_threshold_count; |
| +extern unsigned int low_mem_threshold_last; |
| +extern const struct file_operations low_mem_notify_fops; |
| +extern bool low_mem_margin_enabled; |
| +extern unsigned long low_mem_lowest_seen_anon_mem; |
| +extern unsigned int low_mem_ram_vs_swap_weight; |
| +extern struct ratelimit_state low_mem_logging_ratelimit; |
| + |
| +#ifdef CONFIG_SYSFS |
| +extern void low_mem_threshold_notify(void); |
| +#else |
| +static inline void low_mem_threshold_notify(void) |
| +{ |
| +} |
| +#endif |
| + |
| +/* |
| + * Compute available memory used by files that can be reclaimed quickly. |
| + */ |
| +static inline unsigned long get_available_file_mem(void) |
| +{ |
| + unsigned long file_mem = |
| + global_node_page_state(NR_ACTIVE_FILE) + |
| + global_node_page_state(NR_INACTIVE_FILE); |
| + unsigned long dirty_mem = global_node_page_state(NR_FILE_DIRTY); |
| + unsigned long min_file_mem = min_filelist_kbytes >> (PAGE_SHIFT - 10); |
| + unsigned long clean_file_mem = file_mem - dirty_mem; |
| + /* Conservatively estimate the amount of available_file_mem */ |
| + unsigned long available_file_mem = (clean_file_mem > min_file_mem) ? |
| + (clean_file_mem - min_file_mem) : 0; |
| + return available_file_mem; |
| +} |
| + |
| +/* |
| + * Available anonymous memory. |
| + */ |
| +static inline unsigned long get_available_anon_mem(void) |
| +{ |
| + return global_node_page_state(NR_ACTIVE_ANON) + |
| + global_node_page_state(NR_INACTIVE_ANON); |
| +} |
| + |
| +/* |
| + * Compute "available" memory, that is either free memory or memory that can be |
| + * reclaimed quickly, adjusted for the presence of swap. |
| + */ |
| +static inline unsigned long get_available_mem_adj(void) |
| +{ |
| + /* free_mem is completely unallocated; clean file-backed memory |
| + * (file_mem - dirty_mem) is easy to reclaim, except for the last |
| + * min_filelist_kbytes. totalreserve_pages is the reserve of pages that |
| + * are not available to user space. |
| + */ |
| + unsigned long raw_free_mem = global_zone_page_state(NR_FREE_PAGES); |
| + unsigned long free_mem = (raw_free_mem > totalreserve_pages) ? |
| + raw_free_mem - totalreserve_pages : 0; |
| + unsigned long available_mem = free_mem + |
| + get_available_file_mem(); |
| + unsigned long swappable_pages = min_t(unsigned long, |
| + get_nr_swap_pages(), get_available_anon_mem()); |
| + /* |
| + * The contribution of swap is reduced by a factor of |
| + * low_mem_ram_vs_swap_weight. |
| + */ |
| + return available_mem + swappable_pages / low_mem_ram_vs_swap_weight; |
| +} |
| + |
| +#ifdef CONFIG_LOW_MEM_NOTIFY |
| +void low_mem_notify(void); |
| + |
| +extern atomic_t in_low_mem_check; |
| + |
| +/* |
| + * Returns TRUE if we are in a low memory state. |
| + */ |
| +static inline bool low_mem_check(void) |
| +{ |
| + static bool was_low_mem; /* = false, as per style guide */ |
| + /* We declare a low-memory condition when a combination of RAM and swap |
| + * space is low. |
| + */ |
| + unsigned long available_mem = get_available_mem_adj(); |
| + /* |
| + * For backwards compatibility with the older margin interface, we will |
| + * trigger the /dev/chromeos-low_mem device when we are below the |
| + * lowest threshold |
| + */ |
| + bool is_low_mem = available_mem < low_mem_thresholds[0]; |
| + unsigned int threshold_lowest = UINT_MAX; |
| + int i; |
| + |
| + if (!low_mem_margin_enabled) |
| + return false; |
| + |
| + if (atomic_xchg(&in_low_mem_check, 1)) |
| + return was_low_mem; |
| + |
| + if (unlikely(is_low_mem && !was_low_mem) && |
| + __ratelimit(&low_mem_logging_ratelimit)) { |
| + pr_info("entering low_mem (avail RAM = %lu kB, avail swap %lu kB, avail file %lu kB, anon mem: %lu kB)\n", |
| + available_mem * PAGE_SIZE / 1024, |
| + get_nr_swap_pages() * PAGE_SIZE / 1024, |
| + get_available_file_mem() * PAGE_SIZE / 1024, |
| + get_available_anon_mem() * PAGE_SIZE / 1024); |
| + } |
| + was_low_mem = is_low_mem; |
| + |
| + if (is_low_mem) |
| + low_mem_notify(); |
| + |
| + for (i = 0; i < low_mem_threshold_count; i++) |
| + if (available_mem < low_mem_thresholds[i]) { |
| + threshold_lowest = i; |
| + break; |
| + } |
| + |
| + /* we crossed one or more thresholds */ |
| + if (unlikely(threshold_lowest < low_mem_threshold_last)) |
| + low_mem_threshold_notify(); |
| + |
| + low_mem_threshold_last = threshold_lowest; |
| + |
| + atomic_set(&in_low_mem_check, 0); |
| + |
| + return is_low_mem; |
| +} |
| +#else |
| +static inline void low_mem_notify(void) |
| +{ |
| +} |
| + |
| +static inline bool low_mem_check(void) |
| +{ |
| + return false; |
| +} |
| +#endif |
| + |
| +#endif |
| diff --git a/mm/Kconfig b/mm/Kconfig |
| --- a/mm/Kconfig |
| +++ b/mm/Kconfig |
| @@ -909,6 +909,17 @@ config ANON_VMA_NAME |
| area from being merged with adjacent virtual memory areas due to the |
| difference in their name. |
| |
| +config LOW_MEM_NOTIFY |
| + bool "Create device that lets processes detect low-memory conditions" |
| + default n |
| + help |
| + A process can poll the /dev/low_mem device to be notified of |
| + low-memory conditions. The process can then attempt to free memory |
| + before a OOM condition develops and the OOM killer takes over. This |
| + is meant to be used in systems with no or very little swap space. In |
| + the presence of large swap space, the system is likely to become |
| + unusable before the OOM killer is triggered. |
| + |
| source "mm/damon/Kconfig" |
| |
| endmenu |
| diff --git a/mm/Makefile b/mm/Makefile |
| --- a/mm/Makefile |
| +++ b/mm/Makefile |
| @@ -119,6 +119,7 @@ obj-$(CONFIG_SECRETMEM) += secretmem.o |
| obj-$(CONFIG_CMA_SYSFS) += cma_sysfs.o |
| obj-$(CONFIG_USERFAULTFD) += userfaultfd.o |
| obj-$(CONFIG_IDLE_PAGE_TRACKING) += page_idle.o |
| +obj-$(CONFIG_LOW_MEM_NOTIFY) += low-mem-notify.o |
| obj-$(CONFIG_DEBUG_PAGE_REF) += debug_page_ref.o |
| obj-$(CONFIG_DAMON) += damon/ |
| obj-$(CONFIG_HARDENED_USERCOPY) += usercopy.o |
| diff --git a/mm/low-mem-notify.c b/mm/low-mem-notify.c |
| new file mode 100644 |
| --- /dev/null |
| +++ b/mm/low-mem-notify.c |
| @@ -0,0 +1,293 @@ |
| +/* |
| + * mm/low-mem-notify.c |
| + * |
| + * Sends low-memory notifications to processes via /dev/low-mem. |
| + * |
| + * Copyright (C) 2012 The Chromium OS Authors |
| + * This program is free software, released under the GPL. |
| + * Based on a proposal by Minchan Kim |
| + * |
| + * A process that polls /dev/low-mem is notified of a low-memory situation. |
| + * The intent is to allow the process to free some memory before the OOM killer |
| + * is invoked. |
| + * |
| + * A low-memory condition is estimated by subtracting anonymous memory |
| + * (i.e. process data segments), kernel memory, and a fixed amount of |
| + * file-backed memory from total memory. This is just a heuristic, as in |
| + * general we don't know how much memory can be reclaimed before we try to |
| + * reclaim it, and that's too expensive or too late. |
| + * |
| + * This is tailored to Chromium OS, where a single program (the browser) |
| + * controls most of the memory, and (currently) no swap space is used. |
| + */ |
| + |
| + |
| +#include <linux/low-mem-notify.h> |
| +#include <linux/module.h> |
| +#include <linux/sched.h> |
| +#include <linux/wait.h> |
| +#include <linux/poll.h> |
| +#include <linux/slab.h> |
| +#include <linux/mm.h> |
| +#include <linux/ctype.h> |
| + |
| +#define MB (1 << 20) |
| + |
| +static DECLARE_WAIT_QUEUE_HEAD(low_mem_wait); |
| +static atomic_t low_mem_state = ATOMIC_INIT(0); |
| +atomic_t in_low_mem_check = ATOMIC_INIT(0); |
| + |
| +/* This is a list of thresholds in pages and should be in ascending order. */ |
| +unsigned long low_mem_thresholds[LOW_MEM_THRESHOLD_MAX] = { |
| + 50 * MB / PAGE_SIZE }; |
| +unsigned int low_mem_threshold_count = 1; |
| + |
| +/* last observed threshold */ |
| +unsigned int low_mem_threshold_last = UINT_MAX; |
| +bool low_mem_margin_enabled = true; |
| +unsigned int low_mem_ram_vs_swap_weight = 4; |
| + |
| +/* Limit logging low memory to once per second. */ |
| +DEFINE_RATELIMIT_STATE(low_mem_logging_ratelimit, 1 * HZ, 1); |
| + |
| +unsigned long low_mem_lowest_seen_anon_mem; |
| +const unsigned long low_mem_anon_mem_delta = 10 * MB / PAGE_SIZE; |
| +static struct kernfs_node *low_mem_available_dirent; |
| + |
| +struct low_mem_notify_file_info { |
| + unsigned long unused; |
| +}; |
| + |
| +void low_mem_notify(void) |
| +{ |
| + atomic_set(&low_mem_state, true); |
| + wake_up(&low_mem_wait); |
| +} |
| + |
| +static int low_mem_notify_open(struct inode *inode, struct file *file) |
| +{ |
| + struct low_mem_notify_file_info *info; |
| + int err = 0; |
| + |
| + info = kmalloc(sizeof(*info), GFP_KERNEL); |
| + if (!info) { |
| + err = -ENOMEM; |
| + goto out; |
| + } |
| + |
| + file->private_data = info; |
| +out: |
| + return err; |
| +} |
| + |
| +static int low_mem_notify_release(struct inode *inode, struct file *file) |
| +{ |
| + kfree(file->private_data); |
| + return 0; |
| +} |
| + |
| +static __poll_t low_mem_notify_poll(struct file *file, poll_table *wait) |
| +{ |
| + unsigned int ret = 0; |
| + |
| + /* Update state to reflect any recent freeing. */ |
| + atomic_set(&low_mem_state, low_mem_check()); |
| + |
| + poll_wait(file, &low_mem_wait, wait); |
| + |
| + if (low_mem_margin_enabled && atomic_read(&low_mem_state) != 0) |
| + ret = POLLIN; |
| + |
| + return ret; |
| +} |
| + |
| +const struct file_operations low_mem_notify_fops = { |
| + .open = low_mem_notify_open, |
| + .release = low_mem_notify_release, |
| + .poll = low_mem_notify_poll, |
| +}; |
| +EXPORT_SYMBOL(low_mem_notify_fops); |
| + |
| +#ifdef CONFIG_SYSFS |
| + |
| +#define LOW_MEM_ATTR(_name) \ |
| + static struct kobj_attribute low_mem_##_name##_attr = \ |
| + __ATTR(_name, 0644, low_mem_##_name##_show, \ |
| + low_mem_##_name##_store) |
| + |
| +static ssize_t low_mem_margin_show(struct kobject *kobj, |
| + struct kobj_attribute *attr, char *buf) |
| +{ |
| + if (low_mem_margin_enabled && low_mem_threshold_count) { |
| + int i; |
| + ssize_t written = 0; |
| + |
| + for (i = 0; i < low_mem_threshold_count; i++) |
| + written += sprintf(buf + written, "%lu ", |
| + low_mem_thresholds[i] * PAGE_SIZE / MB); |
| + written += sprintf(buf + written, "\n"); |
| + return written; |
| + } else |
| + return sprintf(buf, "off\n"); |
| +} |
| + |
| +static ssize_t low_mem_margin_store(struct kobject *kobj, |
| + struct kobj_attribute *attr, |
| + const char *buf, size_t count) |
| +{ |
| + int i = 0, consumed = 0; |
| + const char *start = buf; |
| + char *endp; |
| + unsigned long thresholds[LOW_MEM_THRESHOLD_MAX]; |
| + |
| + memset(thresholds, 0, sizeof(thresholds)); |
| + /* |
| + * Even though the API does not say anything about this, the string in |
| + * buf is zero-terminated (as long as count < PAGE_SIZE) because buf is |
| + * a newly allocated zero-filled page. Most other sysfs handlers rely |
| + * on this too. |
| + */ |
| + if (strncmp("off", buf, 3) == 0) { |
| + pr_info("low_mem: disabling notifier\n"); |
| + low_mem_margin_enabled = false; |
| + return count; |
| + } |
| + if (strncmp("on", buf, 2) == 0) { |
| + pr_info("low_mem: enabling notifier\n"); |
| + low_mem_margin_enabled = true; |
| + return count; |
| + } |
| + /* |
| + * This takes a space separated list of thresholds in ascending order, |
| + * and a trailing newline is optional. |
| + */ |
| + while (consumed < count) { |
| + if (i >= LOW_MEM_THRESHOLD_MAX) { |
| + pr_warn("low-mem: too many thresholds"); |
| + return -EINVAL; |
| + } |
| + /* special case for trailing newline */ |
| + if (*start == '\n') |
| + break; |
| + |
| + thresholds[i] = simple_strtoul(start, &endp, 0); |
| + if ((endp == start) && *endp != '\n') |
| + return -EINVAL; |
| + |
| + /* make sure each is larger than the last one */ |
| + if (i && thresholds[i] <= thresholds[i - 1]) { |
| + pr_warn("low-mem: thresholds not in increasing order: %lu then %lu\n", |
| + thresholds[i - 1], thresholds[i]); |
| + return -EINVAL; |
| + } |
| + |
| + if (thresholds[i] * (MB / PAGE_SIZE) > totalram_pages()) { |
| + pr_warn("low-mem: threshold too high\n"); |
| + return -EINVAL; |
| + } |
| + |
| + consumed += endp - start + 1; |
| + start = endp + 1; |
| + i++; |
| + } |
| + |
| + low_mem_threshold_count = i; |
| + low_mem_margin_enabled = !!low_mem_threshold_count; |
| + |
| + /* Convert to pages outside the allocator fast path. */ |
| + for (i = 0; i < low_mem_threshold_count; i++) { |
| + low_mem_thresholds[i] = |
| + thresholds[i] * (MB / PAGE_SIZE); |
| + pr_info("low_mem: threshold[%d] %lu MB\n", i, |
| + low_mem_thresholds[i] * PAGE_SIZE / MB); |
| + } |
| + |
| + return count; |
| +} |
| +LOW_MEM_ATTR(margin); |
| + |
| +static ssize_t low_mem_ram_vs_swap_weight_show(struct kobject *kobj, |
| + struct kobj_attribute *attr, |
| + char *buf) |
| +{ |
| + return sprintf(buf, "%u\n", low_mem_ram_vs_swap_weight); |
| +} |
| + |
| +static ssize_t low_mem_ram_vs_swap_weight_store(struct kobject *kobj, |
| + struct kobj_attribute *attr, |
| + const char *buf, size_t count) |
| +{ |
| + unsigned long weight; |
| + int err; |
| + |
| + err = kstrtoul(buf, 10, &weight); |
| + if (err) |
| + return -EINVAL; |
| + /* The special value 0 represents infinity. */ |
| + low_mem_ram_vs_swap_weight = weight == 0 ? |
| + -1U : (unsigned int) weight; |
| + pr_info("low_mem: setting ram weight to %u\n", |
| + low_mem_ram_vs_swap_weight); |
| + return count; |
| +} |
| +LOW_MEM_ATTR(ram_vs_swap_weight); |
| + |
| +static ssize_t low_mem_available_show(struct kobject *kobj, |
| + struct kobj_attribute *attr, |
| + char *buf) |
| +{ |
| + unsigned long available_mem = get_available_mem_adj(); |
| + |
| + return sprintf(buf, "%lu\n", |
| + available_mem / (MB / PAGE_SIZE)); |
| +} |
| + |
| +static ssize_t low_mem_available_store(struct kobject *kobj, |
| + struct kobj_attribute *attr, |
| + const char *buf, size_t count) |
| +{ |
| + return -EINVAL; |
| +} |
| +LOW_MEM_ATTR(available); |
| + |
| +static struct attribute *low_mem_attrs[] = { |
| + &low_mem_margin_attr.attr, |
| + &low_mem_ram_vs_swap_weight_attr.attr, |
| + &low_mem_available_attr.attr, |
| + NULL, |
| +}; |
| + |
| +static struct attribute_group low_mem_attr_group = { |
| + .attrs = low_mem_attrs, |
| + .name = "chromeos-low_mem", |
| +}; |
| + |
| +void low_mem_threshold_notify(void) |
| +{ |
| + if (low_mem_available_dirent) |
| + sysfs_notify_dirent(low_mem_available_dirent); |
| +} |
| + |
| +static int __init low_mem_init(void) |
| +{ |
| + struct kernfs_node *low_mem_node; |
| + int err = sysfs_create_group(mm_kobj, &low_mem_attr_group); |
| + if (err) |
| + pr_err("low_mem: register sysfs failed\n"); |
| + |
| + low_mem_node = sysfs_get_dirent(mm_kobj->sd, "chromeos-low_mem"); |
| + if (low_mem_node) { |
| + low_mem_available_dirent = |
| + sysfs_get_dirent(low_mem_node, "available"); |
| + sysfs_put(low_mem_node); |
| + } |
| + |
| + if (!low_mem_available_dirent) |
| + pr_warn("unable to find dirent for \"available\" attribute\n"); |
| + |
| + low_mem_lowest_seen_anon_mem = totalram_pages(); |
| + return err; |
| +} |
| +module_init(low_mem_init) |
| + |
| +#endif |
| diff --git a/mm/page_alloc.c b/mm/page_alloc.c |
| --- a/mm/page_alloc.c |
| +++ b/mm/page_alloc.c |
| @@ -75,6 +75,7 @@ |
| #include <linux/khugepaged.h> |
| #include <linux/buffer_head.h> |
| #include <linux/delayacct.h> |
| +#include <linux/low-mem-notify.h> |
| #include <asm/sections.h> |
| #include <asm/tlbflush.h> |
| #include <asm/div64.h> |
| @@ -4782,6 +4783,7 @@ should_reclaim_retry(gfp_t gfp_mask, unsigned order, |
| * several times in the row. |
| */ |
| if (*no_progress_loops > MAX_RECLAIM_RETRIES) { |
| + low_mem_notify(); |
| /* Before OOM, exhaust highatomic_reserve */ |
| return unreserve_highatomic_pageblock(ac, true); |
| } |
| @@ -5379,6 +5381,8 @@ struct page *__alloc_pages(gfp_t gfp, unsigned int order, int preferred_nid, |
| &alloc_gfp, &alloc_flags)) |
| return NULL; |
| |
| + low_mem_check(); |
| + |
| /* |
| * Forbid the first pass from falling back to types that fragment |
| * memory until all local zones are considered. |
| diff --git a/tools/mm/low-mem-test.c b/tools/mm/low-mem-test.c |
| new file mode 100644 |
| --- /dev/null |
| +++ b/tools/mm/low-mem-test.c |
| @@ -0,0 +1,178 @@ |
| +/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| + * This program is free software, released under the GPL. |
| + * Based on code by Minchan Kim |
| + * |
| + * User program that tests low-memory notifications. |
| + * |
| + * Compile with -lpthread |
| + * for instance |
| + * i686-pc-linux-gnu-gcc low-mem-test.c -o low-mem-test -lpthread |
| + * |
| + * Run as: low-mem-test <allocation size> <allocation interval (microseconds)> |
| + * |
| + * This program runs in two threads. One thread continuously allocates memory |
| + * in the given chunk size, waiting for the specified microsecond interval |
| + * between allocations. The other runs in a loop that waits for a low-memory |
| + * notification, then frees some of the memory that the first thread has |
| + * allocated. |
| + */ |
| + |
| +#include <poll.h> |
| +#include <sys/types.h> |
| +#include <sys/stat.h> |
| +#include <fcntl.h> |
| +#include <stdio.h> |
| +#include <pthread.h> |
| +#include <stdlib.h> |
| +#include <string.h> |
| + |
| +int memory_chunk_size = 10000000; |
| +int wait_time_us = 10000; |
| +int autotesting; |
| + |
| +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
| + |
| +struct node { |
| + void *memory; |
| + struct node *prev; |
| + struct node *next; |
| +}; |
| + |
| +struct node head, tail; |
| + |
| +void work(void) |
| +{ |
| + int i; |
| + |
| + while (1) { |
| + struct node *new = malloc(sizeof(struct node)); |
| + if (new == NULL) { |
| + perror("allocating node"); |
| + exit(1); |
| + } |
| + new->memory = malloc(memory_chunk_size); |
| + if (new->memory == NULL) { |
| + perror("allocating chunk"); |
| + exit(1); |
| + } |
| + |
| + pthread_mutex_lock(&mutex); |
| + new->next = &head; |
| + new->prev = head.prev; |
| + new->prev->next = new; |
| + new->next->prev = new; |
| + for (i = 0; i < memory_chunk_size / 4096; i++) { |
| + /* touch page */ |
| + ((unsigned char *) new->memory)[i * 4096] = 1; |
| + } |
| + |
| + pthread_mutex_unlock(&mutex); |
| + |
| + if (!autotesting) { |
| + printf("+"); |
| + fflush(stdout); |
| + } |
| + |
| + usleep(wait_time_us); |
| + } |
| +} |
| + |
| +void free_memory(void) |
| +{ |
| + struct node *old; |
| + pthread_mutex_lock(&mutex); |
| + old = tail.next; |
| + if (old == &head) { |
| + fprintf(stderr, "no memory left to free\n"); |
| + exit(1); |
| + } |
| + old->prev->next = old->next; |
| + old->next->prev = old->prev; |
| + free(old->memory); |
| + free(old); |
| + pthread_mutex_unlock(&mutex); |
| + if (!autotesting) { |
| + printf("-"); |
| + fflush(stdout); |
| + } |
| +} |
| + |
| +void *poll_thread(void *dummy) |
| +{ |
| + struct pollfd pfd; |
| + int fd = open("/dev/chromeos-low-mem", O_RDONLY); |
| + if (fd == -1) { |
| + perror("/dev/chromeos-low-mem"); |
| + exit(1); |
| + } |
| + |
| + pfd.fd = fd; |
| + pfd.events = POLLIN; |
| + |
| + if (autotesting) { |
| + /* Check that there is no memory shortage yet. */ |
| + poll(&pfd, 1, 0); |
| + if (pfd.revents != 0) { |
| + exit(0); |
| + } else { |
| + fprintf(stderr, "expected no events but " |
| + "poll() returned 0x%x\n", pfd.revents); |
| + exit(1); |
| + } |
| + } |
| + |
| + while (1) { |
| + poll(&pfd, 1, -1); |
| + if (autotesting) { |
| + /* Free several chunks and check that the notification |
| + * is gone. */ |
| + free_memory(); |
| + free_memory(); |
| + free_memory(); |
| + free_memory(); |
| + free_memory(); |
| + poll(&pfd, 1, 0); |
| + if (pfd.revents == 0) { |
| + exit(0); |
| + } else { |
| + fprintf(stderr, "expected no events but " |
| + "poll() returned 0x%x\n", pfd.revents); |
| + exit(1); |
| + } |
| + } |
| + free_memory(); |
| + } |
| +} |
| + |
| +int main(int argc, char **argv) |
| +{ |
| + pthread_t threadid; |
| + |
| + head.next = NULL; |
| + head.prev = &tail; |
| + tail.next = &head; |
| + tail.prev = NULL; |
| + |
| + if (argc != 3 && (argc != 2 || strcmp(argv[1], "autotesting"))) { |
| + fprintf(stderr, |
| + "usage: low-mem-test <alloc size in bytes> " |
| + "<alloc interval in microseconds>\n" |
| + "or: low-mem-test autotesting\n"); |
| + exit(1); |
| + } |
| + |
| + if (argc == 2) { |
| + autotesting = 1; |
| + } else { |
| + memory_chunk_size = atoi(argv[1]); |
| + wait_time_us = atoi(argv[2]); |
| + } |
| + |
| + if (pthread_create(&threadid, NULL, poll_thread, NULL)) { |
| + perror("pthread"); |
| + return 1; |
| + } |
| + |
| + work(); |
| + return 0; |
| +} |
| -- |
| 2.35.0.rc0.227.g00780c9af4-goog |
| |