La réponse est d'utiliser des cpusets. L'utilitaire python cpuset facilite leur configuration.
Concepts de base
3 processeurs
root
:présent dans toutes les configurations et contient tous les processeurs (non blindés )system
:contient les processeurs utilisés pour les tâches système - celles qui doivent s'exécuter mais qui ne sont pas "importantes" (non blindées )user
:contient les processeurs utilisés pour les tâches "importantes" - celles que nous voulons exécuter en mode "temps réel" (blindé )
Le shield
commande gère ces 3 cpusets.
Pendant l'installation, il déplace toutes les tâches mobiles dans le cpuset non protégé (system
) et pendant le démontage, il déplace toutes les tâches mobiles dans le root
cpuset.Après l'installation, la sous-commande vous permet de déplacer des tâches dans le bouclier (user
) cpuset, et en plus, pour déplacer des tâches spéciales (threads du noyau) de root
à system
(et donc hors du user
ensemble de processeur).
Commandes :
Nous créons d'abord un bouclier. Naturellement, la disposition du bouclier dépendra de la machine/de la tâche. Par exemple, supposons que nous ayons une machine non NUMA à 4 cœurs :nous voulons dédier 3 cœurs au bouclier , et laissez 1 cœur pour les tâches sans importance; puisqu'il n'est pas NUMA, nous n'avons pas besoin de spécifier de paramètres de nœud de mémoire et nous laissons les threads du noyau s'exécuter dans le root
cpuset (c'est-à-dire :sur tous les processeurs)
$ cset shield --cpu 1-3
Certains threads du noyau (ceux qui ne sont pas liés à des processeurs spécifiques) peuvent être déplacés dans le system
processeur. (En général, ce n'est pas une bonne idée de déplacer les threads du noyau qui ont été liés à un processeur spécifique)
$ cset shield --kthread on
Maintenant, listons ce qui s'exécute dans le bouclier (user
) ou non blindé (system
) ensembles de processeurs :(-v
pour verbeux, qui listera les noms des processus) (ajoutez un 2ème -v
pour afficher plus de 80 caractères)
$ cset shield --shield -v
$ cset shield --unshield -v -v
Si nous voulons arrêter le bouclier (démontage)
$ cset shield --reset
Exécutons maintenant un processus dans le bouclier (commandes suivant '--'
sont passés à la commande à exécuter, pas à cset
)
$ cset shield --exec mycommand -- -arg1 -arg2
Si nous avons déjà un processus en cours d'exécution que nous voulons déplacer dans le bouclier (notez que nous pouvons déplacer plusieurs processus en passant une liste séparée par des virgules ou des plages (tout processus de la plage sera déplacé, même s'il y a des lacunes))
$ cset shield --shield --pid 1234
$ cset shield --shield --pid 1234,1236
$ cset shield --shield --pid 1234,1237,1238-1240
Concepts avancés
cset set/proc
- ceux-ci vous donnent un contrôle plus fin des cpusets
Définir
Créer, ajuster, renommer, déplacer et détruire des cpusets
Commandes
Créez un cpuset, en utilisant les cpus 1-3, utilisez le nœud NUMA 1 et appelez-le "my_cpuset1"
$ cset set --cpu=1-3 --mem=1 --set=my_cpuset1
Modifiez "my_cpuset1" pour n'utiliser que les processeurs 1 et 3
$ cset set --cpu=1,3 --mem=1 --set=my_cpuset1
Détruire un cpuset
$ cset set --destroy --set=my_cpuset1
Renommer un cpuset existant
$ cset set --set=my_cpuset1 --newname=your_cpuset1
Créer un cpuset hiérarchique
$ cset set --cpu=3 --mem=1 --set=my_cpuset1/my_subset1
Lister les cpusets existants (profondeur de niveau 1)
$ cset set --list
Lister le cpuset existant et ses enfants
$ cset set --list --set=my_cpuset1
Lister tous les cpusets existants
$ cset set --list --recurse
Procéder
Gérer les threads et les processus
Commandes
Lister les tâches en cours d'exécution dans un cpuset
$ cset proc --list --set=my_cpuset1 --verbose
Exécuter une tâche dans un cpuset
$ cset proc --set=my_cpuset1 --exec myApp -- --arg1 --arg2
Déplacer une tâche
$ cset proc --toset=my_cpuset1 --move --pid 1234
$ cset proc --toset=my_cpuset1 --move --pid 1234,1236
$ cset proc --toset=my_cpuset1 --move --pid 1238-1340
Déplacer une tâche et tous ses frères
$ cset proc --move --toset=my_cpuset1 --pid 1234 --threads
Déplacer toutes les tâches d'un cpuset à un autre
$ cset proc --move --fromset=my_cpuset1 --toset=system
Déplacer les threads du noyau non épinglés dans un cpuset
$ cset proc --kthread --fromset=root --toset=system
Déplacez de force les threads du noyau (y compris ceux qui sont épinglés à un processeur spécifique) dans un cpuset (remarque :cela peut avoir des conséquences désastreuses pour le système - assurez-vous de savoir ce que vous faites)
$ cset proc --kthread --fromset=root --toset=system --force
Exemple de hiérarchie
Nous pouvons utiliser des cpusets hiérarchiques pour créer des regroupements prioritaires
- Créer un
system
cpuset avec 1 cpu (0) - Créer un
prio_low
ensemble de processeurs avec 1 processeur (1) - Créer un
prio_met
ensemble de processeurs avec 2 processeurs (1-2) - Créer un
prio_high
ensemble de processeurs avec 3 processeurs (1-3) - Créer un
prio_all
cpuset avec les 4 processeurs (0-3) (notez que c'est la même chose que root ; il est considéré comme une bonne pratique de garder une séparation avec root)
Pour atteindre ce qui précède, vous créez prio_all, puis créez un sous-ensemble prio_high sous prio_all, etc
$ cset set --cpu=0 --set=system
$ cset set --cpu=0-3 --set=prio_all
$ cset set --cpu=1-3 --set=/prio_all/prio_high
$ cset set --cpu=1-2 --set=/prio_all/prio_high/prio_med
$ cset set --cpu=1 --set=/prio_all/prio_high/prio_med/prio_low
Je peux penser à deux autres façons de le faire (mais pas aussi élégantes que cset, qui ne semble pas bénéficier d'un niveau de support fantastique de la part de Redhat) :
1) Définissez tout, y compris le PID 1 - agréable et facile (mais, apparemment - je n'ai jamais vu de problèmes moi-même - cela peut entraîner des inefficacités dans le planificateur). Le script ci-dessous (qui doit être exécuté en tant que root) exécute l'ensemble de tâches sur tous les processus en cours d'exécution, y compris init (pid 1) ; cela épinglera tous les processus en cours d'exécution à un ou plusieurs "cœurs indésirables", et en épinglant également init, cela garantira que tous les processus futurs sont également démarrés dans la liste des "cœurs indésirables" :
#!/bin/bash
if [[ -z $1 ]]; then
printf "Usage: %s '<csv list of cores to set as junk in double quotes>'", $0
exit -1;
fi
for i in `ps -eLfad |awk '{ print $4 } '|grep -v PID | xargs echo `; do
taskset -pc $1 $i;
done
2) utilisez le paramètre de noyau isolcpus (voici la documentation de https://www.kernel.org/doc/Documentation/kernel-parameters.txt):
isolcpus= [KNL,SMP] Isolate CPUs from the general scheduler.
Format:
<cpu number>,...,<cpu number>
or
<cpu number>-<cpu number>
(must be a positive range in ascending order)
or a mixture
<cpu number>,...,<cpu number>-<cpu number>
This option can be used to specify one or more CPUs
to isolate from the general SMP balancing and scheduling
algorithms. You can move a process onto or off an
"isolated" CPU via the CPU affinity syscalls or cpuset.
<cpu number> begins at 0 and the maximum value is
"number of CPUs in system - 1".
This option is the preferred way to isolate CPUs. The
alternative -- manually setting the CPU mask of all
tasks in the system -- can cause problems and
suboptimal load balancer performance.
J'ai utilisé ces deux mécanismes plus les cset pour plusieurs projets (d'ailleurs, veuillez pardonner l'auto-promotion flagrante :-)), je viens de déposer un brevet pour un outil appelé Pontus Vision ThreadManager qui propose des stratégies d'épinglage optimales pour tout plate-forme x86 donnée à toute charge de travail logicielle donnée ; après l'avoir testé sur un site client, j'ai obtenu de très bons résultats (réduction de 270 % des latences maximales), donc cela vaut la peine d'épingler et d'isoler le processeur.