| /* |
| * 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; |
| |
| /* Limit logging low memory to once per second. */ |
| DEFINE_RATELIMIT_STATE(low_mem_logging_ratelimit, 1 * HZ, 1); |
| |
| 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 = strict_strtoul(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) |
| { |
| unsigned long available_mem = get_available_mem_adj(); |
| |
| 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); |
| return err; |
| } |
| module_init(low_mem_init) |
| |
| #endif |