В данной статье я расскажу, как на Linux изолировать ядро процессора с помощью утилиты isolcpus.
В результате изоляции планировщик ОС Linux не будет размещать на указанных ядрах никакие пользовательские процессы. Запустить нужный процесс на изолированном ядре возможно будет только с помощью утилиты taskset. В результате изоляции на выделенных ядрах будут работать потоки только вашего процесса, планировщик не будет пытаться перенести их на другие ядра, а также не будет пытаться запустить на указанных ядрах другие процессы, которые бы вытесняли и прерывали потоки исполнения вашего процесса.
Эксперимент проводился на виртуальной машине VirtualBox, на которой была запущена инсталляция CentOS 8 с помощью Vagrant. Для виртуальной машины я выделил 3 ядра на своем дестопном 4-хядерном процессоре.
Утилита lscpu покажет всю информацию о процессорах в том числе количество имеющихся ядер на каждом из процессоров.
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 3
On-line CPU(s) list: 0-2
Thread(s) per core: 1
Core(s) per socket: 3
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 94
Model name: Intel(R) Core(TM) i5-6400T CPU @ 2.20GHz
Stepping: 3
CPU MHz: 2208.002
BogoMIPS: 4416.00
Hypervisor vendor: KVM
Virtualization type: full
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 6144K
NUMA node0 CPU(s): 0-2
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase avx2 invpcid rdseed clflushopt md_clear flush_l1d
Как и ожидалось у меня 1 процессор, на котором выделено три ядра CPU0, CPU1, CPU2. Нумерация ядер начинается 0. Для наглядности я изолирую ядро CPU1, т.е. второе из трех имеющихся.
Утилита isolcpus запускается при старте ядра Linux. Для этого необходимо внести изменения в параметры старта ядра. На сайте RedHat есть пошаговая инструкция, как это сделать. Для внесения изменений надо работать под root или использовать sudo.
Для начала надо сделать страховые копии конфигурации GRUB
# cp /etc/default/grub /etc/default/grub-backup # cp /boot/grub2/grub.cfg /boot/grub2/grub.cfg-backup
Потом открыть файл для редактирования в vi:
# vi /etc/default/grub
Найти строку GRUB_CMDLINE_LINUX и добавить в конце строки isolcpus=1. Для того, чтобы изолировать два ядра (например, СPU0 и CPU2), укажите в GRUB_CMDLINE_LINUX isolcpus=0,2.
GRUB_CMDLINE_LINUX="resume=UUID=89eb8bd0-55b8-4925-be59-b98c8a4e23cc rhgb quiet isolcpus=1"
Сохранить файл. И потом обновить конфигурацию GRUB:
# grub2-mkconfig -o /boot/grub2/grub.cfg
После этого надо перезагрузить Linux. Если все было сделано правильно, Linux успешно перезагрузится после чего можно проверить, как все сработало. Для примера я собрал Spring Boot проект и запустил его командой:
# mvn spring-boot:run &
С помощью утилиты jps я определил pid моего процесса:
# jps 3296 Jps 2933 DemoApplication 2908 Launcher
Затем с помощью утилиты pidstat я смотрю на каких CPU работает мое приложение:
# pidstat -t -p 2933 CPU Command 2 java 2 |__java 0 |__java 0 |__VM Thread 2 |__Reference Handl 2 |__Finalizer 2 |__Signal Dispatch 0 |__Service Thread 0 |__Monitor Deflati 0 |__C1 CompilerThre 2 |__Sweeper thread 2 |__Notification Th 0 |__VM Periodic Tas 2 |__Common-Cleaner 2 |__Catalina-utilit 2 |__Catalina-utilit 2 |__container-0 2 |__http-nio-8080-e 2 |__http-nio-8080-e 0 |__http-nio-8080-e 2 |__http-nio-8080-e 2 |__http-nio-8080-e 0 |__http-nio-8080-e 2 |__http-nio-8080-e 0 |__http-nio-8080-e 2 |__http-nio-8080-e 2 |__http-nio-8080-e 0 |__http-nio-8080-P 2 |__http-nio-8080-A
Как видите, все потоки процесса разбросаны планировщиком по CPU0 и CPU2, а CPU1 отсутствует в списке. Можно посмотреть свойства процесса, где можно увидеть, что для данного процесса доступны только CPU0 и CPU2:
# cat /proc/2933/status | grep Cpus_allowed_list Cpus_allowed_list: 0,2
«Убъем» процесс и попробуем запустить его на изолированном ядре CPU1 с помощью утилиты taskset:
# taskset -c 1 mvn spring-boot:run &
Утилита pidstat покажет, что все потоки запущенного процесса работают только на CPU0:
# pidstat -t -p 3357 CPU Command 1 java 1 |__java 1 |__java 1 |__VM Thread 1 |__Reference Handl 1 |__Finalizer 1 |__Signal Dispatch 1 |__Service Thread 1 |__Monitor Deflati 1 |__C1 CompilerThre 1 |__Sweeper thread 1 |__Notification Th 1 |__VM Periodic Tas 1 |__Common-Cleaner 1 |__Catalina-utilit 1 |__Catalina-utilit 1 |__container-0 1 |__http-nio-8080-e 1 |__http-nio-8080-e 1 |__http-nio-8080-e 1 |__http-nio-8080-e 1 |__http-nio-8080-e 1 |__http-nio-8080-e 1 |__http-nio-8080-e 1 |__http-nio-8080-e 1 |__http-nio-8080-e 1 |__http-nio-8080-e 1 |__http-nio-8080-P 1 |__http-nio-8080-A
Примечательно, что с помощью утилиты numactl мы не сможем запустить процесс на CPU1. Numactl выдает ошибку, что данное ядро недоступно для запуска приложения.
# numactl --physcpubind=1 mvn spring-boot:run libnuma: Warning: cpu argument 1 is out of range
Это связано с тонкостями логики кода утилиты numactl. Утилита taskset в этом отношении действует «грубее» и, может быть, — «правильнее».