Commit 10befea9 authored by Roman Gushchin's avatar Roman Gushchin Committed by Linus Torvalds

mm: memcg/slab: use a single set of kmem_caches for all allocations

Instead of having two sets of kmem_caches: one for system-wide and
non-accounted allocations and the second one shared by all accounted
allocations, we can use just one.

The idea is simple: space for obj_cgroup metadata can be allocated on
demand and filled only for accounted allocations.

It allows to remove a bunch of code which is required to handle kmem_cache
clones for accounted allocations.  There is no more need to create them,
accumulate statistics, propagate attributes, etc.  It's a quite
significant simplification.

Also, because the total number of slab_caches is reduced almost twice (not
all kmem_caches have a memcg clone), some additional memory savings are
expected.  On my devvm it additionally saves about 3.5% of slab memory.

[guro@fb.com: fix build on MIPS]
  Link: http://lkml.kernel.org/r/20200717214810.3733082-1-guro@fb.comSuggested-by: default avatarJohannes Weiner <hannes@cmpxchg.org>
Signed-off-by: default avatarRoman Gushchin <guro@fb.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Reviewed-by: default avatarVlastimil Babka <vbabka@suse.cz>
Reviewed-by: default avatarShakeel Butt <shakeelb@google.com>
Cc: Christoph Lameter <cl@linux.com>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Tejun Heo <tj@kernel.org>
Cc: Naresh Kamboju <naresh.kamboju@linaro.org>
Link: http://lkml.kernel.org/r/20200623174037.3951353-18-guro@fb.comSigned-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 15999eef
...@@ -155,8 +155,6 @@ struct kmem_cache *kmem_cache_create_usercopy(const char *name, ...@@ -155,8 +155,6 @@ struct kmem_cache *kmem_cache_create_usercopy(const char *name,
void kmem_cache_destroy(struct kmem_cache *); void kmem_cache_destroy(struct kmem_cache *);
int kmem_cache_shrink(struct kmem_cache *); int kmem_cache_shrink(struct kmem_cache *);
void memcg_create_kmem_cache(struct kmem_cache *cachep);
/* /*
* Please use this macro to create slab caches. Simply specify the * Please use this macro to create slab caches. Simply specify the
* name of the structure and maybe some flags that are listed above. * name of the structure and maybe some flags that are listed above.
......
...@@ -72,9 +72,6 @@ struct kmem_cache { ...@@ -72,9 +72,6 @@ struct kmem_cache {
int obj_offset; int obj_offset;
#endif /* CONFIG_DEBUG_SLAB */ #endif /* CONFIG_DEBUG_SLAB */
#ifdef CONFIG_MEMCG
struct memcg_cache_params memcg_params;
#endif
#ifdef CONFIG_KASAN #ifdef CONFIG_KASAN
struct kasan_cache kasan_info; struct kasan_cache kasan_info;
#endif #endif
......
...@@ -108,17 +108,7 @@ struct kmem_cache { ...@@ -108,17 +108,7 @@ struct kmem_cache {
struct list_head list; /* List of slab caches */ struct list_head list; /* List of slab caches */
#ifdef CONFIG_SYSFS #ifdef CONFIG_SYSFS
struct kobject kobj; /* For sysfs */ struct kobject kobj; /* For sysfs */
struct work_struct kobj_remove_work;
#endif #endif
#ifdef CONFIG_MEMCG
struct memcg_cache_params memcg_params;
/* For propagation, maximum size of a stored attr */
unsigned int max_attr_size;
#ifdef CONFIG_SYSFS
struct kset *memcg_kset;
#endif
#endif
#ifdef CONFIG_SLAB_FREELIST_HARDENED #ifdef CONFIG_SLAB_FREELIST_HARDENED
unsigned long random; unsigned long random;
#endif #endif
......
...@@ -2800,6 +2800,26 @@ static void commit_charge(struct page *page, struct mem_cgroup *memcg) ...@@ -2800,6 +2800,26 @@ static void commit_charge(struct page *page, struct mem_cgroup *memcg)
} }
#ifdef CONFIG_MEMCG_KMEM #ifdef CONFIG_MEMCG_KMEM
int memcg_alloc_page_obj_cgroups(struct page *page, struct kmem_cache *s,
gfp_t gfp)
{
unsigned int objects = objs_per_slab_page(s, page);
void *vec;
vec = kcalloc_node(objects, sizeof(struct obj_cgroup *), gfp,
page_to_nid(page));
if (!vec)
return -ENOMEM;
if (cmpxchg(&page->obj_cgroups, NULL,
(struct obj_cgroup **) ((unsigned long)vec | 0x1UL)))
kfree(vec);
else
kmemleak_not_leak(vec);
return 0;
}
/* /*
* Returns a pointer to the memory cgroup to which the kernel object is charged. * Returns a pointer to the memory cgroup to which the kernel object is charged.
* *
...@@ -2826,7 +2846,10 @@ struct mem_cgroup *mem_cgroup_from_obj(void *p) ...@@ -2826,7 +2846,10 @@ struct mem_cgroup *mem_cgroup_from_obj(void *p)
off = obj_to_index(page->slab_cache, page, p); off = obj_to_index(page->slab_cache, page, p);
objcg = page_obj_cgroups(page)[off]; objcg = page_obj_cgroups(page)[off];
return obj_cgroup_memcg(objcg); if (objcg)
return obj_cgroup_memcg(objcg);
return NULL;
} }
/* All other pages use page->mem_cgroup */ /* All other pages use page->mem_cgroup */
......
...@@ -1379,11 +1379,7 @@ static struct page *kmem_getpages(struct kmem_cache *cachep, gfp_t flags, ...@@ -1379,11 +1379,7 @@ static struct page *kmem_getpages(struct kmem_cache *cachep, gfp_t flags,
return NULL; return NULL;
} }
if (charge_slab_page(page, flags, cachep->gfporder, cachep)) { charge_slab_page(page, flags, cachep->gfporder, cachep);
__free_pages(page, cachep->gfporder);
return NULL;
}
__SetPageSlab(page); __SetPageSlab(page);
/* Record if ALLOC_NO_WATERMARKS was set when allocating the slab */ /* Record if ALLOC_NO_WATERMARKS was set when allocating the slab */
if (sk_memalloc_socks() && page_is_pfmemalloc(page)) if (sk_memalloc_socks() && page_is_pfmemalloc(page))
...@@ -3799,8 +3795,8 @@ static int setup_kmem_cache_nodes(struct kmem_cache *cachep, gfp_t gfp) ...@@ -3799,8 +3795,8 @@ static int setup_kmem_cache_nodes(struct kmem_cache *cachep, gfp_t gfp)
} }
/* Always called with the slab_mutex held */ /* Always called with the slab_mutex held */
static int __do_tune_cpucache(struct kmem_cache *cachep, int limit, static int do_tune_cpucache(struct kmem_cache *cachep, int limit,
int batchcount, int shared, gfp_t gfp) int batchcount, int shared, gfp_t gfp)
{ {
struct array_cache __percpu *cpu_cache, *prev; struct array_cache __percpu *cpu_cache, *prev;
int cpu; int cpu;
...@@ -3845,30 +3841,6 @@ static int __do_tune_cpucache(struct kmem_cache *cachep, int limit, ...@@ -3845,30 +3841,6 @@ static int __do_tune_cpucache(struct kmem_cache *cachep, int limit,
return setup_kmem_cache_nodes(cachep, gfp); return setup_kmem_cache_nodes(cachep, gfp);
} }
static int do_tune_cpucache(struct kmem_cache *cachep, int limit,
int batchcount, int shared, gfp_t gfp)
{
int ret;
struct kmem_cache *c;
ret = __do_tune_cpucache(cachep, limit, batchcount, shared, gfp);
if (slab_state < FULL)
return ret;
if ((ret < 0) || !is_root_cache(cachep))
return ret;
lockdep_assert_held(&slab_mutex);
c = memcg_cache(cachep);
if (c) {
/* return value determined by the root cache only */
__do_tune_cpucache(c, limit, batchcount, shared, gfp);
}
return ret;
}
/* Called with slab_mutex held always */ /* Called with slab_mutex held always */
static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp) static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp)
{ {
...@@ -3881,13 +3853,6 @@ static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp) ...@@ -3881,13 +3853,6 @@ static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp)
if (err) if (err)
goto end; goto end;
if (!is_root_cache(cachep)) {
struct kmem_cache *root = memcg_root_cache(cachep);
limit = root->limit;
shared = root->shared;
batchcount = root->batchcount;
}
if (limit && shared && batchcount) if (limit && shared && batchcount)
goto skip_setup; goto skip_setup;
/* /*
......
...@@ -30,28 +30,6 @@ struct kmem_cache { ...@@ -30,28 +30,6 @@ struct kmem_cache {
struct list_head list; /* List of all slab caches on the system */ struct list_head list; /* List of all slab caches on the system */
}; };
#else /* !CONFIG_SLOB */
/*
* This is the main placeholder for memcg-related information in kmem caches.
* Both the root cache and the child cache will have it. Some fields are used
* in both cases, other are specific to root caches.
*
* @root_cache: Common to root and child caches. NULL for root, pointer to
* the root cache for children.
*
* The following fields are specific to root caches.
*
* @memcg_cache: pointer to memcg kmem cache, used by all non-root memory
* cgroups.
* @work: work struct used to create the non-root cache.
*/
struct memcg_cache_params {
struct kmem_cache *root_cache;
struct kmem_cache *memcg_cache;
struct work_struct work;
};
#endif /* CONFIG_SLOB */ #endif /* CONFIG_SLOB */
#ifdef CONFIG_SLAB #ifdef CONFIG_SLAB
...@@ -196,7 +174,6 @@ int __kmem_cache_shutdown(struct kmem_cache *); ...@@ -196,7 +174,6 @@ int __kmem_cache_shutdown(struct kmem_cache *);
void __kmem_cache_release(struct kmem_cache *); void __kmem_cache_release(struct kmem_cache *);
int __kmem_cache_shrink(struct kmem_cache *); int __kmem_cache_shrink(struct kmem_cache *);
void slab_kmem_cache_release(struct kmem_cache *); void slab_kmem_cache_release(struct kmem_cache *);
void kmem_cache_shrink_all(struct kmem_cache *s);
struct seq_file; struct seq_file;
struct file; struct file;
...@@ -263,43 +240,6 @@ static inline bool kmem_cache_debug_flags(struct kmem_cache *s, slab_flags_t fla ...@@ -263,43 +240,6 @@ static inline bool kmem_cache_debug_flags(struct kmem_cache *s, slab_flags_t fla
} }
#ifdef CONFIG_MEMCG_KMEM #ifdef CONFIG_MEMCG_KMEM
static inline bool is_root_cache(struct kmem_cache *s)
{
return !s->memcg_params.root_cache;
}
static inline bool slab_equal_or_root(struct kmem_cache *s,
struct kmem_cache *p)
{
return p == s || p == s->memcg_params.root_cache;
}
/*
* We use suffixes to the name in memcg because we can't have caches
* created in the system with the same name. But when we print them
* locally, better refer to them with the base name
*/
static inline const char *cache_name(struct kmem_cache *s)
{
if (!is_root_cache(s))
s = s->memcg_params.root_cache;
return s->name;
}
static inline struct kmem_cache *memcg_root_cache(struct kmem_cache *s)
{
if (is_root_cache(s))
return s;
return s->memcg_params.root_cache;
}
static inline struct kmem_cache *memcg_cache(struct kmem_cache *s)
{
if (is_root_cache(s))
return s->memcg_params.memcg_cache;
return NULL;
}
static inline struct obj_cgroup **page_obj_cgroups(struct page *page) static inline struct obj_cgroup **page_obj_cgroups(struct page *page)
{ {
/* /*
...@@ -317,21 +257,8 @@ static inline bool page_has_obj_cgroups(struct page *page) ...@@ -317,21 +257,8 @@ static inline bool page_has_obj_cgroups(struct page *page)
return ((unsigned long)page->obj_cgroups & 0x1UL); return ((unsigned long)page->obj_cgroups & 0x1UL);
} }
static inline int memcg_alloc_page_obj_cgroups(struct page *page, int memcg_alloc_page_obj_cgroups(struct page *page, struct kmem_cache *s,
struct kmem_cache *s, gfp_t gfp) gfp_t gfp);
{
unsigned int objects = objs_per_slab_page(s, page);
void *vec;
vec = kcalloc_node(objects, sizeof(struct obj_cgroup *), gfp,
page_to_nid(page));
if (!vec)
return -ENOMEM;
kmemleak_not_leak(vec);
page->obj_cgroups = (struct obj_cgroup **) ((unsigned long)vec | 0x1UL);
return 0;
}
static inline void memcg_free_page_obj_cgroups(struct page *page) static inline void memcg_free_page_obj_cgroups(struct page *page)
{ {
...@@ -348,38 +275,25 @@ static inline size_t obj_full_size(struct kmem_cache *s) ...@@ -348,38 +275,25 @@ static inline size_t obj_full_size(struct kmem_cache *s)
return s->size + sizeof(struct obj_cgroup *); return s->size + sizeof(struct obj_cgroup *);
} }
static inline struct kmem_cache *memcg_slab_pre_alloc_hook(struct kmem_cache *s, static inline struct obj_cgroup *memcg_slab_pre_alloc_hook(struct kmem_cache *s,
struct obj_cgroup **objcgp, size_t objects,
size_t objects, gfp_t flags) gfp_t flags)
{ {
struct kmem_cache *cachep;
struct obj_cgroup *objcg; struct obj_cgroup *objcg;
if (memcg_kmem_bypass()) if (memcg_kmem_bypass())
return s; return NULL;
cachep = READ_ONCE(s->memcg_params.memcg_cache);
if (unlikely(!cachep)) {
/*
* If memcg cache does not exist yet, we schedule it's
* asynchronous creation and let the current allocation
* go through with the root cache.
*/
queue_work(system_wq, &s->memcg_params.work);
return s;
}
objcg = get_obj_cgroup_from_current(); objcg = get_obj_cgroup_from_current();
if (!objcg) if (!objcg)
return s; return NULL;
if (obj_cgroup_charge(objcg, flags, objects * obj_full_size(s))) { if (obj_cgroup_charge(objcg, flags, objects * obj_full_size(s))) {
obj_cgroup_put(objcg); obj_cgroup_put(objcg);
cachep = NULL; return NULL;
} }
*objcgp = objcg; return objcg;
return cachep;
} }
static inline void mod_objcg_state(struct obj_cgroup *objcg, static inline void mod_objcg_state(struct obj_cgroup *objcg,
...@@ -398,15 +312,27 @@ static inline void mod_objcg_state(struct obj_cgroup *objcg, ...@@ -398,15 +312,27 @@ static inline void mod_objcg_state(struct obj_cgroup *objcg,
static inline void memcg_slab_post_alloc_hook(struct kmem_cache *s, static inline void memcg_slab_post_alloc_hook(struct kmem_cache *s,
struct obj_cgroup *objcg, struct obj_cgroup *objcg,
size_t size, void **p) gfp_t flags, size_t size,
void **p)
{ {
struct page *page; struct page *page;
unsigned long off; unsigned long off;
size_t i; size_t i;
if (!objcg)
return;
flags &= ~__GFP_ACCOUNT;
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
if (likely(p[i])) { if (likely(p[i])) {
page = virt_to_head_page(p[i]); page = virt_to_head_page(p[i]);
if (!page_has_obj_cgroups(page) &&
memcg_alloc_page_obj_cgroups(page, s, flags)) {
obj_cgroup_uncharge(objcg, obj_full_size(s));
continue;
}
off = obj_to_index(s, page, p[i]); off = obj_to_index(s, page, p[i]);
obj_cgroup_get(objcg); obj_cgroup_get(objcg);
page_obj_cgroups(page)[off] = objcg; page_obj_cgroups(page)[off] = objcg;
...@@ -425,13 +351,19 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct page *page, ...@@ -425,13 +351,19 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct page *page,
struct obj_cgroup *objcg; struct obj_cgroup *objcg;
unsigned int off; unsigned int off;
if (!memcg_kmem_enabled() || is_root_cache(s)) if (!memcg_kmem_enabled())
return;
if (!page_has_obj_cgroups(page))
return; return;
off = obj_to_index(s, page, p); off = obj_to_index(s, page, p);
objcg = page_obj_cgroups(page)[off]; objcg = page_obj_cgroups(page)[off];
page_obj_cgroups(page)[off] = NULL; page_obj_cgroups(page)[off] = NULL;
if (!objcg)
return;
obj_cgroup_uncharge(objcg, obj_full_size(s)); obj_cgroup_uncharge(objcg, obj_full_size(s));
mod_objcg_state(objcg, page_pgdat(page), cache_vmstat_idx(s), mod_objcg_state(objcg, page_pgdat(page), cache_vmstat_idx(s),
-obj_full_size(s)); -obj_full_size(s));
...@@ -439,35 +371,7 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct page *page, ...@@ -439,35 +371,7 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct page *page,
obj_cgroup_put(objcg); obj_cgroup_put(objcg);
} }
extern void slab_init_memcg_params(struct kmem_cache *);
#else /* CONFIG_MEMCG_KMEM */ #else /* CONFIG_MEMCG_KMEM */
static inline bool is_root_cache(struct kmem_cache *s)
{
return true;
}
static inline bool slab_equal_or_root(struct kmem_cache *s,
struct kmem_cache *p)
{
return s == p;
}
static inline const char *cache_name(struct kmem_cache *s)
{
return s->name;
}
static inline struct kmem_cache *memcg_root_cache(struct kmem_cache *s)
{
return s;
}
static inline struct kmem_cache *memcg_cache(struct kmem_cache *s)
{
return NULL;
}
static inline bool page_has_obj_cgroups(struct page *page) static inline bool page_has_obj_cgroups(struct page *page)
{ {
return false; return false;
...@@ -488,16 +392,17 @@ static inline void memcg_free_page_obj_cgroups(struct page *page) ...@@ -488,16 +392,17 @@ static inline void memcg_free_page_obj_cgroups(struct page *page)
{ {
} }
static inline struct kmem_cache *memcg_slab_pre_alloc_hook(struct kmem_cache *s, static inline struct obj_cgroup *memcg_slab_pre_alloc_hook(struct kmem_cache *s,
struct obj_cgroup **objcgp, size_t objects,
size_t objects, gfp_t flags) gfp_t flags)
{ {
return NULL; return NULL;
} }
static inline void memcg_slab_post_alloc_hook(struct kmem_cache *s, static inline void memcg_slab_post_alloc_hook(struct kmem_cache *s,
struct obj_cgroup *objcg, struct obj_cgroup *objcg,
size_t size, void **p) gfp_t flags, size_t size,
void **p)
{ {
} }
...@@ -505,11 +410,6 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct page *page, ...@@ -505,11 +410,6 @@ static inline void memcg_slab_free_hook(struct kmem_cache *s, struct page *page,
void *p) void *p)
{ {
} }
static inline void slab_init_memcg_params(struct kmem_cache *s)
{
}
#endif /* CONFIG_MEMCG_KMEM */ #endif /* CONFIG_MEMCG_KMEM */
static inline struct kmem_cache *virt_to_cache(const void *obj) static inline struct kmem_cache *virt_to_cache(const void *obj)
...@@ -523,27 +423,18 @@ static inline struct kmem_cache *virt_to_cache(const void *obj) ...@@ -523,27 +423,18 @@ static inline struct kmem_cache *virt_to_cache(const void *obj)
return page->slab_cache; return page->slab_cache;
} }
static __always_inline int charge_slab_page(struct page *page, static __always_inline void charge_slab_page(struct page *page,
gfp_t gfp, int order, gfp_t gfp, int order,
struct kmem_cache *s) struct kmem_cache *s)
{ {
if (memcg_kmem_enabled() && !is_root_cache(s)) {
int ret;
ret = memcg_alloc_page_obj_cgroups(page, s, gfp);
if (ret)
return ret;
}
mod_node_page_state(page_pgdat(page), cache_vmstat_idx(s), mod_node_page_state(page_pgdat(page), cache_vmstat_idx(s),
PAGE_SIZE << order); PAGE_SIZE << order);
return 0;
} }
static __always_inline void uncharge_slab_page(struct page *page, int order, static __always_inline void uncharge_slab_page(struct page *page, int order,
struct kmem_cache *s) struct kmem_cache *s)
{ {
if (memcg_kmem_enabled() && !is_root_cache(s)) if (memcg_kmem_enabled())
memcg_free_page_obj_cgroups(page); memcg_free_page_obj_cgroups(page);
mod_node_page_state(page_pgdat(page), cache_vmstat_idx(s), mod_node_page_state(page_pgdat(page), cache_vmstat_idx(s),
...@@ -555,12 +446,11 @@ static inline struct kmem_cache *cache_from_obj(struct kmem_cache *s, void *x) ...@@ -555,12 +446,11 @@ static inline struct kmem_cache *cache_from_obj(struct kmem_cache *s, void *x)
struct kmem_cache *cachep; struct kmem_cache *cachep;
if (!IS_ENABLED(CONFIG_SLAB_FREELIST_HARDENED) && if (!IS_ENABLED(CONFIG_SLAB_FREELIST_HARDENED) &&
!memcg_kmem_enabled() &&
!kmem_cache_debug_flags(s, SLAB_CONSISTENCY_CHECKS)) !kmem_cache_debug_flags(s, SLAB_CONSISTENCY_CHECKS))
return s; return s;
cachep = virt_to_cache(x); cachep = virt_to_cache(x);
if (WARN(cachep && !slab_equal_or_root(cachep, s), if (WARN(cachep && cachep != s,
"%s: Wrong slab cache. %s but object is from %s\n", "%s: Wrong slab cache. %s but object is from %s\n",
__func__, s->name, cachep->name)) __func__, s->name, cachep->name))
print_tracking(cachep, x); print_tracking(cachep, x);
...@@ -613,7 +503,7 @@ static inline struct kmem_cache *slab_pre_alloc_hook(struct kmem_cache *s, ...@@ -613,7 +503,7 @@ static inline struct kmem_cache *slab_pre_alloc_hook(struct kmem_cache *s,
if (memcg_kmem_enabled() && if (memcg_kmem_enabled() &&
((flags & __GFP_ACCOUNT) || (s->flags & SLAB_ACCOUNT))) ((flags & __GFP_ACCOUNT) || (s->flags & SLAB_ACCOUNT)))
return memcg_slab_pre_alloc_hook(s, objcgp, size, flags); *objcgp = memcg_slab_pre_alloc_hook(s, size, flags);
return s; return s;
} }
...@@ -632,8 +522,8 @@ static inline void slab_post_alloc_hook(struct kmem_cache *s, ...@@ -632,8 +522,8 @@ static inline void slab_post_alloc_hook(struct kmem_cache *s,
s->flags, flags); s->flags, flags);
} }
if (memcg_kmem_enabled() && !is_root_cache(s)) if (memcg_kmem_enabled())
memcg_slab_post_alloc_hook(s, objcg, size, p); memcg_slab_post_alloc_hook(s, objcg, flags, size, p);
} }
#ifndef CONFIG_SLOB #ifndef CONFIG_SLOB
......
...@@ -130,36 +130,6 @@ int __kmem_cache_alloc_bulk(struct kmem_cache *s, gfp_t flags, size_t nr, ...@@ -130,36 +130,6 @@ int __kmem_cache_alloc_bulk(struct kmem_cache *s, gfp_t flags, size_t nr,
return i; return i;
} }
#ifdef CONFIG_MEMCG_KMEM
static void memcg_kmem_cache_create_func(struct work_struct *work)
{
struct kmem_cache *cachep = container_of(work, struct kmem_cache,
memcg_params.work);
memcg_create_kmem_cache(cachep);
}
void slab_init_memcg_params(struct kmem_cache *s)
{
s->memcg_params.root_cache = NULL;
s->memcg_params.memcg_cache = NULL;
INIT_WORK(&s->memcg_params.work, memcg_kmem_cache_create_func);
}
static void init_memcg_params(struct kmem_cache *s,
struct kmem_cache *root_cache)
{
if (root_cache)
s->memcg_params.root_cache = root_cache;
else
slab_init_memcg_params(s);
}
#else
static inline void init_memcg_params(struct kmem_cache *s,
struct kmem_cache *root_cache)
{
}
#endif /* CONFIG_MEMCG_KMEM */
/* /*
* Figure out what the alignment of the objects will be given a set of * Figure out what the alignment of the objects will be given a set of
* flags, a user specified alignment and the size of the objects. * flags, a user specified alignment and the size of the objects.
...@@ -197,9 +167,6 @@ int slab_unmergeable(struct kmem_cache *s) ...@@ -197,9 +167,6 @@ int slab_unmergeable(struct kmem_cache *s)
if (slab_nomerge || (s->flags & SLAB_NEVER_MERGE)) if (slab_nomerge || (s->flags & SLAB_NEVER_MERGE))
return 1; return 1;
if (!is_root_cache(s))
return 1;
if (s->ctor) if (s->ctor)
return 1; return 1;
...@@ -286,7 +253,6 @@ static struct kmem_cache *create_cache(const char *name, ...@@ -286,7 +253,6 @@ static struct kmem_cache *create_cache(const char *name,
s->useroffset = useroffset; s->useroffset = useroffset;
s->usersize = usersize; s->usersize = usersize;
init_memcg_params(s, root_cache);
err = __kmem_cache_create(s, flags); err = __kmem_cache_create(s, flags);
if (err) if (err)
goto out_free_cache; goto out_free_cache;
...@@ -344,7 +310,6 @@ kmem_cache_create_usercopy(const char *name, ...@@ -344,7 +310,6 @@ kmem_cache_create_usercopy(const char *name,
get_online_cpus(); get_online_cpus();
get_online_mems(); get_online_mems();
memcg_get_cache_ids();
mutex_lock(&slab_mutex); mutex_lock(&slab_mutex);
...@@ -394,7 +359,6 @@ kmem_cache_create_usercopy(const char *name, ...@@ -394,7 +359,6 @@ kmem_cache_create_usercopy(const char *name,
out_unlock: out_unlock:
mutex_unlock(&slab_mutex); mutex_unlock(&slab_mutex);
memcg_put_cache_ids();
put_online_mems(); put_online_mems();
put_online_cpus(); put_online_cpus();
...@@ -507,87 +471,6 @@ static int shutdown_cache(struct kmem_cache *s) ...@@ -507,87 +471,6 @@ static int shutdown_cache(struct kmem_cache *s)
return 0; return 0;
} }
#ifdef CONFIG_MEMCG_KMEM
/*
* memcg_create_kmem_cache - Create a cache for non-root memory cgroups.
* @root_cache: The parent of the new cache.
*
* This function attempts to create a kmem cache that will serve allocation
* requests going all non-root memory cgroups to @root_cache. The new cache
* inherits properties from its parent.
*/
void memcg_create_kmem_cache(struct kmem_cache *root_cache)
{
struct kmem_cache *s = NULL;
char *cache_name;
get_online_cpus();
get_online_mems();
mutex_lock(&slab_mutex);
if (root_cache->memcg_params.memcg_cache)
goto out_unlock;
cache_name = kasprintf(GFP_KERNEL, "%s-memcg", root_cache->name);
if (!cache_name)
goto out_unlock;
s = create_cache(cache_name, root_cache->object_size,
root_cache->align,
root_cache->flags & CACHE_CREATE_MASK,
root_cache->useroffset, root_cache->usersize,
root_cache->ctor, root_cache);
/*
* If we could not create a memcg cache, do not complain, because
* that's not critical at all as we can always proceed with the root
* cache.
*/
if (IS_ERR(s)) {
kfree(cache_name);
goto out_unlock;
}
/*
* Since readers won't lock (see memcg_slab_pre_alloc_hook()), we need a
* barrier here to ensure nobody will see the kmem_cache partially
* initialized.
*/
smp_wmb();
root_cache->memcg_params.memcg_cache = s;
out_unlock:
mutex_unlock(&slab_mutex);
put_online_mems();
put_online_cpus();
}
static int shutdown_memcg_caches(struct kmem_cache *s)
{
BUG_ON(!is_root_cache(s));
if (s->memcg_params.memcg_cache)
WARN_ON(shutdown_cache(s->memcg_params.memcg_cache));
return 0;
}
static void cancel_memcg_cache_creation(struct kmem_cache *s)
{
cancel_work_sync(&s->memcg_params.work);
}
#else
static inline int shutdown_memcg_caches(struct kmem_cache *s)
{
return 0;
}
static inline void cancel_memcg_cache_creation(struct kmem_cache *s)
{
}
#endif /* CONFIG_MEMCG_KMEM */
void slab_kmem_cache_release(struct kmem_cache *s) void slab_kmem_cache_release(struct kmem_cache *s)
{ {
__kmem_cache_release(s); __kmem_cache_release(s);
...@@ -602,8 +485,6 @@ void kmem_cache_destroy(struct kmem_cache *s) ...@@ -602,8 +485,6 @@ void kmem_cache_destroy(struct kmem_cache *s)
if (unlikely(!s)) if (unlikely(!s))
return; return;
cancel_memcg_cache_creation(s);
get_online_cpus(); get_online_cpus();
get_online_mems(); get_online_mems();
...@@ -613,10 +494,7 @@ void kmem_cache_destroy(struct kmem_cache *s) ...@@ -613,10 +494,7 @@ void kmem_cache_destroy(struct kmem_cache *s)
if (s->refcount) if (s->refcount)
goto out_unlock; goto out_unlock;
err = shutdown_memcg_caches(s); err = shutdown_cache(s);
if (!err)
err = shutdown_cache(s);
if (err) { if (err) {
pr_err("kmem_cache_destroy %s: Slab cache still has objects\n", pr_err("kmem_cache_destroy %s: Slab cache still has objects\n",
s->name); s->name);
...@@ -653,33 +531,6 @@ int kmem_cache_shrink(struct kmem_cache *cachep) ...@@ -653,33 +531,6 @@ int kmem_cache_shrink(struct kmem_cache *cachep)
} }
EXPORT_SYMBOL(kmem_cache_shrink); EXPORT_SYMBOL(kmem_cache_shrink);
/**
* kmem_cache_shrink_all - shrink root and memcg caches
* @s: The cache pointer
*/
void kmem_cache_shrink_all(struct kmem_cache *s)
{
struct kmem_cache *c;
if (!IS_ENABLED(CONFIG_MEMCG_KMEM) || !is_root_cache(s)) {
kmem_cache_shrink(s);
return;
}
get_online_cpus();
get_online_mems();
kasan_cache_shrink(s);
__kmem_cache_shrink(s);
c = memcg_cache(s);
if (c) {
kasan_cache_shrink(c);
__kmem_cache_shrink(c);
}
put_online_mems();
put_online_cpus();
}
bool slab_is_available(void) bool slab_is_available(void)
{ {
return slab_state >= UP; return slab_state >= UP;
...@@ -708,8 +559,6 @@ void __init create_boot_cache(struct kmem_cache *s, const char *name, ...@@ -708,8 +559,6 @@ void __init create_boot_cache(struct kmem_cache *s, const char *name,
s->useroffset = useroffset; s->useroffset = useroffset;
s->usersize = usersize; s->usersize = usersize;
slab_init_memcg_params(s);
err = __kmem_cache_create(s, flags); err = __kmem_cache_create(s, flags);
if (err) if (err)
...@@ -1098,25 +947,6 @@ void slab_stop(struct seq_file *m, void *p) ...@@ -1098,25 +947,6 @@ void slab_stop(struct seq_file *m, void *p)
mutex_unlock(&slab_mutex); mutex_unlock(&slab_mutex);
} }
static void
memcg_accumulate_slabinfo(struct kmem_cache *s, struct slabinfo *info)
{
struct kmem_cache *c;
struct slabinfo sinfo;
c = memcg_cache(s);
if (c) {
memset(&sinfo, 0, sizeof(sinfo));
get_slabinfo(c, &sinfo);
info->active_slabs += sinfo.active_slabs;
info->num_slabs += sinfo.num_slabs;
info->shared_avail += sinfo.shared_avail;
info->active_objs += sinfo.active_objs;
info->num_objs += sinfo.num_objs;
}
}
static void cache_show(struct kmem_cache *s, struct seq_file *m) static void cache_show(struct kmem_cache *s, struct seq_file *m)
{ {
struct slabinfo sinfo; struct slabinfo sinfo;
...@@ -1124,10 +954,8 @@ static void cache_show(struct kmem_cache *s, struct seq_file *m) ...@@ -1124,10 +954,8 @@ static void cache_show(struct kmem_cache *s, struct seq_file *m)
memset(&sinfo, 0, sizeof(sinfo)); memset(&sinfo, 0, sizeof(sinfo));
get_slabinfo(s, &sinfo); get_slabinfo(s, &sinfo);
memcg_accumulate_slabinfo(s, &sinfo);
seq_printf(m, "%-17s %6lu %6lu %6u %4u %4d", seq_printf(m, "%-17s %6lu %6lu %6u %4u %4d",
cache_name(s), sinfo.active_objs, sinfo.num_objs, s->size, s->name, sinfo.active_objs, sinfo.num_objs, s->size,
sinfo.objects_per_slab, (1 << sinfo.cache_order)); sinfo.objects_per_slab, (1 << sinfo.cache_order));
seq_printf(m, " : tunables %4u %4u %4u", seq_printf(m, " : tunables %4u %4u %4u",
...@@ -1144,8 +972,7 @@ static int slab_show(struct seq_file *m, void *p) ...@@ -1144,8 +972,7 @@ static int slab_show(struct seq_file *m, void *p)
if (p == slab_caches.next) if (p == slab_caches.next)
print_slabinfo_header(m); print_slabinfo_header(m);
if (is_root_cache(s)) cache_show(s, m);
cache_show(s, m);
return 0; return 0;
} }
...@@ -1170,13 +997,13 @@ void dump_unreclaimable_slab(void) ...@@ -1170,13 +997,13 @@ void dump_unreclaimable_slab(void)
pr_info("Name Used Total\n"); pr_info("Name Used Total\n");
list_for_each_entry_safe(s, s2, &slab_caches, list) { list_for_each_entry_safe(s, s2, &slab_caches, list) {
if (!is_root_cache(s) || (s->flags & SLAB_RECLAIM_ACCOUNT)) if (s->flags & SLAB_RECLAIM_ACCOUNT)
continue; continue;
get_slabinfo(s, &sinfo); get_slabinfo(s, &sinfo);
if (sinfo.num_objs > 0) if (sinfo.num_objs > 0)
pr_info("%-17s %10luKB %10luKB\n", cache_name(s), pr_info("%-17s %10luKB %10luKB\n", s->name,
(sinfo.active_objs * s->size) / 1024, (sinfo.active_objs * s->size) / 1024,
(sinfo.num_objs * s->size) / 1024); (sinfo.num_objs * s->size) / 1024);
} }
...@@ -1235,53 +1062,6 @@ static int __init slab_proc_init(void) ...@@ -1235,53 +1062,6 @@ static int __init slab_proc_init(void)
} }
module_init(slab_proc_init); module_init(slab_proc_init);
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_MEMCG_KMEM)
/*
* Display information about kmem caches that have memcg cache.
*/
static int memcg_slabinfo_show(struct seq_file *m, void *unused)
{
struct kmem_cache *s, *c;
struct slabinfo sinfo;
mutex_lock(&slab_mutex);
seq_puts(m, "# <name> <css_id[:dead|deact]> <active_objs> <num_objs>");
seq_puts(m, " <active_slabs> <num_slabs>\n");
list_for_each_entry(s, &slab_caches, list) {
/*
* Skip kmem caches that don't have the memcg cache.
*/
if (!s->memcg_params.memcg_cache)
continue;
memset(&sinfo, 0, sizeof(sinfo));
get_slabinfo(s, &sinfo);
seq_printf(m, "%-17s root %6lu %6lu %6lu %6lu\n",
cache_name(s), sinfo.active_objs, sinfo.num_objs,
sinfo.active_slabs, sinfo.num_slabs);
c = s->memcg_params.memcg_cache;
memset(&sinfo, 0, sizeof(sinfo));
get_slabinfo(c, &sinfo);
seq_printf(m, "%-17s %4d %6lu %6lu %6lu %6lu\n",
cache_name(c), root_mem_cgroup->css.id,
sinfo.active_objs, sinfo.num_objs,
sinfo.active_slabs, sinfo.num_slabs);
}
mutex_unlock(&slab_mutex);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(memcg_slabinfo);
static int __init memcg_slabinfo_init(void)
{
debugfs_create_file("memcg_slabinfo", S_IFREG | S_IRUGO,
NULL, NULL, &memcg_slabinfo_fops);
return 0;
}
late_initcall(memcg_slabinfo_init);
#endif /* CONFIG_DEBUG_FS && CONFIG_MEMCG_KMEM */
#endif /* CONFIG_SLAB || CONFIG_SLUB_DEBUG */ #endif /* CONFIG_SLAB || CONFIG_SLUB_DEBUG */
static __always_inline void *__do_krealloc(const void *p, size_t new_size, static __always_inline void *__do_krealloc(const void *p, size_t new_size,
......
...@@ -218,14 +218,10 @@ enum track_item { TRACK_ALLOC, TRACK_FREE }; ...@@ -218,14 +218,10 @@ enum track_item { TRACK_ALLOC, TRACK_FREE };
#ifdef CONFIG_SYSFS #ifdef CONFIG_SYSFS
static int sysfs_slab_add(struct kmem_cache *); static int sysfs_slab_add(struct kmem_cache *);
static int sysfs_slab_alias(struct kmem_cache *, const char *); static int sysfs_slab_alias(struct kmem_cache *, const char *);
static void memcg_propagate_slab_attrs(struct kmem_cache *s);
static void sysfs_slab_remove(struct kmem_cache *s);
#else #else
static inline int sysfs_slab_add(struct kmem_cache *s) { return 0; } static inline int sysfs_slab_add(struct kmem_cache *s) { return 0; }
static inline int sysfs_slab_alias(struct kmem_cache *s, const char *p) static inline int sysfs_slab_alias(struct kmem_cache *s, const char *p)
{ return 0; } { return 0; }
static inline void memcg_propagate_slab_attrs(struct kmem_cache *s) { }
static inline void sysfs_slab_remove(struct kmem_cache *s) { }
#endif #endif
static inline void stat(const struct kmem_cache *s, enum stat_item si) static inline void stat(const struct kmem_cache *s, enum stat_item si)
...@@ -1624,10 +1620,8 @@ static inline struct page *alloc_slab_page(struct kmem_cache *s, ...@@ -1624,10 +1620,8 @@ static inline struct page *alloc_slab_page(struct kmem_cache *s,
else else
page = __alloc_pages_node(node, flags, order); page = __alloc_pages_node(node, flags, order);
if (page && charge_slab_page(page, flags, order, s)) { if (page)
__free_pages(page, order); charge_slab_page(page, flags, order, s);
page = NULL;
}
return page; return page;
} }
...@@ -3920,7 +3914,6 @@ int __kmem_cache_shutdown(struct kmem_cache *s) ...@@ -3920,7 +3914,6 @@ int __kmem_cache_shutdown(struct kmem_cache *s)
if (n->nr_partial || slabs_node(s, node)) if (n->nr_partial || slabs_node(s, node))
return 1; return 1;
} }
sysfs_slab_remove(s);
return 0; return 0;
} }
...@@ -4358,7 +4351,6 @@ static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache) ...@@ -4358,7 +4351,6 @@ static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
p->slab_cache = s; p->slab_cache = s;
#endif #endif
} }
slab_init_memcg_params(s);
list_add(&s->list, &slab_caches); list_add(&s->list, &slab_caches);
return s; return s;
} }
...@@ -4414,7 +4406,7 @@ struct kmem_cache * ...@@ -4414,7 +4406,7 @@ struct kmem_cache *
__kmem_cache_alias(const char *name, unsigned int size, unsigned int align, __kmem_cache_alias(const char *name, unsigned int size, unsigned int align,
slab_flags_t flags, void (*ctor)(void *)) slab_flags_t flags, void (*ctor)(void *))
{ {
struct kmem_cache *s, *c; struct kmem_cache *s;
s = find_mergeable(size, align, flags, name, ctor); s = find_mergeable(size, align, flags, name, ctor);
if (s) { if (s) {
...@@ -4427,12 +4419,6 @@ __kmem_cache_alias(const char *name, unsigned int size, unsigned int align, ...@@ -4427,12 +4419,6 @@ __kmem_cache_alias(const char *name, unsigned int size, unsigned int align,
s->object_size = max(s->object_size, size); s->object_size = max(s->object_size, size);
s->inuse = max(s->inuse, ALIGN(size, sizeof(void *))); s->inuse = max(s->inuse, ALIGN(size, sizeof(void *)));
c = memcg_cache(s);
if (c) {
c->object_size = s->object_size;
c->inuse = max(c->inuse, ALIGN(size, sizeof(void *)));
}
if (sysfs_slab_alias(s, name)) { if (sysfs_slab_alias(s, name)) {
s->refcount--; s->refcount--;
s = NULL; s = NULL;
...@@ -4454,7 +4440,6 @@ int __kmem_cache_create(struct kmem_cache *s, slab_flags_t flags) ...@@ -4454,7 +4440,6 @@ int __kmem_cache_create(struct kmem_cache *s, slab_flags_t flags)
if (slab_state <= UP) if (slab_state <= UP)
return 0; return 0;
memcg_propagate_slab_attrs(s);
err = sysfs_slab_add(s); err = sysfs_slab_add(s);
if (err) if (err)
__kmem_cache_release(s); __kmem_cache_release(s);
...@@ -5312,7 +5297,7 @@ static ssize_t shrink_store(struct kmem_cache *s, ...@@ -5312,7 +5297,7 @@ static ssize_t shrink_store(struct kmem_cache *s,
const char *buf, size_t length) const char *buf, size_t length)
{ {
if (buf[0] == '1') if (buf[0] == '1')
kmem_cache_shrink_all(s); kmem_cache_shrink(s);
else else
return -EINVAL; return -EINVAL;
return length; return length;
...@@ -5536,99 +5521,9 @@ static ssize_t slab_attr_store(struct kobject *kobj, ...@@ -5536,99 +5521,9 @@ static ssize_t slab_attr_store(struct kobject *kobj,
return -EIO; return -EIO;
err = attribute->store(s, buf, len); err = attribute->store(s, buf, len);
#ifdef CONFIG_MEMCG
if (slab_state >= FULL && err >= 0 && is_root_cache(s)) {
struct kmem_cache *c;
mutex_lock(&slab_mutex);
if (s->max_attr_size < len)
s->max_attr_size = len;
/*
* This is a best effort propagation, so this function's return
* value will be determined by the parent cache only. This is
* basically because not all attributes will have a well
* defined semantics for rollbacks - most of the actions will
* have permanent effects.
*
* Returning the error value of any of the children that fail
* is not 100 % defined, in the sense that users seeing the
* error code won't be able to know anything about the state of
* the cache.
*
* Only returning the error code for the parent cache at least
* has well defined semantics. The cache being written to
* directly either failed or succeeded, in which case we loop
* through the descendants with best-effort propagation.
*/
c = memcg_cache(s);
if (c)
attribute->store(c, buf, len);
mutex_unlock(&slab_mutex);
}
#endif
return err; return err;
} }
static void memcg_propagate_slab_attrs(struct kmem_cache *s)
{
#ifdef CONFIG_MEMCG
int i;
char *buffer = NULL;
struct kmem_cache *root_cache;
if (is_root_cache(s))
return;
root_cache = s->memcg_params.root_cache;
/*
* This mean this cache had no attribute written. Therefore, no point
* in copying default values around
*/
if (!root_cache->max_attr_size)
return;
for (i = 0; i < ARRAY_SIZE(slab_attrs); i++) {
char mbuf[64];
char *buf;
struct slab_attribute *attr = to_slab_attr(slab_attrs[i]);
ssize_t len;
if (!attr || !attr->store || !attr->show)
continue;
/*
* It is really bad that we have to allocate here, so we will
* do it only as a fallback. If we actually allocate, though,
* we can just use the allocated buffer until the end.
*
* Most of the slub attributes will tend to be very small in
* size, but sysfs allows buffers up to a page, so they can
* theoretically happen.
*/
if (buffer)
buf = buffer;
else if (root_cache->max_attr_size < ARRAY_SIZE(mbuf) &&
!IS_ENABLED(CONFIG_SLUB_STATS))
buf = mbuf;
else {
buffer = (char *) get_zeroed_page(GFP_KERNEL);
if (WARN_ON(!buffer))
continue;
buf = buffer;
}
len = attr->show(root_cache, buf);
if (len > 0)
attr->store(s, buf, len);
}
if (buffer)
free_page((unsigned long)buffer);
#endif /* CONFIG_MEMCG */
}
static void kmem_cache_release(struct kobject *k) static void kmem_cache_release(struct kobject *k)
{ {
slab_kmem_cache_release(to_slab(k)); slab_kmem_cache_release(to_slab(k));
...@@ -5648,10 +5543,6 @@ static struct kset *slab_kset; ...@@ -5648,10 +5543,6 @@ static struct kset *slab_kset;
static inline struct kset *cache_kset(struct kmem_cache *s) static inline struct kset *cache_kset(struct kmem_cache *s)
{ {
#ifdef CONFIG_MEMCG
if (!is_root_cache(s))
return s->memcg_params.root_cache->memcg_kset;
#endif
return slab_kset; return slab_kset;
} }
...@@ -5694,27 +5585,6 @@ static char *create_unique_id(struct kmem_cache *s) ...@@ -5694,27 +5585,6 @@ static char *create_unique_id(struct kmem_cache *s)
return name; return name;
} }
static void sysfs_slab_remove_workfn(struct work_struct *work)
{
struct kmem_cache *s =
container_of(work, struct kmem_cache, kobj_remove_work);
if (!s->kobj.state_in_sysfs)
/*
* For a memcg cache, this may be called during
* deactivation and again on shutdown. Remove only once.
* A cache is never shut down before deactivation is
* complete, so no need to worry about synchronization.
*/
goto out;
#ifdef CONFIG_MEMCG
kset_unregister(s->memcg_kset);
#endif
out:
kobject_put(&s->kobj);
}
static int sysfs_slab_add(struct kmem_cache *s) static int sysfs_slab_add(struct kmem_cache *s)
{ {
int err; int err;
...@@ -5722,8 +5592,6 @@ static int sysfs_slab_add(struct kmem_cache *s) ...@@ -5722,8 +5592,6 @@ static int sysfs_slab_add(struct kmem_cache *s)
struct kset *kset = cache_kset(s); struct kset *kset = cache_kset(s);
int unmergeable = slab_unmergeable(s); int unmergeable = slab_unmergeable(s);
INIT_WORK(&s->kobj_remove_work, sysfs_slab_remove_workfn);
if (!kset) { if (!kset) {
kobject_init(&s->kobj, &slab_ktype); kobject_init(&s->kobj, &slab_ktype);
return 0; return 0;
...@@ -5760,16 +5628,6 @@ static int sysfs_slab_add(struct kmem_cache *s) ...@@ -5760,16 +5628,6 @@ static int sysfs_slab_add(struct kmem_cache *s)
if (err) if (err)
goto out_del_kobj; goto out_del_kobj;
#ifdef CONFIG_MEMCG
if (is_root_cache(s) && memcg_sysfs_enabled) {
s->memcg_kset = kset_create_and_add("cgroup", NULL, &s->kobj);
if (!s->memcg_kset) {
err = -ENOMEM;
goto out_del_kobj;
}
}
#endif
if (!unmergeable) { if (!unmergeable) {
/* Setup first alias */ /* Setup first alias */
sysfs_slab_alias(s, s->name); sysfs_slab_alias(s, s->name);
...@@ -5783,19 +5641,6 @@ static int sysfs_slab_add(struct kmem_cache *s) ...@@ -5783,19 +5641,6 @@ static int sysfs_slab_add(struct kmem_cache *s)
goto out; goto out;
} }
static void sysfs_slab_remove(struct kmem_cache *s)
{
if (slab_state < FULL)
/*
* Sysfs has not been setup yet so no need to remove the
* cache from sysfs.
*/
return;
kobject_get(&s->kobj);
schedule_work(&s->kobj_remove_work);
}
void sysfs_slab_unlink(struct kmem_cache *s) void sysfs_slab_unlink(struct kmem_cache *s)
{ {
if (slab_state >= FULL) if (slab_state >= FULL)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment