mm: memcontrol: lockless page counters
Memory is internally accounted in bytes, using spinlock-protected 64-bit counters, even though the smallest accounting delta is a page. The counter interface is also convoluted and does too many things. Introduce a new lockless word-sized page counter API, then change all memory accounting over to it. The translation from and to bytes then only happens when interfacing with userspace. The removed locking overhead is noticable when scaling beyond the per-cpu charge caches - on a 4-socket machine with 144-threads, the following test shows the performance differences of 288 memcgs concurrently running a page fault benchmark: vanilla: 18631648.500498 task-clock (msec) # 140.643 CPUs utilized ( +- 0.33% ) 1,380,638 context-switches # 0.074 K/sec ( +- 0.75% ) 24,390 cpu-migrations # 0.001 K/sec ( +- 8.44% ) 1,843,305,768 page-faults # 0.099 M/sec ( +- 0.00% ) 50,134,994,088,218 cycles # 2.691 GHz ( +- 0.33% ) <not supported> stalled-cycles-frontend <not supported> stalled-cycles-backend 8,049,712,224,651 instructions # 0.16 insns per cycle ( +- 0.04% ) 1,586,970,584,979 branches # 85.176 M/sec ( +- 0.05% ) 1,724,989,949 branch-misses # 0.11% of all branches ( +- 0.48% ) 132.474343877 seconds time elapsed ( +- 0.21% ) lockless: 12195979.037525 task-clock (msec) # 133.480 CPUs utilized ( +- 0.18% ) 832,850 context-switches # 0.068 K/sec ( +- 0.54% ) 15,624 cpu-migrations # 0.001 K/sec ( +- 10.17% ) 1,843,304,774 page-faults # 0.151 M/sec ( +- 0.00% ) 32,811,216,801,141 cycles # 2.690 GHz ( +- 0.18% ) <not supported> stalled-cycles-frontend <not supported> stalled-cycles-backend 9,999,265,091,727 instructions # 0.30 insns per cycle ( +- 0.10% ) 2,076,759,325,203 branches # 170.282 M/sec ( +- 0.12% ) 1,656,917,214 branch-misses # 0.08% of all branches ( +- 0.55% ) 91.369330729 seconds time elapsed ( +- 0.45% ) On top of improved scalability, this also gets rid of the icky long long types in the very heart of memcg, which is great for 32 bit and also makes the code a lot more readable. Notable differences between the old and new API: - res_counter_charge() and res_counter_charge_nofail() become page_counter_try_charge() and page_counter_charge() resp. to match the more common kernel naming scheme of try_do()/do() - res_counter_uncharge_until() is only ever used to cancel a local counter and never to uncharge bigger segments of a hierarchy, so it's replaced by the simpler page_counter_cancel() - res_counter_set_limit() is replaced by page_counter_limit(), which expects its callers to serialize against themselves - res_counter_memparse_write_strategy() is replaced by page_counter_limit(), which rounds down to the nearest page size - rather than up. This is more reasonable for explicitely requested hard upper limits. - to keep charging light-weight, page_counter_try_charge() charges speculatively, only to roll back if the result exceeds the limit. Because of this, a failing bigger charge can temporarily lock out smaller charges that would otherwise succeed. The error is bounded to the difference between the smallest and the biggest possible charge size, so for memcg, this means that a failing THP charge can send base page charges into reclaim upto 2MB (4MB) before the limit would have been reached. This should be acceptable. [akpm@linux-foundation.org: add includes for WARN_ON_ONCE and memparse] [akpm@linux-foundation.org: add includes for WARN_ON_ONCE, memparse, strncmp, and PAGE_SIZE] Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Acked-by: Michal Hocko <mhocko@suse.cz> Acked-by: Vladimir Davydov <vdavydov@parallels.com> Cc: Tejun Heo <tj@kernel.org> Cc: David Rientjes <rientjes@google.com> Cc: Stephen Rothwell <sfr@canb.auug.org.au> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
8df0c2dcf6
commit
3e32cb2e0a
@ -52,9 +52,9 @@ Brief summary of control files.
|
||||
tasks # attach a task(thread) and show list of threads
|
||||
cgroup.procs # show list of processes
|
||||
cgroup.event_control # an interface for event_fd()
|
||||
memory.usage_in_bytes # show current res_counter usage for memory
|
||||
memory.usage_in_bytes # show current usage for memory
|
||||
(See 5.5 for details)
|
||||
memory.memsw.usage_in_bytes # show current res_counter usage for memory+Swap
|
||||
memory.memsw.usage_in_bytes # show current usage for memory+Swap
|
||||
(See 5.5 for details)
|
||||
memory.limit_in_bytes # set/show limit of memory usage
|
||||
memory.memsw.limit_in_bytes # set/show limit of memory+Swap usage
|
||||
|
@ -447,9 +447,8 @@ memcg_kmem_newpage_charge(gfp_t gfp, struct mem_cgroup **memcg, int order)
|
||||
/*
|
||||
* __GFP_NOFAIL allocations will move on even if charging is not
|
||||
* possible. Therefore we don't even try, and have this allocation
|
||||
* unaccounted. We could in theory charge it with
|
||||
* res_counter_charge_nofail, but we hope those allocations are rare,
|
||||
* and won't be worth the trouble.
|
||||
* unaccounted. We could in theory charge it forcibly, but we hope
|
||||
* those allocations are rare, and won't be worth the trouble.
|
||||
*/
|
||||
if (gfp & __GFP_NOFAIL)
|
||||
return true;
|
||||
|
51
include/linux/page_counter.h
Normal file
51
include/linux/page_counter.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef _LINUX_PAGE_COUNTER_H
|
||||
#define _LINUX_PAGE_COUNTER_H
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <asm/page.h>
|
||||
|
||||
struct page_counter {
|
||||
atomic_long_t count;
|
||||
unsigned long limit;
|
||||
struct page_counter *parent;
|
||||
|
||||
/* legacy */
|
||||
unsigned long watermark;
|
||||
unsigned long failcnt;
|
||||
};
|
||||
|
||||
#if BITS_PER_LONG == 32
|
||||
#define PAGE_COUNTER_MAX LONG_MAX
|
||||
#else
|
||||
#define PAGE_COUNTER_MAX (LONG_MAX / PAGE_SIZE)
|
||||
#endif
|
||||
|
||||
static inline void page_counter_init(struct page_counter *counter,
|
||||
struct page_counter *parent)
|
||||
{
|
||||
atomic_long_set(&counter->count, 0);
|
||||
counter->limit = PAGE_COUNTER_MAX;
|
||||
counter->parent = parent;
|
||||
}
|
||||
|
||||
static inline unsigned long page_counter_read(struct page_counter *counter)
|
||||
{
|
||||
return atomic_long_read(&counter->count);
|
||||
}
|
||||
|
||||
int page_counter_cancel(struct page_counter *counter, unsigned long nr_pages);
|
||||
void page_counter_charge(struct page_counter *counter, unsigned long nr_pages);
|
||||
int page_counter_try_charge(struct page_counter *counter,
|
||||
unsigned long nr_pages,
|
||||
struct page_counter **fail);
|
||||
int page_counter_uncharge(struct page_counter *counter, unsigned long nr_pages);
|
||||
int page_counter_limit(struct page_counter *counter, unsigned long limit);
|
||||
int page_counter_memparse(const char *buf, unsigned long *nr_pages);
|
||||
|
||||
static inline void page_counter_reset_watermark(struct page_counter *counter)
|
||||
{
|
||||
counter->watermark = page_counter_read(counter);
|
||||
}
|
||||
|
||||
#endif /* _LINUX_PAGE_COUNTER_H */
|
@ -54,8 +54,8 @@
|
||||
#include <linux/security.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/page_counter.h>
|
||||
#include <linux/memcontrol.h>
|
||||
#include <linux/res_counter.h>
|
||||
#include <linux/static_key.h>
|
||||
#include <linux/aio.h>
|
||||
#include <linux/sched.h>
|
||||
@ -1062,7 +1062,7 @@ enum cg_proto_flags {
|
||||
};
|
||||
|
||||
struct cg_proto {
|
||||
struct res_counter memory_allocated; /* Current allocated memory. */
|
||||
struct page_counter memory_allocated; /* Current allocated memory. */
|
||||
struct percpu_counter sockets_allocated; /* Current number of sockets. */
|
||||
int memory_pressure;
|
||||
long sysctl_mem[3];
|
||||
@ -1214,34 +1214,26 @@ static inline void memcg_memory_allocated_add(struct cg_proto *prot,
|
||||
unsigned long amt,
|
||||
int *parent_status)
|
||||
{
|
||||
struct res_counter *fail;
|
||||
int ret;
|
||||
page_counter_charge(&prot->memory_allocated, amt);
|
||||
|
||||
ret = res_counter_charge_nofail(&prot->memory_allocated,
|
||||
amt << PAGE_SHIFT, &fail);
|
||||
if (ret < 0)
|
||||
if (page_counter_read(&prot->memory_allocated) >
|
||||
prot->memory_allocated.limit)
|
||||
*parent_status = OVER_LIMIT;
|
||||
}
|
||||
|
||||
static inline void memcg_memory_allocated_sub(struct cg_proto *prot,
|
||||
unsigned long amt)
|
||||
{
|
||||
res_counter_uncharge(&prot->memory_allocated, amt << PAGE_SHIFT);
|
||||
}
|
||||
|
||||
static inline u64 memcg_memory_allocated_read(struct cg_proto *prot)
|
||||
{
|
||||
u64 ret;
|
||||
ret = res_counter_read_u64(&prot->memory_allocated, RES_USAGE);
|
||||
return ret >> PAGE_SHIFT;
|
||||
page_counter_uncharge(&prot->memory_allocated, amt);
|
||||
}
|
||||
|
||||
static inline long
|
||||
sk_memory_allocated(const struct sock *sk)
|
||||
{
|
||||
struct proto *prot = sk->sk_prot;
|
||||
|
||||
if (mem_cgroup_sockets_enabled && sk->sk_cgrp)
|
||||
return memcg_memory_allocated_read(sk->sk_cgrp);
|
||||
return page_counter_read(&sk->sk_cgrp->memory_allocated);
|
||||
|
||||
return atomic_long_read(prot->memory_allocated);
|
||||
}
|
||||
@ -1255,7 +1247,7 @@ sk_memory_allocated_add(struct sock *sk, int amt, int *parent_status)
|
||||
memcg_memory_allocated_add(sk->sk_cgrp, amt, parent_status);
|
||||
/* update the root cgroup regardless */
|
||||
atomic_long_add_return(amt, prot->memory_allocated);
|
||||
return memcg_memory_allocated_read(sk->sk_cgrp);
|
||||
return page_counter_read(&sk->sk_cgrp->memory_allocated);
|
||||
}
|
||||
|
||||
return atomic_long_add_return(amt, prot->memory_allocated);
|
||||
|
@ -978,9 +978,12 @@ config RESOURCE_COUNTERS
|
||||
This option enables controller independent resource accounting
|
||||
infrastructure that works with cgroups.
|
||||
|
||||
config PAGE_COUNTER
|
||||
bool
|
||||
|
||||
config MEMCG
|
||||
bool "Memory Resource Controller for Control Groups"
|
||||
depends on RESOURCE_COUNTERS
|
||||
select PAGE_COUNTER
|
||||
select EVENTFD
|
||||
help
|
||||
Provides a memory resource controller that manages both anonymous
|
||||
|
@ -55,6 +55,7 @@ obj-$(CONFIG_FS_XIP) += filemap_xip.o
|
||||
obj-$(CONFIG_MIGRATION) += migrate.o
|
||||
obj-$(CONFIG_QUICKLIST) += quicklist.o
|
||||
obj-$(CONFIG_TRANSPARENT_HUGEPAGE) += huge_memory.o
|
||||
obj-$(CONFIG_PAGE_COUNTER) += page_counter.o
|
||||
obj-$(CONFIG_MEMCG) += memcontrol.o page_cgroup.o vmpressure.o
|
||||
obj-$(CONFIG_CGROUP_HUGETLB) += hugetlb_cgroup.o
|
||||
obj-$(CONFIG_MEMORY_FAILURE) += memory-failure.o
|
||||
|
637
mm/memcontrol.c
637
mm/memcontrol.c
File diff suppressed because it is too large
Load Diff
207
mm/page_counter.c
Normal file
207
mm/page_counter.c
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Lockless hierarchical page accounting & limiting
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc., Johannes Weiner
|
||||
*/
|
||||
|
||||
#include <linux/page_counter.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/bug.h>
|
||||
#include <asm/page.h>
|
||||
|
||||
/**
|
||||
* page_counter_cancel - take pages out of the local counter
|
||||
* @counter: counter
|
||||
* @nr_pages: number of pages to cancel
|
||||
*
|
||||
* Returns whether there are remaining pages in the counter.
|
||||
*/
|
||||
int page_counter_cancel(struct page_counter *counter, unsigned long nr_pages)
|
||||
{
|
||||
long new;
|
||||
|
||||
new = atomic_long_sub_return(nr_pages, &counter->count);
|
||||
|
||||
/* More uncharges than charges? */
|
||||
WARN_ON_ONCE(new < 0);
|
||||
|
||||
return new > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* page_counter_charge - hierarchically charge pages
|
||||
* @counter: counter
|
||||
* @nr_pages: number of pages to charge
|
||||
*
|
||||
* NOTE: This does not consider any configured counter limits.
|
||||
*/
|
||||
void page_counter_charge(struct page_counter *counter, unsigned long nr_pages)
|
||||
{
|
||||
struct page_counter *c;
|
||||
|
||||
for (c = counter; c; c = c->parent) {
|
||||
long new;
|
||||
|
||||
new = atomic_long_add_return(nr_pages, &c->count);
|
||||
/*
|
||||
* This is indeed racy, but we can live with some
|
||||
* inaccuracy in the watermark.
|
||||
*/
|
||||
if (new > c->watermark)
|
||||
c->watermark = new;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* page_counter_try_charge - try to hierarchically charge pages
|
||||
* @counter: counter
|
||||
* @nr_pages: number of pages to charge
|
||||
* @fail: points first counter to hit its limit, if any
|
||||
*
|
||||
* Returns 0 on success, or -ENOMEM and @fail if the counter or one of
|
||||
* its ancestors has hit its configured limit.
|
||||
*/
|
||||
int page_counter_try_charge(struct page_counter *counter,
|
||||
unsigned long nr_pages,
|
||||
struct page_counter **fail)
|
||||
{
|
||||
struct page_counter *c;
|
||||
|
||||
for (c = counter; c; c = c->parent) {
|
||||
long new;
|
||||
/*
|
||||
* Charge speculatively to avoid an expensive CAS. If
|
||||
* a bigger charge fails, it might falsely lock out a
|
||||
* racing smaller charge and send it into reclaim
|
||||
* early, but the error is limited to the difference
|
||||
* between the two sizes, which is less than 2M/4M in
|
||||
* case of a THP locking out a regular page charge.
|
||||
*
|
||||
* The atomic_long_add_return() implies a full memory
|
||||
* barrier between incrementing the count and reading
|
||||
* the limit. When racing with page_counter_limit(),
|
||||
* we either see the new limit or the setter sees the
|
||||
* counter has changed and retries.
|
||||
*/
|
||||
new = atomic_long_add_return(nr_pages, &c->count);
|
||||
if (new > c->limit) {
|
||||
atomic_long_sub(nr_pages, &c->count);
|
||||
/*
|
||||
* This is racy, but we can live with some
|
||||
* inaccuracy in the failcnt.
|
||||
*/
|
||||
c->failcnt++;
|
||||
*fail = c;
|
||||
goto failed;
|
||||
}
|
||||
/*
|
||||
* Just like with failcnt, we can live with some
|
||||
* inaccuracy in the watermark.
|
||||
*/
|
||||
if (new > c->watermark)
|
||||
c->watermark = new;
|
||||
}
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
for (c = counter; c != *fail; c = c->parent)
|
||||
page_counter_cancel(c, nr_pages);
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/**
|
||||
* page_counter_uncharge - hierarchically uncharge pages
|
||||
* @counter: counter
|
||||
* @nr_pages: number of pages to uncharge
|
||||
*
|
||||
* Returns whether there are remaining charges in @counter.
|
||||
*/
|
||||
int page_counter_uncharge(struct page_counter *counter, unsigned long nr_pages)
|
||||
{
|
||||
struct page_counter *c;
|
||||
int ret = 1;
|
||||
|
||||
for (c = counter; c; c = c->parent) {
|
||||
int remainder;
|
||||
|
||||
remainder = page_counter_cancel(c, nr_pages);
|
||||
if (c == counter && !remainder)
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* page_counter_limit - limit the number of pages allowed
|
||||
* @counter: counter
|
||||
* @limit: limit to set
|
||||
*
|
||||
* Returns 0 on success, -EBUSY if the current number of pages on the
|
||||
* counter already exceeds the specified limit.
|
||||
*
|
||||
* The caller must serialize invocations on the same counter.
|
||||
*/
|
||||
int page_counter_limit(struct page_counter *counter, unsigned long limit)
|
||||
{
|
||||
for (;;) {
|
||||
unsigned long old;
|
||||
long count;
|
||||
|
||||
/*
|
||||
* Update the limit while making sure that it's not
|
||||
* below the concurrently-changing counter value.
|
||||
*
|
||||
* The xchg implies two full memory barriers before
|
||||
* and after, so the read-swap-read is ordered and
|
||||
* ensures coherency with page_counter_try_charge():
|
||||
* that function modifies the count before checking
|
||||
* the limit, so if it sees the old limit, we see the
|
||||
* modified counter and retry.
|
||||
*/
|
||||
count = atomic_long_read(&counter->count);
|
||||
|
||||
if (count > limit)
|
||||
return -EBUSY;
|
||||
|
||||
old = xchg(&counter->limit, limit);
|
||||
|
||||
if (atomic_long_read(&counter->count) <= count)
|
||||
return 0;
|
||||
|
||||
counter->limit = old;
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* page_counter_memparse - memparse() for page counter limits
|
||||
* @buf: string to parse
|
||||
* @nr_pages: returns the result in number of pages
|
||||
*
|
||||
* Returns -EINVAL, or 0 and @nr_pages on success. @nr_pages will be
|
||||
* limited to %PAGE_COUNTER_MAX.
|
||||
*/
|
||||
int page_counter_memparse(const char *buf, unsigned long *nr_pages)
|
||||
{
|
||||
char unlimited[] = "-1";
|
||||
char *end;
|
||||
u64 bytes;
|
||||
|
||||
if (!strncmp(buf, unlimited, sizeof(unlimited))) {
|
||||
*nr_pages = PAGE_COUNTER_MAX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytes = memparse(buf, &end);
|
||||
if (*end != '\0')
|
||||
return -EINVAL;
|
||||
|
||||
*nr_pages = min(bytes / PAGE_SIZE, (u64)PAGE_COUNTER_MAX);
|
||||
|
||||
return 0;
|
||||
}
|
@ -9,13 +9,13 @@
|
||||
int tcp_init_cgroup(struct mem_cgroup *memcg, struct cgroup_subsys *ss)
|
||||
{
|
||||
/*
|
||||
* The root cgroup does not use res_counters, but rather,
|
||||
* The root cgroup does not use page_counters, but rather,
|
||||
* rely on the data already collected by the network
|
||||
* subsystem
|
||||
*/
|
||||
struct res_counter *res_parent = NULL;
|
||||
struct cg_proto *cg_proto, *parent_cg;
|
||||
struct mem_cgroup *parent = parent_mem_cgroup(memcg);
|
||||
struct page_counter *counter_parent = NULL;
|
||||
struct cg_proto *cg_proto, *parent_cg;
|
||||
|
||||
cg_proto = tcp_prot.proto_cgroup(memcg);
|
||||
if (!cg_proto)
|
||||
@ -29,9 +29,9 @@ int tcp_init_cgroup(struct mem_cgroup *memcg, struct cgroup_subsys *ss)
|
||||
|
||||
parent_cg = tcp_prot.proto_cgroup(parent);
|
||||
if (parent_cg)
|
||||
res_parent = &parent_cg->memory_allocated;
|
||||
counter_parent = &parent_cg->memory_allocated;
|
||||
|
||||
res_counter_init(&cg_proto->memory_allocated, res_parent);
|
||||
page_counter_init(&cg_proto->memory_allocated, counter_parent);
|
||||
percpu_counter_init(&cg_proto->sockets_allocated, 0, GFP_KERNEL);
|
||||
|
||||
return 0;
|
||||
@ -50,7 +50,7 @@ void tcp_destroy_cgroup(struct mem_cgroup *memcg)
|
||||
}
|
||||
EXPORT_SYMBOL(tcp_destroy_cgroup);
|
||||
|
||||
static int tcp_update_limit(struct mem_cgroup *memcg, u64 val)
|
||||
static int tcp_update_limit(struct mem_cgroup *memcg, unsigned long nr_pages)
|
||||
{
|
||||
struct cg_proto *cg_proto;
|
||||
int i;
|
||||
@ -60,20 +60,17 @@ static int tcp_update_limit(struct mem_cgroup *memcg, u64 val)
|
||||
if (!cg_proto)
|
||||
return -EINVAL;
|
||||
|
||||
if (val > RES_COUNTER_MAX)
|
||||
val = RES_COUNTER_MAX;
|
||||
|
||||
ret = res_counter_set_limit(&cg_proto->memory_allocated, val);
|
||||
ret = page_counter_limit(&cg_proto->memory_allocated, nr_pages);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
cg_proto->sysctl_mem[i] = min_t(long, val >> PAGE_SHIFT,
|
||||
cg_proto->sysctl_mem[i] = min_t(long, nr_pages,
|
||||
sysctl_tcp_mem[i]);
|
||||
|
||||
if (val == RES_COUNTER_MAX)
|
||||
if (nr_pages == PAGE_COUNTER_MAX)
|
||||
clear_bit(MEMCG_SOCK_ACTIVE, &cg_proto->flags);
|
||||
else if (val != RES_COUNTER_MAX) {
|
||||
else {
|
||||
/*
|
||||
* The active bit needs to be written after the static_key
|
||||
* update. This is what guarantees that the socket activation
|
||||
@ -102,11 +99,20 @@ static int tcp_update_limit(struct mem_cgroup *memcg, u64 val)
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
RES_USAGE,
|
||||
RES_LIMIT,
|
||||
RES_MAX_USAGE,
|
||||
RES_FAILCNT,
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(tcp_limit_mutex);
|
||||
|
||||
static ssize_t tcp_cgroup_write(struct kernfs_open_file *of,
|
||||
char *buf, size_t nbytes, loff_t off)
|
||||
{
|
||||
struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
|
||||
unsigned long long val;
|
||||
unsigned long nr_pages;
|
||||
int ret = 0;
|
||||
|
||||
buf = strstrip(buf);
|
||||
@ -114,10 +120,12 @@ static ssize_t tcp_cgroup_write(struct kernfs_open_file *of,
|
||||
switch (of_cft(of)->private) {
|
||||
case RES_LIMIT:
|
||||
/* see memcontrol.c */
|
||||
ret = res_counter_memparse_write_strategy(buf, &val);
|
||||
ret = page_counter_memparse(buf, &nr_pages);
|
||||
if (ret)
|
||||
break;
|
||||
ret = tcp_update_limit(memcg, val);
|
||||
mutex_lock(&tcp_limit_mutex);
|
||||
ret = tcp_update_limit(memcg, nr_pages);
|
||||
mutex_unlock(&tcp_limit_mutex);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
@ -126,43 +134,36 @@ static ssize_t tcp_cgroup_write(struct kernfs_open_file *of,
|
||||
return ret ?: nbytes;
|
||||
}
|
||||
|
||||
static u64 tcp_read_stat(struct mem_cgroup *memcg, int type, u64 default_val)
|
||||
{
|
||||
struct cg_proto *cg_proto;
|
||||
|
||||
cg_proto = tcp_prot.proto_cgroup(memcg);
|
||||
if (!cg_proto)
|
||||
return default_val;
|
||||
|
||||
return res_counter_read_u64(&cg_proto->memory_allocated, type);
|
||||
}
|
||||
|
||||
static u64 tcp_read_usage(struct mem_cgroup *memcg)
|
||||
{
|
||||
struct cg_proto *cg_proto;
|
||||
|
||||
cg_proto = tcp_prot.proto_cgroup(memcg);
|
||||
if (!cg_proto)
|
||||
return atomic_long_read(&tcp_memory_allocated) << PAGE_SHIFT;
|
||||
|
||||
return res_counter_read_u64(&cg_proto->memory_allocated, RES_USAGE);
|
||||
}
|
||||
|
||||
static u64 tcp_cgroup_read(struct cgroup_subsys_state *css, struct cftype *cft)
|
||||
{
|
||||
struct mem_cgroup *memcg = mem_cgroup_from_css(css);
|
||||
struct cg_proto *cg_proto = tcp_prot.proto_cgroup(memcg);
|
||||
u64 val;
|
||||
|
||||
switch (cft->private) {
|
||||
case RES_LIMIT:
|
||||
val = tcp_read_stat(memcg, RES_LIMIT, RES_COUNTER_MAX);
|
||||
if (!cg_proto)
|
||||
return PAGE_COUNTER_MAX;
|
||||
val = cg_proto->memory_allocated.limit;
|
||||
val *= PAGE_SIZE;
|
||||
break;
|
||||
case RES_USAGE:
|
||||
val = tcp_read_usage(memcg);
|
||||
if (!cg_proto)
|
||||
val = atomic_long_read(&tcp_memory_allocated);
|
||||
else
|
||||
val = page_counter_read(&cg_proto->memory_allocated);
|
||||
val *= PAGE_SIZE;
|
||||
break;
|
||||
case RES_FAILCNT:
|
||||
if (!cg_proto)
|
||||
return 0;
|
||||
val = cg_proto->memory_allocated.failcnt;
|
||||
break;
|
||||
case RES_MAX_USAGE:
|
||||
val = tcp_read_stat(memcg, cft->private, 0);
|
||||
if (!cg_proto)
|
||||
return 0;
|
||||
val = cg_proto->memory_allocated.watermark;
|
||||
val *= PAGE_SIZE;
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
@ -183,10 +184,10 @@ static ssize_t tcp_cgroup_reset(struct kernfs_open_file *of,
|
||||
|
||||
switch (of_cft(of)->private) {
|
||||
case RES_MAX_USAGE:
|
||||
res_counter_reset_max(&cg_proto->memory_allocated);
|
||||
page_counter_reset_watermark(&cg_proto->memory_allocated);
|
||||
break;
|
||||
case RES_FAILCNT:
|
||||
res_counter_reset_failcnt(&cg_proto->memory_allocated);
|
||||
cg_proto->memory_allocated.failcnt = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user