Cgroup内核文档翻译(7)——Documentation/cgroup-v1/cpusets.txt

CPUSETS(处理器组)
-------

内容:
=========

1. Cpusets
1.1 什么是cpusets?
1.2 为什么需要cpusets?
1.3 cpusets如何实现?
1.4 什么是专用cpusets?
1.5 什么是memory_pressure?
1.6 什么是内存扩散?
1.7 什么是sched_load_balance?
1.8 什么是sched_relax_domain_level?
1.9 如何使用cpusets?
2.使用示例和语法
2.1 基本用法
2.2 添加/删除cpus
2.3 设置标志
2.4 附加流程
3.问题
4.联系方式


1. Cpusets
==========

1.1什么是cpusets?
----------------------

Cpuset提供了一种将一组CPU和内存节点分配给一组任务的机制。 在本文档中,“内存节点”是指包含内存的在线节点。

处理器组将任务的CPU和内存位置限制为仅任务当前CPU集内的资源。 它们形成了在虚拟文件系统中可见的嵌套层次结构。 除了已经存在的功能以外,这些是管理大型系统上动态作业放置所需的基本功能。

cpuset使用Documentation/cgroup-v1/cgroups.txt中描述的通用cgroup子系统。

通过使用 sched_setaffinity(2) 系统调用将CPU包括在其CPU亲和力掩码中,以及使用 mbind(2) 和 set_mempolicy(2) 系统调用将内存节点包括在其内存策略中,均通过该任务的cpuset进行过滤,过滤掉不在该cpuset中的所有CPU或内存节点。 调度程序不会在其 cpus_allowed 向量中不允许的CPU上调度任务并且内核page分配器不会在请求任务的 mems_allowed 向量中不允许的节点上分配页面。

用户级代码可以按名称在cgroup虚拟文件系统中创建和销毁cpuset,管理这些cpuset的属性和权限,以及为每个cpuset分配了哪些CPU和内存节点,指定并查询任务分配给哪个cpuset,并列出 分配给cpuset的任务pid。

通常,仅通过让操作系统自动在请求的任务之间共享可用的CPU和内存资源,便可以以足够的效率来运行大小较小的系统。

但是大型系统可以通过将作业明确放置在适当大小的系统子集上而受益,大型系统得益于精心的处理器和内存放置以减少内存访问时间和竞争,并且通常代表客户较大的投资。

这在以下方面尤其有价值:
  *运行同一Web应用程序的多个实例的Web服务器,
  *运行不同应用程序的服务器(例如,Web服务器和数据库),或
  *运行具有高性能要求的大型HPC应用程序的NUMA系统。

这些子集或“软分区”必须能够随着作业组合的变化而动态调整,而不会影响其他同时执行的作业。 当更改内存位置时,也可以移动正在运行的作业页面的位置。

内核cpuset补丁提供了有效实现此类子集所需的最低限度的基本的内核机制。 它利用Linux内核中现有的CPU和内存放置功能,以避免对关键调度程序或内存分配器代码产生任何其他影响。

1.3 cpusets如何实现?
---------------------------------

Cpuset提供了Linux内核机制来约束一个或一组进程使用哪个或哪些CPU和内存节点

Linux内核已经有一对机制来指定可以在哪个CPU上调度任务(sched_setaffinity),以及可以在哪个Memory Node上获得内存(mbind,set_mempolicy)

Cpusets扩展了以下两种机制:

- Cpuset是内核已知的一组允许的CPU和内存节点。
- 系统中的每个任务都通过任务结构体中指向引用计数的cgroup结构的指针连接到cpuset。
- 对sched_setaffinity的调用仅过滤到该任务的cpuset中允许的CPU。
- 对mbind和set_mempolicy的调用仅过滤到该任务的cpuset中允许的那些内存节点。
- 根cpuset包含所有系统CPU和内存节点- 对于任何cpuset,可以定义包含父CPU和内存节点资源的子集的子cpuset。
- 可以将cpuset的层次结构mount在/dev/cpuset上,以便从用户空间进行浏览和操作。
- 一个cpuset可能被标记为“独占”,以确保没有其他cpuset(直接祖先和后代除外)可以重叠包含任何CPU或内存节点。
- 您可以列出附加到任何cpuset的所有任务(按pid)。

cpusets的实现需要一些简单的钩子连接到内核的其余部分,而在性能关键路径中则没有:

-在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。 没有为cpuset添加新的系统调用-通过此cpuset文件系统提供对查询和修改cpuset的所有支持。

每个任务的 /proc/<pid>/status 文件增加了四行,分别以以下两种格式显示任务的 cpus_allowed(它可以在哪些CPU上调度)和 mems_allowed(它可以在哪些内存节点上分配内存)。 下面的例子:

Cpus_allowed:   ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list:      0-127
Mems_allowed:   ffffffff,ffffffff
Mems_allowed_list:      0-63

每个cpuset由cgroup文件系统中的目录表示,该目录包含(在标准cgroup文件之上)描述该cpuset的以下文件:

 -cpuset.cpus:该cpuset中的CPU列表
 -cpuset.mems:该cpuset中的内存节点列表
 -cpuset.memory_migrate 标志:如果设置,则将内存页移至cpusets节点
 -cpuset.cpu_exclusive 标志:cpu放置是否独占?
 -cpuset.mem_exclusive 标志:内存放置是否独占?
 -cpuset.mem_hardwall 标志:内存分配是否为硬壁
 -cpuset.memory_pressure:测量cpuset中有多少分页压力
 -cpuset.memory_spread_page 标志:如果设置,则在允许的节点上平均分布页面缓存
 -cpuset.memory_spread_slab 标志:如果设置,则在允许的节点上平均分布slab缓存
 -cpuset.sched_load_balance 标志:如果设置,则在该cpuset上的CPU内进行负载平衡
 -cpuset.sched_relax_domain_level:迁移任务时的搜索范围

此外,只有根cpuset才具有以下文件:

 -cpuset.memory_pressure_enabled 标志:计算memory_pressure?

使用mkdir系统调用或shell命令创建新的cpuset。 如上所述,通过写入该cpusets目录中的相应文件,可以修改cpuset的属性,例如其标志,允许的CPU和内存节点以及附加的任务。

嵌套cpuset的命名层次结构允许将大型系统划分为嵌套的,可动态更改的“软分区”。

将每个任务的任何子代在派生时自动继承的每个任务附加到cpuset,可以将系统上的工作负载组织到相关的任务集中,从而使每个任务集都只能使用特定cpuset的CPU和内存节点 。 如果必要的cpuset文件系统目录的权限允许,则任务可以重新附加到任何其他cpuset。

使用sched_setaffinity,mbind和set_mempolicy系统调用,使这种“大型”系统的管理与在单个任务和内存区域上完成的详细放置顺利集成。

以下规则适用于每个cpuset:

  - 其CPU和内存节点必须是其父级的子集。
  - 除非其父级将其标记为独占,否则子级不能将其标记为独占。
  - 如果其cpu或内存是互斥的,则它们不得与任何同级重叠。

这些规则以及cpuset的自然层次结构可以有效执行排他保证,而不必每次更改任何cpuset时都扫描所有cpuset,以确保没有任何重叠cpuset。 另外,使用Linux虚拟文件系统(vfs),以最少的附加内核代码量,表示了cpuset层次结构可为cpuset提供熟悉的权限和名称空间,

根(top_cpuset)cpuset中的cpus和mems文件是只读的。 cpus文件使用CPU热插拔通知程序自动跟踪 cpu_online_mask 的值,而mems文件使用 cpuset_track_online_nodes()挂钩自动跟踪 node_states[N_MEMORY](即具有内存的节点)的值。

1.4 什么是独占cpusets?
--------------------------------

如果cpuset是cpu或mem独占的,则除直接祖先或后代之外,其他任何cpuset均不得共享任何与其相同的CPU或内存节点。

cpuset.mem_exclusive * 或 * cpuset.mem_hardwall 的 cpuset 是“硬壁的”,即,它限制了内核在多个用户之间通常共享的页面,缓冲区和其他数据的内核分配。
所有cpuset,无论是否为硬墙,都限制为用户空间分配内存。 这样可以配置系统,以便几个独立的作业可以共享公用内核数据,例如文件系统页面,同时将每个作业的用户分配隔离在其自己的cpuset中。 为此,请构造一个大型的 mem_exclusive cpuset以容纳所有作业,并为每个单独的作业构造子级 non-mem_exclusive cpuset。 即使是 mem_exclusive cpuset,也只允许将少量的典型内核内存(例如来自中断处理程序的请求)带到外部。

1.5 什么是 memory_pressure?
-----------------------------
cpuset的memory_pressure提供了每个cpuset的简单度量,用于度量cpuset中的任务试图释放cpuset节点上的使用中的内存以满足其他内存请求的速率

这使批处理管理器可以监视在专用cpuset中运行的作业,以有效地检测该作业造成的内存压力级别。

这在运行大量提交作业的紧密管理的系统上很有用,它们可以选择终止或重新分配那些试图使用比分配给它们的节点更多的内存的优先级的作业,以及紧密耦合,长期运行的作业, 大规模并行科学计算作业,如果开始使用超出允许范围的内存,将大大无法达到所需的性能目标。

此机制为批处理管理器提供了一种非常经济的方式来监视cpuset的内存压力迹象。 由批次管理器或其他用户代码决定如何处理并采取措施。

==>除非通过在特殊文件 /dev/cpuset/memory_pressure_enabled(只有顶层目录下才有此文件) 中写入“ 1”来启用此功能,否则 __alloc_pages() 的重新平衡代码中针对该度量的钩子将简化为仅注意到 cpuset_memory_pressure_enabled 标志为零。 因此,只有启用了此功能的系统才能计算指标。

为什么要按每cpuset运行平均值:

由于此仪表是按CPU而不是按任务或mm的,因此在大型系统上,监视此指标的批处理调度程序所施加的系统负载将大大减少,因为可以避免在每组查询上都扫描任务列表。

因为此仪表是运行平均值,而不是累加计数器,所以批处理调度程序可以通过一次读取来检测内存压力,而不必在一段时间内读取和累加结果。

由于此仪表是per-cpuset而不是per-task或per-mm的,因此批处理调度程序可以通过一次读取就可以获取关键信息,CPU的内存压力。必须查询和累积cpuset中所有(动态变化的)任务集的结果。。

如果每个cpuset简单数字滤波器(输入同步(直接)页面回收代码),则该数字滤波器会保留(需要一个自旋锁和每个cpuset 3个数据字),并由附加到该cpuset的任何任务更新。

每个cpuset文件提供一个整数,表示由cpuset中的任务导致的直接页面回收的近期的速率(半衰期为10秒),以每秒尝试回收的次数乘以1000为单位。

1.6什么是内存扩散?
---------------------------
每个cpuset有两个布尔标志文件,它们控制内核在哪里为文件系统缓冲区分配页面并与内核数据结构相关。 它们分别称为“cpuset.memory_spread_page” 和 “cpuset.memory_spread_slab”。

如果设置了per-cpuset布尔标志文件'cpuset.memory_spread_page',则内核将在允许错误任务使用的所有节点上平均分配文件系统缓冲区(页面缓存),而不是优先放置这些页面在任务正在运行的节点上。

如果设置了per-cpuset的布尔标志文件'cpuset.memory_spread_slab',则内核将在允许故障任务使用的所有节点上平均分配一些文件系统相关的slab缓存,例如inode和dentries,而不是更愿意将这些页面放在运行任务的节点上。

这些标志的设置不会影响任务的匿名数据段或堆栈段页面。

默认情况下,两种内存扩散均处于关闭状态,并且在任务运行所在的本地节点上分配了内存页,除非由任务的NUMA内存策略或cpuset配置修改,只要有足够的可用内存页即可。

创建新的cpuset时,它们会继承其父级的内存扩展设置。

设置内存扩展会导致对受影响的页面或slab缓存的分配忽略任务的NUMA内存,而是进行扩展。 使用 mbind() 或 set_mempolicy() 调用来设置NUMA内存的任务不会注意到这些调用的任何变化,因为它们包含任务的内存扩展设置。 如果关闭了内存扩展,那么当前指定的NUMA内存规则将再次应用于内存页面分配。

“cpuset.memory_spread_page”和“ cpuset.memory_spread_slab”都是布尔标志文件。默认情况下,它们包含“0”,表示该cpuset的功能已关闭。 如果将“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()返回的节点中的页面中分配的缓存。

cpuset_mem_spread_node()例程也很简单。 它使用每个任务的转子 cpuset_mem_spread_rotor 的值来选择当前任务的 mems_allowed 中的下一个节点,以优先进行分配。

这种内存放置策略在其他情况下也称为循环或交错。

对于需要将线程本地数据放置在相应节点上但需要访问大型文件系统数据集的作业,该策略可以提供实质性的改进,这些大型文件系统数据集需要分布在作业cpuset中的多个节点上才能适应。 如果没有此策略,尤其是对于可能在数据集中读取一个线程的作业,则作业cpuset中各节点之间的内存分配可能会变得非常不均匀。

1.7什么是sched_load_balance?
--------------------------------

内核调度程序(kernel/sched/core.c)自动进行负载均衡任务。 如果未充分利用一个CPU,则在该CPU上运行的内核代码将在 cpusets 和 sched_setaffinity 之类的放置机制的约束下,在其他负载更重的CPU上查找任务并将这些任务移至自身

负载均衡的算法的成本及其对关键共享内核数据结构(如任务列表)的影响,随着所均衡的CPU数量的增加,线性增加的幅度更大。 因此,调度程序支持将系统CPU划分为多个调度域(小核、大核、超大核),以便仅在每个调度域内进行负载均衡。 每个调度域都覆盖系统中CPU的某些子集。 没有两个预定域重叠; 一些CPU可能不在任何计划的域中,因此将无法实现负载均衡。

简而言之,在两个较小的sched域之间进行负载均衡的成本要比在一个较大的sched域中低,但是这样做意味着两个域之一中的过载不会与另一个域进行负载平衡。

默认情况下,有一个调度域覆盖所有CPU,包括那些启动时使用内核参数“ isolcpus =”标记为隔离的CPU。但是,隔离的CPU将不会参与负载均衡,并且除非明确分配,否则不会在其上运行任务。

所有CPU上的默认负载均衡均不适用于以下两种情况:
1)在大型系统上,跨多个CPU的负载均衡花销非常大。 如果使用cpusets管理系统以将独立的作业放置在不同的CPU组上,则不需要完全的负载平衡。
2)在某些CPU上支持实时的系统需要使这些CPU上的系统开销最小化,包括避免不需要的任务负载均衡。

启用per-cpuset标志“ cpuset.sched_load_balance”(默认设置)时,它要求该cpuset中允许“ cpuset.cpus”的所有CPU都包含在单个调度域中,以确保负载均衡可以移动任务 (未通过sched_setaffinity绑定)从该cpuset中的任何CPU到任何其他CPU。

当禁用per-cpuset标志“ cpuset.sched_load_balance”时,调度程序将避免在该cpuset中跨CPU进行负载平衡(除非有必要),因为某些重叠的cpuset启用了“ sched_load_balance”。

因此,例如,如果顶部cpuset启用了标志“ cpuset.sched_load_balance”,则调度程序将具有覆盖所有CPU的一个调度域,而其他任何cpuset中的“ cpuset.sched_load_balance”标志的设置都无关紧要 ,因为我们已经完全负载平衡。

因此,在以上两种情况下,应禁用顶部的cpuset的“ cpuset.sched_load_balance”标志,并且仅某些较小的子cpuset启用此标志

这样做时,您通常不希望将任何未固定的任务留在可能占用大量CPU的顶级CPU中,因为根据此标志设置的具体情况,此类任务可能被人为地限制在某些后代cpuset中。 即使这样的任务可以在其他一些CPU中使用空闲的CPU周期,内核调度程序也可能不会考虑将该任务与未充分利用的CPU进行负载平衡的可能性。

当然,固定在特定CPU上的任务可以留在禁用“ cpuset.sched_load_balance”的cpuset中,因为这些任务无论如何哪都不去。

在cpuset和sched域之间存在不匹配。 Cpusets是分层的和嵌套的。 Sched domains是平坦的;它们不重叠,并且每个CPU最多在一个调度域中。

调度域必须是平坦的,因为部分重叠的CPU组之间的负载均衡可能会带来不稳定的动态变化,这超出了我们的理解。 因此,如果两个部分重叠的cpuset中的每一个都启用了标志“ cpuset.sched_load_balance”,那么我们将形成一个单独的sched域,这是两者的超集。 我们不会将任务移到其cpuset之外的CPU,但是考虑到这种可能性,调度程序负载平衡代码可能会浪费一些计算周期。

这种不匹配就是为什么在cpuset启用了标志“ cpuset.sched_load_balance”与sched域配置之间不存在简单的一对一关系的原因。如果cpuset启用了该标志,它将在所有CPU上保持平衡,但是如果禁用该标志,则只有在没有其他重叠的cpuset启用该标志的情况下,才能确保没有负载平衡。

如果两个cpuset允许部分重叠的'cpuset.cpus',并且只有其中一个启用了此标志,则另一个可能会发现其只有部分任务负载平衡了,仅在重叠的CPU上的任务实现了。
这只是上面几段给出的 top_cpuset 示例的一般情况。 在一般情况下,就像在顶级cpuset情况下一样,不要在这样的部分负载平衡的cpuset中保留可能使用少量CPU的任务,因为它们可能被人为地限制在允许它们使用的CPU的某些子集中。 缺乏其他CPU的负载平衡。

isolcpus = kernel boot选项将“ cpuset.isolcpus”中的CPU从负载平衡中排除,并且无论任何cpuset中的“ cpuset.sched_load_balance”的值如何,都将永远不会进行负载平衡。

每个per-cpuset的标志'cpuset.sched_load_balance'默认为启用(与大多数cpuset标志相反。)为cpuset启用后,内核将确保它可以在该cpuset中的所有CPU上进行负载平衡(确保该cpuset的cpus_allowed中的所有CPU都位于相同的调度域中。)

如果两个重叠的cpuset都启用了“ cpuset.sched_load_balance”,则它们将(必须是)都在同一调度域中。

如果顶层的cpuset默认使能了'cpuset.sched_load_balance',则通过上述方法,意味着存在一个覆盖整个系统的调度域,而与其他cpuset设置无关。

内核承诺用户空间,它将避免在可能的地方进行负载平衡。 它会尽可能选择调度域的粒度分区,同时仍为启用了“ cpuset.sched_load_balance”的cpuset允许的任何CPU组提供负载平衡。

内部内核cpuset到调度程序的接口将cpuset代码传递给调度程序代码,该代码是系统中负载均衡CPU的一个分区。 该分区是一组CPU的子集(表示为struct cpumask数组),成对不相交,涵盖必须进行负载平衡的所有CPU。

cpuset代码将构建一个新的此类分区,并将其传递给调度程序的调度域设置代码,以在需要时重新构建sched域:

-具有非空CPU的cpuset的'cpuset.sched_load_balance'标志更改,
-或启用了此标志的从cpuset中来或到cpuset中去的CPU,
-或具有非空CPU且启用了此标志的cpuset的'cpuset.sched_relax_domain_level'值更改,
-或删除了具有非空CPU且启用了此标志的cpuset,
-或cpu离线/在线。

此分区准确地定义了调度程序应设置的调度域-一个为分区中的每个元素(结构cpumask)的调度域。

调度程序会记住当前活动的调度域分区。 从cpuset代码调用调度程序例程 partition_sched_domains() 来更新这些调度域时,它会针对每个更改将请求的新分区与当前分区进行比较,并更新其调度域,删除旧分区并添加新分区。

1.8 什么是 sched_relax_domain_level?
--------------------------------------

在调度域中,调度程序以两种方式迁移任务。 滴答中的定期负载平衡和在一些调度事件发生时进行负载平衡

唤醒任务后,调度程序将尝试在空闲CPU上移动任务。 例如,如果在CPU X上运行的任务A激活了同一CPU X上的另一个任务B,并且如果CPU Y是X的兄弟CPU(同级CPU)并处于空闲状态,则调度程序将任务B迁移到CPU Y,以便任务B可以在CPU Y上启动而无需在CPU X上等待任务A。

而且,如果CPU在其运行队列上没有可运行的任务了,则该CPU在进入idle之前会尝试从其他繁忙的CPU中提取额外的任务来帮助繁忙的CPU。

当然,查找可移动的任务和/或空闲的CPU会花费一些搜索成本,调度程序可能不会每次都搜索域中的所有CPU。 实际上,在某些体系结构中,事件的搜索范围限制在CPU所位于的同一套接字或节点中,而滴答中的负载平衡将全部搜索

例如,假设CPU Z距离CPU X相对较远。即使在CPU X和同级兄弟忙时CPU Z处于空闲状态,调度程序也无法将唤醒的任务B从X迁移到Z,因为它不在搜索范围之内。
结果,CPU X上的任务B需要等待任务A或等待下一个滴答的负载平衡。 对于某些特殊情况的应用程序,等待1个滴答可能时间太长。

“cpuset.sched_relax_domain_level”文件允许您请求根据需要更改此搜索范围。 该文件采用int值,该值最好按如下所示按级别指示搜索范围的大小,否则采用初始值-1表示cpuset没有请求。

-1:无要求。 使用系统默认值或遵循其他人的要求。
0:无搜索。
1:搜索同级(内核中的超线程)。
2:在包中搜索核心。
3:在节点中搜索cpus [=非NUMA系统上的系统范围]
4:在[NUMA系统上]大量节点中搜索节点
5:整个搜索系统[在NUMA系统上]

系统默认值取决于体系结构。 可以使用 relax_domain_level= boot parameter来更改系统默认值。加:set_domain_attribute()中还有是否使能idle balance。

该文件是每个cpuset的文件(Qcom全为1),会影响cpuset所属的调度域。 因此,如果禁用了cpuset的标志'cpuset.sched_load_balance',则'cpuset.sched_relax_domain_level'无效,因为没有属于该cpuset的调度域。

如果多个cpuset重叠,因此它们形成单个调度域,则使用其中的最大值。 请注意,如果一个请求为0,而其他请求为-1,则使用0。

请注意,修改此文件将有好有坏,并且是否可接受取决于您的情况。 如果不确定,请不要修改此文件。

如果您的情况是:
-由于您的特殊应用程序的行为或对CPU缓存的特殊硬件支持,可以假设每个cpu之间的迁移成本非常小(对您而言)。
-搜索成本对您没有影响,或者您可以通过管理cpuset使其紧凑来使搜索成本足够小。
-即使牺牲了高速缓存命中率等,也需要等待时间。然后增加“ sched_relax_domain_level”将使您受益。

1.9 如何使用cpusets?
--------------------------

为了最大程度地减少cpuset对诸如调度程序之类的关键内核代码的影响,并且由于内核不支持直接更新另一个任务的内存位置的一个任务这一事实,对更改其cpuset CPU的任务的影响 或“内存节点”的放置,或更改任务所连接到的cpuset的内容,都很微妙。

如果修改了一个cpuset的内存节点,那么对于附加到该cpuset的每个任务,内核在下一次尝试为该任务分配内存页时,内核将注意到该任务的cpuset中的更改,并根据其更新per-task内存位置以保留在新的cpusets内存位置内。如果任务使用内存策略 MPOL_BIND,并且绑定到的节点与其新的cpuset重叠,则任务将继续使用新cpuset中仍允许使用的 MPOL_BIND 节点子集。如果任务使用的是 MPOL_BIND,而现在在新的cpuset中不允许使用其任何 MPOL_BIND 节点,则该任务实质上将被视为已将 MPOL_BIND 绑定到新的cpuset(即使它的NUMA放置由get_mempolicy()查询,不变)。如果任务从一个cpuset移动到另一个,则内核将在下一次尝试为该任务分配内存页面时调整任务的内存放置。

如果修改了cpuset的“ cpuset.cpus”,则该cpuset中的每个任务将立即更改其允许的CPU位置。 同样,如果将任务的pid写入另一个cpuset的“tasks”文件,则其允许的CPU放置将立即更改。 如果使用 sched_setaffinity()调用将此类任务绑定到其cpuset的某个子集,则将允许该任务在其新cpuset允许的任何CPU上运行,从而消除了先前 sched_setaffinity()调用的影响。

总之,在下一次为该任务分配页面时,内核会更新其cpuset已更改的任务的内存位置,并立即更新处理器的位置。

通常,一旦分配了页面(假设一个主内存的物理页面),该页面将停留在分配的任何节点上,只要它保持分配状态即可,即使cpusets内存放置策略“cpuset.mems”随后发生了变化。 如果将cpuset标志文件'cpuset.memory_migrate'设置为true,则将任务附加到该cpuset时,该任务在其先前cpuset中的节点上分配给它的任何页面都将迁移到任务的新cpuset。 如果可能,在这些迁移操作期间将保留页面在cpuset中的相对位置。 例如,如果页面位于先前cpuset的第二个有效节点上,则该页面将被放置在新cpuset的第二个有效节点上。

同样,如果将“ cpuset.memory_migrate”设置为true,则如果修改了该cpuset的“ cpuset.mems”文件,则将移动分配给该cpuset中任务的页面,这些页面位于以前的“ cpuset.mems”设置中的节点上。 到新设置的“内存”中的节点。 不在任务的先前cpuset中或不在cpuset的先前“ cpuset.mems”设置中的页面将不会移动。

上面有一个例外。 如果使用热插拔功能来删除当前分配给一个cpuset的所有CPU,则该cpuset中的所有任务将被移到最近的具有非空cpus的祖先。但是,如果cpuset与另一个对任务附加有限制的cgroup子系统绑定,则某些(或全部)任务的移动可能会失败。 在这种情况下,这些任务将保留在原始cpuset中,并且内核将自动更新其cpus_allowed以允许所有联机CPU。 当可用的用于删除内存节点的内存热插拔功能可用时,类似的例外也将适用于此。 通常,与使所有允许的CPU或内存节点脱机的任务饿死相比,内核更喜欢违反cpuset放置。

有第二个例外。 GFP_ATOMIC请求是必须立即满足的内核内部分配。 如果GFP_ATOMIC分配失败,内核可能会丢弃某些请求,在极少数情况下甚至会出现panic情况。 如果无法在当前任务的cpuset中满足该请求,则我们释放cpuset,并在可以找到它的任何地方寻找内存。 违反cpuset比强调内核更好。

要开始一个将要被包含在cpuset中的新作业,步骤如下:

1mkdir /sys/fs/cgroup/cpuset
2mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
3)通过在/sys/fs/cgroup/cpuset虚拟文件系统中执行mkdir和write(或echo)来创建新的cpuset。
4)开始一项将成为新工作的“奠基人”的任务。
5)通过将其pid写入该cpuset的/sys/fs/cgroup/cpuset tasks文件中,将该任务附加到新的cpuset。
6)从此创始父任务fork,执行或克隆作业任务。

例如,以下命令序列将设置一个名为“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

有几种查询或修改cpuset的方法:

-通过Shell中的各种cd,mkdirechocat,rmdir命令或C中的等效命令直接通过cpuset文件系统。
-通过C库libcpuset。
-通过C库libcgroup。
(http://sourceforge.net/projects/libcg/)
-通过python应用程序cset。
(http://code.google.com/p/cpuset/)

sched_setaffinity()调用也可以使用SGI的runon或Robert Love的任务集在shell提示符下完成。 可以使用numactl命令(Andi Kleen的numa软件包的一部分)在shell提示符下完成mbind和set_mempolicy调用。

2.使用示例和语法
===========================

2.1基本用法
---------------

可以通过cpuset虚拟文件系统完成使用cpuset的创建,修改。

要安装它,请键入:

# mount -t cgroup -o cpuset cpuset /sys/fs/cgroup/cpuset

然后,在/sys/fs/cgroup/cpuset下,您可以找到与系统中cpusets的树相对应的树。
例如,/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

您还可以通过在此目录中使用mkdir在cpuset中创建cpuset。

# mkdir my_sub_cs

要删除cpuset,只需使用rmdir:

# rmdir my_sub_cs

如果正在使用cpuset(内部有cpuset,或已附加进程),则此操作将失败。

请注意,由于遗留原因,“ cpuset”文件系统作为cgroup文件系统的包装器存在。

命令

mount -t cpuset X /sys/fs/cgroup/cpuset

相当于

mount -t cgroup -ocpuset,noprefix X /sys/fs/cgroup/cpuset
echo "/sbin/cpuset_release_agent" > /sys/fs/cgroup/cpuset/release_agent

2.2 添加/删除cpus
------------------------

这是在cpuset目录中的cpus或mems文件中写入时使用的语法:

# /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添加到CPU集,请编写新的CPU列表,包括要添加的CPU。 将6添加到上述cpuset中:

# /bin/echo 1-4,6 > cpuset.cpus        -> set cpus list to cpus 1,2,3,4,6

同样,要从cpuset中删除CPU,请写入新的CPU列表,而不删除要删除的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.问题
============

问:这个'/bin/echo'是怎么回事?
答:bash的内置“ echo”命令不会检查对write()的调用是否有错误。 如果在cpuset文件系统中使用它,则将无法判断命令是成功还是失败。

问:当我一次性附加多个进程时,只有第一行才真正被附加!
答:我们每次调用write()只能返回一个错误代码。 因此,您还应该只放置一个pid。

4.联系方式
==========
网址:http://www.bullopensource.org/cpuset

原文地址:https://www.cnblogs.com/hellokitty2/p/14288835.html