Comment contrôler sur quel cœur un processus s'exécute?
je peux comprendre comment on peut écrire un programme qui utilise plusieurs processus ou threads: fork() un nouveau processus et utiliser la CIB, ou créer plusieurs threads et utiliser ce genre de mécanismes de communication.
je comprends aussi le changement de contexte. C'est-à-dire, avec seulement une CPU, le système d'exploitation planifie le temps pour chaque processus (et il y a des tonnes d'algorithmes de planification là-bas) et nous réussissons ainsi à exécuter plusieurs processus simultanément.
et maintenant que nous avons des processeurs multi-cœurs (ou des ordinateurs multi-processeurs), nous pourrions avoir deux processus fonctionnant simultanément sur deux cœurs séparés.
ma question concerne le dernier scénario: comment le noyau contrôle-t-il le noyau sur lequel un processus s'exécute? Quels appels système (sous Linux, ou même Windows) programment un processus sur un noyau spécifique?
la raison pour laquelle je demande: je travaille sur un projet pour l'école où nous allons explorer un sujet récent dans l'informatique et j'ai choisi architectures multicœurs. Il semble y avoir beaucoup de matériel sur la façon de programmer dans ce genre d'environnement (comment surveiller les conditions d'impasse ou de course), mais pas beaucoup sur le contrôle des noyaux individuels eux-mêmes. J'aimerais être en mesure d'écrire quelques programmes de démonstration et de présenter quelques instructions d'assemblage ou code C à l'effet de "voir, j'exécute une boucle infinie sur le 2e noyau, regardez le pic dans L'utilisation CPU pour que le noyau spécifique ".
des exemples de code? Ou des tutoriels?
edit: Pour clarifier - beaucoup de gens ont dit que c'est le but de l'OS, et que l'on doit laisser l'OS en prendre soin. Je suis complètement d'accord! Mais ce que je demande (ou j'essaie de comprendre), c'est ce que le système d'exploitation fait pour faire ça. Pas l'algorithme d'ordonnancement, mais plus "une fois qu'un noyau est choisi, quelles instructions doivent être exécutées pour que ce noyau commence à aller chercher instructions?"
9 réponses
comme d'autres l'ont mentionné, l'affinité du processeur est spécifique au système D'exploitation . Si vous voulez faire ça en dehors des limites du système d'exploitation, vous êtes là pour vous amuser, et par là je veux dire la douleur.
cela dit, d'autres ont parlé de SetProcessAffinityMask
pour Win32. Personne n'a mentionné la façon dont le noyau Linux définissait les affinités du processeur, et je le ferai. Vous devez utiliser la fonction sched_setaffinity
. Voici un joli tutoriel sur comment faire.
Normalement, la décision sur base d'une application fonctionnera sur est faite par le système. Cependant, vous pouvez définir l ' "affinité" pour une application à un noyau spécifique pour dire au système D'exploitation de ne lancer l'application que sur ce noyau. Normalement, ce n'est pas une bonne idée, mais il existe quelques rares cas où il pourrait faire sens.
pour ce faire dans windows, utilisez le Gestionnaire des tâches, faites un clic droit sur le processus, et choisissez"Set Affinity". Vous pouvez le faire programmatiquement dans Windows en utilisant des fonctions comme SetThreadAffinityMask, SetProcessAffinityMask ou SetThreadIdealProcessor.
ETA:
si vous êtes intéressé par la façon dont L'OS fait réellement la programmation, vous pourriez vouloir vérifier ces liens:
article Wikipedia sur la commutation de contexte
article Wikipedia sur la programmation
planification dans le noyau linux
avec la plupart des OS modernes, L'OS programme un thread à exécuter sur un noyau pour une courte tranche de temps. Lorsque la tranche de temps expire, ou que le thread effectue une opération IO qui lui fait volontairement céder le noyau, L'OS programmera un autre thread pour exécuter sur le noyau (s'il y a des threads prêts à exécuter). Exactement quel thread est programmé dépend de l'algorithme de programmation de L'OS.
les détails de la mise en œuvre exactement comment la le commutateur de contexte se produit sont dépendants de CPU & OS. Il s'agit généralement d'un passage en mode noyau, L'OS sauvegardant l'état du thread précédent, chargeant l'état du nouveau thread, puis revenant en mode utilisateur et reprenant le thread nouvellement chargé. L'article de changement de contexte I lié à ci-dessus a un peu plus de détails à ce sujet.
Rien n'indique de base "maintenant commencer l'exécution de ce processus".
Le noyau ne pas voir processus, il connait le code exécutable et les différents niveaux de fonctionnement et les limites associées à des instructions qui peuvent être exécutées.
lorsque l'ordinateur démarre, pour des raisons de simplicité, un seul noyau / processeur est actif et exécute en fait n'importe quel code. Puis si OS est multiprocesseur capable, il active d'autres noyaux avec un certain système instruction spécifique, d'autres noyaux plus probablement ramasser exactement le même endroit que d'autres noyaux et courir à partir de là.
donc ce que scheduler fait est de regarder à travers les structures internes du système D'exploitation (tâche/processus/file d'attente) et d'en choisir une et de la marquer comme étant en cours d'exécution à son cœur. Puis d'autres instances de planificateur tournant sur d'autres noyaux ne le toucheront pas jusqu'à ce que la tâche soit à nouveau en état d'attente (et non marquée comme épinglée à un noyau spécifique). Après que la tâche est marquée comme étant en cours d'exécution, scheduler exécute le commutateur à l'userland avec reprise de la tâche à la pointe, il a été précédemment suspendu.
techniquement, il n'y a rien qui empêche les cœurs d'exécuter exactement le même code au même moment (et beaucoup de fonctions déverrouillées le font), mais à moins que le code soit écrit pour s'attendre à cela, il va probablement pisser sur lui-même.
le scénario devient plus étrange avec des modèles de mémoire plus exotiques (ci-dessus suppose un espace de mémoire de travail linéaire simple "habituel") où les noyaux ne voient pas nécessairement tous même mémoire et il peut y avoir des exigences sur la récupération de code à partir d'autres embrayages du noyau, mais il est beaucoup plus facile de gérer en gardant simplement la tâche épinglée au noyau (architecture AFAIK SONY PS3 avec SPU est comme ça).
le OpenMPI projet a une "bibliothèque 151950920" pour définir l'affinité du processeur sur Linux d'une manière portable.
il y a quelque temps, j'ai utilisé ceci dans un projet et ça a bien fonctionné.
mise en garde: je me souviens faiblement qu'il y avait des problèmes à trouver comment le système d'exploitation numérote les noyaux. J'ai utilisé ceci dans un système CPU 2 Xeon avec 4 cœurs chacun.
Un coup d'oeil à cat /proc/cpuinfo
pourrait aider. Sur la boîte que j'ai utilisée, c'est assez bizarre. Bouilli vers le bas la sortie est à la fin.
évidemment, les noyaux également numérotés sont sur le premier cpu et les noyaux étrangement numérotés sont sur le second cpu. Cependant, si je me souviens bien, il y avait un problème avec les caches. Sur ces processeurs Intel Xeon, deux cœurs sur chaque CPU partagent leurs caches L2 (Je ne me souviens pas si le processeur a un cache L3). Je pense que l' les processeurs virtuels 0 et 2 partageaient un cache L2, 1 et 3 partageaient un, 4 et 6 partageaient un et 5 et 7 partageaient un.
à cause de cette bizarrerie (il y a 1,5 ans je n'ai pas pu trouver de documentation sur la numérotation des processus sous Linux), je serais prudent de faire ce genre de réglage de bas niveau. Toutefois, il y a clairement certaines utilisations. Si votre code s'exécute sur quelques types de machines alors il pourrait être utile de faire ce genre de réglage. Une autre application serait dans un domaine spécifique un langage comme StreamIt où le compilateur pourrait faire ce sale travail et calculer un horaire intelligent.
processor : 0
physical id : 0
siblings : 4
core id : 0
cpu cores : 4
processor : 1
physical id : 1
siblings : 4
core id : 0
cpu cores : 4
processor : 2
physical id : 0
siblings : 4
core id : 1
cpu cores : 4
processor : 3
physical id : 1
siblings : 4
core id : 1
cpu cores : 4
processor : 4
physical id : 0
siblings : 4
core id : 2
cpu cores : 4
processor : 5
physical id : 1
siblings : 4
core id : 2
cpu cores : 4
processor : 6
physical id : 0
siblings : 4
core id : 3
cpu cores : 4
processor : 7
physical id : 1
siblings : 4
core id : 3
cpu cores : 4
pour connaître le nombre de processeurs au lieu d'utiliser /proc/cpuinfo, il suffit d'exécuter:
nproc
pour exécuter un procédé sur un groupe de processeurs spécifiques:
taskset --cpu-list 1,2 my_command
dira que ma commande ne peut fonctionner qu'avec le cpu 1 ou 2.
pour exécuter un programme sur 4 processeurs faisant 4 choses différentes, utilisez le paramétrage. L'argument du programme lui dit de faire quelque chose de différent:
for i in `seq 0 1 3`;
do
taskset --cpu-list $i my_command $i;
done
A un bon exemple de cela est de traiter 8 millions d'opération dans un tableau de sorte que 0 à (2mil-1) va au processeur 1, 2mil à (4mil-1) au processeur 2 et ainsi de suite.
vous pouvez regarder la charge sur chaque processus en installant htop en utilisant apt-get/yum et en tournant à la ligne de commande:
htop
Comme d'autres l'ont mentionné, il est contrôlé par le système d'exploitation. Selon L'OS, il peut ou non vous fournir des appels système qui vous permettent d'affecter le noyau sur lequel un processus donné s'exécute. Cependant, vous devriez généralement laisser L'OS faire le comportement par défaut. Si vous avez un système 4-core avec 37 processus en cours d'exécution, et 34 de ces processus sont endormis, il va programmer les 3 processus actifs restants sur des cœurs séparés.
vous aurez probablement ce n'est que dans les applications multithread très spécialisées que la vitesse de jeu avec les affinités du cœur peut être augmentée. Par exemple, supposons que vous avez un système avec 2 processeurs double-cœur. Supposons que vous avez une application avec 3 threads, et deux de threads opèrent lourdement sur le même ensemble de données, alors que le troisième thread utilise un ensemble différent de données. Dans ce cas, vous bénéficieriez le plus d'avoir les deux threads qui interagissent sur le même processeur et le troisième thread sur l'autre processeur, depuis lors ils peuvent partager une cache. Le système D'exploitation n'a aucune idée de la mémoire à laquelle chaque thread doit accéder, de sorte qu'il ne peut pas affecter les threads aux noyaux de manière appropriée.
si vous êtes intéressé par comment le système d'exploitation, lire sur programmation . Les détails précis du multiprocessing sur x86 se trouvent dans les manuels de développement de logiciels des Architectures Intel 64 et IA-32 . Volume 3A, chapitres 7 et 8 informations, mais gardez à l'esprit que ces manuels sont extrêmement techniques.
L'OS sait comment faire cela, vous n'avez pas à. Vous pourriez rencontrer toutes sortes de problèmes si vous avez spécifié qui, de base, que certains pourraient effectivement ralentir le processus. Laissez le système d'exploitation le comprendre, vous avez juste besoin de démarrer le nouveau fil.
par exemple, si vous avez dit à un processus de démarrer sur core x, mais que le core x était déjà sous une lourde charge, vous seriez moins bien loti que si vous aviez juste laissé le système D'exploitation s'en charger.
Je ne connais pas les instructions de montage. Mais la fonction API de windows est SetProcessAffinityMask . Vous pouvez voir un exemple de quelque chose que j'ai bricolé ensemble il y a un certain temps pour exécuter Picasa sur un seul noyau
Linux sched_setaffinity
c minimal
dans cet exemple, nous obtenons l'affinité, la modifions et vérifions si elle a pris effet avec sched_getcpu()
.
#define _GNU_SOURCE
#include <assert.h>
#include <sched.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void print_affinity() {
cpu_set_t mask;
long nproc, i;
if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
perror("sched_getaffinity");
assert(false);
} else {
nproc = sysconf(_SC_NPROCESSORS_ONLN);
printf("sched_getaffinity = ");
for (i = 0; i < nproc; i++) {
printf("%d ", CPU_ISSET(i, &mask));
}
printf("\n");
}
}
int main(void) {
cpu_set_t mask;
print_affinity();
printf("sched_getcpu = %d\n", sched_getcpu());
CPU_ZERO(&mask);
CPU_SET(0, &mask);
if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
perror("sched_setaffinity");
assert(false);
}
print_affinity();
/* TODO is it guaranteed to have taken effect already? Always worked on my tests. */
printf("sched_getcpu = %d\n", sched_getcpu());
return EXIT_SUCCESS;
}
compiler et exécuter avec:
gcc -std=c99 main.c
./a.out
sortie D'échantillon:
sched_getaffinity = 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
sched_getcpu = 9
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
sched_getcpu = 0
ce qui signifie que:
- activé, et le processus a été exécuté au hasard sur core 9 (le 10ème)
- après avoir fixé l'affinité au seul premier noyau, le processus a été nécessairement déplacé au noyau 0 (le premier)
, Il est aussi amusant pour exécuter ce programme par le biais de taskset
:
taskset -c 1,3 ./a.out
qui donne la sortie de la forme:
sched_getaffinity = 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
sched_getcpu = 2
sched_getaffinity = 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
sched_getcpu = 0
et nous voyons donc qu'il a limité l'affinité dès le début.
cela fonctionne parce que l'affinité est héritée par des processus de l'enfant, qui taskset
est bifurcation: Comment éviter d'hériter l'affinité CPU par processus de l'enfant bifurqué?
testé dans Ubuntu 16.04, GitHub upstream .
x86 métal nu
si vous êtes ce hardcore: à quoi ressemble le langage d'assemblage multicore?
comment Linux le met en œuvre