本文属于内核文档翻译,翻译时没有遵照原文,添加了一些作者的理解,目的不是为了替代内核文档,可以作为阅读内核文档的引子,作者鼓励读者阅读原有的内核文档。原文参考3.10.514内核文档cpuset.txt
内容
1.cpuset
1.1:什么是cpuset?
1.2:为什么需要cpuset?
1.3:cpuset是如何实现的?
1.4:什么是互斥cpuset?
1.5:mem_pressure是什么意思?
1.6:什么是memory spead?
1.7:什么是sched_load_balance?
1.8:什么是sched_relax_domain_level?
1.9:如何用cpuset?
2.使用举例,以及语法
2.1:基本用法
2.2:添加删除cpu
2.3:设置标志位
2.4:进程绑定
1.Cpusets
==========
1.1 什么是cpuset?
cpuset基本功能是限制某一组进程只运行在某些cpu和内存节点上,举个简单例子:系统中有4个进程,4个内节点,4个cpu.利用cpuset可以让第1,2个进程只运行在第1,2颗cpu上并且只在第1,2个内存节点上分配内存。cpuset是基于cgroup子系统实现(关于cgroup子系统可以参考内核文档 Documentation/cgroups/cgroups.txt.)使用cpuset上述功能可以让系统管理员动态调整进程运行所在的cpu和内存节点。
linux提供的如下三个系统调用完成类似的功能:
sched_setaffinity(俗称进程亲核性)系统调用,设置某进程(或线程)只能运行在某些cpu上。
mbind(内存绑定)和set_mempolicy(内存策略)系统调用,设置进程只能在某些内存节点上分配内存。
那亲核性,内存绑定,内存策略和cpuset是什么关系呢?
如果一个进程即设置了亲核性,内存绑定,内存策略,又设置了cpuset,那么进程运行需要的内存和cpu就会是前后两者的交集。例如:进程1通过亲核性设置只能跑在1,2两个cpu上,并且通过mbind或者set_mempolicy限制只能在内存节点1,2上分配内存;接着又用cpuset限制其只能在1,3核心上运行,并且只能在内存节点1,3上分配内存,那么结果是:进程1只能运行在1核心上,并且只能在内存节点1上分配内存。
cpuset使用sysfs提供用户态接口,可以通过普通文件读写,将某颗cpu动态的加入到cpuset从而允许cpuset中的所有进程使用这颗cpu,也可以将某个进程加入到cpuset,从而让该进程接受cpuset的限制。可以将某个进程从一个cpuset移动到另一个cpuset。可以动态的创建,删除cpuset,修改cpuset的属性。
遗留问题:
1.mbind和set_mempolicy对用户态内存和内核态内存限制,表现的特性是不是一样?如果不一样都有哪些差异呢?
1.2 为什么需要cpuset?
大型的服务器中,有上百颗cpu,若干内存节点。尤其在NUMA架构下,cpu访问不同内存节点的速度不同,这种情况增加了进程调度和进程内存分配目标node管理的难度。比较新的小型系统使用linux内核自带的调度功能和内存管理方案就能得到很好表现,但是在比较大的系统中如果精心调整不同应用所在的cpu和内存节点会大大提高服务器性能表现。
cpuset在一下应用场合会显得特别有价值
1.对于跑了很多相同的应用实例的大型web server
2.对于跑了不同应用的大型server(例如:同时跑了web server相关应用,又跑了数据库应用)
3.大型NUMA系统
cpuset必须允许动态调整,并且不影响服务器上其他不相关cpuset中运行的进程。比如:可以将某个cpu动态的加入到某个cpuset,可以从某个cpuset中将某个cpu移除,可以将进程加入到cpuset,也可以将某个进程从一个cpuset迁移到另一个cpuset。内核的cpuset补丁,提供了最基本的实现上述功能的机制,在实现上最大限度使用原有的cpu和内存节点分配机制,尽可能避免影响现有的调度器,以及内存分配核心功能的代码。
个人觉得相对于亲核性,内存绑定,内存策略几个系统调用,cpuset的优势在于:前者必须通过系统调用,而系统调用的目标是进程,也就是说系统调用是面向单个进程。而cpuset则是另一种思路是先将几个cpu和几个内存节点放到cpuset中,在将相关的进程放到cpuset中 从而达到进程接受cpuset的限制,cpuset面向的系统资源和进程集合。
1.3 cpuset是如何实现的?
在内核引入cpuset之前,已经有一些其他的机制来限制某个进程只能被调度到某些cpu上运行(sched_setaffinity),限制某些进程的内存申请只能在某些内存节点上分配(mbind,set_mempolicy)
cpuset在如下几个方面对sched_setaffinity,mbind,set_mempolicy做了扩展
1.cpuset从概念上可以理解成一组cpu和一组内存节点的集合
2.进程描述符中有一个指针指向了cgroup数据结构(cpuset是cgroup的一个子系统),通过这个指针,可以将进程添加到具体的cpuset
3.对于在某个cpuset中的进程,调用sched_setaffinity那么亲核性设置的cpu集合会经过cpuset的过滤,也就是说亲核性最后设置的结果一定是seched_setaffinity和cpuset的交集。
4.mbind和set_mempolicy设置的node节点也要经过cpuset的过滤,内存绑定,内存策略设置的node也要经过cpuset规则设置的node的限制
5.根cpuset中包含了系统中所有的内存节点和cpu
6.针对任何的cpuset,用户可以定义该cpuset的子cpuset,子cpuset中包含的cpu和内存节点是该cpuset(父亲cpuset)的子集
7.cpuset的层级可以被挂在在/dev/cpuset,也就是一个伪文件系统,通过操作该文件系统的文件来动态管理cpuset
8.一个cpuset可以被标志位“互斥”的,一个被标志位互斥的cpuset,该cpuset的兄弟cpuset中包含的cpu和内存节点不能和该cpuset中的cpu和内存节点有交集
9.可以找到一个cpuset中所有进程的pid
cpuset实现需要在现有的内核代码流程中实现些钩子函数,但是这些钩子函数都不在内核控制路径的的关键(热点)路径上。
1.在init/main.c中需要在系统初始化的时候初始化根cpuset
2.在fork和exit流程中需要将某个进程放入cpuset或者将进程从cpuset中拿出来
3.在sched.c的调度或者进程迁移中,确保进程迁移的目标cpu在cpuset中
4.在mbind和set_mempolicy中用cpuset的内存节点对这两个系统调用的内存节点过滤
5.在page_alloc.c中限制内存分配只能在cpuset中的内存节点上分配内存
6.在vmscan.c中限制page回复到当前的cpuset(不知道啥意思)
如果想对cpuset操作,就必须挂在cgroup文件系统,内核没有提供额外的系统调用对cpuset做修改,维护。所有的修改维护操作都是通过修改该文件系统中的文件来完整。
在/proc/#pid/status中添加了如下几行来说明一个进程运行必须在某些cpu上,并且进程分配内存必须在某些内存节点上
Cpus_allowed: ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list: 0-127
Mems_allowed: ffffffff,ffffffff
Mems_allowed_list: 0-63
每一个cpuset对应cgroup文件系统中的一个目录,目录下有些文件,用来描述cpuset的属性。对应的文件里列表如下:
1.cpuset.cpus cpuset中的cpu列表
2.cpuset.mems cpuset中的内存节点列表
3.cpuset.memory_migrate
4.cpuset.cpu_exclusive cpuset是否是cpu互斥的(参考1.4节)
5.cpuset.mem_exclusive cpuset是否是内存互斥的(参考1.4节)
6.cpuset.mem_hardwall cpuset是否是hardwalled的(参考1.4节)
7.cpuset.memory_pressure
8.cpuset.memory_spread_page 如果被设置了,将该cpuset中进程上下文申请的page cache平均分布到cpuset中的各个节点
9.cpuset.memory_spread_slab 如果被设置了,将该cpuset中进程上下文申请到的slab对象平均分布到cpuset中的各个内存节点
10.cpuset.sched_load_balance
11.cpuset.sched_relax_domain_level
下面文件只有在根cpuset中才有:
12.cpuset.memory_pressure_enabled
可以通过mkdir 命令在shell环境下创建cpuset,而新创建的cpuset的属性,可以通过普通的文件读写修改(具体文件就是上面提到的12个文件)。而通过在一个cpuset对一个的目录下创建子目录可以创建子cpuset,通过这个层级模型可以把一个大型系统从软件上切分成若干个小型的系统
【未完待续】
1.4 什么是互斥cpuset
每个cpuset有两个bool类型的参数cpuset.mem_exclusive,cpuset.cpu_exclusive,分别表示当前的cpuset是否是内存互斥和cpu互斥的。为了理解这个概念必须理解cgroup层级树的概念:其实cpuset做为cgroup一个子系统实现,也遵循了cgroup层级树的概念。举个例子,例如:有一个cpuset0,可以在cpuset0下再建立若干个cpuset。比如:建立cpuset0-0,cpuset0-1两个孩子cpuset。cpuset0-0和cpuset0-1互称位兄弟,cpuset0属于父亲。孩子cpuset中的cpu和node节点集合必须是父亲cpuset中cpu和内存节点集合的子集。理解了层级树的概念,再来看cpu互斥的概念:如果某个cpuset是cpu互斥的那么该cpuset中的cpu是不能被该cpuset的兄弟cpuset共享的,当然该cpuset中的cpu是能够和该cpuset的祖先cpuset和子孙cpuset共享的。理解了cpu互斥的概念,要理解内存互斥的概念,将上述cpu互斥概念中的cpu替换成内存节点就可以了。
下面特别说明下关于内存互斥cpuset
cpuset还有一个bool类型的参数cpuset.mem_hardwall,如果一个cpuset被设置成内存互斥或者被设置了cpuset.mem_hardwall,那么都认为这个cpuset是mem_hardwalled。一个mem_hardwalled的cpuset其中的进程在内核态申请的内存(主要有直接申请的页,以及通过slab系统分配的内存,还有文件系统page cache)会受到cpuset的限制,进而在该cpuset内的内存节点中分配内存。最为对比:不管cpuset是否是mem_haardwalled,cpuset中的进程在用户态申请的内存都会受到cpuset中内存节点的限制。另外从上面的阐述也可以得到一个总要结论:其实cpuset.headwall是一个相对独立的概念:它限制了cpuset中进程在内核态申请的内存来至于cpuset中的node节点。而mem_exclusive除了提供了内存互斥的功能,还隐含了cpuset.mem_hardwall的功能。
通过上面阐述得到如下两个重要结论:
1.如果一个cpuset 是hardwalled的,那么这个cpuset中的进程的内存分配(不管是用户态的还是内核态的)都要接受cpuset中内存节点的限制。
2.如果一个cpuset不是hardwalled的,那么这个cpuset中的进程的内核态内存分配不受cpuset中内存节点的限制,而用户态内存分配受到cpuset中内存节点的限制。
这么来说就有一个非常有用的场景:
比如又一个文件服务器,有5个内存节点,其中有两组业务进程在大量的读写文件,我们需要将一组进程用户态内存分配限制在内存节点1,2上,而另一组用户态内存分配限制在内存节点3,4上,而所有两组进程需要共同访问的page cache限制在1,2,3,4内存节点上。剩下一个内存节点(5)给其他进程用。而除去这两组进程,不允许其他进程内存分配(不管是和心态还是用户态)落到1,2,3,4上。我们就可以先建立一个cpuset0标记mem_exclusive,并将1,2,3,4节点添加到cpuset0,并在cpuset0下创建一个cpuset0-0 不设置mem_exclusive,将第一组进程添加到其中,并将1,2节点添加到其中,同样建立cpuset0-1,不设置mem_exclusive,并将第二组进程添加到其中,同时将内存节点3,4添加到其中。再建立一个cpuset1 设置mem_exclusive 并将系统中剩余的进程加入到cpuset1中,并将内存节点5放入其中。
1.5什么是mem_pressure?
【未完待续】
1.6什么是memory spread?