From bea1123abee6a264ce2984faa1a753c73d77ce00 Mon Sep 17 00:00:00 2001 From: Yi Tao Date: Mon, 30 Nov 2020 17:30:36 +0800 Subject: [PATCH 2530/2944] alinux: cgroup: introduce cache struct and function to #31531504 To speed up creating cgroup, we define some common structs and functions used by subsytems. cache is organized by a list. each element in list consists of a pointer pointing to an area where record data to be reused. How to deal with data is duty of user. subsystems using cache need to provide callback functions to define behavior such as how to clean data before putting to cache and how to free data when releasing cache. Signed-off-by: Yi Tao Suggested-by: Shanpei Chen Acked-by: Xunlei Pang Acked-by: Michael Wang --- include/linux/cgroup-defs.h | 73 +++++++++++++++++++ init/Kconfig | 5 ++ kernel/cgroup/cgroup.c | 168 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h index b45357b..aaeb2de 100644 --- a/include/linux/cgroup-defs.h +++ b/include/linux/cgroup-defs.h @@ -99,6 +99,79 @@ enum { __CFTYPE_NOT_ON_DFL = (1 << 17), /* not on default hierarchy */ }; +struct cache_header { + struct list_head header; + spinlock_t lock; + + /* number of nodes that are ready to be used, at head of list */ + unsigned int ready_node; + + /* number of nodes that can become ready_node, at tail of list */ + unsigned int empty_node; + + /* limit of ready_node */ + unsigned int limit; + + /* cache is available */ + bool online; + + /* callback function to clean up data before putting to cache */ + void (*clean_up)(void **ptr); + + /* callback function to release cache */ + void (*release_cache)(void **ptr); +}; + +struct cache_node { + struct list_head node; + + /* pointer of a structure to reuse */ + void *ptr[]; +}; + +#define CACHE_HEADER(name, __limit, __clean_up, __release_cache) \ + struct cache_header name = \ + { \ + .header = LIST_HEAD_INIT(name.header), \ + .lock = __SPIN_LOCK_UNLOCKED(name.lock), \ + .limit = __limit, \ + .ready_node = 0, \ + .empty_node = 0, \ + .online = true, \ + .clean_up = __clean_up, \ + .release_cache = __release_cache, \ + } + +#define DEFAULT_CACHE_SIZE 0 + +#ifdef CONFIG_CGROUP_CACHE + +extern void init_cache_header(struct cache_header *ch, unsigned int limit, + void (*clean_up)(void **ptr), + void (*release_cache)(void **ptr)); +extern bool get_from_cache(struct cache_header *ch, void **dst, unsigned int cnt); +extern bool put_to_cache(struct cache_header *ch, void **src, unsigned int cnt); +extern void change_cache_limit(struct cache_header *ch, unsigned int limit); +extern bool cache_not_full(struct cache_header *ch); + +#else /* CONFIG_CGROUP_CACHE */ + +static inline void init_cache_header(struct cache_header *ch, unsigned int limit, + void (*clean_up)(void **ptr), + void (*release_cache)(void **ptr)) {} +static inline bool get_from_cache(struct cache_header *ch, void **dst, unsigned int cnt) +{ + return false; +} +static inline bool put_to_cache(struct cache_header *ch, void **src, unsigned int cnt) +{ + return false; +} +static inline void change_cache_limit(struct cache_header *ch, unsigned int limit) {} +static inline bool cache_not_full(struct cache_header *ch) { return false; } + +#endif + /* * cgroup_file is the handle for a file instance created in a cgroup which * is used, for example, to generate file changed notifications. This can diff --git a/init/Kconfig b/init/Kconfig index 2880f9a..376ba03 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -725,6 +725,11 @@ menuconfig CGROUPS if CGROUPS +config CGROUP_CACHE + bool "Cgroup cache" + help + Use cache to speed up creating cgroup. + config PAGE_COUNTER bool diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index b275e6b..2c47345 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -6297,3 +6297,171 @@ static int __init cgroup_sysfs_init(void) subsys_initcall(cgroup_sysfs_init); #endif /* CONFIG_SYSFS */ + +#ifdef CONFIG_CGROUP_CACHE + +void init_cache_header(struct cache_header *ch, unsigned int limit, + void (*clean_up)(void **ptr), + void (*release_cache)(void **ptr)) +{ + INIT_LIST_HEAD(&ch->header); + spin_lock_init(&ch->lock); + ch->limit = limit; + ch->ready_node = 0; + ch->empty_node = 0; + ch->online = true; + ch->clean_up = clean_up; + ch->release_cache = release_cache; +} +EXPORT_SYMBOL(init_cache_header); + +/** + * get_from_cache - get cache data + * @ch: cache header where get data + * @dst: dst of cache data + * @cnt: number of pointers to get + * + * copy cache data from ready_node.ptr to @dst and transform this ready_node to + * empty_node. + */ + +bool get_from_cache(struct cache_header *ch, void **dst, unsigned int cnt) +{ + struct cache_node *cn; + + spin_lock(&ch->lock); + if (!ch->ready_node || !ch->online) { + spin_unlock(&ch->lock); + return false; + } + + cn = list_first_entry(&ch->header, struct cache_node, node); + list_del(&cn->node); + ch->ready_node--; + memcpy(dst, cn->ptr, sizeof(void *) * cnt); + + /* limit may change, if too many empty_node in list, free extra */ + if (ch->empty_node + ch->ready_node > ch->limit) { + spin_unlock(&ch->lock); + kfree(cn); + } else { + list_add_tail(&cn->node, &ch->header); + ch->empty_node++; + spin_unlock(&ch->lock); + } + + return true; + +} + +static struct cache_node *alloc_cache_node(unsigned int cnt) +{ + struct cache_node *cn; + + cn = kmalloc(sizeof(*cn) + sizeof(void *) * cnt, GFP_KERNEL); + if (!cn) + return NULL; + + return cn; +} + +/** + * put_to_cache - put cache data + * @ch: cache header where put data + * @src: src of cache data + * @cnt: number of pointers to get + * + * copy cache data from empty_node.ptr to @dst and transform this empty_node to + * ready_node. + */ +bool put_to_cache(struct cache_header *ch, void **src, unsigned int cnt) +{ + struct cache_node *cn; + + spin_lock(&ch->lock); + if (!ch->online || ch->ready_node >= ch->limit) { + spin_unlock(&ch->lock); + return false; + } + + /* get an empty_node */ + if (!ch->empty_node) { + spin_unlock(&ch->lock); + cn = alloc_cache_node(cnt); + if (!cn) + return false; + spin_lock(&ch->lock); + } else { + cn = list_last_entry(&ch->header, struct cache_node, node); + list_del(&cn->node); + ch->empty_node--; + } + spin_unlock(&ch->lock); + + ch->clean_up(src); + + memcpy(cn->ptr, src, sizeof(void *) * cnt); + + spin_lock(&ch->lock); + list_add(&cn->node, &ch->header); + ch->ready_node++; + spin_unlock(&ch->lock); + + return true; +} + +/** + * change_cache_limit - change cache_limit of cache header + * @ch: cache header + * @limit: new limit to set + */ +void change_cache_limit(struct cache_header *ch, unsigned int limit) +{ + struct cache_node *cn; + + spin_lock(&ch->lock); + ch->limit = limit; + if (ch->ready_node <= ch->limit) { + spin_unlock(&ch->lock); + return; + } + /* disable cache until extra ready_node be released */ + ch->online = false; + spin_unlock(&ch->lock); + + /* + * cache header is offline, release extra ready_node. because cache + * header is offline, and there is no pre_node, any operation to cache + * header will return false. so releasing extra ready_node doesn't have + * to hold lock. + */ + while (ch->ready_node > ch->limit) { + cn = list_first_entry(&ch->header, struct cache_node, node); + list_del(&cn->node); + ch->ready_node--; + + ch->release_cache(cn->ptr); + kfree(cn); + } + + /* release empty_node */ + while (ch->empty_node) { + cn = list_last_entry(&ch->header, struct cache_node, node); + list_del(&cn->node); + ch->empty_node--; + + kfree(cn); + } + + /* release done, online cache header */ + ch->online = true; +} +EXPORT_SYMBOL(change_cache_limit); + +bool cache_not_full(struct cache_header *ch) +{ + return ch->ready_node < ch->limit; +} +EXPORT_SYMBOL(cache_not_full); + +#endif /* CONFIG_CGROUP_CACHE */ -- 1.8.3.1