ubuntu:kernel:cpu_sets
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
ubuntu:kernel:cpu_sets [2019/11/30 12:33] – created peter | ubuntu:kernel:cpu_sets [2019/12/15 21:49] (current) – removed peter | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Ubuntu - Kernel - CPU Sets ====== | ||
- | |||
- | Cpusets provide a mechanism for assigning a set of CPUs and Memory Nodes to a set of tasks. | ||
- | |||
- | Cpusets constrain the CPU and Memory placement of tasks to only the resources within a task's current cpuset. | ||
- | |||
- | Cpusets use the generic cgroup subsystem described in [[Kernel: | ||
- | |||
- | 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. | ||
- | |||
- | 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. | ||
- | |||
- | ---- | ||
- | |||
- | ===== 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" | ||
- | |||
- | The kernel cpuset patch provides the minimum essential kernel mechanisms required to efficiently implement such subsets. | ||
- | |||
- | ---- | ||
- | |||
- | ===== 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 / | ||
- | * 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/ | ||
- | * in fork and exit, to attach and detach a task from its cpuset. | ||
- | * in sched_setaffinity, | ||
- | * in sched.c migrate_live_tasks(), | ||
- | * 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, | ||
- | * in vmscan.c, to restrict page recovery to the current cpuset. | ||
- | |||
- | You should mount the " | ||
- | |||
- | The **[[Proc: | ||
- | |||
- | < | ||
- | Cpus_allowed: | ||
- | Cpus_allowed_list: | ||
- | Mems_allowed: | ||
- | Mems_allowed_list: | ||
- | </ | ||
- | |||
- | 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**: | ||
- | * **cpuset.mems**: | ||
- | * **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**: | ||
- | * **cpuset.memory_spread_page** flag: if set, spread page cache evenly on allowed nodes. | ||
- | * **cpuset.memory_spread_slab** flag: if set, spread slab cache evenly on allowed nodes. | ||
- | * **cpuset.sched_load_balance** flag: if set, load balance within CPUs on that cpuset. | ||
- | * **cpuset.sched_relax_domain_level**: | ||
- | |||
- | In addition, the root cpuset only 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 named hierarchical structure of nested cpusets allows partitioning a large system into nested, dynamically changeable, " | ||
- | |||
- | 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. | ||
- | |||
- | 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**, | ||
- | |||
- | 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. | ||
- | |||
- | The cpus and mems files in the root (top_cpuset) cpuset are read-only. | ||
- | |||
- | ---- | ||
- | |||
- | ===== 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 " | ||
- | isolating each job's user allocation in its own cpuset. | ||
- | |||
- | 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. | ||
- | |||
- | ---- | ||
- | |||
- | ===== 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. | ||
- | |||
- | ==> Unless this feature is enabled by writing " | ||
- | |||
- | 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, | ||
- | |||
- | 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. | ||
- | |||
- | ---- | ||
- | |||
- | ===== 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. | ||
- | |||
- | If the per-cpuset boolean flag file **' | ||
- | 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 **' | ||
- | |||
- | 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, | ||
- | |||
- | 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. | ||
- | |||
- | Both **' | ||
- | |||
- | The implementation is simple. | ||
- | |||
- | Setting the flag **' | ||
- | |||
- | Similarly, setting **' | ||
- | pages from the node returned by **cpuset_mem_spread_node()**. | ||
- | |||
- | The **cpuset_mem_spread_node()** routine is also simple. | ||
- | |||
- | 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. | ||
- | |||
- | ---- | ||
- | |||
- | ===== What is sched_load_balance? | ||
- | |||
- | The kernel scheduler (kernel/ | ||
- | |||
- | 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. | ||
- | |||
- | 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, except those marked isolated using the kernel boot time " | ||
- | |||
- | This default load balancing across all CPUs is not well suited for the following two situations: | ||
- | |||
- | - On large systems, load balancing across many CPUs is expensive. | ||
- | - 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 " | ||
- | |||
- | When the per-cpuset flag " | ||
- | --except-- in so far as is necessary because some overlapping cpuset has " | ||
- | |||
- | So, for example, if the top cpuset has the flag " | ||
- | CPUs, and the setting of the " | ||
- | |||
- | Therefore in the above two situations, the top cpuset flag " | ||
- | |||
- | 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. | ||
- | |||
- | Of course, tasks pinned to a particular CPU can be left in a cpuset that disables " | ||
- | |||
- | There is an impedance mismatch here, between cpusets and sched domains. | ||
- | 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. | ||
- | |||
- | This mismatch is why there is not a simple one-to-one relation between which cpusets have the flag " | ||
- | |||
- | If two cpusets have partially overlapping ' | ||
- | |||
- | ---- | ||
- | |||
- | ==== sched_load_balance implementation details ==== | ||
- | |||
- | The per-cpuset flag ' | ||
- | in the same sched domain.) | ||
- | |||
- | If two overlapping cpusets both have ' | ||
- | |||
- | If, as is the default, the top cpuset has ' | ||
- | |||
- | 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 ' | ||
- | |||
- | 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 ' | ||
- | * or CPUs come or go from a cpuset with this flag enabled, | ||
- | * or ' | ||
- | * or a cpuset with non-empty CPUs and with this flag enabled is removed, | ||
- | * or a cpu is offlined/ | ||
- | |||
- | 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. | ||
- | |||
- | ---- | ||
- | |||
- | ===== 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, | ||
- | |||
- | 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. | ||
- | |||
- | The ' | ||
- | indicates size of searching range in levels ideally 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] ) | ||
- | |||
- | The system default is architecture dependent. | ||
- | |||
- | This file is per-cpuset and affect the sched domain where the cpuset belongs to. Therefore if the flag ' | ||
- | is disabled, then ' | ||
- | |||
- | 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. | ||
- | |||
- | If your situation is: | ||
- | |||
- | * The migration costs between each cpu can be assumed considerably small(for you) due to your special application' | ||
- | * The searching cost doesn' | ||
- | * The latency is required even it sacrifices cache hit rate etc. | ||
- | |||
- | then increasing ' | ||
- | |||
- | ---- | ||
- | |||
- | ===== 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 a cpuset has its ' | ||
- | if a task's pid is written to another cpusets ' | ||
- | |||
- | 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 ' | ||
- | |||
- | Also if ' | ||
- | |||
- | There is an exception to the above. | ||
- | |||
- | There is a second exception to the above. | ||
- | |||
- | To start a new job that is to be contained within a cpuset, the steps are: | ||
- | |||
- | - mkdir /dev/cpuset | ||
- | - mount -t cgroup -ocpuset cpuset /dev/cpuset | ||
- | - Create the new cpuset by doing mkdir' | ||
- | - Attach that task to the new cpuset by writing its pid to the /dev/cpuset tasks file for that cpuset. | ||
- | - fork, exec or clone the job tasks from this founding father task. | ||
- | |||
- | For example, the following sequence of commands will setup a cpuset named " | ||
- | |||
- | <code bash> | ||
- | mount -t cgroup -ocpuset cpuset /dev/cpuset | ||
- | cd /dev/cpuset | ||
- | mkdir Charlie | ||
- | cd Charlie | ||
- | /bin/echo 2-3 > cpuset.cpus | ||
- | /bin/echo 1 > cpuset.mems | ||
- | /bin/echo $$ > tasks | ||
- | sh | ||
- | # The subshell ' | ||
- | # The next line should display '/ | ||
- | cat / | ||
- | </ | ||
- | |||
- | 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. (http:// | ||
- | * via the python application cset. (http:// | ||
- | |||
- | The sched_setaffinity calls can also be done at the shell prompt using SGI's runon or Robert Love's taskset. | ||
- | |||
- | ---- | ||
- | |||
- | ===== Usage Examples and Syntax ===== | ||
- | |||
- | ==== Basic Usage ==== | ||
- | |||
- | Creating, modifying, using the cpusets can be done through the cpuset virtual filesystem. | ||
- | |||
- | To mount it, type: | ||
- | |||
- | <code bash> | ||
- | mount -t cgroup -o cpuset cpuset /dev/cpuset | ||
- | </ | ||
- | |||
- | Then under /dev/cpuset you can find a tree that corresponds to the tree of the cpusets in the system. For instance, /dev/cpuset is the cpuset that holds the whole system. | ||
- | |||
- | If you want to create a new cpuset under / | ||
- | |||
- | <code bash> | ||
- | cd /dev/cpuset | ||
- | mkdir my_cpuset | ||
- | </ | ||
- | |||
- | Now you want to do something with this cpuset. | ||
- | |||
- | <code bash> | ||
- | cd my_cpuset | ||
- | </ | ||
- | |||
- | In this directory you can find several files: | ||
- | |||
- | <code bash> | ||
- | ls | ||
- | |||
- | cpuset.cpu_exclusive | ||
- | cpuset.cpus | ||
- | cpuset.mem_exclusive | ||
- | cpuset.mem_hardwall | ||
- | cpuset.memory_migrate | ||
- | cpuset.memory_pressure | ||
- | cpuset.memory_spread_page | ||
- | </ | ||
- | |||
- | 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: | ||
- | |||
- | <code bash> | ||
- | /bin/echo 1 > cpuset.cpu_exclusive | ||
- | </ | ||
- | |||
- | Add some cpus: | ||
- | |||
- | <code bash> | ||
- | /bin/echo 0-7 > cpuset.cpus | ||
- | </ | ||
- | |||
- | Add some mems: | ||
- | |||
- | <code bash> | ||
- | /bin/echo 0-7 > cpuset.mems | ||
- | </ | ||
- | |||
- | Now attach your shell to this cpuset: | ||
- | |||
- | <code bash> | ||
- | /bin/echo $$ > tasks | ||
- | </ | ||
- | |||
- | You can also create cpusets inside your cpuset by using mkdir in this directory. | ||
- | |||
- | <code bash> | ||
- | mkdir my_sub_cs | ||
- | </ | ||
- | |||
- | To remove a cpuset, just use rmdir: | ||
- | |||
- | <code bash> | ||
- | 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 " | ||
- | |||
- | The command | ||
- | |||
- | <code bash> | ||
- | mount -t cpuset X /dev/cpuset | ||
- | </ | ||
- | |||
- | is equivalent to | ||
- | |||
- | <code bash> | ||
- | mount -t cgroup -ocpuset, | ||
- | echo "/ | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | ==== Adding/ | ||
- | |||
- | This is the syntax to use when writing in the cpus or mems files in cpuset directories: | ||
- | |||
- | <code bash> | ||
- | /bin/echo 1-4 > cpuset.cpus | ||
- | /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: | ||
- | |||
- | <code bash> | ||
- | /bin/echo 1-4,6 > cpuset.cpus | ||
- | </ | ||
- | |||
- | 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: | ||
- | |||
- | <code bash> | ||
- | # /bin/echo "" | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | ==== Setting flags ==== | ||
- | |||
- | The syntax is very simple: | ||
- | |||
- | <code bash> | ||
- | /bin/echo 1 > cpuset.cpu_exclusive | ||
- | /bin/echo 0 > cpuset.cpu_exclusive | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | ==== Attaching processes ==== | ||
- | |||
- | <code bash> | ||
- | /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: | ||
- | |||
- | <code bash> | ||
- | /bin/echo PID1 > tasks | ||
- | /bin/echo PID2 > tasks | ||
- | ... | ||
- | /bin/echo PIDn > tasks | ||
- | </ | ||
- | |||
- | ---- | ||
- | |||
- | ===== Questions ===== | ||
- | |||
- | Q: What's up with this '/ | ||
- | |||
- | A: bash's builtin ' | ||
- | |||
- | 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. | ||
- | |||
ubuntu/kernel/cpu_sets.1575117219.txt.gz · Last modified: 2020/07/15 09:30 (external edit)