blob: ff03e3056253ac8b3259e66e21460bad49b75b40 [file] [log] [blame]
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