web-dev-qa-db-fra.com

Comment contrôler sur quel cœur s'exécute un processus?

Je peux comprendre comment on peut écrire un programme qui utilise plusieurs processus ou threads: fork () un nouveau processus et utiliser IPC, ou créer plusieurs threads et utiliser ces sortes de mécanismes de communication.

Je comprends également le changement de contexte. C'est-à-dire qu'avec une seule CPU, le système d'exploitation planifie le temps pour chaque processus (et il y a des tonnes d'algorithmes de planification) et nous obtenons ainsi l'exécution simultanée de plusieurs processus.

Et maintenant que nous avons des processeurs multicœurs (ou des ordinateurs multiprocesseurs), nous pourrions avoir deux processus s'exécutant simultanément sur deux cœurs distincts.

Ma question concerne le dernier scénario: comment le noyau contrôle-t-il sur quel cœur un processus s'exécute? Quels appels système (sous Linux ou même Windows) planifient un processus sur un noyau spécifique?

La raison pour laquelle je pose la question: je travaille sur un projet pour l'école où nous allons explorer un sujet récent en informatique - et j'ai choisi des architectures multicœurs. Il semble y avoir beaucoup de matériel sur la façon de programmer dans ce type d'environnement (comment surveiller les impasses ou les conditions de course) mais pas beaucoup sur le contrôle des cœurs individuels eux-mêmes. Je serais ravi de pouvoir écrire quelques programmes de démonstration et présenter des instructions d'assemblage ou du code C à l'effet de "Voir, j'exécute une boucle infinie sur le 2ème cœur, regardez le pic d'utilisation du CPU pour ce noyau spécifique ".

Des exemples de code? Ou des tutoriels?

edit: Pour plus de clarté - beaucoup de gens ont dit que c'est le but de l'OS, et que l'on devrait laisser l'OS s'en occuper. Je suis complètement d'accord! Mais alors ce que je demande (ou j'essaie de comprendre), c'est ce que le système d'exploitation fait réellement pour faire cela. Pas l'algorithme de planification, mais plutôt "une fois qu'un noyau est choisi, quelles instructions doivent être exécutées pour que ce noyau commence à récupérer les instructions?"

56
poundifdef

Comme d'autres l'ont mentionné, l'affinité du processeur est spécifique au système d'exploitation . Si vous voulez faire cela en dehors des limites du système d'exploitation, vous vous amuserez beaucoup, et j'entends par là la douleur.

Cela dit, d'autres ont mentionné SetProcessAffinityMask pour Win32. Personne n'a mentionné la façon dont le noyau Linux définit l'affinité du processeur, et je le ferai donc. Vous devez utiliser le sched_setaffinity une fonction. Voici n joli tutoriel sur la façon dont.

35
Randolpho

Normalement, la décision concernant le noyau sur lequel une application s'exécutera est prise par le système. Cependant, vous pouvez définir "l'affinité" pour une application sur un cœur spécifique pour indiquer au système d'exploitation de n'exécuter l'application que sur ce cœur. Normalement, ce n'est pas une bonne idée, mais il existe de rares cas où cela peut avoir un sens.

Pour ce faire dans Windows, utilisez le gestionnaire de tâches, faites un clic droit sur le processus et choisissez "Définir l'affinité". Vous pouvez le faire par programme dans Windows à l'aide de fonctions telles que SetThreadAffinityMask, SetProcessAffinityMask ou SetThreadIdealProcessor.

ETA:

Si vous êtes intéressé par la façon dont le système d'exploitation effectue réellement la planification, vous pouvez consulter ces liens:

article Wikipedia sur le changement de contexte

article Wikipedia sur la planification

Planification dans le noyau linux

Avec la plupart des systèmes d'exploitation modernes, le système d'exploitation planifie un thread à exécuter sur un noyau pendant une courte période de temps. Lorsque la tranche de temps expire ou que le thread effectue une opération IO qui le fait céder volontairement le core, le système d'exploitation planifie un autre thread pour qu'il s'exécute sur le core (s'il existe des threads prêts à L'exécution exacte du thread dépend de l'algorithme de planification du système d'exploitation.

Les détails d'implémentation de la façon exacte dont le changement de contexte se produit dépendent du processeur et du système d'exploitation. Cela impliquera généralement un passage en mode noyau, le système d'exploitation enregistrant l'état du thread précédent, chargeant l'état du nouveau thread, puis revenant au mode utilisateur et reprenant le thread nouvellement chargé. L'article sur le changement de contexte que j'ai lié à ci-dessus contient un peu plus de détails à ce sujet.

31
Eric Petroelje

Rien ne dit au noyau "maintenant commencez à exécuter ce processus".

Le noyau ne voit pas processus, il ne connaît que le code exécutable et les différents niveaux d'exécution et les limitations associées aux instructions qui peuvent être exécutées.

Lors du démarrage de l'ordinateur, par souci de simplicité, un seul cœur/processeur est actif et exécute réellement n'importe quel code. Ensuite, si le système d'exploitation est capable de multiprocesseur, il active d'autres cœurs avec des instructions spécifiques au système, d'autres cœurs sont probablement récupérés exactement au même endroit que les autres cœurs et exécutés à partir de là.

Donc, ce planificateur fait qu'il examine les structures internes du système d'exploitation (tâche/processus/file d'attente de threads) et en sélectionne une et la marque comme fonctionnant en son cœur. Les autres instances du planificateur s'exécutant sur d'autres cœurs ne le toucheront pas tant que la tâche ne sera pas en attente (et non marquée comme épinglée sur un cœur spécifique). Une fois la tâche marquée comme en cours d'exécution, le planificateur exécute le basculement vers l'espace utilisateur avec la reprise de la tâche au point où elle a été précédemment suspendue.

Techniquement, rien n'empêche les cœurs d'exécuter exactement le même code en même temps (et de nombreuses fonctions déverrouillées le font), mais à moins que du code ne soit écrit pour s'y attendre, il pissera probablement sur lui-même.

Le scénario est plus étrange avec des modèles de mémoire plus exotiques (ci-dessus suppose un espace de mémoire de travail unique linéaire "habituel") où les cœurs ne voient pas nécessairement tous la même mémoire et il peut y avoir des exigences sur la récupération de code à partir des embrayages d'autres cœurs, mais il est beaucoup plus facile à gérer simplement garder la tâche épinglée au cœur (l'architecture AFAIK Sony PS3 avec SPU est comme ça).

5
Pasi Savolainen

Le projet OpenMPI a une bibliothèque pour définir l'affinité du processeur on Linux de manière portable.

Il y a quelques temps, je l'ai utilisé dans un projet et cela a bien fonctionné.

Avertissement: Je me souviens vaguement qu'il y avait des problèmes pour savoir comment le système d'exploitation numérotait les cœurs. Je l'ai utilisé dans un système CPU 2 Xeon avec 4 cœurs chacun.

Un coup d'oeil cat /proc/cpuinfo pourrait aider. Sur la boite que j'ai utilisée, c'est assez bizarre. La sortie réduite est à la fin.

Évidemment, les cœurs numérotés uniformément sont sur le premier processeur et les cœurs impair sont sur le deuxième processeur. 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 les processeurs virtuels 0 et 2 ont partagé un cache L2, 1 et 3 ont partagé un, 4 et 6 ont partagé un et 5 et 7 en ont partagé un.

En raison 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 type de réglage de bas niveau. Cependant, il existe clairement des utilisations. Si votre code fonctionne sur quelques types de machines, il peut être utile de faire ce type de réglage. Une autre application serait dans un langage spécifique à un domaine comme StreamIt où le compilateur pourrait faire ce sale boulot et calculer un calendrier 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
4
Manuel

Pour connaître le nombre de processeurs au lieu d'utiliser/proc/cpuinfo, lancez simplement:

nproc

Pour exécuter un processus sur un groupe de processeurs spécifiques:

taskset --cpu-list 1,2 my_command 

dira que ma commande ne peut fonctionner que sur cpu 1 ou 2.

Pour exécuter un programme sur 4 processeurs en 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

Un bon exemple de cela concerne 8 millions d'opérations 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 de chaque processus en installant htop en utilisant apt-get/yum et en exécutant la ligne de commande:

 htop
3
Eamonn Kenny

Comme d'autres l'ont mentionné, il est contrôlé par le système d'exploitation. Selon le système d'exploitation, il peut ou non vous fournir des appels système qui vous permettent d'affecter le cœur sur lequel s'exécute un processus donné. Cependant, vous devez généralement laisser le système d'exploitation faire le comportement par défaut. Si vous avez un système à 4 cœurs avec 37 processus en cours d'exécution et que 34 de ces processus sont en veille, il va planifier les 3 processus actifs restants sur des cœurs séparés.

Vous ne verrez probablement un gain de vitesse qu'en jouant avec les affinités principales dans des applications multithread très spécialisées. Par exemple, supposons que vous ayez un système avec 2 processeurs dual-core. Supposons que vous ayez une application avec 3 threads et que deux threads fonctionnent fortement sur le même ensemble de données, tandis que le troisième thread utilise un ensemble de données différent. Dans ce cas, vous bénéficieriez le plus en ayant les deux threads qui interagissent sur le même processeur et le troisième thread sur l'autre processeur, car ils peuvent alors partager un cache. Le système d'exploitation n'a aucune idée de la mémoire à laquelle chaque thread doit accéder, il peut donc ne pas allouer les threads aux cœurs de manière appropriée.

Si vous êtes intéressé par comment le système d'exploitation, lisez la suite planification . Les moindres détails du multitraitement sur x86 peuvent être trouvés dans les Intel 64 et IA-32 Architectures Software Developer's Manuals . Les chapitres 7 et 8 du volume 3A contiennent des informations pertinentes, mais gardez à l'esprit que ces manuels sont extrêmement techniques.

2
Adam Rosenfield

L'OS sait comment faire cela, vous n'avez pas à le faire. Vous pouvez rencontrer toutes sortes de problèmes si vous spécifiez sur quel cœur exécuter, dont certains pourraient en fait ralentir le processus. Laissez le système d'exploitation le comprendre, il vous suffit de démarrer le nouveau thread.

Par exemple, si vous demandiez à un processus de démarrer sur le noyau x, mais que le noyau x était déjà sous une lourde charge, votre situation serait pire que si vous veniez de laisser le système d'exploitation le gérer.

1
Ed S.

Je ne connais pas les instructions de montage. Mais la fonction API Windows est SetProcessAffinityMask . Vous pouvez voir n exemple de quelque chose que j'ai bricolé il y a quelque temps pour exécuter Picasa sur un seul cœur

1
Will Rickards

Linux sched_setaffinity Exemple minimal exécutable C

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;
}

Compilez et exécutez avec:

gcc -std=c99 main.c
./a.out

Exemple de sortie:

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:

  • au départ, tous mes 16 cœurs étaient activés, et le processus s'exécutait de manière aléatoire sur le noyau 9 (le 10e)
  • après avoir défini l'affinité pour le premier cœur uniquement, le processus a été nécessairement déplacé vers le cœur 0 (le premier)

Il est également amusant d'exécuter ce programme via taskset:

taskset -c 1,3 ./a.out

Ce qui donne une sortie de 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 que cela a limité l'affinité dès le départ.

Cela fonctionne car l'affinité est héritée par les processus enfants, ce que taskset est en train de bifurquer: Comment empêcher l'héritage de l'affinité CPU par le processus enfant forké?

Testé dans Ubuntu 16.04, GitHub en amont .

x86 métal nu

Si vous êtes aussi inconditionnel: A quoi ressemble le langage d'assemblage multicœur?

Comment Linux l'implémente

Comment fonctionne sched_setaffinity ()?