一个资源管理系统的设计--解析linux的cgroup实现

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介:
将实体打散成不可再分的微粒,这样就可以使设计灵活化,最大限度的减少数据冗余。以CRM系统为例,虽然管理是基于一组控制元素而不是一个控制元素的,设计的时候还是以一个控制元素为基础。
     linux的cgroup系统可谓是一个典范,它轻量地实现了诸如solaris的“容器”的概念,也许也是对linux本身“命名空间”的一种冲击。它是分层的,也可以说是树形的结构,一个“控制组”拥有一个ROOT,每一个ROOT控制一组元素,在这个ROOT当中可以建立很多的“组”(cgroup),每一个组还可以建立下级的组...。每新建一个cgroup必须确定一组它关心的cgroup_subsys,比如cpuset,memory,ns等,怎么确定呢?这是通过文件系统的mount实现的,当你执行:
mount -t cgroup cgroup -o cpuset memory my
的时候,你就建立一个ROOT,这个ROOT包含所有的进程,然后你可以在my目录中执行mkdir group1 group2,这样就建立了两个cgroup,实际上,当你mount的时候,系统就建立了一个虚拟的组group-root,如果你执行cat my/tasks,你会发现它包含了所有的进程,如果你执行cat my/group1/tasks,你会发现它是空的,因为还没有任何的进程被加入进去。现在执行echo 761>my/group1/tasks,那么pid为761的进程将被加入到group1,通过修改group1目录下的文件就可以对这个group1中当前tasks文件中包含的所有的进程进行控制了。这一切是如何实现的?实际上linux内核代码的cgroup子系统实现了类似数据库的结构,包括表结构和查询引擎,通过阅读代码可以看出,每一个进程task包含一个css_set类型的字段:
struct css_set {
    struct kref ref;
    struct hlist_node hlist;
    struct list_head tasks;      //解决冗余,包含所有使用这个set的进程
    struct list_head cg_links;   //解决冗余,包含所有参与管理这个set的cgroup。
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
};
既然它被一组subsys控制,为何不直接将每个subsys本身包含在task中呢?这是为了解决数据冗余的问题,因为其他的进程也可以受这些subsys控制。
     有必要说一下上面的css_set结构中的cg_links字段,它包含了所有的参与管理这个set的cgroup,为何会这样呢?难道一个set不是由一个cgroup管理的吗?不是的,要知道管理是基于“一组”subsys的,而这一组并不一定是全部的编译进内核也就是内核支持的subsys。比如你分别用-o参数cpu,memory...mount了5个cgroup文件系统,那么一个进程关联的css_set就会由5个cgroup管理,每一个mount的ROOT会管理一个,这个ROOT会用文件系统的方式管理进程分别属于该ROOT的哪个cgroup。
     下面看一下cgroup_subsys_state,这个结构可以看作静态cgroup_subsys结构的动态实例,静态的cgroup_subsys中包含了一些通用的方法,而动态的cgroup_subsys_state则仅仅是一个父类,具体的数据和额外的方法通过继承它来实现,比如:
struct mem_cgroup {
    struct cgroup_subsys_state css;
    struct res_counter res;
    struct mem_cgroup_lru_info info;
    int    prev_priority;    /* for recording reclaim priority */
    struct mem_cgroup_stat stat;
};
任何时候,只要你得到了一个cgroup_subsys_state,并且根据其subsys_id确认它是关于memory的,那么就可以通过:
static inline struct cgroup_subsys_state *task_subsys_state(
    struct task_struct *task, int subsys_id)
{
    return rcu_dereference(task->cgroups->subsys[subsys_id]);
}

container_of(task_subsys_state(p, mem_cgroup_subsys_id), struct mem_cgroup, css);
来取得这个可被称为子类实例的mem_cgroup,接下来就可以操作它的数据了。终于可以看一下cgroup_subsys_state了:
struct cgroup_subsys_state {
    struct cgroup *cgroup;
    atomic_t refcnt;
    unsigned long flags;
};
这个结构很简单,cgroup是它绑定的一个cgroup实例,从名称上也可以看出cgroup_subsys_state结构是动态的,它表示进程的subsys的state,由于它是被cgroup管理的,因此它也只能有一个cgroup与其绑定。
     接下来看一下cgroup结构,这好像是一个重量级的结构,其实不然,它仅仅起到一个粘合的作用,换句话说就是管理者:
struct cgroup {
    unsigned long flags;         
    atomic_t count;
    struct list_head sibling;     //此和以下几个实现了树型结构
    struct list_head children;     
    struct cgroup *parent;     
    struct dentry *dentry;          
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; //这里仅包含对应ROOT相关的subsys
    struct cgroupfs_root *root;     //对应的那个ROOT
    struct cgroup *top_cgroup;
    struct list_head css_sets;    //包含所有的它参与管理的css_set
    struct list_head release_list;
}
前面提到过,进程要显式加入一个ROOT的cgroup,在加入的时候可能会为进程绑定一个新的css_set(必须保证css_set的subsys数组完全相同才能重用,如果之前没有这样的css_set建立,只好新建立一个),只要绑定了一个新的css_set,这个set就要加入到cgroup的css_sets链表中,最简单的可以在css_set中添加一个字段用于此目的,与此同时cgroup中也要增加一个list_head结构用来链接css_set的cg_links字段,这样做为何不好呢?它增加了两个数据结构的耦合性,同时也增加了数据的冗余性,因为一个cgroup的ROOT负责一组subsys,一个进程也是和一组subsys关联,因此只需要一个进程的一组subsys中被同一个ROOT管理的第一个加入到cgroup链表中就可以表示一个进程受到了这个cgroup的管理,比如进程p1加入group1,该group1的ROOT管理cpu和memory,那么其css_set的subsys数组中只需要cpu_id的这个subsys加入cgroup的css_sets链表就可以了,为了代码的简单,因此引入了一个中间结构,那就是cg_cgroup_link:
struct cg_cgroup_link {
    struct list_head cgrp_link_list;    //代表一个css_set加入到cgroup
    struct list_head cg_link_list;        //代表一个cgroup加入到css_set
    struct css_set *cg;            //指回css_set
    ...  //后续的内核还要指回cgroup,这里的内核是2.6.26
};
在此必须说一下为何要有cg_cgroup_link这个结构体。如果在css_set中和cgroup中直接加入链表元素是解决不了多对多问题的,比如所有参与管理一个css_set的cgroup都将其链表元素加入 css_set的链表,反过来css_set的链表元素也应该加入一个cgroup的链表,代表它是该cgroup管理的css_set之一,现在问题来了,前面说过一个css_set可以属于很多ROOT,那么它到底加入哪个cgroup的链表呢?毕竟css_set和cgroup之间是如此单项一对多的关系耦合,因此解除耦合的办法就是设计一个中间结构,那就是cg_cgroup_link。
     很多时候,很多人在网上写了一大堆关于分析“linux内核”的文章,很多文章都是仅仅分析代码流程,但是很少有文章能说明为何这么做(除非文章的作者着手提交一个补丁或者其它...)。其实linux内核就是一个数据库设计的教程,它不但展示了表结构,而且还有查询引擎,故而linux内核绝对是绝妙的哦!比如在input子系统中,input_handle这个结构体也是和cg_cgroup_link意义一样的,也是为了解决多对多的问题而设置的,还有一个明显的例子,那就是linux内核中的总线驱动架构。
     由此看来,学习linux内核可以学到两大当今时髦的东西,一个就是OO,另一个就是数据库的设计,千万不要以为linux内核仅仅是底层的东西,搞应用的人不用学习,其实各个领域是相通的,我相信,当一个顶级的文学家听说了广义相对论的时候,他也一定会提出一些自己的看法的。我经常看历史著作,那些作者们看起来对任何领域都很感兴趣...
附:看一下cgroup的静态数据结构们吧
首先看一下一个静态的超类,那就是cgroup_subsys,它包含了一系列的接口,但是没有实现!
struct cgroup_subsys {
    struct cgroup_subsys_state *(*create)(struct cgroup_subsys *ss,
                          struct cgroup *cgrp);
    ...//类似的接口
    int subsys_id;
    int active;
    int disabled;
    int early_init;
#define MAX_CGROUP_TYPE_NAMELEN 32
    const char *name;
    struct cgroupfs_root *root;
    struct list_head sibling; //用于挂载一系列的state
    void *private;            //用于扩展
};
每一个挂载(mount)的cgroup文件系统都有一个cgroupfs_root实例:
struct cgroupfs_root {
    struct super_block *sb;
    unsigned long subsys_bits;
    unsigned long actual_subsys_bits;
    struct list_head subsys_list;      //本ROOT关注的subsys
    struct cgroup top_cgroup;
    int number_of_cgroups;
    struct list_head root_list;
    unsigned long flags;
    char release_agent_path[PATH_MAX];

};

 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1271176



相关文章
|
1月前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
95 3
|
1月前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
81 2
|
12天前
|
存储 缓存 监控
Linux缓存管理:如何安全地清理系统缓存
在Linux系统中,内存管理至关重要。本文详细介绍了如何安全地清理系统缓存,特别是通过使用`/proc/sys/vm/drop_caches`接口。内容包括清理缓存的原因、步骤、注意事项和最佳实践,帮助你在必要时优化系统性能。
132 78
|
15天前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
52 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
1月前
|
缓存 Java Linux
如何解决 Linux 系统中内存使用量耗尽的问题?
如何解决 Linux 系统中内存使用量耗尽的问题?
140 48
|
11天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
66 13
|
1月前
|
缓存 并行计算 Linux
深入解析Linux操作系统的内核优化策略
本文旨在探讨Linux操作系统内核的优化策略,包括内核参数调整、内存管理、CPU调度以及文件系统性能提升等方面。通过对这些关键领域的分析,我们可以理解如何有效地提高Linux系统的性能和稳定性,从而为用户提供更加流畅和高效的计算体验。
32 2
|
1月前
|
Ubuntu Linux 网络安全
linux系统ubuntu中在命令行中打开图形界面的文件夹
在Ubuntu系统中,通过命令行打开图形界面的文件夹是一个高效且实用的操作。无论是使用Nautilus、Dolphin还是Thunar,都可以根据具体桌面环境选择合适的文件管理器。通过上述命令和方法,可以简化日常工作,提高效率。同时,解决权限问题和图形界面问题也能确保操作的顺利进行。掌握这些技巧,可以使Linux操作更加便捷和灵活。
39 3
|
12天前
|
Ubuntu Linux C++
Win10系统上直接使用linux子系统教程(仅需五步!超简单,快速上手)
本文介绍了如何在Windows 10上安装并使用Linux子系统。首先,通过应用商店安装Windows Terminal和Linux系统(如Ubuntu)。接着,在控制面板中启用“适用于Linux的Windows子系统”并重启电脑。最后,在Windows Terminal中选择安装的Linux系统即可开始使用。文中还提供了注意事项和进一步配置的链接。
35 0
|
1月前
|
存储 运维 Linux
如何在 Linux 系统中使用 envsubst 命令替换环境变量?
`envsubst` 是 Linux 系统中用于替换文本中环境变量值的实用工具。本文分三部分介绍其工作原理、使用方法及实际应用,包括配置文件替换、脚本执行中环境变量替换和动态生成文件等场景,帮助用户高效利用 `envsubst` 进行开发和运维工作。
65 4

热门文章

最新文章