| From 553bf9a92f74476859993151ff04a1709a1c138f 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 |
| |
| [rebase66(tzungbi): |
| Revised commit message. |
| Squashed: |
| FIXUP: CHROMIUM: Add /dev/low-mem device for low-memory notification. |
| CHROMIUM: low-mem-notify: Do not use min_filelist_kbytes |
| (https://crrev.com/c/4937820) |
| ] |
| Signed-off-by: Tzung-Bi Shih <tzungbi@chromium.org> |
| --- |
| drivers/char/mem.c | 4 + |
| include/linux/low-mem-notify.h | 22 ++ |
| mm/Kconfig | 11 + |
| mm/Makefile | 1 + |
| mm/low-mem-notify.c | 398 +++++++++++++++++++++++++++++++++ |
| mm/page_alloc.c | 8 +- |
| tools/mm/low-mem-test.c | 178 +++++++++++++++ |
| 7 files changed, 620 insertions(+), 2 deletions(-) |
| 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 |
| index 3c6670cf905f116a2aebc773f8f9bb8d6714ac85..b3a8e9162baf0cf1a759e56b94e1e82c83e65cd7 100644 |
| --- 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> |
| |
| #define DEVMEM_MINOR 1 |
| #define DEVPORT_MINOR 4 |
| @@ -702,6 +703,9 @@ static const struct memdev { |
| #ifdef CONFIG_PRINTK |
| [11] = { "kmsg", &kmsg_fops, 0, 0644 }, |
| #endif |
| +#ifdef CONFIG_LOW_MEM_NOTIFY |
| + [12] = { "chromeos-low-mem", &low_mem_notify_fops, 0, 0666 }, |
| +#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 |
| index 0000000000000000000000000000000000000000..0c2c18a9cc39df44fcdca15bc700991e1ed90fc4 |
| --- /dev/null |
| +++ b/include/linux/low-mem-notify.h |
| @@ -0,0 +1,22 @@ |
| +#ifndef _LINUX_LOW_MEM_NOTIFY_H |
| +#define _LINUX_LOW_MEM_NOTIFY_H |
| + |
| +#include <linux/types.h> |
| + |
| +#ifdef CONFIG_LOW_MEM_NOTIFY |
| +extern const struct file_operations low_mem_notify_fops; |
| + |
| +void low_mem_notify(void); |
| +bool low_mem_check(void); |
| +#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 |
| index be72538b1be42d7131ca21d4e8fcb92e7403d245..12815d17f74b64acfaa5b3a7f5b8f750ea50c7ab 100644 |
| --- a/mm/Kconfig |
| +++ b/mm/Kconfig |
| @@ -1278,6 +1278,17 @@ config LOCK_MM_AND_FIND_VMA |
| config IOMMU_MM_DATA |
| bool |
| |
| +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 |
| index e4b5b75aaec9c1192e3a60e922d90f0704155615..a8e3a329b0c9de041f1fea33a7de89b370216d25 100644 |
| --- a/mm/Makefile |
| +++ b/mm/Makefile |
| @@ -120,6 +120,7 @@ obj-$(CONFIG_CMA_SYSFS) += cma_sysfs.o |
| obj-$(CONFIG_USERFAULTFD) += userfaultfd.o |
| obj-$(CONFIG_IDLE_PAGE_TRACKING) += page_idle.o |
| obj-$(CONFIG_DEBUG_PAGEALLOC) += debug_page_alloc.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 |
| index 0000000000000000000000000000000000000000..b9b15853a3f54b6ffa7163f3753dfcf4481e30bf |
| --- /dev/null |
| +++ b/mm/low-mem-notify.c |
| @@ -0,0 +1,398 @@ |
| +/* |
| + * 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/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> |
| +#include <linux/ratelimit.h> |
| +#include <linux/stddef.h> |
| +#include <linux/swap.h> |
| +#include <linux/low-mem-notify.h> |
| + |
| +#define MB (1 << 20) |
| + |
| +static DECLARE_WAIT_QUEUE_HEAD(low_mem_wait); |
| +static atomic_t low_mem_state = ATOMIC_INIT(0); |
| + |
| +/* We support up to this many different thresholds. */ |
| +#define LOW_MEM_THRESHOLD_MAX 5 |
| + |
| +/* This is a list of thresholds in pages and should be in ascending order. */ |
| +static unsigned long low_mem_thresholds[LOW_MEM_THRESHOLD_MAX] = { |
| + 50 * MB / PAGE_SIZE |
| +}; |
| +static unsigned int low_mem_threshold_count = 1; |
| + |
| +static bool low_mem_margin_enabled = true; |
| +static unsigned int low_mem_ram_vs_swap_weight = 4; |
| + |
| +void low_mem_notify(void) |
| +{ |
| + atomic_set(&low_mem_state, true); |
| + wake_up(&low_mem_wait); |
| +} |
| + |
| +/* |
| + * Compute available memory used by files that can be reclaimed quickly. |
| + */ |
| +static 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); |
| + /* Since mglru is enabled by default, min_filelist_kbytes is not needed */ |
| + unsigned long min_file_mem = 0; |
| + unsigned long clean_file_mem = file_mem > dirty_mem ? |
| + file_mem - dirty_mem : 0; |
| + /* 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 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 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_SYSFS |
| +static void low_mem_threshold_notify(void); |
| +#else |
| +static void low_mem_threshold_notify(void) |
| +{ |
| +} |
| +#endif |
| + |
| +/* |
| + * Returns TRUE if we are in a low memory state. |
| + */ |
| +bool low_mem_check(void) |
| +{ |
| + static bool was_low_mem; /* = false, as per style guide */ |
| + static atomic_t in_low_mem_check = ATOMIC_INIT(0); |
| + /* last observed threshold */ |
| + static unsigned int low_mem_threshold_last = UINT_MAX; |
| + /* Limit logging low memory to once per second. */ |
| + static DEFINE_RATELIMIT_STATE(low_mem_logging_ratelimit, 1 * HZ, 1); |
| + |
| + /* 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_read(&in_low_mem_check) || 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; |
| +} |
| + |
| +static int low_mem_notify_open(struct inode *inode, struct file *file) |
| +{ |
| + return 0; |
| +} |
| + |
| +static int low_mem_notify_release(struct inode *inode, struct file *file) |
| +{ |
| + return 0; |
| +} |
| + |
| +static __poll_t low_mem_notify_poll(struct file *file, poll_table *wait) |
| +{ |
| + /* 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)) |
| + return POLLIN; |
| + |
| + return 0; |
| +} |
| + |
| +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) |
| +{ |
| + int i; |
| + ssize_t written = 0; |
| + |
| + if (!low_mem_margin_enabled || !low_mem_threshold_count) |
| + return sprintf(buf, "off\n"); |
| + |
| + 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; |
| +} |
| + |
| +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) |
| +{ |
| + int err; |
| + unsigned weight; |
| + |
| + err = kstrtouint(buf, 10, &weight); |
| + if (err) |
| + return -EINVAL; |
| + |
| + /* The special value 0 represents infinity. */ |
| + low_mem_ram_vs_swap_weight = !weight ? -1 : 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", |
| +}; |
| + |
| +static struct kernfs_node *low_mem_available_dirent; |
| + |
| +static 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) |
| +{ |
| + int err; |
| + struct kernfs_node *low_mem_node; |
| + |
| + err = sysfs_create_group(mm_kobj, &low_mem_attr_group); |
| + if (err) { |
| + pr_err("low_mem: register sysfs failed\n"); |
| + return err; |
| + } |
| + |
| + 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"); |
| + |
| + return 0; |
| +} |
| +module_init(low_mem_init) |
| + |
| +#endif |
| diff --git a/mm/page_alloc.c b/mm/page_alloc.c |
| index 150d4f23b01048ed7af53a74ec3e12a208fc17b5..98bd395be6e09e235554a3db4d8621cb0dad6288 100644 |
| --- a/mm/page_alloc.c |
| +++ b/mm/page_alloc.c |
| @@ -53,6 +53,7 @@ |
| #include <linux/khugepaged.h> |
| #include <linux/delayacct.h> |
| #include <linux/cacheinfo.h> |
| +#include <linux/low-mem-notify.h> |
| #include <asm/div64.h> |
| #include "internal.h" |
| #include "shuffle.h" |
| @@ -3950,9 +3951,10 @@ should_reclaim_retry(gfp_t gfp_mask, unsigned order, |
| else |
| (*no_progress_loops)++; |
| |
| - if (*no_progress_loops > MAX_RECLAIM_RETRIES) |
| + if (*no_progress_loops > MAX_RECLAIM_RETRIES) { |
| + low_mem_notify(); |
| goto out; |
| - |
| + } |
| |
| /* |
| * Keep reclaiming pages while there is a chance this will lead |
| @@ -4557,6 +4559,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 |
| index 0000000000000000000000000000000000000000..e5cc8390b3e0fd4cd8bdd7e033756b10fbc8e21d |
| --- /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.43.0.429.g432eaa2c6b-goog |
| |