[PATCH] cpuset: mark number_of_cpusets read_mostly
[deliverable/linux.git] / kernel / cpuset.c
index e9917d71628a039d5f0e157cc603f08ef8bb4959..681a5d58d40dcf5b40d73da17639b4c7729a936c 100644 (file)
@@ -39,6 +39,7 @@
 #include <linux/namei.h>
 #include <linux/pagemap.h>
 #include <linux/proc_fs.h>
+#include <linux/rcupdate.h>
 #include <linux/sched.h>
 #include <linux/seq_file.h>
 #include <linux/slab.h>
 
 #define CPUSET_SUPER_MAGIC             0x27e0eb
 
+/*
+ * Tracks how many cpusets are currently defined in system.
+ * When there is only one cpuset (the root cpuset) we can
+ * short circuit some hooks.
+ */
+int number_of_cpusets __read_mostly;
+
 /* See "Frequency meter" comments, below. */
 
 struct fmeter {
@@ -241,6 +249,11 @@ static struct super_block *cpuset_sb;
  * a tasks cpuset pointer we use task_lock(), which acts on a spinlock
  * (task->alloc_lock) already in the task_struct routinely used for
  * such matters.
+ *
+ * P.S.  One more locking exception.  RCU is used to guard the
+ * update of a tasks cpuset pointer by attach_task() and the
+ * access of task->cpuset->mems_generation via that pointer in
+ * the routine cpuset_update_task_memory_state().
  */
 
 static DECLARE_MUTEX(manage_sem);
@@ -596,21 +609,31 @@ static void guarantee_online_mems(const struct cpuset *cs, nodemask_t *pmask)
  * Do not call this routine if in_interrupt().
  *
  * Call without callback_sem or task_lock() held.  May be called
- * with or without manage_sem held.  Except in early boot or
- * an exiting task, when tsk->cpuset is NULL, this routine will
- * acquire task_lock().  We don't need to use task_lock to guard
+ * with or without manage_sem held.  Doesn't need task_lock to guard
  * against another task changing a non-NULL cpuset pointer to NULL,
  * as that is only done by a task on itself, and if the current task
  * is here, it is not simultaneously in the exit code NULL'ing its
  * cpuset pointer.  This routine also might acquire callback_sem and
  * current->mm->mmap_sem during call.
  *
- * The task_lock() is required to dereference current->cpuset safely.
- * Without it, we could pick up the pointer value of current->cpuset
- * in one instruction, and then attach_task could give us a different
- * cpuset, and then the cpuset we had could be removed and freed,
- * and then on our next instruction, we could dereference a no longer
- * valid cpuset pointer to get its mems_generation field.
+ * Reading current->cpuset->mems_generation doesn't need task_lock
+ * to guard the current->cpuset derefence, because it is guarded
+ * from concurrent freeing of current->cpuset by attach_task(),
+ * using RCU.
+ *
+ * The rcu_dereference() is technically probably not needed,
+ * as I don't actually mind if I see a new cpuset pointer but
+ * an old value of mems_generation.  However this really only
+ * matters on alpha systems using cpusets heavily.  If I dropped
+ * that rcu_dereference(), it would save them a memory barrier.
+ * For all other arch's, rcu_dereference is a no-op anyway, and for
+ * alpha systems not using cpusets, another planned optimization,
+ * avoiding the rcu critical section for tasks in the root cpuset
+ * which is statically allocated, so can't vanish, will make this
+ * irrelevant.  Better to use RCU as intended, than to engage in
+ * some cute trick to save a memory barrier that is impossible to
+ * test, for alpha systems using cpusets heavily, which might not
+ * even exist.
  *
  * This routine is needed to update the per-task mems_allowed data,
  * within the tasks context, when it is trying to allocate memory
@@ -622,35 +645,22 @@ void cpuset_update_task_memory_state()
 {
        int my_cpusets_mem_gen;
        struct task_struct *tsk = current;
-       struct cpuset *cs = tsk->cpuset;
-
-       if (unlikely(!cs))
-               return;
+       struct cpuset *cs;
 
-       task_lock(tsk);
+       rcu_read_lock();
+       cs = rcu_dereference(tsk->cpuset);
        my_cpusets_mem_gen = cs->mems_generation;
-       task_unlock(tsk);
+       rcu_read_unlock();
 
        if (my_cpusets_mem_gen != tsk->cpuset_mems_generation) {
-               nodemask_t oldmem = tsk->mems_allowed;
-               int migrate;
-
                down(&callback_sem);
                task_lock(tsk);
                cs = tsk->cpuset;       /* Maybe changed when task not locked */
-               migrate = is_memory_migrate(cs);
                guarantee_online_mems(cs, &tsk->mems_allowed);
                tsk->cpuset_mems_generation = cs->mems_generation;
                task_unlock(tsk);
                up(&callback_sem);
-               numa_policy_rebind(&oldmem, &tsk->mems_allowed);
-               if (!nodes_equal(oldmem, tsk->mems_allowed)) {
-                       if (migrate) {
-                               do_migrate_pages(tsk->mm, &oldmem,
-                                       &tsk->mems_allowed,
-                                       MPOL_MF_MOVE_ALL);
-                       }
-               }
+               mpol_rebind_task(tsk, &tsk->mems_allowed);
        }
 }
 
@@ -805,12 +815,28 @@ static int update_cpumask(struct cpuset *cs, char *buf)
 }
 
 /*
+ * Handle user request to change the 'mems' memory placement
+ * of a cpuset.  Needs to validate the request, update the
+ * cpusets mems_allowed and mems_generation, and for each
+ * task in the cpuset, rebind any vma mempolicies and if
+ * the cpuset is marked 'memory_migrate', migrate the tasks
+ * pages to the new memory.
+ *
  * Call with manage_sem held.  May take callback_sem during call.
+ * Will take tasklist_lock, scan tasklist for tasks in cpuset cs,
+ * lock each such tasks mm->mmap_sem, scan its vma's and rebind
+ * their mempolicies to the cpusets new mems_allowed.
  */
 
 static int update_nodemask(struct cpuset *cs, char *buf)
 {
        struct cpuset trialcs;
+       nodemask_t oldmem;
+       struct task_struct *g, *p;
+       struct mm_struct **mmarray;
+       int i, n, ntasks;
+       int migrate;
+       int fudge;
        int retval;
 
        trialcs = *cs;
@@ -818,6 +844,11 @@ static int update_nodemask(struct cpuset *cs, char *buf)
        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;
+       }
        if (nodes_empty(trialcs.mems_allowed)) {
                retval = -ENOSPC;
                goto done;
@@ -832,6 +863,81 @@ static int update_nodemask(struct cpuset *cs, char *buf)
        cs->mems_generation = atomic_read(&cpuset_mems_generation);
        up(&callback_sem);
 
+       set_cpuset_being_rebound(cs);           /* causes mpol_copy() rebind */
+
+       fudge = 10;                             /* spare mmarray[] slots */
+       fudge += cpus_weight(cs->cpus_allowed); /* imagine one fork-bomb/cpu */
+       retval = -ENOMEM;
+
+       /*
+        * Allocate mmarray[] to hold mm reference for each task
+        * in cpuset cs.  Can't kmalloc GFP_KERNEL while holding
+        * tasklist_lock.  We could use GFP_ATOMIC, but with a
+        * few more lines of code, we can retry until we get a big
+        * enough mmarray[] w/o using GFP_ATOMIC.
+        */
+       while (1) {
+               ntasks = atomic_read(&cs->count);       /* guess */
+               ntasks += fudge;
+               mmarray = kmalloc(ntasks * sizeof(*mmarray), GFP_KERNEL);
+               if (!mmarray)
+                       goto done;
+               write_lock_irq(&tasklist_lock);         /* block fork */
+               if (atomic_read(&cs->count) <= ntasks)
+                       break;                          /* got enough */
+               write_unlock_irq(&tasklist_lock);       /* try again */
+               kfree(mmarray);
+       }
+
+       n = 0;
+
+       /* Load up mmarray[] with mm reference for each task in cpuset. */
+       do_each_thread(g, p) {
+               struct mm_struct *mm;
+
+               if (n >= ntasks) {
+                       printk(KERN_WARNING
+                               "Cpuset mempolicy rebind incomplete.\n");
+                       continue;
+               }
+               if (p->cpuset != cs)
+                       continue;
+               mm = get_task_mm(p);
+               if (!mm)
+                       continue;
+               mmarray[n++] = mm;
+       } while_each_thread(g, p);
+       write_unlock_irq(&tasklist_lock);
+
+       /*
+        * Now that we've dropped the tasklist spinlock, we can
+        * rebind the vma mempolicies of each mm in mmarray[] to their
+        * new cpuset, and release that mm.  The mpol_rebind_mm()
+        * call takes mmap_sem, which we couldn't take while holding
+        * tasklist_lock.  Forks can happen again now - the mpol_copy()
+        * cpuset_being_rebound check will catch such forks, and rebind
+        * their vma mempolicies too.  Because we still hold the global
+        * cpuset manage_sem, we know that no other rebind effort will
+        * be contending for the global variable cpuset_being_rebound.
+        * It's ok if we rebind the same mm twice; mpol_rebind_mm()
+        * is idempotent.  Also migrate pages in each mm to new nodes.
+        */
+       migrate = is_memory_migrate(cs);
+       for (i = 0; i < n; i++) {
+               struct mm_struct *mm = mmarray[i];
+
+               mpol_rebind_mm(mm, &cs->mems_allowed);
+               if (migrate) {
+                       do_migrate_pages(mm, &oldmem, &cs->mems_allowed,
+                                                       MPOL_MF_MOVE_ALL);
+               }
+               mmput(mm);
+       }
+
+       /* We're done rebinding vma's to this cpusets new mems_allowed. */
+       kfree(mmarray);
+       set_cpuset_being_rebound(NULL);
+       retval = 0;
 done:
        return retval;
 }
@@ -1004,6 +1110,7 @@ static int attach_task(struct cpuset *cs, char *pidbuf, char **ppathbuf)
        struct cpuset *oldcs;
        cpumask_t cpus;
        nodemask_t from, to;
+       struct mm_struct *mm;
 
        if (sscanf(pidbuf, "%d", &pid) != 1)
                return -EIO;
@@ -1043,7 +1150,7 @@ static int attach_task(struct cpuset *cs, char *pidbuf, char **ppathbuf)
                return -ESRCH;
        }
        atomic_inc(&cs->count);
-       tsk->cpuset = cs;
+       rcu_assign_pointer(tsk->cpuset, cs);
        task_unlock(tsk);
 
        guarantee_online_cpus(cs, &cpus);
@@ -1053,9 +1160,17 @@ static int attach_task(struct cpuset *cs, char *pidbuf, char **ppathbuf)
        to = cs->mems_allowed;
 
        up(&callback_sem);
+
+       mm = get_task_mm(tsk);
+       if (mm) {
+               mpol_rebind_mm(mm, &to);
+               mmput(mm);
+       }
+
        if (is_memory_migrate(cs))
                do_migrate_pages(tsk->mm, &from, &to, MPOL_MF_MOVE_ALL);
        put_task_struct(tsk);
+       synchronize_rcu();
        if (atomic_dec_and_test(&oldcs->count))
                check_for_release(oldcs, ppathbuf);
        return 0;
@@ -1664,6 +1779,7 @@ static long cpuset_create(struct cpuset *parent, const char *name, int mode)
 
        down(&callback_sem);
        list_add(&cs->sibling, &cs->parent->children);
+       number_of_cpusets++;
        up(&callback_sem);
 
        err = cpuset_create_dir(cs, name, mode);
@@ -1726,6 +1842,7 @@ static int cpuset_rmdir(struct inode *unused_dir, struct dentry *dentry)
        spin_unlock(&d->d_lock);
        cpuset_d_remove_dir(d);
        dput(d);
+       number_of_cpusets--;
        up(&callback_sem);
        if (list_empty(&parent->children))
                check_for_release(parent, &pathbuf);
@@ -1734,6 +1851,21 @@ static int cpuset_rmdir(struct inode *unused_dir, struct dentry *dentry)
        return 0;
 }
 
+/*
+ * cpuset_init_early - just enough so that the calls to
+ * cpuset_update_task_memory_state() in early init code
+ * are harmless.
+ */
+
+int __init cpuset_init_early(void)
+{
+       struct task_struct *tsk = current;
+
+       tsk->cpuset = &top_cpuset;
+       tsk->cpuset->mems_generation = atomic_read(&cpuset_mems_generation);
+       return 0;
+}
+
 /**
  * cpuset_init - initialize cpusets at system boot
  *
@@ -1769,6 +1901,7 @@ int __init cpuset_init(void)
        root->d_inode->i_nlink++;
        top_cpuset.dentry = root;
        root->d_inode->i_op = &cpuset_dir_inode_operations;
+       number_of_cpusets = 1;
        err = cpuset_populate_dir(root);
        /* memory_pressure_enabled is in root cpuset only */
        if (err == 0)
@@ -1871,14 +2004,14 @@ void cpuset_exit(struct task_struct *tsk)
  * tasks cpuset.
  **/
 
-cpumask_t cpuset_cpus_allowed(const struct task_struct *tsk)
+cpumask_t cpuset_cpus_allowed(struct task_struct *tsk)
 {
        cpumask_t mask;
 
        down(&callback_sem);
-       task_lock((struct task_struct *)tsk);
+       task_lock(tsk);
        guarantee_online_cpus(tsk->cpuset, &mask);
-       task_unlock((struct task_struct *)tsk);
+       task_unlock(tsk);
        up(&callback_sem);
 
        return mask;
@@ -1889,6 +2022,29 @@ void cpuset_init_current_mems_allowed(void)
        current->mems_allowed = NODE_MASK_ALL;
 }
 
+/**
+ * cpuset_mems_allowed - return mems_allowed mask from a tasks cpuset.
+ * @tsk: pointer to task_struct from which to obtain cpuset->mems_allowed.
+ *
+ * Description: Returns the nodemask_t mems_allowed of the cpuset
+ * attached to the specified @tsk.  Guaranteed to return some non-empty
+ * subset of node_online_map, even if this means going outside the
+ * tasks cpuset.
+ **/
+
+nodemask_t cpuset_mems_allowed(struct task_struct *tsk)
+{
+       nodemask_t mask;
+
+       down(&callback_sem);
+       task_lock(tsk);
+       guarantee_online_mems(tsk->cpuset, &mask);
+       task_unlock(tsk);
+       up(&callback_sem);
+
+       return mask;
+}
+
 /**
  * cpuset_zonelist_valid_mems_allowed - check zonelist vs. curremt mems_allowed
  * @zl: the zonelist to be checked
@@ -1959,7 +2115,7 @@ static const struct cpuset *nearest_exclusive_ancestor(const struct cpuset *cs)
  *     GFP_USER     - only nodes in current tasks mems allowed ok.
  **/
 
-int cpuset_zone_allowed(struct zone *z, gfp_t gfp_mask)
+int __cpuset_zone_allowed(struct zone *z, gfp_t gfp_mask)
 {
        int node;                       /* node that zone z is on */
        const struct cpuset *cs;        /* current cpuset ancestors */
This page took 0.027324 seconds and 5 git commands to generate.