blob: 0a7b07162314cc8fe671db80baba09e9b7b21c57 [file] [log] [blame]
/*
* 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>
static DECLARE_WAIT_QUEUE_HEAD(low_mem_wait);
static atomic_t low_mem_state = ATOMIC_INIT(0);
unsigned low_mem_margin_mb = 50;
bool low_mem_margin_enabled = true;
unsigned long low_mem_minfree;
unsigned int low_mem_ram_vs_swap_weight = 4;
/*
* We're interested in worst-case anon memory usage when the low-memory
* notification fires. To contain logging, we limit our interest to
* non-trivial steps.
*/
unsigned long low_mem_lowest_seen_anon_mem;
const unsigned long low_mem_anon_mem_delta = 10 * 1024 * 1024 / PAGE_SIZE;
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 unsigned int 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, is_low_mem_situation());
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)
return sprintf(buf, "%u\n", low_mem_margin_mb);
else
return sprintf(buf, "off\n");
}
static unsigned low_mem_margin_to_minfree(unsigned margin_mb)
{
return margin_mb * (1024 * 1024 / PAGE_SIZE);
}
static ssize_t low_mem_margin_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int err;
unsigned long margin;
/*
* 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;
}
err = kstrtoul(buf, 10, &margin);
if (err)
return -EINVAL;
if (margin * ((1024 * 1024) / PAGE_SIZE) > totalram_pages)
return -EINVAL;
/* Notify when the "free" memory is below margin megabytes. */
low_mem_margin_enabled = true;
low_mem_margin_mb = (unsigned int) margin;
/* Convert to pages outside the allocator fast path. */
low_mem_minfree = low_mem_margin_to_minfree(low_mem_margin_mb);
pr_info("low_mem: setting minfree to %lu kB\n",
low_mem_minfree * (PAGE_SIZE / 1024));
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)
{
const int lru_base = NR_LRU_BASE - LRU_BASE;
unsigned long available_mem = get_available_mem_adj(lru_base);
return sprintf(buf, "%lu\n",
available_mem / (1024 * 1024 / 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 int __init low_mem_init(void)
{
int err = sysfs_create_group(mm_kobj, &low_mem_attr_group);
if (err)
pr_err("low_mem: register sysfs failed\n");
low_mem_minfree = low_mem_margin_to_minfree(low_mem_margin_mb);
low_mem_lowest_seen_anon_mem = totalram_pages;
return err;
}
module_init(low_mem_init)
#endif