Fix NUMA Memory Policy Reference Counting
[sfrench/cifs-2.6.git] / mm / mempolicy.c
index 4c0f99996811702055927f4447f347a2a837621d..3d6ac9505d0716e83c1eeee3a58ede5c5baa0a1b 100644 (file)
@@ -149,7 +149,7 @@ static struct zonelist *bind_zonelist(nodemask_t *nodes)
           lower zones etc. Avoid empty zones because the memory allocator
           doesn't like them. If you implement node hot removal you
           have to fix that. */
-       k = policy_zone;
+       k = MAX_NR_ZONES - 1;
        while (1) {
                for_each_node_mask(nd, *nodes) { 
                        struct zone *z = &NODE_DATA(nd)->node_zones[k];
@@ -955,6 +955,11 @@ asmlinkage long sys_migrate_pages(pid_t pid, unsigned long maxnode,
                goto out;
        }
 
+       if (!nodes_subset(new, node_online_map)) {
+               err = -EINVAL;
+               goto out;
+       }
+
        err = security_task_movememory(task);
        if (err)
                goto out;
@@ -1072,21 +1077,37 @@ asmlinkage long compat_sys_mbind(compat_ulong_t start, compat_ulong_t len,
 
 #endif
 
-/* Return effective policy for a VMA */
+/*
+ * get_vma_policy(@task, @vma, @addr)
+ * @task - task for fallback if vma policy == default
+ * @vma   - virtual memory area whose policy is sought
+ * @addr  - address in @vma for shared policy lookup
+ *
+ * Returns effective policy for a VMA at specified address.
+ * Falls back to @task or system default policy, as necessary.
+ * Returned policy has extra reference count if shared, vma,
+ * or some other task's policy [show_numa_maps() can pass
+ * @task != current].  It is the caller's responsibility to
+ * free the reference in these cases.
+ */
 static struct mempolicy * get_vma_policy(struct task_struct *task,
                struct vm_area_struct *vma, unsigned long addr)
 {
        struct mempolicy *pol = task->mempolicy;
+       int shared_pol = 0;
 
        if (vma) {
-               if (vma->vm_ops && vma->vm_ops->get_policy)
+               if (vma->vm_ops && vma->vm_ops->get_policy) {
                        pol = vma->vm_ops->get_policy(vma, addr);
-               else if (vma->vm_policy &&
+                       shared_pol = 1; /* if pol non-NULL, add ref below */
+               } else if (vma->vm_policy &&
                                vma->vm_policy->policy != MPOL_DEFAULT)
                        pol = vma->vm_policy;
        }
        if (!pol)
                pol = &default_policy;
+       else if (!shared_pol && pol != current->mempolicy)
+               mpol_get(pol);  /* vma or other task's policy */
        return pol;
 }
 
@@ -1202,18 +1223,45 @@ static inline unsigned interleave_nid(struct mempolicy *pol,
 }
 
 #ifdef CONFIG_HUGETLBFS
-/* Return a zonelist suitable for a huge page allocation. */
-struct zonelist *huge_zonelist(struct vm_area_struct *vma, unsigned long addr)
+/*
+ * huge_zonelist(@vma, @addr, @gfp_flags, @mpol)
+ * @vma = virtual memory area whose policy is sought
+ * @addr = address in @vma for shared policy lookup and interleave policy
+ * @gfp_flags = for requested zone
+ * @mpol = pointer to mempolicy pointer for reference counted 'BIND policy
+ *
+ * Returns a zonelist suitable for a huge page allocation.
+ * If the effective policy is 'BIND, returns pointer to policy's zonelist.
+ * If it is also a policy for which get_vma_policy() returns an extra
+ * reference, we must hold that reference until after allocation.
+ * In that case, return policy via @mpol so hugetlb allocation can drop
+ * the reference.  For non-'BIND referenced policies, we can/do drop the
+ * reference here, so the caller doesn't need to know about the special case
+ * for default and current task policy.
+ */
+struct zonelist *huge_zonelist(struct vm_area_struct *vma, unsigned long addr,
+                               gfp_t gfp_flags, struct mempolicy **mpol)
 {
        struct mempolicy *pol = get_vma_policy(current, vma, addr);
+       struct zonelist *zl;
 
+       *mpol = NULL;           /* probably no unref needed */
        if (pol->policy == MPOL_INTERLEAVE) {
                unsigned nid;
 
                nid = interleave_nid(pol, vma, addr, HPAGE_SHIFT);
-               return NODE_DATA(nid)->node_zonelists + gfp_zone(GFP_HIGHUSER);
+               __mpol_free(pol);               /* finished with pol */
+               return NODE_DATA(nid)->node_zonelists + gfp_zone(gfp_flags);
        }
-       return zonelist_policy(GFP_HIGHUSER, pol);
+
+       zl = zonelist_policy(GFP_HIGHUSER, pol);
+       if (unlikely(pol != &default_policy && pol != current->mempolicy)) {
+               if (pol->policy != MPOL_BIND)
+                       __mpol_free(pol);       /* finished with pol */
+               else
+                       *mpol = pol;    /* unref needed after allocation */
+       }
+       return zl;
 }
 #endif
 
@@ -1258,6 +1306,7 @@ struct page *
 alloc_page_vma(gfp_t gfp, struct vm_area_struct *vma, unsigned long addr)
 {
        struct mempolicy *pol = get_vma_policy(current, vma, addr);
+       struct zonelist *zl;
 
        cpuset_update_task_memory_state();
 
@@ -1267,7 +1316,19 @@ alloc_page_vma(gfp_t gfp, struct vm_area_struct *vma, unsigned long addr)
                nid = interleave_nid(pol, vma, addr, PAGE_SHIFT);
                return alloc_page_interleave(gfp, 0, nid);
        }
-       return __alloc_pages(gfp, 0, zonelist_policy(gfp, pol));
+       zl = zonelist_policy(gfp, pol);
+       if (pol != &default_policy && pol != current->mempolicy) {
+               /*
+                * slow path: ref counted policy -- shared or vma
+                */
+               struct page *page =  __alloc_pages(gfp, 0, zl);
+               __mpol_free(pol);
+               return page;
+       }
+       /*
+        * fast path:  default or task policy
+        */
+       return __alloc_pages(gfp, 0, zl);
 }
 
 /**
@@ -1604,11 +1665,11 @@ void __init numa_policy_init(void)
 
        policy_cache = kmem_cache_create("numa_policy",
                                         sizeof(struct mempolicy),
-                                        0, SLAB_PANIC, NULL, NULL);
+                                        0, SLAB_PANIC, NULL);
 
        sn_cache = kmem_cache_create("shared_policy_node",
                                     sizeof(struct sp_node),
-                                    0, SLAB_PANIC, NULL, NULL);
+                                    0, SLAB_PANIC, NULL);
 
        /*
         * Set interleaving policy for system init. Interleaving is only
@@ -1866,6 +1927,7 @@ int show_numa_map(struct seq_file *m, void *v)
        struct numa_maps *md;
        struct file *file = vma->vm_file;
        struct mm_struct *mm = vma->vm_mm;
+       struct mempolicy *pol;
        int n;
        char buffer[50];
 
@@ -1876,8 +1938,13 @@ int show_numa_map(struct seq_file *m, void *v)
        if (!md)
                return 0;
 
-       mpol_to_str(buffer, sizeof(buffer),
-                           get_vma_policy(priv->task, vma, vma->vm_start));
+       pol = get_vma_policy(priv->task, vma, vma->vm_start);
+       mpol_to_str(buffer, sizeof(buffer), pol);
+       /*
+        * unref shared or other task's mempolicy
+        */
+       if (pol != &default_policy && pol != current->mempolicy)
+               __mpol_free(pol);
 
        seq_printf(m, "%08lx %s", vma->vm_start, buffer);