The Linux Completely Fair Scheduler (CFS) is designed to balance execution threads dynamically across all available processor cores, maximising aggregate system throughput. However, in latency-sensitive environments—such as high-frequency financial trading systems, real-time audio processing daemons, scientific compute clusters, and dedicated gaming servers—allowing the kernel scheduler to migrate threads between CPU cores introduces noticeable cache invalidation penalties. When a thread hops from CPU 1 to CPU 5, its warm Level 1 and Level 2 CPU caches must be reloaded from slower shared L3 cache or main system RAM. Processor affinity (or CPU pinning) binds a process to execute exclusively on a designated set of logical cores. The taskset command-line utility provides direct control over Linux processor affinity masks.
Understanding Processor Affinity and Bitmasks
In the Linux kernel, CPU affinity is represented internally as a bitmask where each bit corresponds to a logical processor core:
0x00000001 (0x1):Logical CPU 00x00000002 (0x2):Logical CPU 10x00000003 (0x3):Logical CPU 0 and CPU 10x00000004 (0x4):Logical CPU 20x0000000F (0xF):Logical CPUs 0, 1, 2, and 3
While taskset accepts hexadecimal masks, modern implementations also support the user-friendly -c (CPU list) flag, allowing administrators to specify comma-separated core numbers and ranges (e.g., 0,2,4-7) directly.
Querying the Current CPU Affinity of a Running Process
To inspect the affinity mask of an active process, supply its Process ID (PID) to taskset:
taskset -p 18450
Terminal returns the current hexadecimal mask:
pid 18450's current affinity mask: ff
A mask of ff (binary 11111111) indicates that the process is unpinned and permitted to execute on any of the system’s first 8 logical cores. To view this affinity as a human-readable list of CPU identifiers instead, include the -c flag:
taskset -cp 18450
The output clearly displays: pid 18450's current affinity list: 0-7.
Pinning an Existing Running Process to Specific Cores
To lock an active process to a single dedicated core or cluster of cores, specify the core list followed by the target PID:
sudo taskset -cp 2,3 18450
The kernel updates the task structure immediately:
pid 18450's current affinity list: 0-7
pid 18450's new affinity list: 2,3
The operating system scheduler will now exclusively dispatch process 18450 onto logical CPU 2 or CPU 3, ensuring that the process maintains warm L1/L2 cache lines while leaving remaining cores entirely free for background system tasks.
Launching a New Application with Pre-Configured Affinity
You can also enforce CPU pinning at binary launch time, preventing the application from ever executing on unassigned cores:
taskset -c 0,1 /usr/local/bin/compute-engine --workers 4
The newly instantiated binary and all child threads spawned during execution inherit affinity mask 0,1, strictly isolating the workload to those two cores.
Pinning Multi-Threaded Processes with -a
Complex applications (such as MySQL, Java JVM, or Nginx) spawn multiple independent POSIX worker threads. By default, applying taskset -p <PID> modifies only the main thread. To enforce CPU affinity across all existing worker threads within a multi-threaded process, add the -a (all threads) flag:
sudo taskset -acp 4-7 18450
The command traverses all Light-Weight Processes (LWPs) associated with thread group ID 18450, pinning every active worker thread across cores 4 through 7 uniformly.
NUMA Node Considerations and Hyper-Threading
When configuring affinity on high-performance multi-socket systems, always cross-reference core numbers against your Non-Uniform Memory Access (NUMA) topology using numactl --hardware. Pinning a process to cores on Socket 0 while allocating memory on Socket 1 creates cross-bus memory interconnect latency. For optimal performance, pin compute tasks to cores residing on the exact same physical NUMA node as their allocated memory.