Cgroup中提供了众多子系统用于实现系统资源管理,其中有一个子系统就是Memory子系统,这个子系统提供了对内存资源和swap资源的使用限制和统计。但是Cgroup中的Memory子系统中的资源使用统计其实用的是resource counter机制来实现的,本文就resource counter机制进行了一次全方位的分析主要分析其相关的基础数据结构,和一些内核API信息,以及resource counter机制是如何作用于Memory子系统。
本文参考linux内核Cgroup文档中关于resource counter的文档
resource counter机制中最核心的数据结构是res_counter这个数据结构这个数据结构是用过嵌入到其他数据结构中进行使用的,这个数据结构的定义如下:
struct res_counter {
/* * the current resource consumption level */
unsigned long long usage; //这个成员是用来进行资源统计的,记录资源的当前的使用量,其单位是根据被嵌入结构来决定的。
/* * the maximal value of the usage from the counter creation */
unsigned long long max_usage; //这个资源是用来记录历史记录中使用过的最大资源量。这个字段是用来进行调优的下文会说到
/* * the limit that usage cannot exceed */
unsigned long long limit; //资源最大限制,一旦分配超过这个限制将会导致失败,并且会让failcnt,资源分配失败次数加1
/* * the limit that usage can be exceed */
unsigned long long soft_limit; //资源软限制,是可以超过的,但是无法超过资源最大限制
/* * the number of unsuccessful attempts to consume the resource */
unsigned long long failcnt; //资源分配失败的次数
/* * the lock to protect all of the above. * the routines below consider this to be IRQ-safe */
spinlock_t lock; //自旋锁
/* * Parent counter, used for hierarchial resource accounting */
struct res_counter *parent; //形成资源统计组
};
resource counter机制中是具有父子关系,子资源控制中可以设置的资源限制是无法超过父资源限制的。这个结构体用于放到Memory子系统对应的核心结构体中实现Memory子系统的资源统计。
内核为了方便进行资源统计,也提供了一系列的API用来进行资源统计,这里仅列出一些常见的API
void res_counter_init(struct res_counter *counter, struct res_counter *parent)
{
spin_lock_init(&counter->lock);
counter->limit = RESOURCE_MAX;
counter->soft_limit = RESOURCE_MAX;
counter->parent = parent;
}
其实现很简单,初始化自旋锁,然后设置了最大资源限制,指定了其父资源限制,但是我发现这里应该少了对conuter的usage字段进行初始化为0,我猜想
应该在是分配内存的时候已经进行了内存置0了。
int res_counter_charge_locked(struct res_counter *counter, unsigned long val,
bool force)
{
int ret = 0;
if (counter->usage + val > counter->limit) {
counter->failcnt++;
ret = -ENOMEM;
if (!force)
return ret;
}
counter->usage += val;
if (counter->usage > counter->max_usage)
counter->max_usage = counter->usage;
return ret;
}
资源使用进行统计,并记录分配失败的次数和更新资源最大使用数量。父资源使用量是所有孩子的资源使用量总和。
static int __res_counter_charge(struct res_counter *counter, unsigned long val,
struct res_counter **limit_fail_at, bool force)
{
int ret, r;
unsigned long flags;
struct res_counter *c, *u;
r = ret = 0;
*limit_fail_at = NULL;
local_irq_save(flags);
for (c = counter; c != NULL; c = c->parent) {
spin_lock(&c->lock);
r = res_counter_charge_locked(c, val, force);
spin_unlock(&c->lock);
if (r < 0 && !ret) {
ret = r;
*limit_fail_at = c;
if (!force)
break;
}
}
if (ret < 0 && !force) {
for (u = counter; u != c; u = u->parent) {
spin_lock(&u->lock);
res_counter_uncharge_locked(u, val);
spin_unlock(&u->lock);
}
}
local_irq_restore(flags);
return ret;
}
递归对资统计组进行资源使用统计,统计的时候进行了加锁,如果资源使用统计失败则使用limit_fail_at进行记录,如果没有设置force标志则不继续进行资源统计了,否则继续递归进行资源统计,
int res_counter_charge(struct res_counter *counter, unsigned long val,
struct res_counter **limit_fail_at)
{
return __res_counter_charge(counter, val, limit_fail_at, false);
}
传入要统计的资源也就是val,以及相关的资源统计结构体。还有一个limit_fail_at这是用来指向资源使用统计失败的res_counter结构体的。
int res_counter_charge_locked(struct res_counter *counter, unsigned long val,
bool force)
{
int ret = 0;
if (counter->usage + val > counter->limit) {
counter->failcnt++;
ret = -ENOMEM;
if (!force)
return ret;
}
counter->usage += val;
if (counter->usage > counter->max_usage)
counter->max_usage = counter->usage;
return ret;
}
u64 res_counter_uncharge_locked(struct res_counter *counter, unsigned long val)
{
if (WARN_ON(counter->usage < val))
val = counter->usage;
counter->usage -= val;
return counter->usage;
}
需要锁保护的资源释放统计,这里使用了WARN_ON来对这个counter->usage < val进行条件测试,即使条件不成立也不会导致系统出现严重的问题所以这里选择了WARN_ON而不是BUG_ON。使用BUG_ON会导致内核panic.
u64 res_counter_uncharge_until(struct res_counter *counter,
struct res_counter *top,
unsigned long val)
{
unsigned long flags;
struct res_counter *c;
u64 ret = 0;
local_irq_save(flags);
for (c = counter; c != top; c = c->parent) {
u64 r;
spin_lock(&c->lock);
r = res_counter_uncharge_locked(c, val);
if (c == counter)
ret = r;
spin_unlock(&c->lock);
}
local_irq_restore(flags);
return ret;
}
u64 res_counter_uncharge(struct res_counter *counter, unsigned long val)
{
return res_counter_uncharge_until(counter, NULL, val);
}
关中断然后开始递归进行资源回收计数。
关于resource counter相关的内核API还有其它的一些可以参考内核源码,其声明在include/linux/res_counter.h
resource counter中核心结构体中的一些成员对于我们进行资源限制调优有着很重要的帮助
Resource Counter机制如何在内核中去使用呢?,下面给出了一般情况下Resource Counter的使用
Step1: 将Resource Countre机制中的核心数据结构嵌入目标数据结构
struct my_group {
struct res_counter res;
<other fields>
}
Setp2: 在资源分配中进行资源统计
int alloc_something(...)
{
if (res_counter_charge(res_counter_ptr, amount) < 0)
return -ENOMEM;
<allocate the resource and return to the caller>
}
Setp3: 在资源回收中进行资源统计
void release_something(...)
{
res_counter_uncharge(res_counter_ptr, amount);
<release the resource>
}
Setp4: 提供一些读取这些资源值的API
Setp5: Compile and Run it