- A+
英文原文:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cpusets.html
Copyright (C) 2004 BULL SA. Written by Simon.Derr@bull.net Portions Copyright (c) 2004-2006 Silicon Graphics, Inc. Modified by Paul Jackson <pj@sgi.com> Modified by Christoph Lameter <cl@linux.com> Modified by Paul Menage <menage@google.com> Modified by Hidetoshi Seto <seto.hidetoshi@jp.fujitsu.com>
1 CPUSETS
1.1 CPUSET是什么?
CPUSETS提供一种机制来给任务分配CPU和内存节点。在本文中“内存节点”指的是包含内存的在线节点(on-line node)。
CPUSETS把任务的cpu和内存限制在当前cpuset内的资源上。他们在虚拟文件系统内组成一个嵌套的层次结构。一些关键的钩子被用来管理任务动态调度。
CPUSETS使用在cgroup文章中描述的通用的cgroup子系统。
根据任务请求,使用sched_setaffinity(2)系统调用中的CPU亲和性掩码来包含CPU,使用mbind(2)和set_mempolicy(2)系统调用中的内存策略来包含内存节点,这些都是是通过任务的cpuset来过滤的,滤除任何不在该cpuset中的CPU和内存节点。调度器不会把任务调度到cpus_allowed向量组(vector)不允许的CPU上,内核页存分配器(page allocator)不会分配页面给mems_allowed向量组(vector)不允许的内存节点上。
用户空间代码可以根据cgrup虚拟文件系统中的名字来创建和销毁cpusets,管理它的属性和权限,以及CPU和内存节点,定义和查询任务被分配给哪个cpuset,枚举cpuset中的任务pids。
1.2 为什么需要cpusets?
大计算机系统的管理,有许多处理器(CPUs)、复杂内存缓存架构,NUMA结构的多内存节点,给进程的高效调度和内存管理带来了额外的挑战。
通过让操作系统自动共享请求的任务之间可用的CPU和内存资源,小型系统能够高效运行。
但是那些较大的系统,虽然得益于精心配置的处理器和内存调度,可以减少内存访问次数及访问竞争,但也意味着客户会有更大的投入,他也还是可以通过给任务安排合适大小的系统子集来受益。
这些情况下尤其有价值:
- Web服务器运行多个相同的web应用实例,
- 服务器运行不同的应用(例如web服务器和数据库),或者
- NUMA系统运行要求高性能特性的大HPC应用
这些子集或者说“软分区”(soft partitions)必须能随着任务的改变动态调整而不能影响到其他正在并发执行的任务。运行任务的页存位置也可以随着内存位置的改变而移动。
内核cpuset补丁提供了实现这个子集的最小化基本内核机制。它权衡了内核中现存的CPU和内存调度功能,避免对关键的调度器和内存分配器代码带来额外的影响。
1.3 cpusets是如何实现的?
cpusets提供了一种内核机制来限制进程或者进程集合使用的CPU和内存节点。
内核已经有一对机制来定义任务可以被调度到哪个CPU(sched_setaffinity)和获得内存的哪个节点(mbind, set_mempolicy)。
cpusets是这样来扩展这两种机制的:
- cpusets是允许使用的CPU和内存节点的集合。
- 系统中的每个任务被绑定到cpuset,是通过任务结构中的指针指向引用计数的cgroup结构实现的。
- 对sched_setaffinity的调用会选择任务所在cpuset中允许的CPU。
- 对mbind和set_mempolicy的调用会选择任务所在cpuset中允许的内存节点。
- 根cpuset(root cpuset)包含所有的系统CPU和内存节点。
- 对任何cpuset来说,可以定义子cpusets,包含了父CPU和内存节点资源的子集合。
- cpusets的层次架构挂载在/dev/cpuset,可以从用户空间来浏览和操作。
- cpuset可以标记为独占的,以确保没有其他的cpuset(除了直系祖宗和后代)会包含重叠的CPU和内存节点。
- 可以枚举绑定到cpuset上的所有任务(通过pid)
cpusets的实现需要很少的简单的钩子来连接到内核,他们都不在性能关键路径上(performance critical paths):
- 在init/main.c中,系统启动时初始化根cpuset。
- 在fork和exit时,从cpuset中绑定和解绑任务。
- 在sched_setaffinity中的掩码来标记cpuset所允许的CPU。
- 在sched.c的migrate_live_tasks()函数中,来保持任务在cpuset允许的CPU之间迁移。
- 在mbind和set_mempolicy的系统调用,用掩码来标记在cpuset允许的内存节点。
- 在page_alloc.c中,限定内存分配到允许节点。
- 在vmscan.c中限制页存恢复到当前的cpuset。
你应当挂载cgroup文件类型来使能对cpuset的浏览和修改。没有为cpusets添加新的系统调用,cpusets的所有查询和修改都是通过cpuset文件系统来支持的。
每个任务的/proc/
Cpus_allowed: ffffffff,ffffffff,ffffffff,ffffffff Cpus_allowed_list: 0-127 Mems_allowed: ffffffff,ffffffff Mems_allowed_list: 0-63
每个cpuset是用cgroup文件系统中的目录表示的,(在标准cgroup文件顶部)包含这些文件:
- cpuset.cpus: cpuset中的CPU列表
- cpuset.mems: cpuset中的内存节点列表
- cpuset.memory_migrate: 是否移动内存页到cpuset节点?
- cpuset.cpu_exclusive: 是否独占CPU?
- cpuset.mem_exclusive: 是否独占内存?
- cpuset.mem_hardwall: 内存分配是否hardwalled?
- cpuset.memory_pressure: cpuset中的页存压力测量
- cpuset.memory_spread_page标记: 是否使用扩展高速页存(page cache)
- cpuset.memory_spread_slab标记: 是否使用扩展高速slab(slab cache)
- cpuset.sched_load_balance标记: 是否使用负载均衡
- cpuset.sched_relax_domain_level: 任务迁移时的搜索范围
此外,根cpuset下还有下列文件:
- cpuset.memory_pressure_enabled:是否计算内存压力
使用mkdir系统调用或者shell命令来创建新的cpusets。允许使用CPU和内存节点、以及绑定任务等cpuset属性(例如标记),可以通过写入上面列举的cpusets目录中的对应文件来修改。
嵌套cpuset层次结构允许把大系统分割成嵌套的动态更改的软分区。
每个任务对cpuset的绑定,在fork时被它的子任务自动继承,允许系统上的工作负载组织到相关的任务集合,每个集合被限定为使用指定的cpuset中的CPU和内存节点。任务可以被重新绑定到其他的cpuset,只要cpuset文件系统目录权限允许就行。
这种在大的系统级的管理,和在单个任务和内存区域上使用sched_setaffinity、mbind以及set_mempolicy系统调用所做的细节处理,实现了平滑的集成。
下列规则适用于每个cpuset:
- CPU和内存节点必须是它的父级的子集。
- 除非他的父级是独占的,否则不能标记为独占。
- 如果cpu或者内存是独占的,他们不能跟任何兄弟级别有重叠。
这些规则和cpusets层次的独占性,不必每次扫描所有cpuset的改变,就能保证独占性的cpuset不会有重叠发生。而且,用linux虚拟文件系统vfs来表示cpuset层次架构,为cpusets提供了权限和命名空间,只用最小的额外的内核代码改变就有效实现了。
在根cpuset(顶层cpuset)中的cpus和内存文件是只读的。cpu文件使用cpu热插拔通知器自动跟踪cpu_online_mask的值,使用cpuset_track_online_nodes()函数钩子来自动跟踪node_states[N_MEMORY](就是内存节点)的值。
cpuset.effective_cpus和cpuset.effective_mems文件分别是cpuset.cpus和cpuset.mems文件的只读副本。如果cpuset文件系统以特定的cpuset_v2_mode选项挂载,这些文件的行为将和cpuset v2版本的相关文件类似。换句话说,热插拔事件不会改变cpuset.cpus和cpuset.mems。这些事件将只影响到cpuset.effective_cpus和cpuset.effective_mems,他们会显示cpuset当前真正在使用的cpu和内存节点。更多的cpuset v2版本的行为请参看Control Group v2版本。
1.4 什么是独占cpusets(exclusive cpusets)?
如果一个cpuset是独占式cpu或内存,除了直系祖宗和子孙外,其他cpuset不可以共享相同的CPU和内存节点。
有cpuset.mem_exclusive或者cpuset.mem_hardwall属性的cpuset是“hardwalled”,也就是说,它限制内核分配page,buffer和内核多用户公共共享的其他数据。所有的cpusets,不管是否是hardwalled,禁止为用户空间分配内存。当分隔cpuset中的任务进行用户分配时,可以使能系统的配置,以便几个互不相关的任务能共享公共的内核数据,例如文件系统页存。要做到这点,需要构建一个大的内存独占的cpuset来持有所有任务,为每个单独的任务构建非独占内存的子cpuset。仅仅少量的内核内存(例如来自中断处理器的请求),允许从独占内存的cpuset之外获取。
1.5 什么是内存压力 (memory_pressure)?
cpusets的内存压力提供了简单的cpuset百分比度量方法,任务可以试着释放cpuset节点中的使用内存来满足额外的内存需求。
这样能使得运行在专用cpuset中的任务监测管理员可以有效的探测任务正在产生的内存压力处理在什么水平。
这样是有用的,不管是一个运行大量混合任务的管理系统(其任务可以选择结束或者重新按重要程度排列,这些任务想要使用比节点允许的更多内存),还是一个紧密耦合的长期运行的大量的并行科学计算任务,如果使用超出允许范围的更多内存,他们明显不能满足性能目标。
这个机制给管理员提供了一种非常经济的方式来监测cpuset的内存压力信号。随后就看管理员或者其他用户如何来决定采取什么措施了。
除非这个特性已经通过写“1”到文件/dev/cpuset/memory_pressure_enabled的方式使能,否则在__alloc_pages()函数代码中的钩子就会收到通知.因此只有使能了这个特性的系统可以计算度量。
为什么按cpuset来平均运行:
- 因为这种测量是按cpuset的,而不是按任务的,受监控测量调度器影响的系统负载在大系统中会明显减少,因为查询可以避免批量扫描任务列表。
- 因为这种测量是运行时平均(runing average),而不是累加计数器,调度器能以单次读取的方式来探测到内存压力,而不用周期性的读取和计算结果。
- 因为这种测量是按cpuset而不是按任务,调度器以单次读取的方式得到cpuset内存压力,而不用去查询和计算所有cpuset中的任务集合。
每个cpuset的简单数字过滤器(需要spinlock和每个cpuset数据的3个词汇)被保留,如果他进入了同步(直接)页存回收代码的话,就由绑定到cpuset的任务来更新。
每个cpuset文件提供了一个整数来表示最近cpuset中的任务造成的直接页存回收的比率,以每秒千次尝试回收为单位。
1.6 什么是内存扩展memory spread?
每个cpuset有两个布尔值标记文件来控制内核在哪里为文件系统缓冲和相关的内核数据结构分配页存,他们就是cpuset.memory_spread_page和cpuset.memory_spread_slab。
如果设置了cpuset.memory_spread_page文件,那么内核将在所有的允许使用故障处理任务的节点上平均地扩展文件系统缓冲(页存),而不是把这些页存放置在该任务运行的节点上。
如果设置了cpuset.memory_spread_slab,那么内核将为索引节点(inodes)和目录项(dentries)平均地扩展跟slab缓存相关的文件系统。他将在所有允许使用故障处理任务的节点上扩展文件系统,而不是在该任务正在运行的节点上放置页存。
这些标记的设置并不会影响到匿名数据段或者任务的堆栈段页。
默认情况下,两种内存扩展都是关闭的,除了根据任务的NUMA内存策略或者cpuset配置修改之外,只要有充足的空闲内存页可用,内存页就会被分配到运行任务的本地节点上。
当新的cpusets创建,他们会继承他们父级的内存扩展设置。
设置内存扩展会影响到相关的页存分配或者slab缓存分配,任务的NUMA内存策略会被忽略。使用mbind()或者set_mempolicy()调用来设置NUMA内存策略的任务,由于他们包含了内存扩展设置,将不会在这些调用中通知任何变化。如果关闭了内存扩展,那么当前定义的NUMA内存策略就会对内存页存分配再次适用。
cpuset.memory_spread_page和cpuset.memory_spread_slab都是布尔值标记文件。默认情况下他们包含“0”值,表示特性是关闭的,如果向文件中写入“1”,就会打开这个命名特性。
实现方式很简单。
设置cpuset.memory_spread_page将会为该cpuset中或者随后要加入该cpuset的每个进程打开标记PFA_SPREAD_PAGE。针对页面缓存的页存分配函数调用被修改以对PFA_SPREAD_PAGE标记进行内嵌检查,如果设置该标记,对cpuset_mem_spread_node()的调用会返回将要分配的节点。
同样地,设置cpuset.memory_spread_slab将会打开进程标记PFA_SPREAD_SLAB。从cpuset_mem_spread_node()返回的页存节点会被标记为slab缓存。
cpuset_mem_spread_node()程序也很简单, 它使用每个任务的cpuset_mem_spread_rotor值来选择当前任务允许内存中的下一个节点作为分配结果。
这种内存调度策略,称为轮询调度(round-robin)或者交叉调度(interleave).
这种策略能为这些任务提供重大的改进,需要放置线程本地数据在相关节点上的任务,需要访问大文件系统数据集合而他们在任务的cpuset中必须被扩展来跨多个节点的任务。如果没有这些策略,特别是对可能有一个正在读取数据集合的线程的任务,跨节点的分配就会变得非常不方便。
1.7 什么是负载均衡调度sched_load_balance?
内核调度器(kernel/sched/core.c)自动均衡负载任务。如果一个CPU未充分利用,运行在该CPU上的内核代码将搜寻其他过载CPU上的任务,移动这些任务到自己的CPU上,当然它是在cpusets和sched_setaffinity调度机制的限制之内。
负载均衡的算法成本和它对关键共享内核数据结构(例如任务列表)的影响,相比正在做均衡化的CPU数量是线性增加的。因此调度器已经支持把系统CPU分割成很多调度域(sched domain),以便它只需要在每个调度与内做负载均衡。每个调度域覆盖了系统中的CPU子集;没有哪两个调度域是重叠;有些CPU可以不在任何调度域内,因此也不会被负载均衡。
简而言之,在两个较小的调度域上做均衡比在一个大的调度域上的成本要少,但是这么做意味着,在两个调度域的其中一个过载,将不会均衡到另一个上。
默认情况下,有一个调度域会覆盖所有CPU,包括那些用内核启动时间“isolcpus=”参数标记为孤立的(isolated)的CPU。然而,这些孤立的CPU不会参与负载均衡,除非明确指派,不然也不会有任务运行其上。
这个默认的跨所有CPU的负载均衡不是很适合下面两种情况:
- 在大系统中,负载均衡跨很多CPU是很昂贵的。如果系统是使用cpuset来放置不相关的任务到不同的CPU集合的方式来管理的,完全的负载均衡就是没有必要的。
- 那些支持某些CPU实时性的系统必须减少在这些CPU上的系统开销,包括避免任务不必要地负载均衡。
当cpuset标记cpuset.sched_load_balance被使能(默认值),它就请求cpuset.cpus中的包含的所有的CPU包含到单个调度域中,确保负载均衡能从cpuset中的一个CPU移动任务(没有被sched_setaffinity固定住的)到任意其他的CPU上。
当cpuset的cpuset.sched_load_balance标记被禁用,那么调度器就会避免在该cpuset上负载均衡,除非是在某些已经使能了sched_load_balance的重叠的cpuset上。
举个例子,如果顶层cpuset使能了cpuset.sched_load_balance,那么调度器将有一个调度域覆盖所有的CPU,在任何其他的cpuset内设置cpuset.sched_load_balance标记将不会有问题,因为我们已经完全地负载均衡了。
因此在上述两种情况中,顶层cpuset的cpuset.sched_load_balance标记应该禁用,只有那些较小的子cpuset可以使能这个标记。
当这么做了之后,你通常就不要想在顶层使用了大量CPU的cpuset中放置任何未绑固(unpinned)的任务,因为这些任务,根据其子系(decendant)中设置的特性标记,可能被人为地限定到了某些CPU子集上。纵使这个任务能使用其他某些CPU中的空闲CPU周期,内核调度器也不可能会考虑负载均衡到那些未使用的CPU上。
当然,绑固到特定CPU上的任务可能会被放在禁用了cpuset.sched_load_balance标记的cpuset里面,然后这些任务就不会再到任何其它的地方了。
这里在cpusets和调度域之间有一个匹配误差(impedance mismatch)。cpuset是分层的和嵌套的,调度域是扁平的。他们不会重叠,每个CPU至少在一个调度域中。
对调度域来说,它必须是扁平的,因为跨越了部分重叠CPU集合的负载均衡将带来超出我们理解的不稳定动态。因此如果两个部分重叠的cpuset中的每一个都使能了cpuset.sched_load_balance标记,那么我们就执行单个调度域,它是这两个cpuset的超集。我们将不会移动任务到cpuset之外的CPU上,但是调度器负载均衡代码可能浪费了一些计算周期来考虑这个可能性。
这种不匹配就是为什么在cpuset.sched_load_balance标记被使能的cpuset和调度域配置之间没有一种简单的一对一关系。如果cpuset使能了标记,它将得到了跨所有CPU的均衡,但是如果禁用了标记,它将只会确保没有负载均衡,只要没有其他重叠的cpuset使能了这个标记。
如果两个cpuset有部分重叠的cpuset.cpus,只要其中一个使能了这个标记,那么另一个可能发现他的任务只是在重叠的CPU上被部分地负载均衡了。这只是上面示例图中顶层cpuset的常见情况。在一般情况下,在顶层cpuset案例中,不会放置可能使用大量CPU的任务在部分负载均衡的cpuset中,他们可能会人为被限定到某些CPU的子集中,不能负载均衡到其他的CPU上。
那些通过“isolcpus=”内核启动参数设置在cpuset.isolcpus中的CPU会被从负载均衡中排除,永远不会被负载均衡,不管是否在任何cpuset中设置了cpuset.sched_load_balance值。
1.7.1 sched_load_balance实现细节
每个cpuset的cpuset.sched_load_balance标记默认是使能的(跟大部分的cpuset标记相反),当使能标记之后,内核将确保能够在cpuset内的所有CPU上负载均衡。(确保所有的在cpus_allowed标记内的所有CPU在同一个调度域内)
如果两个重叠的cpuset都使能了cpuset.sched_load_balance,那么他们将都在同一个调度域内。
如果顶层cpuset默认使能了cpuset.sched_load_balance,那么上面的情况就意味着,不管其他的cpuset设置了什么,有一个调度域覆盖了整个系统,
内核保证用户空间将会避免负载均衡。它将尽可能合适的选择调度域的分割力度,以便仍旧可以给使能cpuset.sched_load_balance标记的任何CPU集合提供负载均衡。
内部的面向调度器接口的内核cpuset,从cpuset代码传递系统负载均衡的CPU partition(a partition of the load balanced CPUs)到调度器代码。这个partition是互不相交的CPUs的子集(以cpumask结构数组的形式呈现),它必须要做负载均衡。
cpuset代码构建了一个新的这样的partition(翻译成‘分拆集’?),把它传递给调度器的调度域构建代码,以便在下列情况下按需重建调度域:
- cpuset中CPUs不为空,cpuset.sched_load_balance标记发生变化,
- 或者CPUs调进/调出cpuset,而这个cpuset.sched_load_balance标记已经使能了,
- 或者cpuset中CPUs不为空,cpuset.sched_load_balance标记已经使能了,cpuset.sched_relax_domain_level的值发生了变化,
- 或者cpuset中CPUs不为空,而标记已经使能的cpuset被移除了。
- 或者cpu被脱机/联机(offlined/onlined)
这个partition清楚地定义了调度器应该构建什么样的调度域:为partition内的每个单元(cpumask结构)建一个调度域。
调度器会记住当前激活的调度域partitions。当调度器程序partition_sched_domains() 被从cpuset代码调用来更新这些调度域的时候,它会比较当前的和新请求的partition,然后更新调度域,移除旧的,添加新的。
1.8 什么是sched_relax_domain_level?
TODO: 以后再翻译......
1.9 如何使用cpusets?
为了最小化cpuset对关键内核代码(例如调度器)的影响,又由于内核不支持一个任务直接更新另一个任务的内存安置(memory placement),一个任务改变它自己的cpuset上的CPU和内存节点,或者一个任务改绑某个任务到某个cpuset上,这些影响是很小的。
如果修改了cpuset的内存节点,那么对于绑定其上的任务来说,下次内核就为这些任务分配内存,将会通知任务的cpuset发生了变化,然后更新每个任务的内存布局,从而继续保持在新的cpuset内存布局内。如果任务正在使用内存策略MPOL_BIND,而已经被绑定的节点跟它的新cpuset有重叠,那么任务将继续使用MPOL_BIND节点的子集而不管它们是否仍然在新cpuset中被允许,(有几句废话翻译起来很费劲...)
如果修改了cpuset的cpuset.cpus,cpuset中的任务将立即改变他们的CPU位置(CPU placement).同样的,如果任务的pid被写到另一个cpuset的tasks文件,那么也会立刻改变他的CPU位置。如果任务已经使用sched_setaffinity() 调用来绑定到某个cpuset子集上,该任务允许运行在新cpuset的CPU上。
简而言之,cpuset被改变,内核就会改变任务的内存位置,该任务的下次页存分配和处理器位置立刻被更新。
一旦页存被分配了主内存的物理页,那么页存就可能驻留在已分配的任何节点上,哪怕cpuset内存调度策略cpuset.mems随后就会改变。如果cpuset.memory_migrate标记设为true,那么当任务被绑定到cpuset上,该任务在旧cpuset内存节点上分配的页存都会被迁移到新的cpuset内存节点上。在此迁移操作期间,cpuset中的相对页存位置会被预留。举个例子,如果页存在旧cpuset的第二个有效节点上,那么页存可能会被放在新cpuset的第二个有效节点上。
如果cpuset.memory_migrate设为true,然后修改了cpuset.mems,分配在cpuset.mems的旧节点上的页存将会被移动到新设置的内存节点上。不在该任务旧cpuset上的页存、或者不在cpuset旧的cpuset.mems设置中的页存则不会移动。
上述情况有一个例外。如果用来移除所有CPU的热插拔(hotplug)功能分配给了某个cpuset,该cpuset上的所有任务就会移动到最近的带有非空CPU的祖先上。但是,如果cpuset跟其他有任务绑定限制的cgourp子系统绑定,一些(或者所有)任务的移动可能会失败。在绑定失败的情况下,这些任务仍旧驻留在原来的cpuset上,内核将自动更新他们的cpus_allowed。当移除内存节点的内存插拔功能可用,该异常情况处理也是一样。一般情况下,内核会违反cpuset调度规则,它会让有挨饿任务的cpuset中的CPU或者内存节点都脱机离线(offline)。
还有一种例外,GFP_ATOMIC请求是内核必须立即满足的内部分配。如果GFP_ATOMIC请分配失败,即使发生panic,内核也会摘除(drop)某个请求。如果请求不能在该任务的cpuset内得到满足,那么我们就解开(relex)cpuset,尽可能寻找内存。违反cpuset规则好过给内核增加压力。
把任务包含到cpuset中的操作步骤:
1. mkdir /sys/fs/cgroup/cpuset 2. mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset 3. Create the new cpuset by doing mkdir’s and write’s (or echo’s) in the /sys/fs/cgroup/cpuset virtual file system. 4. Start a task that will be the “founding father” of the new job. 5. Attach that task to the new cpuset by writing its pid to the /sys/fs/cgroup/cpuset tasks file for that cpuset. 6. fork, exec or clone the job tasks from this founding father task.
举个例子,下面的命令序列就是构建名字为Charlie的cpuset,仅仅包含CPU 2和3,内存节点1,在该cpuset中启动一个子shell 'sh':
mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset cd /sys/fs/cgroup/cpuset mkdir Charlie cd Charlie /bin/echo 2-3 > cpuset.cpus /bin/echo 1 > cpuset.mems /bin/echo $$ > tasks sh # The subshell 'sh' is now running in cpuset Charlie # The next line should display '/Charlie' cat /proc/self/cpuset
有几种方式来查询或者修改cpusets:
- 直接通过从cpuset文件系统。使用cd、mkdir、echo、cat、rmdir命令或者等效的C语言函数。
- 通过C语言库libcpuset。
- 通过C语言库libcgroup。 (http://sourceforge.net/projects/libcg/)
- 通过python应用cset。(http://code.google.com/p/cpuset/)
sched_setaffinity函数调用也能在Shell提示符中使用,但要通过SGI’s runon或者Robert Love’s taskset。mbind和set_mempolicy函数调用也可以通过shell命令numactl来操作(Andi Kleen’s numa package)。
2 应用实例和语法
2.1 基本用法
创建修改cpuset可以通过cpuset虚拟文件系统来完成。
挂载文件系统:# mount -t cgroup -o cpuset cpuset /sys/fs/cgroup/cpuset
在/sys/fs/cgroup/cpuset下你可以看到cpuset树形结构,/sys/fs/cgroup/cpuset是整个系统的cpuset。
如果想要在/sys/fs/cgroup/cpuset下创建新的cpuset:
# cd /sys/fs/cgroup/cpuset # mkdir my_cpuset
现在你想要用这个cpuset做点什么:
# cd my_cpuset
在这个目录下你能找到几个文件:
# ls cgroup.clone_children cpuset.memory_pressure cgroup.event_control cpuset.memory_spread_page cgroup.procs cpuset.memory_spread_slab cpuset.cpu_exclusive cpuset.mems cpuset.cpus cpuset.sched_load_balance cpuset.mem_exclusive cpuset.sched_relax_domain_level cpuset.mem_hardwall notify_on_release cpuset.memory_migrate tasks
阅读这些文件,你就会看到cpuset状态信息:CPU和内存节点,使用它的进程,属性等。你可以写这些文件来操作cpuset。
设置标记:
# /bin/echo 1 > cpuset.cpu_exclusive
添加CPU:
# /bin/echo 0-7 > cpuset.cpus
添加内存:
# /bin/echo 0-7 > cpuset.mems
绑定shell到cpuset:
# /bin/echo $$ > tasks
在cpuset内创建cpuset:
# mkdir my_sub_cs
使用rmdir来删除cpuset:
# rmdir my_sub_cs
如果cpuset正在使用中(内部有cpuset或者已经绑定了进程),这个操作可能会失败。
注意:cpuset文件系统是cgroup文件系统中的封装包。
下面的命令:
mount -t cpuset X /sys/fs/cgroup/cpuset
等同于:
mount -t cgroup -o cpuset,noprefix X /sys/fs/cgroup/cpuset echo "/sbin/cpuset_release_agent" > /sys/fs/cgroup/cpuset/release_agent
2.2 添加/移除CPU
下列语法用来写cpuset目录下的cpu或者内存文件:
# /bin/echo 1-4 > cpuset.cpus -> set cpus list to cpus 1,2,3,4 # /bin/echo 1,2,3,4 > cpuset.cpus -> set cpus list to cpus 1,2,3,4
要添加CPU 6 到cpuset:
# /bin/echo 1-4,6 > cpuset.cpus -> set cpus list to cpus 1,2,3,4,6
要删除CPU,也是写入新的列表。
移除所有CPU:
# /bin/echo "" > cpuset.cpus -> clear cpus list
2.3 设置标记
语法很简单:
# /bin/echo 1 > cpuset.cpu_exclusive -> set flag 'cpuset.cpu_exclusive' # /bin/echo 0 > cpuset.cpu_exclusive -> unset flag 'cpuset.cpu_exclusive'
2.4 绑定进程
# /bin/echo PID > tasks
注意:这里是PID,不是PIDs。一次只能绑定一个任务,如果要绑定多个,必须一个接一个的操作:
# /bin/echo PID1 > tasks # /bin/echo PID2 > tasks ... # /bin/echo PIDn > tasks
3 问答
Q: 为什么要使用'/bin/echo'? A: bash内嵌的echo命令不会检查对write()调用的错误,如果你在控制组文件系统中使用它,你将不知道命令是否执行成功还是失败。 Q: 当我绑定很多进程时,只有第一行被真正绑定?When I attach processes, only the first of the line gets really attached ! A: 每次对write()的调用只能返回一个错误,所以你应该就放一个PID。