요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
Memory controls
cpusets.rst:261-388Direct-reclaim pressure metric과 page-cache memory spread를 다룹니다.
Scheduler domains
cpusets.rst:389-615Load-balance domain partition과 event search relax level을 설명합니다.
Placement changes and usage
cpusets.rst:616-884CPU·memory update timing, page migration, hotplug 예외와 실제 filesystem syntax를 정리합니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. _cpusets:
=======
CPUSETS
=======
Copyright (C) 2004 BULL SA.
Written by [email protected]
- Portions Copyright (c) 2004-2006 Silicon Graphics, Inc.
- Modified by Paul Jackson <[email protected]>
- Modified by Christoph Lameter <[email protected]>
- Modified by Paul Menage <[email protected]>
- Modified by Hidetoshi Seto <[email protected]>
.. CONTENTS:
1. Cpusets
1.1 What are cpusets ?
1.2 Why are cpusets needed ?
1.3 How are cpusets implemented ?
1.4 What are exclusive cpusets ?
1.5 What is memory_pressure ?
1.6 What is memory spread ?
1.7 What is sched_load_balance ?
1.8 What is sched_relax_domain_level ?
1.9 How do I use cpusets ?
2. Usage Examples and Syntax
2.1 Basic Usage
2.2 Adding/removing cpus
2.3 Setting flags
2.4 Attaching processes
3. Questions
4. Contact
1. Cpusets
==========
1.1 What are cpusets ?
----------------------
Cpusets provide a mechanism for assigning a set of CPUs and Memory
Nodes to a set of tasks. In this document "Memory Node" refers to
an on-line node that contains memory.
Cpusets constrain the CPU and Memory placement of tasks to only
the resources within a task's current cpuset. They form a nested
hierarchy visible in a virtual file system. These are the essential
hooks, beyond what is already present, required to manage dynamic
job placement on large systems.
Cpusets use the generic cgroup subsystem described in
Documentation/admin-guide/cgroup-v1/cgroups.rst.
Requests by a task, using the sched_setaffinity(2) system call to
include CPUs in its CPU affinity mask, and using the mbind(2) and
set_mempolicy(2) system calls to include Memory Nodes in its memory
policy, are both filtered through that task's cpuset, filtering out any
CPUs or Memory Nodes not in that cpuset. The scheduler will not
schedule a task on a CPU that is not allowed in its cpus_allowed
vector, and the kernel page allocator will not allocate a page on a
node that is not allowed in the requesting task's mems_allowed vector.
User level code may create and destroy cpusets by name in the cgroup
virtual file system, manage the attributes and permissions of these
cpusets and which CPUs and Memory Nodes are assigned to each cpuset,
specify and query to which cpuset a task is assigned, and list the
task pids assigned to a cpuset.
1.2 Why are cpusets needed ?
----------------------------
The management of large computer systems, with many processors (CPUs),
complex memory cache hierarchies and multiple Memory Nodes having
non-uniform access times (NUMA) presents additional challenges for
the efficient scheduling and memory placement of processes.
Frequently more modest sized systems can be operated with adequate
efficiency just by letting the operating system automatically share
the available CPU and Memory resources amongst the requesting tasks.
But larger systems, which benefit more from careful processor and
memory placement to reduce memory access times and contention,
and which typically represent a larger investment for the customer,
can benefit from explicitly placing jobs on properly sized subsets of
the system.
This can be especially valuable on:
* Web Servers running multiple instances of the same web application,
* Servers running different applications (for instance, a web server
and a database), or
* NUMA systems running large HPC applications with demanding
performance characteristics.
These subsets, or "soft partitions" must be able to be dynamically
adjusted, as the job mix changes, without impacting other concurrently
executing jobs. The location of the running jobs pages may also be moved
when the memory locations are changed.
The kernel cpuset patch provides the minimum essential kernel
mechanisms required to efficiently implement such subsets. It
leverages existing CPU and Memory Placement facilities in the Linux
kernel to avoid any additional impact on the critical scheduler or
memory allocator code.
1.3 How are cpusets implemented ?
---------------------------------
Cpusets provide a Linux kernel mechanism to constrain which CPUs and
Memory Nodes are used by a process or set of processes.
The Linux kernel already has a pair of mechanisms to specify on which
CPUs a task may be scheduled (sched_setaffinity) and on which Memory
Nodes it may obtain memory (mbind, set_mempolicy).
Cpusets extends these two mechanisms as follows:
- Cpusets are sets of allowed CPUs and Memory Nodes, known to the
kernel.
- Each task in the system is attached to a cpuset, via a pointer
in the task structure to a reference counted cgroup structure.
- Calls to sched_setaffinity are filtered to just those CPUs
allowed in that task's cpuset.
- Calls to mbind and set_mempolicy are filtered to just
those Memory Nodes allowed in that task's cpuset.
- The root cpuset contains all the systems CPUs and Memory
Nodes.
- For any cpuset, one can define child cpusets containing a subset
of the parents CPU and Memory Node resources.
- The hierarchy of cpusets can be mounted at /dev/cpuset, for
browsing and manipulation from user space.
- A cpuset may be marked exclusive, which ensures that no other
cpuset (except direct ancestors and descendants) may contain
any overlapping CPUs or Memory Nodes.
- You can list all the tasks (by pid) attached to any cpuset.
The implementation of cpusets requires a few, simple hooks
into the rest of the kernel, none in performance critical paths:
- in init/main.c, to initialize the root cpuset at system boot.
- in fork and exit, to attach and detach a task from its cpuset.
- in sched_setaffinity, to mask the requested CPUs by what's
allowed in that task's cpuset.
- in sched.c migrate_live_tasks(), to keep migrating tasks within
the CPUs allowed by their cpuset, if possible.
- in the mbind and set_mempolicy system calls, to mask the requested
Memory Nodes by what's allowed in that task's cpuset.
- in page_alloc.c, to restrict memory to allowed nodes.
- in vmscan.c, to restrict page recovery to the current cpuset.
You should mount the "cgroup" filesystem type in order to enable
browsing and modifying the cpusets presently known to the kernel. No
new system calls are added for cpusets - all support for querying and
modifying cpusets is via this cpuset file system.
The /proc/<pid>/status file for each task has four added lines,
displaying the task's cpus_allowed (on which CPUs it may be scheduled)
and mems_allowed (on which Memory Nodes it may obtain memory),
in the two formats seen in the following example::
Cpus_allowed: ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list: 0-127
Mems_allowed: ffffffff,ffffffff
Mems_allowed_list: 0-63
Each cpuset is represented by a directory in the cgroup file system
containing (on top of the standard cgroup files) the following
files describing that cpuset:
- cpuset.cpus: list of CPUs in that cpuset
- cpuset.mems: list of Memory Nodes in that cpuset
- cpuset.memory_migrate flag: if set, move pages to cpusets nodes
- cpuset.cpu_exclusive flag: is cpu placement exclusive?
- cpuset.mem_exclusive flag: is memory placement exclusive?
- cpuset.mem_hardwall flag: is memory allocation hardwalled
- cpuset.memory_pressure: measure of how much paging pressure in cpuset
- cpuset.memory_spread_page flag: if set, spread page cache evenly on allowed nodes
- cpuset.memory_spread_slab flag: OBSOLETE. Doesn't have any function.
- cpuset.sched_load_balance flag: if set, load balance within CPUs on that cpuset
- cpuset.sched_relax_domain_level: the searching range when migrating tasks
In addition, only the root cpuset has the following file:
- cpuset.memory_pressure_enabled flag: compute memory_pressure?
New cpusets are created using the mkdir system call or shell
command. The properties of a cpuset, such as its flags, allowed
CPUs and Memory Nodes, and attached tasks, are modified by writing
to the appropriate file in that cpusets directory, as listed above.
The named hierarchical structure of nested cpusets allows partitioning
a large system into nested, dynamically changeable, "soft-partitions".
The attachment of each task, automatically inherited at fork by any
children of that task, to a cpuset allows organizing the work load
on a system into related sets of tasks such that each set is constrained
to using the CPUs and Memory Nodes of a particular cpuset. A task
may be re-attached to any other cpuset, if allowed by the permissions
on the necessary cpuset file system directories.
Such management of a system "in the large" integrates smoothly with
the detailed placement done on individual tasks and memory regions
using the sched_setaffinity, mbind and set_mempolicy system calls.
The following rules apply to each cpuset:
- Its CPUs and Memory Nodes must be a subset of its parents.
- It can't be marked exclusive unless its parent is.
- If its cpu or memory is exclusive, they may not overlap any sibling.
These rules, and the natural hierarchy of cpusets, enable efficient
enforcement of the exclusive guarantee, without having to scan all
cpusets every time any of them change to ensure nothing overlaps a
exclusive cpuset. Also, the use of a Linux virtual file system (vfs)
to represent the cpuset hierarchy provides for a familiar permission
and name space for cpusets, with a minimum of additional kernel code.
The cpus and mems files in the root (top_cpuset) cpuset are
read-only. The cpus file automatically tracks the value of
cpu_online_mask using a CPU hotplug notifier, and the mems file
automatically tracks the value of node_states[N_MEMORY]--i.e.,
nodes with memory--using the cpuset_track_online_nodes() hook.
The cpuset.effective_cpus and cpuset.effective_mems files are
normally read-only copies of cpuset.cpus and cpuset.mems files
respectively. If the cpuset cgroup filesystem is mounted with the
special "cpuset_v2_mode" option, the behavior of these files will become
similar to the corresponding files in cpuset v2. In other words, hotplug
events will not change cpuset.cpus and cpuset.mems. Those events will
only affect cpuset.effective_cpus and cpuset.effective_mems which show
the actual cpus and memory nodes that are currently used by this cpuset.
See Documentation/admin-guide/cgroup-v2.rst for more information about
cpuset v2 behavior.
1.4 What are exclusive cpusets ?
--------------------------------
If a cpuset is cpu or mem exclusive, no other cpuset, other than
a direct ancestor or descendant, may share any of the same CPUs or
Memory Nodes.
A cpuset that is cpuset.mem_exclusive *or* cpuset.mem_hardwall is "hardwalled",
i.e. it restricts kernel allocations for page, buffer and other data
commonly shared by the kernel across multiple users. All cpusets,
whether hardwalled or not, restrict allocations of memory for user
space. This enables configuring a system so that several independent
jobs can share common kernel data, such as file system pages, while
isolating each job's user allocation in its own cpuset. To do this,
construct a large mem_exclusive cpuset to hold all the jobs, and
construct child, non-mem_exclusive cpusets for each individual job.
Only a small amount of typical kernel memory, such as requests from
interrupt handlers, is allowed to be taken outside even a
mem_exclusive cpuset.
1.5 What is memory_pressure ?
-----------------------------
The memory_pressure of a cpuset provides a simple per-cpuset metric
of the rate that the tasks in a cpuset are attempting to free up in
use memory on the nodes of the cpuset to satisfy additional memory
requests.
This enables batch managers monitoring jobs running in dedicated
cpusets to efficiently detect what level of memory pressure that job
is causing.
This is useful both on tightly managed systems running a wide mix of
submitted jobs, which may choose to terminate or re-prioritize jobs that
are trying to use more memory than allowed on the nodes assigned to them,
and with tightly coupled, long running, massively parallel scientific
computing jobs that will dramatically fail to meet required performance
goals if they start to use more memory than allowed to them.
This mechanism provides a very economical way for the batch manager
to monitor a cpuset for signs of memory pressure. It's up to the
batch manager or other user code to decide what to do about it and
take action.
==>
Unless this feature is enabled by writing "1" to the special file
/dev/cpuset/memory_pressure_enabled, the hook in the rebalance
code of __alloc_pages() for this metric reduces to simply noticing
that the cpuset_memory_pressure_enabled flag is zero. So only
systems that enable this feature will compute the metric.
Why a per-cpuset, running average:
Because this meter is per-cpuset, rather than per-task or mm,
the system load imposed by a batch scheduler monitoring this
metric is sharply reduced on large systems, because a scan of
the tasklist can be avoided on each set of queries.
Because this meter is a running average, instead of an accumulating
counter, a batch scheduler can detect memory pressure with a
single read, instead of having to read and accumulate results
for a period of time.
Because this meter is per-cpuset rather than per-task or mm,
the batch scheduler can obtain the key information, memory
pressure in a cpuset, with a single read, rather than having to
query and accumulate results over all the (dynamically changing)
set of tasks in the cpuset.
A per-cpuset simple digital filter (requires a spinlock and 3 words
of data per-cpuset) is kept, and updated by any task attached to that
cpuset, if it enters the synchronous (direct) page reclaim code.
A per-cpuset file provides an integer number representing the recent
(half-life of 10 seconds) rate of direct page reclaims caused by
the tasks in the cpuset, in units of reclaims attempted per second,
times 1000.
1.6 What is memory spread ?
---------------------------
There are two boolean flag files per cpuset that control where the
kernel allocates pages for the file system buffers and related in
kernel data structures. They are called 'cpuset.memory_spread_page' and
'cpuset.memory_spread_slab'.
If the per-cpuset boolean flag file 'cpuset.memory_spread_page' is set, then
the kernel will spread the file system buffers (page cache) evenly
over all the nodes that the faulting task is allowed to use, instead
of preferring to put those pages on the node where the task is running.
If the per-cpuset boolean flag file 'cpuset.memory_spread_slab' is set,
then the kernel will spread some file system related slab caches,
such as for inodes and dentries evenly over all the nodes that the
faulting task is allowed to use, instead of preferring to put those
pages on the node where the task is running.
The setting of these flags does not affect anonymous data segment or
stack segment pages of a task.
By default, both kinds of memory spreading are off, and memory
pages are allocated on the node local to where the task is running,
except perhaps as modified by the task's NUMA mempolicy or cpuset
configuration, so long as sufficient free memory pages are available.
When new cpusets are created, they inherit the memory spread settings
of their parent.
Setting memory spreading causes allocations for the affected page
or slab caches to ignore the task's NUMA mempolicy and be spread
instead. Tasks using mbind() or set_mempolicy() calls to set NUMA
mempolicies will not notice any change in these calls as a result of
their containing task's memory spread settings. If memory spreading
is turned off, then the currently specified NUMA mempolicy once again
applies to memory page allocations.
Both 'cpuset.memory_spread_page' and 'cpuset.memory_spread_slab' are boolean flag
files. By default they contain "0", meaning that the feature is off
for that cpuset. If a "1" is written to that file, then that turns
the named feature on.
The implementation is simple.
Setting the flag 'cpuset.memory_spread_page' turns on a per-process flag
PFA_SPREAD_PAGE for each task that is in that cpuset or subsequently
joins that cpuset. The page allocation calls for the page cache
is modified to perform an inline check for this PFA_SPREAD_PAGE task
flag, and if set, a call to a new routine cpuset_mem_spread_node()
returns the node to prefer for the allocation.
Similarly, setting 'cpuset.memory_spread_slab' turns on the flag
PFA_SPREAD_SLAB, and appropriately marked slab caches will allocate
pages from the node returned by cpuset_mem_spread_node().
The cpuset_mem_spread_node() routine is also simple. It uses the
value of a per-task rotor cpuset_mem_spread_rotor to select the next
node in the current task's mems_allowed to prefer for the allocation.
This memory placement policy is also known (in other contexts) as
round-robin or interleave.
This policy can provide substantial improvements for jobs that need
to place thread local data on the corresponding node, but that need
to access large file system data sets that need to be spread across
the several nodes in the jobs cpuset in order to fit. Without this
policy, especially for jobs that might have one thread reading in the
data set, the memory allocation across the nodes in the jobs cpuset
can become very uneven.
1.7 What is sched_load_balance ?
--------------------------------
The kernel scheduler (kernel/sched/core.c) automatically load balances
tasks. If one CPU is underutilized, kernel code running on that
CPU will look for tasks on other more overloaded CPUs and move those
tasks to itself, within the constraints of such placement mechanisms
as cpusets and sched_setaffinity.
The algorithmic cost of load balancing and its impact on key shared
kernel data structures such as the task list increases more than
linearly with the number of CPUs being balanced. So the scheduler
has support to partition the systems CPUs into a number of sched
domains such that it only load balances within each sched domain.
Each sched domain covers some subset of the CPUs in the system;
no two sched domains overlap; some CPUs might not be in any sched
domain and hence won't be load balanced.
Put simply, it costs less to balance between two smaller sched domains
than one big one, but doing so means that overloads in one of the
two domains won't be load balanced to the other one.
By default, there is one sched domain covering all CPUs, including those
marked isolated using the kernel boot time "isolcpus=" argument. However,
the isolated CPUs will not participate in load balancing, and will not
have tasks running on them unless explicitly assigned.
This default load balancing across all CPUs is not well suited for
the following two situations:
1) On large systems, load balancing across many CPUs is expensive.
If the system is managed using cpusets to place independent jobs
on separate sets of CPUs, full load balancing is unnecessary.
2) Systems supporting realtime on some CPUs need to minimize
system overhead on those CPUs, including avoiding task load
balancing if that is not needed.
When the per-cpuset flag "cpuset.sched_load_balance" is enabled (the default
setting), it requests that all the CPUs in that cpusets allowed 'cpuset.cpus'
be contained in a single sched domain, ensuring that load balancing
can move a task (not otherwised pinned, as by sched_setaffinity)
from any CPU in that cpuset to any other.
When the per-cpuset flag "cpuset.sched_load_balance" is disabled, then the
scheduler will avoid load balancing across the CPUs in that cpuset,
--except-- in so far as is necessary because some overlapping cpuset
has "sched_load_balance" enabled.
So, for example, if the top cpuset has the flag "cpuset.sched_load_balance"
enabled, then the scheduler will have one sched domain covering all
CPUs, and the setting of the "cpuset.sched_load_balance" flag in any other
cpusets won't matter, as we're already fully load balancing.
Therefore in the above two situations, the top cpuset flag
"cpuset.sched_load_balance" should be disabled, and only some of the smaller,
child cpusets have this flag enabled.
When doing this, you don't usually want to leave any unpinned tasks in
the top cpuset that might use non-trivial amounts of CPU, as such tasks
may be artificially constrained to some subset of CPUs, depending on
the particulars of this flag setting in descendant cpusets. Even if
such a task could use spare CPU cycles in some other CPUs, the kernel
scheduler might not consider the possibility of load balancing that
task to that underused CPU.
Of course, tasks pinned to a particular CPU can be left in a cpuset
that disables "cpuset.sched_load_balance" as those tasks aren't going anywhere
else anyway.
There is an impedance mismatch here, between cpusets and sched domains.
Cpusets are hierarchical and nest. Sched domains are flat; they don't
overlap and each CPU is in at most one sched domain.
It is necessary for sched domains to be flat because load balancing
across partially overlapping sets of CPUs would risk unstable dynamics
that would be beyond our understanding. So if each of two partially
overlapping cpusets enables the flag 'cpuset.sched_load_balance', then we
form a single sched domain that is a superset of both. We won't move
a task to a CPU outside its cpuset, but the scheduler load balancing
code might waste some compute cycles considering that possibility.
This mismatch is why there is not a simple one-to-one relation
between which cpusets have the flag "cpuset.sched_load_balance" enabled,
and the sched domain configuration. If a cpuset enables the flag, it
will get balancing across all its CPUs, but if it disables the flag,
it will only be assured of no load balancing if no other overlapping
cpuset enables the flag.
If two cpusets have partially overlapping 'cpuset.cpus' allowed, and only
one of them has this flag enabled, then the other may find its
tasks only partially load balanced, just on the overlapping CPUs.
This is just the general case of the top_cpuset example given a few
paragraphs above. In the general case, as in the top cpuset case,
don't leave tasks that might use non-trivial amounts of CPU in
such partially load balanced cpusets, as they may be artificially
constrained to some subset of the CPUs allowed to them, for lack of
load balancing to the other CPUs.
CPUs in "cpuset.isolcpus" were excluded from load balancing by the
isolcpus= kernel boot option, and will never be load balanced regardless
of the value of "cpuset.sched_load_balance" in any cpuset.
1.7.1 sched_load_balance implementation details.
------------------------------------------------
The per-cpuset flag 'cpuset.sched_load_balance' defaults to enabled (contrary
to most cpuset flags.) When enabled for a cpuset, the kernel will
ensure that it can load balance across all the CPUs in that cpuset
(makes sure that all the CPUs in the cpus_allowed of that cpuset are
in the same sched domain.)
If two overlapping cpusets both have 'cpuset.sched_load_balance' enabled,
then they will be (must be) both in the same sched domain.
If, as is the default, the top cpuset has 'cpuset.sched_load_balance' enabled,
then by the above that means there is a single sched domain covering
the whole system, regardless of any other cpuset settings.
The kernel commits to user space that it will avoid load balancing
where it can. It will pick as fine a granularity partition of sched
domains as it can while still providing load balancing for any set
of CPUs allowed to a cpuset having 'cpuset.sched_load_balance' enabled.
The internal kernel cpuset to scheduler interface passes from the
cpuset code to the scheduler code a partition of the load balanced
CPUs in the system. This partition is a set of subsets (represented
as an array of struct cpumask) of CPUs, pairwise disjoint, that cover
all the CPUs that must be load balanced.
The cpuset code builds a new such partition and passes it to the
scheduler sched domain setup code, to have the sched domains rebuilt
as necessary, whenever:
- the 'cpuset.sched_load_balance' flag of a cpuset with non-empty CPUs changes,
- or CPUs come or go from a cpuset with this flag enabled,
- or 'cpuset.sched_relax_domain_level' value of a cpuset with non-empty CPUs
and with this flag enabled changes,
- or a cpuset with non-empty CPUs and with this flag enabled is removed,
- or a cpu is offlined/onlined.
This partition exactly defines what sched domains the scheduler should
setup - one sched domain for each element (struct cpumask) in the
partition.
The scheduler remembers the currently active sched domain partitions.
When the scheduler routine partition_sched_domains() is invoked from
the cpuset code to update these sched domains, it compares the new
partition requested with the current, and updates its sched domains,
removing the old and adding the new, for each change.
1.8 What is sched_relax_domain_level ?
--------------------------------------
In sched domain, the scheduler migrates tasks in 2 ways; periodic load
balance on tick, and at time of some schedule events.
When a task is woken up, scheduler try to move the task on idle CPU.
For example, if a task A running on CPU X activates another task B
on the same CPU X, and if CPU Y is X's sibling and performing idle,
then scheduler migrate task B to CPU Y so that task B can start on
CPU Y without waiting task A on CPU X.
And if a CPU run out of tasks in its runqueue, the CPU try to pull
extra tasks from other busy CPUs to help them before it is going to
be idle.
Of course it takes some searching cost to find movable tasks and/or
idle CPUs, the scheduler might not search all CPUs in the domain
every time. In fact, in some architectures, the searching ranges on
events are limited in the same socket or node where the CPU locates,
while the load balance on tick searches all.
For example, assume CPU Z is relatively far from CPU X. Even if CPU Z
is idle while CPU X and the siblings are busy, scheduler can't migrate
woken task B from X to Z since it is out of its searching range.
As the result, task B on CPU X need to wait task A or wait load balance
on the next tick. For some applications in special situation, waiting
1 tick may be too long.
The 'cpuset.sched_relax_domain_level' file allows you to request changing
this searching range as you like. This file takes int value which
indicates size of searching range in levels approximately as follows,
otherwise initial value -1 that indicates the cpuset has no request.
====== ===========================================================
-1 no request. use system default or follow request of others.
0 no search.
1 search siblings (hyperthreads in a core).
2 search cores in a package.
3 search cpus in a node [= system wide on non-NUMA system]
4 search nodes in a chunk of node [on NUMA system]
5 search system wide [on NUMA system]
====== ===========================================================
Not all levels can be present and values can change depending on the
system architecture and kernel configuration. Check
/sys/kernel/debug/sched/domains/cpu*/domain*/ for system-specific
details.
The system default is architecture dependent. The system default
can be changed using the relax_domain_level= boot parameter.
This file is per-cpuset and affect the sched domain where the cpuset
belongs to. Therefore if the flag 'cpuset.sched_load_balance' of a cpuset
is disabled, then 'cpuset.sched_relax_domain_level' have no effect since
there is no sched domain belonging the cpuset.
If multiple cpusets are overlapping and hence they form a single sched
domain, the largest value among those is used. Be careful, if one
requests 0 and others are -1 then 0 is used.
Note that modifying this file will have both good and bad effects,
and whether it is acceptable or not depends on your situation.
Don't modify this file if you are not sure.
If your situation is:
- The migration costs between each cpu can be assumed considerably
small(for you) due to your special application's behavior or
special hardware support for CPU cache etc.
- The searching cost doesn't have impact(for you) or you can make
the searching cost enough small by managing cpuset to compact etc.
- The latency is required even it sacrifices cache hit rate etc.
then increasing 'sched_relax_domain_level' would benefit you.
1.9 How do I use cpusets ?
--------------------------
In order to minimize the impact of cpusets on critical kernel
code, such as the scheduler, and due to the fact that the kernel
does not support one task updating the memory placement of another
task directly, the impact on a task of changing its cpuset CPU
or Memory Node placement, or of changing to which cpuset a task
is attached, is subtle.
If a cpuset has its Memory Nodes modified, then for each task attached
to that cpuset, the next time that the kernel attempts to allocate
a page of memory for that task, the kernel will notice the change
in the task's cpuset, and update its per-task memory placement to
remain within the new cpusets memory placement. If the task was using
mempolicy MPOL_BIND, and the nodes to which it was bound overlap with
its new cpuset, then the task will continue to use whatever subset
of MPOL_BIND nodes are still allowed in the new cpuset. If the task
was using MPOL_BIND and now none of its MPOL_BIND nodes are allowed
in the new cpuset, then the task will be essentially treated as if it
was MPOL_BIND bound to the new cpuset (even though its NUMA placement,
as queried by get_mempolicy(), doesn't change). If a task is moved
from one cpuset to another, then the kernel will adjust the task's
memory placement, as above, the next time that the kernel attempts
to allocate a page of memory for that task.
If a cpuset has its 'cpuset.cpus' modified, then each task in that cpuset
will have its allowed CPU placement changed immediately. Similarly,
if a task's pid is written to another cpuset's 'tasks' file, then its
allowed CPU placement is changed immediately. If such a task had been
bound to some subset of its cpuset using the sched_setaffinity() call,
the task will be allowed to run on any CPU allowed in its new cpuset,
negating the effect of the prior sched_setaffinity() call.
In summary, the memory placement of a task whose cpuset is changed is
updated by the kernel, on the next allocation of a page for that task,
and the processor placement is updated immediately.
Normally, once a page is allocated (given a physical page
of main memory) then that page stays on whatever node it
was allocated, so long as it remains allocated, even if the
cpusets memory placement policy 'cpuset.mems' subsequently changes.
If the cpuset flag file 'cpuset.memory_migrate' is set true, then when
tasks are attached to that cpuset, any pages that task had
allocated to it on nodes in its previous cpuset are migrated
to the task's new cpuset. The relative placement of the page within
the cpuset is preserved during these migration operations if possible.
For example if the page was on the second valid node of the prior cpuset
then the page will be placed on the second valid node of the new cpuset.
Also if 'cpuset.memory_migrate' is set true, then if that cpuset's
'cpuset.mems' file is modified, pages allocated to tasks in that
cpuset, that were on nodes in the previous setting of 'cpuset.mems',
will be moved to nodes in the new setting of 'mems.'
Pages that were not in the task's prior cpuset, or in the cpuset's
prior 'cpuset.mems' setting, will not be moved.
There is an exception to the above. If hotplug functionality is used
to remove all the CPUs that are currently assigned to a cpuset,
then all the tasks in that cpuset will be moved to the nearest ancestor
with non-empty cpus. But the moving of some (or all) tasks might fail if
cpuset is bound with another cgroup subsystem which has some restrictions
on task attaching. In this failing case, those tasks will stay
in the original cpuset, and the kernel will automatically update
their cpus_allowed to allow all online CPUs. When memory hotplug
functionality for removing Memory Nodes is available, a similar exception
is expected to apply there as well. In general, the kernel prefers to
violate cpuset placement, over starving a task that has had all
its allowed CPUs or Memory Nodes taken offline.
There is a second exception to the above. GFP_ATOMIC requests are
kernel internal allocations that must be satisfied, immediately.
The kernel may drop some request, in rare cases even panic, if a
GFP_ATOMIC alloc fails. If the request cannot be satisfied within
the current task's cpuset, then we relax the cpuset, and look for
memory anywhere we can find it. It's better to violate the cpuset
than stress the kernel.
To start a new job that is to be contained within a cpuset, the steps are:
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.
For example, the following sequence of commands will setup a cpuset
named "Charlie", containing just CPUs 2 and 3, and Memory Node 1,
and then start a subshell 'sh' in that cpuset::
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
There are ways to query or modify cpusets:
- via the cpuset file system directly, using the various cd, mkdir, echo,
cat, rmdir commands from the shell, or their equivalent from C.
- via the C library libcpuset.
- via the C library libcgroup.
(https://github.com/libcgroup/libcgroup/)
- via the python application cset.
(http://code.google.com/p/cpuset/)
The sched_setaffinity calls can also be done at the shell prompt using
SGI's runon or Robert Love's taskset. The mbind and set_mempolicy
calls can be done at the shell prompt using the numactl command
(part of Andi Kleen's numa package).
2. Usage Examples and Syntax
============================
2.1 Basic Usage
---------------
Creating, modifying, using the cpusets can be done through the cpuset
virtual filesystem.
To mount it, type:
# mount -t cgroup -o cpuset cpuset /sys/fs/cgroup/cpuset
Then under /sys/fs/cgroup/cpuset you can find a tree that corresponds to the
tree of the cpusets in the system. For instance, /sys/fs/cgroup/cpuset
is the cpuset that holds the whole system.
If you want to create a new cpuset under /sys/fs/cgroup/cpuset::
# cd /sys/fs/cgroup/cpuset
# mkdir my_cpuset
Now you want to do something with this cpuset::
# cd my_cpuset
In this directory you can find several files::
# 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
Reading them will give you information about the state of this cpuset:
the CPUs and Memory Nodes it can use, the processes that are using
it, its properties. By writing to these files you can manipulate
the cpuset.
Set some flags::
# /bin/echo 1 > cpuset.cpu_exclusive
Add some cpus::
# /bin/echo 0-7 > cpuset.cpus
Add some mems::
# /bin/echo 0-7 > cpuset.mems
Now attach your shell to this cpuset::
# /bin/echo $$ > tasks
You can also create cpusets inside your cpuset by using mkdir in this
directory::
# mkdir my_sub_cs
To remove a cpuset, just use rmdir::
# rmdir my_sub_cs
This will fail if the cpuset is in use (has cpusets inside, or has
processes attached).
Note that for legacy reasons, the "cpuset" filesystem exists as a
wrapper around the cgroup filesystem.
The command::
mount -t cpuset X /sys/fs/cgroup/cpuset
is equivalent to::
mount -t cgroup -ocpuset,noprefix X /sys/fs/cgroup/cpuset
echo "/sbin/cpuset_release_agent" > /sys/fs/cgroup/cpuset/release_agent
2.2 Adding/removing cpus
------------------------
This is the syntax to use when writing in the cpus or mems files
in cpuset directories::
# /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
To add a CPU to a cpuset, write the new list of CPUs including the
CPU to be added. To add 6 to the above cpuset::
# /bin/echo 1-4,6 > cpuset.cpus -> set cpus list to cpus 1,2,3,4,6
Similarly to remove a CPU from a cpuset, write the new list of CPUs
without the CPU to be removed.
To remove all the CPUs::
# /bin/echo "" > cpuset.cpus -> clear cpus list
2.3 Setting flags
-----------------
The syntax is very simple::
# /bin/echo 1 > cpuset.cpu_exclusive -> set flag 'cpuset.cpu_exclusive'
# /bin/echo 0 > cpuset.cpu_exclusive -> unset flag 'cpuset.cpu_exclusive'
2.4 Attaching processes
-----------------------
::
# /bin/echo PID > tasks
Note that it is PID, not PIDs. You can only attach ONE task at a time.
If you have several tasks to attach, you have to do it one after another::
# /bin/echo PID1 > tasks
# /bin/echo PID2 > tasks
...
# /bin/echo PIDn > tasks
3. Questions
============
Q:
what's up with this '/bin/echo' ?
A:
bash's builtin 'echo' command does not check calls to write() against
errors. If you use it in the cpuset file system, you won't be
able to tell whether a command succeeded or failed.
Q:
When I attach processes, only the first of the line gets really attached !
A:
We can only return one error code per call to write(). So you should also
put only ONE pid.
4. Contact
==========
Web: http://www.bullopensource.org/cpuset
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
문서 범위와 구성
1-39이 문서는 BULL SA와 Silicon Graphics의 copyright 아래 `[email protected]`가 작성했으며 Paul Jackson `<[email protected]>`, Christoph Lameter `<[email protected]>`, Paul Menage `<[email protected]>`, Hidetoshi Seto `<[email protected]>`가 수정했습니다.
본문은 cpuset의 정의·필요성·구현, exclusive·memory pressure·memory spread, scheduler load balancing과 relax level, 실제 사용법, syntax 예제와 FAQ를 차례로 설명합니다.
원문의 네 부분과 핵심 주제입니다.
Cpuset 정의와 placement filtering
40-71Cpuset은 task 집합에 CPU 집합과 Memory Node 집합을 할당하는 mechanism입니다. 여기서 Memory Node는 memory를 포함하는 online node를 뜻합니다. 각 task의 CPU·memory placement는 현재 cpuset 안의 resource로 제한되며, cpuset은 virtual filesystem에 보이는 nested hierarchy를 이룹니다.
이 mechanism은 대형 system의 dynamic job placement를 관리하는 데 필요한 최소 hook을 제공합니다. Generic cgroup subsystem 자체는 `Documentation/admin-guide/cgroup-v1/cgroups.rst`에서 설명합니다.
User request와 kernel enforcement가 만나는 지점입니다.
Task가 요청한 mask는 cpuset mask와 교집합을 취합니다.
User-space는 cgroup virtual filesystem에서 cpuset을 name으로 생성·삭제하고 permission과 attribute, CPU·Memory Node assignment를 관리합니다. 또한 task가 속한 cpuset을 지정·조회하고 cpuset별 PID를 열거할 수 있습니다.
대형 NUMA system에서 cpuset이 필요한 이유
72-109CPU가 많고 cache hierarchy가 복잡하며 access time이 균일하지 않은 여러 Memory Node를 가진 NUMA system에서는 process scheduling과 memory placement가 더 어렵습니다. 작은 system은 OS가 자동으로 CPU·memory를 공유해도 충분하지만, 큰 system은 memory access time과 contention을 줄이도록 job을 적절한 system subset에 명시적으로 배치하는 편이 유리합니다.
명시적 soft partition이 특히 유용한 workload입니다.
이 subset, 즉 soft partition은 job mix가 바뀔 때 concurrent job에 영향을 주지 않고 dynamic하게 조정할 수 있어야 합니다. Memory location이 바뀌면 실행 중인 job의 page 위치도 옮길 수 있어야 합니다.
Cpuset은 existing Linux placement facility 위에서 job resource를 조정합니다.
Kernel cpuset patch는 이런 subset을 효율적으로 구현하는 데 필요한 최소 mechanism만 제공합니다. 기존 CPU·memory placement facility를 재사용하므로 scheduler와 memory allocator의 critical path에 추가 영향을 피합니다.
Kernel 구현, filesystem 파일과 계층 규칙
110-239Linux에는 이미 `sched_setaffinity`로 CPU를, `mbind`와 `set_mempolicy`로 Memory Node를 정하는 mechanism이 있습니다. Cpuset은 kernel이 아는 allowed CPU·node set을 만들고, 모든 task를 reference-counted cgroup structure를 가리키는 task-structure pointer로 한 cpuset에 연결합니다.
Performance critical path를 피하면서 placement를 강제하는 주요 hook입니다.
Root cpuset은 system의 모든 CPU와 Memory Node를 포함합니다. Child는 parent resource의 subset만 가질 수 있고 `/dev/cpuset`에 hierarchy를 mount해 user-space에서 조작할 수 있습니다. `cpu_exclusive` 또는 `mem_exclusive`를 설정하면 direct ancestor·descendant를 제외한 다른 cpuset과 해당 resource가 겹치지 않도록 보장합니다.
Cpuset을 위해 새 system call은 추가되지 않습니다. `cgroup` filesystem을 mount하고 directory와 file을 읽고 쓰는 방식으로 kernel에 알려진 cpuset을 조회·수정합니다.
Cpus_allowed: ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list: 0-127
Mems_allowed: ffffffff,ffffffff
Mems_allowed_list: 0-63
각 task의 `/proc/<pid>/status`에는 `Cpus_allowed`, `Cpus_allowed_list`, `Mems_allowed`, `Mems_allowed_list`가 추가되어 허용된 CPU와 Memory Node를 bitmap과 list 형식으로 보여 줍니다.
Standard cgroup file 외에 cpuset controller가 제공하는 핵심 interface입니다.
`mkdir`로 새 cpuset을 만들고 directory 안의 file에 write해 flag, allowed CPU·node와 attached task를 바꿉니다. Child는 fork 때 parent task의 cpuset attachment를 자동 상속하며 permission이 허용하면 다른 cpuset으로 re-attach할 수 있습니다.
각 cpuset의 CPU와 Memory Node는 parent의 subset이어야 하고, parent가 exclusive가 아니면 child도 exclusive가 될 수 없습니다. CPU 또는 memory가 exclusive이면 sibling과 겹칠 수 없습니다. Hierarchy 덕분에 모든 cpuset을 매번 scan하지 않고 이 보장을 효율적으로 검사합니다.
Top cpuset과 child resource, hotplug 관측의 관계입니다.
Root `cpuset.cpus`와 `cpuset.mems`는 read-only입니다. CPU hotplug notifier가 `cpu_online_mask`를, `cpuset_track_online_nodes()` hook이 memory가 있는 `node_states[N_MEMORY]`를 자동 추적합니다. 보통 `cpuset.effective_cpus`와 `cpuset.effective_mems`는 configured file의 read-only copy입니다.
`cpuset_v2_mode` mount option을 쓰면 hotplug가 `cpuset.cpus`와 `cpuset.mems`를 바꾸지 않고 effective file만 현재 실제 resource를 반영합니다. 자세한 v2 동작은 `Documentation/admin-guide/cgroup-v2.rst`를 참고합니다.
Exclusive cpuset과 hardwall
240-260CPU 또는 memory exclusive cpuset은 direct ancestor·descendant를 제외한 다른 cpuset과 같은 CPU나 Memory Node를 공유할 수 없습니다.
`cpuset.mem_exclusive` 또는 `cpuset.mem_hardwall`이 설정된 cpuset은 hardwalled입니다. User-space memory는 모든 cpuset에서 제한하지만, hardwall은 page·buffer와 여러 user가 공유하는 kernel data allocation까지 제한합니다.
일반 cpuset과 hardwalled cpuset의 allocation 차이입니다.
여러 job이 filesystem page를 공유하면서 user allocation을 격리하는 구성입니다.
memory_pressure metric
261-318Cpuset의 `memory_pressure`는 그 cpuset task가 추가 memory request를 만족하려고 allowed node의 사용 중 memory를 free하려 시도하는 rate를 나타내는 간단한 per-cpuset metric입니다. Batch manager는 dedicated cpuset에서 job이 일으키는 memory pressure를 효율적으로 감지할 수 있습니다.
허용된 memory를 넘겨 쓰려는 job을 종료·reprioritize해야 하는 managed system과, paging이 시작되면 performance goal을 크게 놓치는 tightly coupled long-running scientific workload 모두에 유용합니다. Metric은 pressure를 알려 줄 뿐이며 실제 대응은 batch manager나 user code가 결정합니다.
Feature를 켠 system에서만 direct reclaim hook이 metric을 갱신합니다.
이 meter는 per-task나 per-mm가 아니라 per-cpuset이므로 query마다 tasklist를 scan할 필요가 없습니다. 누적 counter가 아니라 running average이므로 scheduler는 시간 간격을 두고 여러 값을 읽어 직접 누적하지 않고도 한 번의 read로 최근 pressure를 판단할 수 있습니다.
작은 비용으로 최근 direct reclaim rate를 표현합니다.
Page cache와 slab memory spread
319-388`cpuset.memory_spread_page`와 `cpuset.memory_spread_slab`은 filesystem buffer와 관련 kernel data page를 어디에 allocate할지 제어하는 boolean flag입니다. Page flag는 page cache를 task가 사용할 수 있는 모든 node에 고르게 분산합니다. Slab flag는 inode·dentry 같은 일부 filesystem-related slab cache를 분산하지만 현재 interface 목록에서는 obsolete이며 기능이 없습니다.
영향을 받는 allocation과 받지 않는 allocation입니다.
기본적으로 spread는 꺼져 있고 free memory가 충분하면 task가 실행 중인 local node에서 page를 allocate합니다. Task NUMA mempolicy 또는 cpuset configuration이 이를 제한할 수 있습니다. Spread를 켜면 대상 page/slab cache는 task의 NUMA mempolicy를 무시하고 분산되며, 끄면 현재 NUMA mempolicy가 다시 적용됩니다. `mbind()`나 `set_mempolicy()` 호출 자체의 보이는 결과는 바뀌지 않습니다.
`cpuset.memory_spread_page`를 켜면 cpuset에 있거나 이후 들어오는 각 task에 `PFA_SPREAD_PAGE`가 설정됩니다. Page-cache allocation이 이 flag를 확인하고 `cpuset_mem_spread_node()`가 preferred node를 반환합니다. Historical slab spread는 `PFA_SPREAD_SLAB`과 같은 routine을 사용합니다.
`cpuset_mem_spread_rotor`가 allowed node를 순환 선택합니다.
이 정책은 round-robin 또는 interleave로도 불립니다. Thread-local data는 해당 node에 두되 한 thread가 읽는 대형 filesystem dataset을 job cpuset의 여러 node에 고르게 펼쳐야 할 때 allocation 쏠림을 줄여 큰 성능 향상을 낼 수 있습니다.
sched_load_balance와 flat sched domain
389-490Kernel scheduler(`kernel/sched/core.c`)는 underutilized CPU가 더 바쁜 CPU에서 movable task를 가져오는 방식으로 자동 load balancing합니다. 하지만 balancing 비용과 task list 같은 shared kernel structure에 미치는 영향은 CPU 수보다 더 빠르게 증가합니다.
Scheduler는 CPU를 겹치지 않는 flat sched domain으로 partition해 domain 안에서만 balance할 수 있습니다. 작은 domain 둘은 큰 domain 하나보다 싸지만 한 domain의 overload를 다른 domain으로 옮기지 못합니다. 어떤 CPU는 domain에 속하지 않아 balance되지 않을 수도 있습니다.
대형 system과 realtime CPU에서 전체-system balancing을 피하는 이유입니다.
기본적으로 `isolcpus=`로 표시한 CPU까지 포함하는 하나의 sched domain이 있지만 isolated CPU는 load balancing에 참여하지 않고 명시적으로 assign하지 않으면 task가 실행되지 않습니다.
Per-cpuset `cpuset.sched_load_balance`가 켜지면 해당 `cpuset.cpus`의 모든 CPU를 한 sched domain 안에 두도록 요청합니다. `sched_setaffinity`로 별도 pin되지 않은 task는 그 cpuset CPU 사이에서 이동할 수 있습니다. Flag를 끄면 overlapping cpuset이 balancing을 요구하는 데 필요한 범위를 제외하고 그 CPU 사이의 balance를 피합니다.
Cpuset hierarchy와 flat sched domain 사이의 impedance mismatch입니다.
따라서 independent job이나 realtime CPU 구성을 만들 때는 top cpuset의 `cpuset.sched_load_balance`를 끄고 필요한 smaller child에서만 켜는 것이 일반적입니다. Top 또는 partially balanced cpuset에 상당한 CPU를 쓰는 unpinned task를 남기면 spare CPU가 있어도 scheduler가 그 이동을 고려하지 않아 일부 CPU subset에 갇힐 수 있습니다.
Cpuset은 hierarchical하고 겹칠 수 있지만 sched domain은 flat하고 서로 겹치지 않습니다. Partially overlapping balancing cpuset은 모두 포함하는 하나의 superset domain으로 합칩니다. Scheduler가 cpuset 밖 CPU로 task를 실제 이동시키지는 않지만 그 가능성을 검토하는 계산은 낭비될 수 있습니다.
Flag를 껐다고 항상 balancing이 사라지는 것은 아닙니다.
Sched-domain partition 재구성
491-539`cpuset.sched_load_balance`는 대부분의 cpuset flag와 달리 기본값이 enabled입니다. Enabled cpuset의 `cpus_allowed` 전체가 같은 sched domain에 들어가도록 보장하며, 서로 겹치는 enabled cpuset은 같은 domain에 있어야 합니다.
Kernel은 balancing requirement를 만족하면서 가능한 한 fine-grained한 sched-domain partition을 선택해 불필요한 balancing을 피하겠다고 user-space에 약속합니다. Cpuset code는 load-balanced CPU partition을 pairwise-disjoint `struct cpumask` array로 scheduler에 전달합니다.
다음 변화가 생기면 cpuset code가 새 partition을 계산합니다.
각 disjoint cpumask가 scheduler domain 하나를 정확히 정의합니다.
이 partition은 scheduler가 만들 domain을 정확히 정의하며 element 하나가 domain 하나에 대응합니다. Scheduler는 현재 active partition을 기억하고 `partition_sched_domains()` 호출 때 새 요청과 비교해 달라진 domain만 제거·추가합니다.
sched_relax_domain_level
540-615Sched domain에서 task migration은 tick의 periodic load balance와 wakeup 같은 scheduling event에서 일어납니다. CPU X의 task A가 task B를 깨우고 sibling CPU Y가 idle이면 scheduler는 B를 Y로 옮겨 A를 기다리지 않게 할 수 있습니다. Runqueue가 빈 CPU도 idle에 들어가기 전에 busy CPU에서 task를 가져오려 합니다.
Movable task나 idle CPU를 찾는 데 비용이 있으므로 event마다 domain 전체를 검색하지 않을 수 있습니다. Architecture에 따라 event search는 socket이나 node 안으로 제한되고 tick balance만 전체를 볼 수 있습니다. 먼 CPU Z가 idle이어도 wakeup search 범위 밖이면 B는 A 또는 다음 tick을 기다려야 합니다.
Search range의 대략적 수준이며 실제 topology는 architecture와 configuration에 따라 다릅니다.
System-specific level은 `/sys/kernel/debug/sched/domains/cpu*/domain*/`에서 확인합니다. Default는 architecture-dependent이며 `relax_domain_level=` boot parameter로 바꿀 수 있습니다.
Cpuset별 요청은 실제로 속한 sched domain 단위로 결합됩니다.
Overlapping cpuset이 한 domain을 만들면 요청 중 가장 큰 값을 사용합니다. 한 곳이 0이고 다른 곳이 -1이면 0이 선택됩니다. Search 범위를 키우면 latency는 줄일 수 있지만 cache hit rate와 search cost를 희생할 수 있으므로 확신이 없으면 수정하지 않아야 합니다.
CPU 간 migration cost가 application이나 hardware 특성상 작고, compact cpuset 관리 등으로 search cost를 감당할 수 있으며, cache hit보다 latency가 중요할 때 level을 높이는 것이 이득일 수 있습니다.
Cpuset 변경 시 task placement와 page migration
616-735Kernel은 다른 task의 memory placement를 직접 update하는 mechanism을 제공하지 않고 critical code의 cpuset 영향도 줄여야 하므로 CPU와 memory 변경의 반영 시점이 다릅니다.
Cpuset resource 또는 task attachment 변경이 적용되는 시점입니다.
Task가 `MPOL_BIND`를 쓰고 새 cpuset과 기존 bound node가 겹치면 여전히 허용된 subset을 계속 사용합니다. 겹치는 node가 하나도 없으면 `get_mempolicy()`로 보이는 NUMA policy 자체는 바뀌지 않지만 실질적으로 새 cpuset node에 bind된 것처럼 처리합니다.
`cpuset.cpus` 변경이나 task 이동은 CPU placement에 즉시 적용됩니다. Task가 과거 `sched_setaffinity()`로 cpuset의 일부 CPU에만 bind돼 있었더라도 새 cpuset으로 이동하면 이전 affinity 효과가 무효화되고 새 cpuset의 모든 allowed CPU에서 실행할 수 있습니다.
보통 이미 allocate된 physical page는 이후 `cpuset.mems`가 바뀌어도 원래 node에 남습니다. `cpuset.memory_migrate`를 켜면 task attach 시 old cpuset node의 page를 new cpuset node로 옮기며 가능하면 old/new mask 안의 상대 위치를 보존합니다. 예를 들어 old cpuset의 두 번째 valid node page는 new cpuset의 두 번째 valid node로 갑니다.
Task 이동과 mems 변경에서 migration 대상 page를 제한합니다.
Task starvation이나 kernel failure를 피하려고 cpuset 제한보다 생존성을 우선합니다.
Hotplug로 cpuset의 CPU가 모두 사라지면 task를 non-empty CPU를 가진 가장 가까운 ancestor로 옮깁니다. 다른 cgroup subsystem의 attach restriction 때문에 실패하면 task는 원래 cpuset에 남고 kernel이 `cpus_allowed`를 모든 online CPU로 넓힙니다. Kernel은 task를 굶기는 것보다 cpuset placement 위반을 택합니다.
`GFP_ATOMIC`은 즉시 만족해야 하는 internal allocation입니다. 실패하면 request drop이나 드물게 panic이 날 수 있으므로 current cpuset 안에서 만족할 수 없으면 제한을 풀어 system 어디서든 memory를 찾습니다.
새 job은 cpuset filesystem을 mount하고 directory와 allowed mask를 설정한 뒤 founding-father task의 PID를 `tasks`에 쓰고 그 task에서 `fork`, `exec`, `clone`해 시작합니다.
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
CPU 2-3과 Memory Node 1에서 subshell을 시작합니다.
Cpuset은 shell의 filesystem command, C library `libcpuset`, C library `libcgroup`(`https://github.com/libcgroup/libcgroup/`), Python application `cset`(`http://code.google.com/p/cpuset/`)으로 조회·수정할 수 있습니다. Shell에서는 SGI의 `runon` 또는 Robert Love의 `taskset`으로 affinity를, Andi Kleen의 numa package에 포함된 `numactl`로 `mbind`·`set_mempolicy`를 다룰 수 있습니다.
Mount, file syntax와 task attachment
736-862Cpuset 생성·수정·사용은 cpuset virtual filesystem을 통해 수행합니다. 다음 명령으로 controller를 mount하면 `/sys/fs/cgroup/cpuset` 아래의 directory tree가 system cpuset hierarchy와 대응합니다.
To mount it, type:
# mount -t cgroup -o cpuset cpuset /sys/fs/cgroup/cpuset
`mkdir my_cpuset`으로 child를 만들고 그 directory에서 file을 읽어 allowed CPU·Memory Node, attached process와 property를 조회하며 write로 값을 바꿉니다.
# 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
Reading them will give you information about the state of this cpuset:
the CPUs and Memory Nodes it can use, the processes that are using
it, its properties. By writing to these files you can manipulate
the cpuset.
Resource 설정 뒤 task를 attach하고 비어 있을 때 제거합니다.
`rmdir`는 child cpuset이나 attached process가 있으면 실패합니다. Legacy `cpuset` filesystem은 cgroup filesystem wrapper이며 다음 두 구성은 동등합니다.
The command::
mount -t cpuset X /sys/fs/cgroup/cpuset
is equivalent to::
mount -t cgroup -ocpuset,noprefix X /sys/fs/cgroup/cpuset
echo "/sbin/cpuset_release_agent" > /sys/fs/cgroup/cpuset/release_agent
Wrapper form은 `cpuset,noprefix` mount와 `/sbin/cpuset_release_agent`를 `release_agent`에 쓰는 구성을 함께 제공합니다.
in cpuset directories::
# /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
To add a CPU to a cpuset, write the new list of CPUs including the
CPU to be added. To add 6 to the above cpuset::
# /bin/echo 1-4,6 > cpuset.cpus -> set cpus list to cpus 1,2,3,4,6
Similarly to remove a CPU from a cpuset, write the new list of CPUs
without the CPU to be removed.
To remove all the CPUs::
# /bin/echo "" > cpuset.cpus -> clear cpus list
`cpuset.cpus`와 `cpuset.mems`에는 `1-4` 또는 `1,2,3,4`처럼 range·comma list를 씁니다. CPU를 추가·제거할 때는 delta가 아니라 새 전체 list를 다시 쓰고, 빈 문자열을 쓰면 list를 비웁니다.
File 유형별 write 규칙입니다.
The syntax is very simple::
# /bin/echo 1 > cpuset.cpu_exclusive -> set flag 'cpuset.cpu_exclusive'
# /bin/echo 0 > cpuset.cpu_exclusive -> unset flag 'cpuset.cpu_exclusive'
2.4 Attaching processes
-----------------------
::
# /bin/echo PID > tasks
Note that it is PID, not PIDs. You can only attach ONE task at a time.
If you have several tasks to attach, you have to do it one after another::
# /bin/echo PID1 > tasks
# /bin/echo PID2 > tasks
...
# /bin/echo PIDn > tasks
Task attach는 `tasks`에 PID 하나를 쓰는 방식입니다. 한 번에 여러 task를 옮기려면 PID마다 write를 하나씩 수행해야 합니다.
FAQ와 contact
863-884Filesystem write 결과와 task attach를 신뢰하기 위한 규칙입니다.
따라서 cpuset filesystem을 shell에서 조작할 때는 `/bin/echo`를 사용하고 `tasks`에는 한 번에 PID 하나만 씁니다.
원 문서의 contact Web 주소는 `http://www.bullopensource.org/cpuset`입니다.
Model and implementation
cpusets.rst:1-260Cpuset의 placement filtering, hierarchy, filesystem interface와 exclusive hardwall을 설명합니다.