Allow Cpuset memory nodesets to expand under pressure

Users don't always know how much memory they will really need, or how
to trade off performance against memory (particularly pagecache) usage.

In order to reduce wastage but still allow applications to get memory
when they need it, this patch adds several files to the cpusets
directory to allow additional memory nodes to be allocated to a cpuset
by the kernel if the cpuset's internal memory pressure gets too high.

The following files are added:

- expansion_limit - the maximum size (in bytes) to which this cpuset's
memory set can be expanded

- expansion_pressure - the abstract memory pressure for tasks within
the cpuset, ranging from 0 (no pressure) to 100 (about to go OOM) at
which expansion can occur

- unused_mems (read-only) - the set of memory nodes that are available
to this cpuset and not assigned to the mems set of any of this
cpuset's children; automatically maintained by the system

- expansion_mems - a vector of nodelists that determine which nodes
should be considered as potential expansion nodes, if available, in
priority order

A callback is added from try_to_free_pages() into the cpuset code,
passing a "memory pressure" value representing how hard the VM is
trying to free memory for the current cpuset. If the pressure is
greater than the cpuset's expansion_pressure, and the cpuset's total
assigned memory nodes sum to less than its expansion_limit, then the
elements of the cpuset's expansion_mems will be checked in turn to
find an intersection with the parent's unused_mems. The first such
intersecting node found is used to expand the cpuset's memory nodes.

The expansion_pressure value for a cpuset can be used as a simple
tuning knob to trade off ease of expansion (== greater memory usage)
versus performance, without requiring users to have a deep
understanding of how the VM allocation/reclaim mechanisms work. 


Controversial points about the patch:

- It makes the synchronization rules in cpuset.c more complex;
specifically the rule that you need to take both manage_mutex and
callback_mutex in order to modify a cpuset is no longer true; hence
even while holding manage_mutex, it's necessary to assume that certain
fields (*->mems_generation, *->mems_allowed, cpuset_mems_generation)
can change under you if you're not holding callback_mutex. This
requires race-detection and retry in update_nodemask() and
update_flag().

- During auto-expansion, a cpuset's mems_allowed can change without
invoking the same mempolicy rebinding/migration code as when
explicitly modified by userspace. I don't think that this is a
problem, since expanding a memory set by one node doesn't really cause
a useful migration event anyway. But I may be wrong ...

- The "memory pressure" used by the expansion functions is distinct
from the existing "memory pressure" reported by cpusets via the
flowmeter abstraction. I experimented with using the flowmeter
abstraction to trigger expansion events, but decided that it wasn't
really suitable - it reports the smoothed frequency of recent memory
reclaim events, rather than how hard we're currently trying to find
free memory; since a failure to find free memory results in an OOM
kill for one of the processes in the CPUset, this value doesn't always
show what we need. Perhaps these two concepts of memory pressure could
be combined in some way to give a single memory pressure value that's
appropriate for both uses; alternatively my "expansion_pressure" could
be renamed to something else.

- In an ideal world, the code to pick and allocate an additional node for the
cpuset belongs in userspace; this would require one of:

  - exporting much more state about the VM to userspace along with a
    userspace application that's checking very frequently for potentially
    OOM cpusets (an app allocating memory in busts can go OOM
    incredibly quickly)

  - a new callback mechanism to userspace that's invoked when internal
    memory pressure reaches a certain value, and requires userspace to
    quickly decide and assign a new node to the ailing cpuset, whilst
    avoiding potential deadlocking issues between the userspace
    process that's updating the cpuset and the memory-allocating
    process that's stuck in the callback

Allowing the user to (periodically, as necessary) specify nodemasks
with decreasing priority, along with a pressure threshold and limit,
and letting the kernel use these parameters when running low on memory
for a cpuset, seems like a good split between policy in userspace and
mechanism in the kernel


TODO: update Documentation/cpusets.txt, once any issues have been resolved

Signed-off-by: Paul Menage <[EMAIL PROTECTED]>

---
 include/linux/cpuset.h |    7 +
 kernel/cpuset.c        |  323 +++++++++++++++++++++++++++++++++++++++++++++++--
 mm/vmscan.c            |   12 +
 3 files changed, 330 insertions(+), 12 deletions(-)

Index: 2.6.19-autoexpand/include/linux/cpuset.h
===================================================================
--- 2.6.19-autoexpand.orig/include/linux/cpuset.h
+++ 2.6.19-autoexpand/include/linux/cpuset.h
@@ -46,6 +46,8 @@ extern int cpuset_excl_nodes_overlap(con
 extern int cpuset_memory_pressure_enabled;
 extern void __cpuset_memory_pressure_bump(void);
 
+extern int cpuset_expand_memset(int pressure, int gfp_mask);
+
 extern struct file_operations proc_cpuset_operations;
 extern char *cpuset_task_status_allowed(struct task_struct *task, char 
*buffer);
 
@@ -106,6 +108,11 @@ static inline int cpuset_excl_nodes_over
 
 static inline void cpuset_memory_pressure_bump(void) {}
 
+static inline int cpuset_expand_memset(int pressure, int gfp_mask)
+{
+       return 0;
+}
+
 static inline char *cpuset_task_status_allowed(struct task_struct *task,
                                                        char *buffer)
 {
Index: 2.6.19-autoexpand/kernel/cpuset.c
===================================================================
--- 2.6.19-autoexpand.orig/kernel/cpuset.c
+++ 2.6.19-autoexpand/kernel/cpuset.c
@@ -72,10 +72,14 @@ struct fmeter {
        spinlock_t lock;        /* guards read or write of above */
 };
 
+#define MAX_EXPANSION_MEM_TIERS 5
+
 struct cpuset {
        unsigned long flags;            /* "unsigned long" so bitops work */
        cpumask_t cpus_allowed;         /* CPUs allowed to tasks in cpuset */
        nodemask_t mems_allowed;        /* Memory Nodes allowed to tasks */
+       int        total_pages;         /* Total memory available */
+       nodemask_t unused_mems;         /* mems_allowed - all child mems */
 
        /*
         * Count is atomic so can incr (fork) or decr (exit) without a lock.
@@ -99,6 +103,15 @@ struct cpuset {
        int mems_generation;
 
        struct fmeter fmeter;           /* memory_pressure filter */
+
+       int expansion_pressure;         /* memory pressure (0-100) at
+                                        * which expansion occurs */
+       u64 expansion_limit;            /* limit in pages at which further
+                                        * expansion is forbidden */
+
+       /* Memory nodes available for expansion of child cpusets */
+       nodemask_t expansion_mems[MAX_EXPANSION_MEM_TIERS];
+
 };
 
 /* bits in struct cpuset flags field */
@@ -205,6 +218,17 @@ static struct super_block *cpuset_sb;
  * If a task is only holding callback_mutex, then it has read-only
  * access to cpusets.
  *
+ * There is one exception to this rule - whilst holding
+ * callback_mutex, a task may migrate a bit from
+ * cs->parent->unused_mems to cs->mems_allowed/cs->unused_mems. This
+ * allows tasks using auto-expanding memsets to increase their memory
+ * size without needing take the manage_mutex from within the memory
+ * allocator. To handle this, anyone modifying the mems_allowed field
+ * for a cpuset should read cpuset_mems_generation and
+ * cs->mems_allowed initially under callback_mutex, and then do a
+ * final check after aquiring callback_mutex but before making the
+ * change, to ensure that cpuset_mems_generation hasn't changed.
+ *
  * The task_struct fields mems_allowed and mems_generation may only
  * be accessed in the context of that task, so require no locks.
  *
@@ -787,7 +811,7 @@ static int update_cpumask(struct cpuset 
  *
  *    Migrate memory region from one set of nodes to another.
  *
- *    Temporarilly set tasks mems_allowed to target nodes of migration,
+ *    Temporarily set tasks mems_allowed to target nodes of migration,
  *    so that the migration code can allocate pages on these nodes.
  *
  *    Call holding manage_mutex, so our current->cpuset won't change
@@ -831,6 +855,34 @@ static void cpuset_migrate_mm(struct mm_
        mutex_unlock(&callback_mutex);
 }
 
+/* Update cs->unused_mems to be cs->mems_allowed - the union of all
+ * child mems_allowed masks. Must be called with callback_mutex
+ * held */
+
+static void update_unused_mems(struct cpuset *cs) {
+       struct cpuset *child;
+       cs->unused_mems = cs->mems_allowed;
+       list_for_each_entry(child, &cs->children, sibling) {
+               nodes_andnot(cs->unused_mems, cs->unused_mems,
+                            child->mems_allowed);
+       }
+}
+
+/* Update cs->total_pages to be the sum of the present_pages on all
+ * nodes available to the cpuset. Must be called with callback_mutex
+ * held */
+static void update_total_pages(struct cpuset *cs) {
+       nodemask_t tmp_nodes = cs->mems_allowed;
+       int total_pages = 0;
+       while (!nodes_empty(tmp_nodes)) {
+               int node = first_node(tmp_nodes);
+               node_clear(node, tmp_nodes);
+               if (node_isset(node, node_online_map))
+                       total_pages += node_present_pages(node);
+       }
+       cs->total_pages = total_pages;
+}
+
 /*
  * Handle user request to change the 'mems' memory placement
  * of a cpuset.  Needs to validate the request, update the
@@ -855,17 +907,24 @@ static int update_nodemask(struct cpuset
        int migrate;
        int fudge;
        int retval;
+       int generation;
+ again:
+       mutex_lock(&callback_mutex);
+       generation = cpuset_mems_generation;
+       oldmem = cs->mems_allowed;
+       mutex_unlock(&callback_mutex);
 
        /* top_cpuset.mems_allowed tracks node_online_map; it's read-only */
        if (cs == &top_cpuset)
                return -EACCES;
 
+       /* Non-atomically reads cs->mems_allowed, but that's OK since
+        * we're about to overwrite it in trialcs anyway */
        trialcs = *cs;
        retval = nodelist_parse(buf, trialcs.mems_allowed);
        if (retval < 0)
                goto done;
        nodes_and(trialcs.mems_allowed, trialcs.mems_allowed, node_online_map);
-       oldmem = cs->mems_allowed;
        if (nodes_equal(oldmem, trialcs.mems_allowed)) {
                retval = 0;             /* Too easy - nothing to do */
                goto done;
@@ -879,8 +938,16 @@ static int update_nodemask(struct cpuset
                goto done;
 
        mutex_lock(&callback_mutex);
+       if (generation != cpuset_mems_generation) {
+               /* We may have raced with an auto-expansion */
+               mutex_unlock(&callback_mutex);
+               goto again;
+       }
        cs->mems_allowed = trialcs.mems_allowed;
        cs->mems_generation = cpuset_mems_generation++;
+       update_unused_mems(cs);
+       if (cs->parent) update_unused_mems(cs->parent);
+       update_total_pages(cs);
        mutex_unlock(&callback_mutex);
 
        set_cpuset_being_rebound(cs);           /* causes mpol_copy() rebind */
@@ -946,9 +1013,9 @@ static int update_nodemask(struct cpuset
        for (i = 0; i < n; i++) {
                struct mm_struct *mm = mmarray[i];
 
-               mpol_rebind_mm(mm, &cs->mems_allowed);
+               mpol_rebind_mm(mm, &trialcs.mems_allowed);
                if (migrate)
-                       cpuset_migrate_mm(mm, &oldmem, &cs->mems_allowed);
+                       cpuset_migrate_mm(mm, &oldmem, &trialcs.mems_allowed);
                mmput(mm);
        }
 
@@ -960,6 +1027,42 @@ done:
        return retval;
 }
 
+/* called with manage_mutex held. Splits buf into whitespace-separated
+ * nodelists (up to MAX_EXPANSION_MEM_TIERS) and stores them in
+ * cs->expansion_mems[] */
+static int update_expansionmask(struct cpuset *cs, char *buf)
+{
+       nodemask_t new_mask;
+       int retval = 0;
+       char *nodelist;
+       int i = 0;
+
+       nodemask_t expansion_mems[MAX_EXPANSION_MEM_TIERS];
+
+       while (i < MAX_EXPANSION_MEM_TIERS) {
+               expansion_mems[i] = NODE_MASK_NONE;
+               nodelist = strsep(&buf, " \t\n");
+               if (!nodelist) break;
+               if (!*nodelist) continue;
+
+               retval = nodelist_parse(nodelist, new_mask);
+               if (retval)
+                       return retval;
+
+               expansion_mems[i] = new_mask;
+               i++;
+       }
+       if (buf)
+               return -ENOSPC;
+
+       mutex_lock(&callback_mutex);
+       memcpy(cs->expansion_mems, expansion_mems, sizeof(cs->expansion_mems));
+       mutex_unlock(&callback_mutex);
+
+       return retval;
+}
+
+
 /*
  * Call with manage_mutex held.
  */
@@ -988,11 +1091,16 @@ static int update_flag(cpuset_flagbits_t
 {
        int turning_on;
        struct cpuset trialcs;
-       int err;
+       int err, mem_exclusive_set;
+       int generation;
 
+ again:
+       mutex_lock(&callback_mutex);
+       generation = cpuset_mems_generation;
+       trialcs = *cs;
+       mutex_unlock(&callback_mutex);
        turning_on = (simple_strtoul(buf, NULL, 10) != 0);
 
-       trialcs = *cs;
        if (turning_on)
                set_bit(bit, &trialcs.flags);
        else
@@ -1001,7 +1109,14 @@ static int update_flag(cpuset_flagbits_t
        err = validate_change(cs, &trialcs);
        if (err < 0)
                return err;
+       mem_exclusive_set =
+               (!is_mem_exclusive(cs) && is_mem_exclusive(&trialcs));
        mutex_lock(&callback_mutex);
+       if (mem_exclusive_set && generation != cpuset_mems_generation) {
+               /* We may have raced with an auto-expansion */
+               mutex_unlock(&callback_mutex);
+               goto again;
+       }
        cs->flags = trialcs.flags;
        mutex_unlock(&callback_mutex);
 
@@ -1127,6 +1242,11 @@ static int attach_task(struct cpuset *cs
 
        if (sscanf(pidbuf, "%d", &pid) != 1)
                return -EIO;
+       /* Here it's safe to access cs->mems_allowed without taking
+        * callback_mutex - the only change permitted while we hold
+        * manage_mutex is an expansion event, which can only set a
+        * single bit in cs->mems_allowed and hence make the
+        * nodes_empty() call transition from true to false. */
        if (cpus_empty(cs->cpus_allowed) || nodes_empty(cs->mems_allowed))
                return -ENOSPC;
 
@@ -1216,6 +1336,10 @@ typedef enum {
        FILE_SPREAD_PAGE,
        FILE_SPREAD_SLAB,
        FILE_TASKLIST,
+       FILE_UNUSED_MEMS,
+       FILE_EXPANSION_PRESSURE,
+       FILE_EXPANSION_MEMS,
+       FILE_EXPANSION_LIMIT,
 } cpuset_filetype_t;
 
 static ssize_t cpuset_common_file_write(struct file *file,
@@ -1230,7 +1354,8 @@ static ssize_t cpuset_common_file_write(
        int retval = 0;
 
        /* Crude upper limit on largest legitimate cpulist user might write. */
-       if (nbytes > 100 + 6 * max(NR_CPUS, MAX_NUMNODES))
+       if (nbytes > 100 + 6 * max(NR_CPUS,
+                                  MAX_NUMNODES * MAX_EXPANSION_MEM_TIERS))
                return -E2BIG;
 
        /* +1 for nul-terminator */
@@ -1286,6 +1411,23 @@ static ssize_t cpuset_common_file_write(
        case FILE_TASKLIST:
                retval = attach_task(cs, buffer, &pathbuf);
                break;
+       case FILE_UNUSED_MEMS:
+               retval = -EACCES;
+               break;
+       case FILE_EXPANSION_PRESSURE:
+               mutex_lock(&callback_mutex);
+               cs->expansion_pressure = simple_strtoul(buffer, NULL, 10);
+               mutex_unlock(&callback_mutex);
+               break;
+       case FILE_EXPANSION_LIMIT:
+               mutex_lock(&callback_mutex);
+               cs->expansion_limit =
+                       simple_strtoull(buffer, NULL, 10) / PAGE_SIZE;
+               mutex_unlock(&callback_mutex);
+               break;
+       case FILE_EXPANSION_MEMS:
+               retval = update_expansionmask(cs, buffer);
+               break;
        default:
                retval = -EINVAL;
                goto out2;
@@ -1341,15 +1483,15 @@ static int cpuset_sprintf_cpulist(char *
        return cpulist_scnprintf(page, PAGE_SIZE, mask);
 }
 
-static int cpuset_sprintf_memlist(char *page, struct cpuset *cs)
+static int cpuset_scnprintf_memlist(char *page, size_t size, nodemask_t *m)
 {
        nodemask_t mask;
 
        mutex_lock(&callback_mutex);
-       mask = cs->mems_allowed;
+       mask = *m;
        mutex_unlock(&callback_mutex);
 
-       return nodelist_scnprintf(page, PAGE_SIZE, mask);
+       return nodelist_scnprintf(page, size, mask);
 }
 
 static ssize_t cpuset_common_file_read(struct file *file, char __user *buf,
@@ -1360,19 +1502,20 @@ static ssize_t cpuset_common_file_read(s
        cpuset_filetype_t type = cft->private;
        char *page;
        ssize_t retval = 0;
-       char *s;
+       char *s, *end;
 
        if (!(page = (char *)__get_free_page(GFP_KERNEL)))
                return -ENOMEM;
 
        s = page;
+       end = page + PAGE_SIZE - 1;
 
        switch (type) {
        case FILE_CPULIST:
                s += cpuset_sprintf_cpulist(s, cs);
                break;
        case FILE_MEMLIST:
-               s += cpuset_sprintf_memlist(s, cs);
+               s += cpuset_scnprintf_memlist(s, end - s, &cs->mems_allowed);
                break;
        case FILE_CPU_EXCLUSIVE:
                *s++ = is_cpu_exclusive(cs) ? '1' : '0';
@@ -1398,6 +1541,29 @@ static ssize_t cpuset_common_file_read(s
        case FILE_SPREAD_SLAB:
                *s++ = is_spread_slab(cs) ? '1' : '0';
                break;
+       case FILE_UNUSED_MEMS:
+               s += cpuset_scnprintf_memlist(s, PAGE_SIZE, &cs->unused_mems);
+               break;
+       case FILE_EXPANSION_PRESSURE:
+               s += sprintf(s, "%d", cs->expansion_pressure);
+               break;
+       case FILE_EXPANSION_LIMIT:
+               s += sprintf(s, "%lld",
+                            ((u64)cs->expansion_limit) * PAGE_SIZE);
+               break;
+       case FILE_EXPANSION_MEMS: {
+               int i;
+
+               for (i = 0; i < MAX_EXPANSION_MEM_TIERS; i++) {
+                       if (nodes_empty(cs->expansion_mems[i])) break;
+
+                       if (i && (s < end) )
+                               *s++ = '\n';
+                       s += cpuset_scnprintf_memlist(s, end - s,
+                                                     &cs->expansion_mems[i]);
+               }
+               break;
+       }
        default:
                retval = -EINVAL;
                goto out;
@@ -1771,6 +1937,26 @@ static struct cftype cft_spread_slab = {
        .private = FILE_SPREAD_SLAB,
 };
 
+static struct cftype cft_unused_mems = {
+       .name = "unused_mems",
+       .private = FILE_UNUSED_MEMS,
+};
+
+static struct cftype cft_expansion_pressure = {
+       .name = "expansion_pressure",
+       .private = FILE_EXPANSION_PRESSURE,
+};
+
+static struct cftype cft_expansion_mems = {
+       .name = "expansion_mems",
+       .private = FILE_EXPANSION_MEMS,
+};
+
+static struct cftype cft_expansion_limit = {
+       .name = "expansion_limit",
+       .private = FILE_EXPANSION_LIMIT,
+};
+
 static int cpuset_populate_dir(struct dentry *cs_dentry)
 {
        int err;
@@ -1795,6 +1981,14 @@ static int cpuset_populate_dir(struct de
                return err;
        if ((err = cpuset_add_file(cs_dentry, &cft_tasks)) < 0)
                return err;
+       if ((err = cpuset_add_file(cs_dentry, &cft_unused_mems)) < 0)
+               return err;
+       if ((err = cpuset_add_file(cs_dentry, &cft_expansion_pressure)) < 0)
+               return err;
+       if ((err = cpuset_add_file(cs_dentry, &cft_expansion_mems)) < 0)
+               return err;
+       if ((err = cpuset_add_file(cs_dentry, &cft_expansion_limit)) < 0)
+               return err;
        return 0;
 }
 
@@ -1827,11 +2021,16 @@ static long cpuset_create(struct cpuset 
                set_bit(CS_SPREAD_SLAB, &cs->flags);
        cs->cpus_allowed = CPU_MASK_NONE;
        cs->mems_allowed = NODE_MASK_NONE;
+       cs->unused_mems = NODE_MASK_NONE;
+       cs->total_pages = 0;
        atomic_set(&cs->count, 0);
        INIT_LIST_HEAD(&cs->sibling);
        INIT_LIST_HEAD(&cs->children);
        cs->mems_generation = cpuset_mems_generation++;
        fmeter_init(&cs->fmeter);
+       cs->expansion_pressure = -1;
+       memset(cs->expansion_mems, 0, sizeof(cs->expansion_mems));
+       cs->expansion_limit = 0;
 
        cs->parent = parent;
 
@@ -1899,6 +2098,7 @@ static int cpuset_rmdir(struct inode *un
        cpuset_d_remove_dir(d);
        dput(d);
        number_of_cpusets--;
+       update_unused_mems(parent);
        mutex_unlock(&callback_mutex);
        if (list_empty(&parent->children))
                check_for_release(parent, &pathbuf);
@@ -1935,6 +2135,8 @@ int __init cpuset_init(void)
 
        top_cpuset.cpus_allowed = CPU_MASK_ALL;
        top_cpuset.mems_allowed = NODE_MASK_ALL;
+       update_total_pages(&top_cpuset);
+       update_unused_mems(&top_cpuset);
 
        fmeter_init(&top_cpuset.fmeter);
        top_cpuset.mems_generation = cpuset_mems_generation++;
@@ -2024,6 +2226,8 @@ static void common_cpu_mem_hotplug_unplu
        guarantee_online_cpus_mems_in_subtree(&top_cpuset);
        top_cpuset.cpus_allowed = cpu_online_map;
        top_cpuset.mems_allowed = node_online_map;
+       update_unused_mems(&top_cpuset);
+       update_total_pages(&top_cpuset);
 
        mutex_unlock(&callback_mutex);
        mutex_unlock(&manage_mutex);
@@ -2069,6 +2273,8 @@ void __init cpuset_init_smp(void)
 {
        top_cpuset.cpus_allowed = cpu_online_map;
        top_cpuset.mems_allowed = node_online_map;
+       update_unused_mems(&top_cpuset);
+       update_total_pages(&top_cpuset);
 
        hotcpu_notifier(cpuset_handle_cpuhp, 0);
 }
@@ -2544,3 +2750,96 @@ char *cpuset_task_status_allowed(struct 
        buffer += sprintf(buffer, "\n");
        return buffer;
 }
+
+static int find_expansion_node(struct cpuset *cs) {
+       struct cpuset *parent;
+       nodemask_t tmp_nodes;
+       int i;
+
+       parent = cs->parent;
+       if (!parent)
+               return -1;
+       if (nodes_empty(parent->unused_mems))
+               return -1;
+       for (i = 0; i < MAX_EXPANSION_MEM_TIERS &&
+                    !nodes_empty(cs->expansion_mems[i]); i++) {
+               nodes_and(tmp_nodes, parent->unused_mems,
+                         cs->expansion_mems[i]);
+               if (!nodes_empty(tmp_nodes)) {
+                       return first_node(tmp_nodes);
+               }
+       }
+       return -1;
+}
+
+static inline int can_expand_memset(struct cpuset *cs, int pressure) {
+       /* if expansion isn't configured, don't expand */
+       if (cs->expansion_pressure < 0) return 0;
+       /* if memory pressure isn't high enough, don't expand */
+       if (pressure < cs->expansion_pressure) return 0;
+       /* if we're at the limit, don't expand */
+       if (cs->total_pages >= cs->expansion_limit) return 0;
+       return 1;
+}
+
+/* Callback from try_to_free_pages() to see whether the current cpuset
+ * can/should grow in response to memory pressure */
+int cpuset_expand_memset(int pressure, int gfp_mask) {
+
+       int retval = 0;
+       struct cpuset *cs = NULL;
+       int node;
+       int ok = 1;
+
+       if (!(gfp_mask & __GFP_WAIT)) goto out;
+
+       /* Simple checks before we take the mutex */
+       rcu_read_lock();
+       cs = rcu_dereference(current->cpuset);
+       /* We should take the lock if this CPUset is expandable */
+       ok = can_expand_memset(cs, pressure);
+       /* We should take the lock if this task is out-of-date */
+       ok |= cs->mems_generation != current->cpuset_mems_generation;
+       rcu_read_unlock();
+       if (!ok) return 0;
+
+       /* It looks like we might be able to expand. Grab the lock and
+        * check again */
+       mutex_lock(&callback_mutex);
+       cs = current->cpuset;
+       if (!nodes_equal(cs->mems_allowed, current->mems_allowed)) {
+               /* Our memset is out of date. Update it, but don't
+                * update our cpuset_mems_generation, since we've not
+                * done a full sync. */
+               guarantee_online_mems(cs, &current->mems_allowed);
+       }
+
+       if (!can_expand_memset(cs, pressure))
+               goto unlock;
+
+       node = find_expansion_node(cs);
+       if (node == -1)
+               goto unlock;
+
+       /* Move one bit from our parent's unused_mems to our own
+        * mems_allowed */
+       node_clear(node, cs->parent->unused_mems);
+       /* Here we're changing cs->mems_allowed without doing the full
+        * migration and mpol_rebind_mm() dance. But that's OK, since
+        * we're not really changing the node set, we're expanding it,
+        * so there wouldn't be any appropriate migration remapping
+        * behaviour anyway. */
+       node_set(node, cs->mems_allowed);
+       node_set(node, cs->unused_mems);
+       cs->mems_generation = cpuset_mems_generation++;
+       update_total_pages(cs);
+       /* Update the task's mems_allowed. We can't do the full
+        * cpuset_update_task_memory_state() since that could itself
+        * try to allocate memory */
+       node_set(node, current->mems_allowed);
+       retval = 1;
+ unlock:
+       mutex_unlock(&callback_mutex);
+ out:
+       return retval;
+}
Index: 2.6.19-autoexpand/mm/vmscan.c
===================================================================
--- 2.6.19-autoexpand.orig/mm/vmscan.c
+++ 2.6.19-autoexpand/mm/vmscan.c
@@ -1058,6 +1058,18 @@ unsigned long try_to_free_pages(struct z
        }
 
        for (priority = DEF_PRIORITY; priority >= 0; priority--) {
+               /* See if we can expand the current cpuset's
+                * nodemask. Translate the current priority value into
+                * an abstract pressure ranging between 0 (no
+                * pressure) and 100 (about to OOM) */
+               int pressure = (DEF_PRIORITY - priority) * 100 / DEF_PRIORITY;
+               if (!(current->flags & PF_KSWAPD) &&
+                   cpuset_expand_memset(pressure, gfp_mask)) {
+                       /* We successfully allocated a new node. This
+                        * counts as progress. */
+                       ret = 1;
+                       goto out;
+               }
                sc.nr_scanned = 0;
                if (!priority)
                        disable_swap_token();

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys - and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
ckrm-tech mailing list
https://lists.sourceforge.net/lists/listinfo/ckrm-tech

Reply via email to