mm: fix typo in refill_stock() comment
[pandora-kernel.git] / mm / memcontrol.c
index 9c9dfcf..6e8533e 100644 (file)
@@ -63,8 +63,15 @@ static int really_do_swap_account __initdata = 1; /* for remember boot option*/
 #define do_swap_account                (0)
 #endif
 
-#define SOFTLIMIT_EVENTS_THRESH (1000)
-#define THRESHOLDS_EVENTS_THRESH (100)
+/*
+ * Per memcg event counter is incremented at every pagein/pageout. This counter
+ * is used for trigger some periodic events. This is straightforward and better
+ * than using jiffies etc. to handle periodic memcg event.
+ *
+ * These values will be used as !((event) & ((1 <<(thresh)) - 1))
+ */
+#define THRESHOLDS_EVENTS_THRESH (7) /* once in 128 */
+#define SOFTLIMIT_EVENTS_THRESH (10) /* once in 1024 */
 
 /*
  * Statistics for memory cgroup.
@@ -79,10 +86,7 @@ enum mem_cgroup_stat_index {
        MEM_CGROUP_STAT_PGPGIN_COUNT,   /* # of pages paged in */
        MEM_CGROUP_STAT_PGPGOUT_COUNT,  /* # of pages paged out */
        MEM_CGROUP_STAT_SWAPOUT, /* # of pages, swapped out */
-       MEM_CGROUP_STAT_SOFTLIMIT, /* decrements on each page in/out.
-                                       used by soft limit implementation */
-       MEM_CGROUP_STAT_THRESHOLDS, /* decrements on each page in/out.
-                                       used by threshold implementation */
+       MEM_CGROUP_EVENTS,      /* incremented at every  pagein/pageout */
 
        MEM_CGROUP_STAT_NSTATS,
 };
@@ -154,7 +158,6 @@ struct mem_cgroup_threshold_ary {
        struct mem_cgroup_threshold entries[0];
 };
 
-static bool mem_cgroup_threshold_check(struct mem_cgroup *mem);
 static void mem_cgroup_threshold(struct mem_cgroup *mem);
 
 /*
@@ -200,7 +203,7 @@ struct mem_cgroup {
         * Should the accounting and control be hierarchical, per subtree?
         */
        bool use_hierarchy;
-       unsigned long   last_oom_jiffies;
+       atomic_t        oom_lock;
        atomic_t        refcnt;
 
        unsigned int    swappiness;
@@ -392,19 +395,6 @@ mem_cgroup_remove_exceeded(struct mem_cgroup *mem,
        spin_unlock(&mctz->lock);
 }
 
-static bool mem_cgroup_soft_limit_check(struct mem_cgroup *mem)
-{
-       bool ret = false;
-       s64 val;
-
-       val = this_cpu_read(mem->stat->count[MEM_CGROUP_STAT_SOFTLIMIT]);
-       if (unlikely(val < 0)) {
-               this_cpu_write(mem->stat->count[MEM_CGROUP_STAT_SOFTLIMIT],
-                               SOFTLIMIT_EVENTS_THRESH);
-               ret = true;
-       }
-       return ret;
-}
 
 static void mem_cgroup_update_tree(struct mem_cgroup *mem, struct page *page)
 {
@@ -542,8 +532,7 @@ static void mem_cgroup_charge_statistics(struct mem_cgroup *mem,
                __this_cpu_inc(mem->stat->count[MEM_CGROUP_STAT_PGPGIN_COUNT]);
        else
                __this_cpu_inc(mem->stat->count[MEM_CGROUP_STAT_PGPGOUT_COUNT]);
-       __this_cpu_dec(mem->stat->count[MEM_CGROUP_STAT_SOFTLIMIT]);
-       __this_cpu_dec(mem->stat->count[MEM_CGROUP_STAT_THRESHOLDS]);
+       __this_cpu_inc(mem->stat->count[MEM_CGROUP_EVENTS]);
 
        preempt_enable();
 }
@@ -563,6 +552,29 @@ static unsigned long mem_cgroup_get_local_zonestat(struct mem_cgroup *mem,
        return total;
 }
 
+static bool __memcg_event_check(struct mem_cgroup *mem, int event_mask_shift)
+{
+       s64 val;
+
+       val = this_cpu_read(mem->stat->count[MEM_CGROUP_EVENTS]);
+
+       return !(val & ((1 << event_mask_shift) - 1));
+}
+
+/*
+ * Check events in order.
+ *
+ */
+static void memcg_check_events(struct mem_cgroup *mem, struct page *page)
+{
+       /* threshold event is triggered in finer grain than soft limit */
+       if (unlikely(__memcg_event_check(mem, THRESHOLDS_EVENTS_THRESH))) {
+               mem_cgroup_threshold(mem);
+               if (unlikely(__memcg_event_check(mem, SOFTLIMIT_EVENTS_THRESH)))
+                       mem_cgroup_update_tree(mem, page);
+       }
+}
+
 static struct mem_cgroup *mem_cgroup_from_cont(struct cgroup *cont)
 {
        return container_of(cgroup_subsys_state(cont,
@@ -1234,32 +1246,102 @@ static int mem_cgroup_hierarchical_reclaim(struct mem_cgroup *root_mem,
        return total;
 }
 
-bool mem_cgroup_oom_called(struct task_struct *task)
+static int mem_cgroup_oom_lock_cb(struct mem_cgroup *mem, void *data)
 {
-       bool ret = false;
-       struct mem_cgroup *mem;
-       struct mm_struct *mm;
+       int *val = (int *)data;
+       int x;
+       /*
+        * Logically, we can stop scanning immediately when we find
+        * a memcg is already locked. But condidering unlock ops and
+        * creation/removal of memcg, scan-all is simple operation.
+        */
+       x = atomic_inc_return(&mem->oom_lock);
+       *val = max(x, *val);
+       return 0;
+}
+/*
+ * Check OOM-Killer is already running under our hierarchy.
+ * If someone is running, return false.
+ */
+static bool mem_cgroup_oom_lock(struct mem_cgroup *mem)
+{
+       int lock_count = 0;
 
-       rcu_read_lock();
-       mm = task->mm;
-       if (!mm)
-               mm = &init_mm;
-       mem = mem_cgroup_from_task(rcu_dereference(mm->owner));
-       if (mem && time_before(jiffies, mem->last_oom_jiffies + HZ/10))
-               ret = true;
-       rcu_read_unlock();
-       return ret;
+       mem_cgroup_walk_tree(mem, &lock_count, mem_cgroup_oom_lock_cb);
+
+       if (lock_count == 1)
+               return true;
+       return false;
 }
 
-static int record_last_oom_cb(struct mem_cgroup *mem, void *data)
+static int mem_cgroup_oom_unlock_cb(struct mem_cgroup *mem, void *data)
 {
-       mem->last_oom_jiffies = jiffies;
+       /*
+        * When a new child is created while the hierarchy is under oom,
+        * mem_cgroup_oom_lock() may not be called. We have to use
+        * atomic_add_unless() here.
+        */
+       atomic_add_unless(&mem->oom_lock, -1, 0);
        return 0;
 }
 
-static void record_last_oom(struct mem_cgroup *mem)
+static void mem_cgroup_oom_unlock(struct mem_cgroup *mem)
+{
+       mem_cgroup_walk_tree(mem, NULL, mem_cgroup_oom_unlock_cb);
+}
+
+static DEFINE_MUTEX(memcg_oom_mutex);
+static DECLARE_WAIT_QUEUE_HEAD(memcg_oom_waitq);
+
+/*
+ * try to call OOM killer. returns false if we should exit memory-reclaim loop.
+ */
+bool mem_cgroup_handle_oom(struct mem_cgroup *mem, gfp_t mask)
 {
-       mem_cgroup_walk_tree(mem, NULL, record_last_oom_cb);
+       DEFINE_WAIT(wait);
+       bool locked;
+
+       /* At first, try to OOM lock hierarchy under mem.*/
+       mutex_lock(&memcg_oom_mutex);
+       locked = mem_cgroup_oom_lock(mem);
+       /*
+        * Even if signal_pending(), we can't quit charge() loop without
+        * accounting. So, UNINTERRUPTIBLE is appropriate. But SIGKILL
+        * under OOM is always welcomed, use TASK_KILLABLE here.
+        */
+       if (!locked)
+               prepare_to_wait(&memcg_oom_waitq, &wait, TASK_KILLABLE);
+       mutex_unlock(&memcg_oom_mutex);
+
+       if (locked)
+               mem_cgroup_out_of_memory(mem, mask);
+       else {
+               schedule();
+               finish_wait(&memcg_oom_waitq, &wait);
+       }
+       mutex_lock(&memcg_oom_mutex);
+       mem_cgroup_oom_unlock(mem);
+       /*
+        * Here, we use global waitq .....more fine grained waitq ?
+        * Assume following hierarchy.
+        * A/
+        *   01
+        *   02
+        * assume OOM happens both in A and 01 at the same time. Tthey are
+        * mutually exclusive by lock. (kill in 01 helps A.)
+        * When we use per memcg waitq, we have to wake up waiters on A and 02
+        * in addtion to waiters on 01. We use global waitq for avoiding mess.
+        * It will not be a big problem.
+        * (And a task may be moved to other groups while it's waiting for OOM.)
+        */
+       wake_up_all(&memcg_oom_waitq);
+       mutex_unlock(&memcg_oom_mutex);
+
+       if (test_thread_flag(TIF_MEMDIE) || fatal_signal_pending(current))
+               return false;
+       /* Give chance to dying process */
+       schedule_timeout(1);
+       return true;
 }
 
 /*
@@ -1353,7 +1435,7 @@ static void drain_local_stock(struct work_struct *dummy)
 
 /*
  * Cache charges(val) which is from res_counter, to local per_cpu area.
- * This will be consumed by consumt_stock() function, later.
+ * This will be consumed by consume_stock() function, later.
  */
 static void refill_stock(struct mem_cgroup *mem, int val)
 {
@@ -1424,19 +1506,21 @@ static int __cpuinit memcg_stock_cpu_callback(struct notifier_block *nb,
  * oom-killer can be invoked.
  */
 static int __mem_cgroup_try_charge(struct mm_struct *mm,
-                       gfp_t gfp_mask, struct mem_cgroup **memcg,
-                       bool oom, struct page *page)
+                       gfp_t gfp_mask, struct mem_cgroup **memcg, bool oom)
 {
        struct mem_cgroup *mem, *mem_over_limit;
        int nr_retries = MEM_CGROUP_RECLAIM_RETRIES;
        struct res_counter *fail_res;
        int csize = CHARGE_SIZE;
 
-       if (unlikely(test_thread_flag(TIF_MEMDIE))) {
-               /* Don't account this! */
-               *memcg = NULL;
-               return 0;
-       }
+       /*
+        * Unlike gloval-vm's OOM-kill, we're not in memory shortage
+        * in system level. So, allow to go ahead dying process in addition to
+        * MEMDIE process.
+        */
+       if (unlikely(test_thread_flag(TIF_MEMDIE)
+                    || fatal_signal_pending(current)))
+               goto bypass;
 
        /*
         * We always charge the cgroup the mm_struct belongs to.
@@ -1463,7 +1547,7 @@ static int __mem_cgroup_try_charge(struct mm_struct *mm,
                unsigned long flags = 0;
 
                if (consume_stock(mem))
-                       goto charged;
+                       goto done;
 
                ret = res_counter_charge(&mem->res, csize, &fail_res);
                if (likely(!ret)) {
@@ -1549,29 +1633,27 @@ static int __mem_cgroup_try_charge(struct mm_struct *mm,
                }
 
                if (!nr_retries--) {
-                       if (oom) {
-                               mem_cgroup_out_of_memory(mem_over_limit, gfp_mask);
-                               record_last_oom(mem_over_limit);
+                       if (!oom)
+                               goto nomem;
+                       if (mem_cgroup_handle_oom(mem_over_limit, gfp_mask)) {
+                               nr_retries = MEM_CGROUP_RECLAIM_RETRIES;
+                               continue;
                        }
-                       goto nomem;
+                       /* When we reach here, current task is dying .*/
+                       css_put(&mem->css);
+                       goto bypass;
                }
        }
        if (csize > PAGE_SIZE)
                refill_stock(mem, csize - PAGE_SIZE);
-charged:
-       /*
-        * Insert ancestor (and ancestor's ancestors), to softlimit RB-tree.
-        * if they exceeds softlimit.
-        */
-       if (page && mem_cgroup_soft_limit_check(mem))
-               mem_cgroup_update_tree(mem, page);
 done:
-       if (mem_cgroup_threshold_check(mem))
-               mem_cgroup_threshold(mem);
        return 0;
 nomem:
        css_put(&mem->css);
        return -ENOMEM;
+bypass:
+       *memcg = NULL;
+       return 0;
 }
 
 /*
@@ -1691,6 +1773,12 @@ static void __mem_cgroup_commit_charge(struct mem_cgroup *mem,
        mem_cgroup_charge_statistics(mem, pc, true);
 
        unlock_page_cgroup(pc);
+       /*
+        * "charge_statistics" updated event counter. Then, check it.
+        * Insert ancestor (and ancestor's ancestors), to softlimit RB-tree.
+        * if they exceeds softlimit.
+        */
+       memcg_check_events(mem, pc->page);
 }
 
 /**
@@ -1760,6 +1848,11 @@ static int mem_cgroup_move_account(struct page_cgroup *pc,
                ret = 0;
        }
        unlock_page_cgroup(pc);
+       /*
+        * check events
+        */
+       memcg_check_events(to, pc->page);
+       memcg_check_events(from, pc->page);
        return ret;
 }
 
@@ -1788,7 +1881,7 @@ static int mem_cgroup_move_parent(struct page_cgroup *pc,
                goto put;
 
        parent = mem_cgroup_from_cont(pcg);
-       ret = __mem_cgroup_try_charge(NULL, gfp_mask, &parent, false, page);
+       ret = __mem_cgroup_try_charge(NULL, gfp_mask, &parent, false);
        if (ret || !parent)
                goto put_back;
 
@@ -1824,7 +1917,7 @@ static int mem_cgroup_charge_common(struct page *page, struct mm_struct *mm,
        prefetchw(pc);
 
        mem = memcg;
-       ret = __mem_cgroup_try_charge(mm, gfp_mask, &mem, true, page);
+       ret = __mem_cgroup_try_charge(mm, gfp_mask, &mem, true);
        if (ret || !mem)
                return ret;
 
@@ -1944,14 +2037,14 @@ int mem_cgroup_try_charge_swapin(struct mm_struct *mm,
        if (!mem)
                goto charge_cur_mm;
        *ptr = mem;
-       ret = __mem_cgroup_try_charge(NULL, mask, ptr, true, page);
+       ret = __mem_cgroup_try_charge(NULL, mask, ptr, true);
        /* drop extra refcnt from tryget */
        css_put(&mem->css);
        return ret;
 charge_cur_mm:
        if (unlikely(!mm))
                mm = &init_mm;
-       return __mem_cgroup_try_charge(mm, mask, ptr, true, page);
+       return __mem_cgroup_try_charge(mm, mask, ptr, true);
 }
 
 static void
@@ -2128,10 +2221,7 @@ __mem_cgroup_uncharge_common(struct page *page, enum charge_type ctype)
        mz = page_cgroup_zoneinfo(pc);
        unlock_page_cgroup(pc);
 
-       if (mem_cgroup_soft_limit_check(mem))
-               mem_cgroup_update_tree(mem, page);
-       if (mem_cgroup_threshold_check(mem))
-               mem_cgroup_threshold(mem);
+       memcg_check_events(mem, page);
        /* at swapout, this memcg will be accessed to record to swap */
        if (ctype != MEM_CGROUP_CHARGE_TYPE_SWAPOUT)
                css_put(&mem->css);
@@ -2340,8 +2430,7 @@ int mem_cgroup_prepare_migration(struct page *page, struct mem_cgroup **ptr)
        unlock_page_cgroup(pc);
 
        if (mem) {
-               ret = __mem_cgroup_try_charge(NULL, GFP_KERNEL, &mem, false,
-                                               page);
+               ret = __mem_cgroup_try_charge(NULL, GFP_KERNEL, &mem, false);
                css_put(&mem->css);
        }
        *ptr = mem;
@@ -3216,20 +3305,6 @@ static int mem_cgroup_swappiness_write(struct cgroup *cgrp, struct cftype *cft,
        return 0;
 }
 
-static bool mem_cgroup_threshold_check(struct mem_cgroup *mem)
-{
-       bool ret = false;
-       s64 val;
-
-       val = this_cpu_read(mem->stat->count[MEM_CGROUP_STAT_THRESHOLDS]);
-       if (unlikely(val < 0)) {
-               this_cpu_write(mem->stat->count[MEM_CGROUP_STAT_THRESHOLDS],
-                               THRESHOLDS_EVENTS_THRESH);
-               ret = true;
-       }
-       return ret;
-}
-
 static void __mem_cgroup_threshold(struct mem_cgroup *memcg, bool swap)
 {
        struct mem_cgroup_threshold_ary *t;
@@ -3366,12 +3441,6 @@ static int mem_cgroup_register_event(struct cgroup *cgrp, struct cftype *cft,
                }
        }
 
-       /*
-        * We need to increment refcnt to be sure that all thresholds
-        * will be unregistered before calling __mem_cgroup_free()
-        */
-       mem_cgroup_get(memcg);
-
        if (type == _MEM)
                rcu_assign_pointer(memcg->thresholds, thresholds_new);
        else
@@ -3465,9 +3534,6 @@ assign:
        /* To be sure that nobody uses thresholds before freeing it */
        synchronize_rcu();
 
-       for (i = 0; i < thresholds->size - size; i++)
-               mem_cgroup_put(memcg);
-
        kfree(thresholds);
 unlock:
        mutex_unlock(&memcg->thresholds_lock);
@@ -3872,8 +3938,7 @@ one_by_one:
                        batch_count = PRECHARGE_COUNT_AT_ONCE;
                        cond_resched();
                }
-               ret = __mem_cgroup_try_charge(NULL, GFP_KERNEL, &mem,
-                                                               false, NULL);
+               ret = __mem_cgroup_try_charge(NULL, GFP_KERNEL, &mem, false);
                if (ret || !mem)
                        /* mem_cgroup_clear_mc() will do uncharge later */
                        return -ENOMEM;