A quoi ressemble le langage d'assemblage multicore?

il était une fois, pour écrire x86 assembleur, par exemple, vous auriez des instructions indiquant "charger le registre EDX avec la valeur 5", "increment the EDX" registre, etc.

avec des CPU modernes qui ont 4 noyaux (ou même plus), au niveau du code machine, est-ce qu'il y a juste 4 CPU séparés (c'est-à-dire y a-t-il seulement 4 registres "EDX" distincts) ? Si oui, quand vous dites "incrémenter le registre EDX", qu'est-ce qui détermine quel registre EDX du CPU est incrémenté? Être il y a un concept de" contexte CPU "ou de" thread " dans l'assembleur x86 maintenant?

Comment fonctionne la communication / synchronisation entre les noyaux?

si vous écriviez un système d'exploitation, quel mécanisme est exposé via le matériel pour vous permettre de programmer l'exécution sur différents noyaux? S'agit-il d'une ou de plusieurs instructions spéciales privilégiées?

si vous écriviez un compilateur d'optimisation / bytecode VM pour un CPU multicore, que devez-vous savoir? spécifiquement sur, disons, x86 pour le faire générer du code qui fonctionne efficacement à travers tous les cœurs?

quelles modifications ont été apportées au code machine x86 pour prendre en charge la fonctionnalité multi-core?

195

10 réponses

Ce n'est pas une réponse directe à la question, mais c'est une réponse à une question qui apparaît dans les commentaires. Essentiellement, la question Est de savoir ce que le support matériel donne à l'opération multi-threadée.

Nicholas Flynt avait raison , au moins en ce qui concerne x86. Dans un environnement multi-threadé (hyperfiletage, multi-core ou multi-processeur), le Bootstrap thread (généralement filetage 0 dans le cœur 0 dans le processeur 0) démarre la récupération du code à partir de l'adresse 0xfffffff0 . Tous les autres fils commencent dans un État de sommeil spécial appelé Wait-for-SIPI . Dans le cadre de son initialisation, le thread primaire envoie un inter-processeur-interruption (IPI) spécial sur L'APIC appelé un SIPI (IPI de démarrage) à chaque thread qui est dans WFS. Le SIPI contient l'adresse à partir de laquelle ce thread devrait commencer à récupérer le code.

ce mécanisme permet à chaque thread d'exécuter du code à partir d'un autre adresse. Tout ce qui est nécessaire est le soutien de logiciel pour chaque fil pour mettre en place ses propres tables et des files d'attente de messagerie. L'OS utilise ceux pour faire la programmation multi-filetée réelle.

en ce qui concerne l'assemblage proprement dit, Comme L'a écrit Nicholas, il n'y a pas de différence entre les assemblages pour une seule application filetée ou multi-filetée. Chaque fil logique a son propre jeu de registre, ainsi écrit:

mov edx, 0

ne mettra à jour EDX que pour le fil courant . Il n'y a aucun moyen de modifier EDX sur un autre processeur en utilisant une seule instruction d'assemblage. Vous avez besoin d'une sorte d'appel système pour demander à L'OS de dire à un autre thread d'exécuter du code qui mettra à jour son propre EDX .

114
répondu Nathan Fellman 2017-05-23 12:18:21

si je comprends bien, chaque "noyau" est un processeur, avec son propre registre. Fondamentalement, le BIOS commence avec un noyau en cours d'exécution, puis le système d'exploitation peut "démarrer" d'autres noyaux en les initialisant et en les pointant vers le code à exécuter, etc.

la synchronisation est faite par L'OS. Généralement, chaque processeur exécute un processus différent pour L'OS, de sorte que la fonctionnalité multi-threading du système d'exploitation est en charge de décider ce qui processus obtient de toucher quel souvenir, et ce qu'il faut faire dans le cas d'une collision de mémoire.

42
répondu Nicholas Flynt 2009-06-11 13:21:22

Minimal praticable Intel x86 métal nu exemple

exemple en métal nu avec toutes les plaques de protection requises . Toutes les parties principales sont traitées ci-dessous.

testé sur Ubuntu 15.10 QEMU 2.3.0 et Lenovo ThinkPad T400.

Le Intel Manuel Volume 3-Guide de Programmation Système - 325384-056US septembre 2015 couvre des SMP dans les chapitres 8, 9 et 10.

Tableau 8-1. "Broadcast INIT-SIPI-SIPI Sequence and Choice of Timeouts" contient un exemple qui ne fait que fonctionner:

MOV ESI, ICR_LOW    ; Load address of ICR low dword into ESI.
MOV EAX, 000C4500H  ; Load ICR encoding for broadcast INIT IPI
                    ; to all APs into EAX.
MOV [ESI], EAX      ; Broadcast INIT IPI to all APs
; 10-millisecond delay loop.
MOV EAX, 000C46XXH  ; Load ICR encoding for broadcast SIPI IP
                    ; to all APs into EAX, where xx is the vector computed in step 10.
MOV [ESI], EAX      ; Broadcast SIPI IPI to all APs
; 200-microsecond delay loop
MOV [ESI], EAX      ; Broadcast second SIPI IPI to all APs
                    ; Waits for the timer interrupt until the timer expires

sur ce code:

  1. la plupart des systèmes d'exploitation rendront la plupart de ces opérations impossibles à partir du cycle 3 (programmes utilisateurs).

    vous devez donc écrire votre propre noyau pour jouer librement avec lui: un programme Linux en userland ne fonctionnera pas.

  2. dans un premier temps, un seul processeur s'exécute, appelé Bootstrap processeur (BSP).

    il doit réveiller les autres (appelés processeurs D'Application (AP)) par des interruptions spéciales appelées interruptions entre processeurs (IPI) .

    ces interruptions peuvent être effectuées en programmant le contrôleur D'interruption Programmable avancé (APIC) par l'intermédiaire du registre de commande D'interruption (ICR)

    le format de L'ICR est documenté à: 10.6 "EMISSION INTERPROCESSOR INTERRUPTS"

    L'IPI se produit dès que nous écrire à l'ICR.

  3. ICR_LOW est défini à 8.4.4 ", les députés de l'Initialisation de l'Exemple" comme:

    ICR_LOW EQU 0FEE00300H
    

    La magie de la valeur 0FEE00300 est l'adresse mémoire de l'ICR, comme indiqué au Tableau 10-1 "Local APIC Registre de Carte d'Adresse"

  4. la méthode la plus simple possible est utilisée dans l'exemple: elle met en place L'ICR pour envoyer des IPI de diffusion qui sont livrés à tous les autres processeurs à l'exception du processeur actuel.

    mais il est également possible, et recommandé par certains , pour obtenir des informations sur les processeurs à travers des structures de données spéciales configurées par le BIOS comme tableaux ACPI ou table de configuration MP Intel et seulement réveiller ceux dont vous avez besoin d'un par un.

  5. XX dans 000C46XXH code pour l'adresse de la première instruction que le processeur exécutera comme:

    CS = XX * 0x100
    IP = 0
    

    rappelez-vous que CS adresses multiples par 0x10 , de sorte que l'adresse mémoire réelle de la première instruction est:

    XX * 0x1000
    

    donc si par exemple XX == 1 , le processeur démarre à 0x1000 .

    nous devons ensuite nous assurer qu'il existe un code de mode réel de 16 bits à exécuter à cet emplacement mémoire, p.ex. avec:

    cld
    mov $init_len, %ecx
    mov $init, %esi
    mov 0x1000, %edi
    rep movsb
    
    .code16
    init:
        xor %ax, %ax
        mov %ax, %ds
        /* Do stuff. */
        hlt
    .equ init_len, . - init
    

    utiliser un script linker est une autre possibilité.

  6. les boucles delay sont une partie ennuyeuse à travailler: il n'y a pas de façon très simple de faire de tels sleeps précisément.

    les méthodes possibles comprennent:

    • puits (utilisé dans mon exemple)
    • HPET
    • calibrer le temps d'une boucle occupée avec ce qui précède ,et l'utiliser à la place

    Related: Comment afficher un numéro sur l'écran et de et de sommeil pendant une seconde avec DOS x86 assemblée?

  7. je pense que le processeur initial doit être en mode protégé pour que cela fonctionne pendant que nous écrivons pour adresser 0FEE00300H qui est trop élevé pour 16-bits

  8. pour communiquer entre les processeurs, nous pouvons utiliser un spinlock sur le processus principal, et modifier la serrure à partir du deuxième noyau.

    nous devrions nous assurer que la réécriture de la mémoire est faite, par exemple par wbinvd .

état partagé entre les transformateurs

8.7.1 "état des processeurs logiques" dit:

les fonctionnalités suivantes font partie de l'état architectural des processeurs logiques dans les processeurs Intel 64 ou IA-32 prise en charge de la technologie Intel Hyper-Threading. Les caractéristiques peuvent être subdivisées en trois groupes:

  • dupliqué pour chaque processeur logique
  • partagé par les processeurs logiques dans un processeur physique
  • partagé ou dupliqué, selon la mise en œuvre

les caractéristiques suivantes sont reproduites pour chaque processeur logique:

  • general purpose registers (EAX, EBX, ECX, EDX, ESI, EDI, ESP, and EBP)
  • registres de segments (CS, DS, SS, ES, FS, et GS)
  • EFLAGS and EIP registers. Notez que le CS et EIP/RIP enregistre pour chaque point logique du processeur flux d'instruction pour le thread exécuté par la logique processeur.
  • FPU x87 registres (ST0 à ST7, mot d'état mot de commande, mot de tag, de données d'opérande pointeur, et de l'instruction pointer)
  • les registres MMX (mm0 à MM7)
  • XMM registers (XMM0 through XMMM7) and the MXCSR register
  • registres de contrôle et registres d'indicateurs de table de système (GDTR, LD tr, IDTR, registre des tâches)
  • registres de débogage (DR0, DR1, DR2, DR3, DR6, DR7) et le contrôle de débogage MSRs
  • Machine check global status (IA32_MCG_STATUS) and machine check capability (IA32_MCG_CAP) MSRs
  • de l'horloge Thermique de la modulation et de gestion de l'Alimentation ACPI contrôle Msr
  • compteur d'horodatage MSRs
  • la plupart des autres registres MSR, y compris la page attribute table (PAT). Voir les exceptions ci-dessous.
  • registres APIC locaux.
  • autres registres à usage général (R8-R15), registres XMM( XMM8-XMM15), registre de contrôle, IA32_EFER on Processeurs Intel 64.

les caractéristiques suivantes sont partagées par les processeurs logiques:

  • enregistreurs de distance de type mémoire (MTRRs)

la question de savoir si les caractéristiques suivantes sont partagées ou dupliquées est propre à la mise en œuvre:

  • IA32_MISC_ENABLE MSR (adresse MSR 1A0H)
  • architecture de vérification automatique (MCA) MSRs (à l'exception des MSRs IA32_MCG_STATUS et IA32_MCG_CAP)
  • la Performance de suivi, de contrôle et de contre Msr

le partage de Cache est discuté à:

les hyperthréades Intel ont un plus grand partage de mémoire cache et de pipeline que les noyaux séparés: https://superuser.com/questions/133082/hyper-threading-and-dual-core-whats-the-difference/995858#995858

noyau Linux 4.2

la principale action d'initialisation semble être arch/x86/kernel/smpboot.c .

BRAS exemples

ARM semble être un peu plus facile à configurer que x86 car il a moins de frais généraux historiques, voici deux exemples de fonctionnement minimal:

TODO: examinez ces exemples, et mieux les expliquer ici.

ce document fournit quelques conseils sur l'utilisation de primitives de synchronisation de bras que vous pouvez ensuite utiliser pour faire des choses amusantes avec plusieurs noyaux: http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf

40

The Unofficial SMP FAQ stack overflow logo


une fois, pour écrire x86 assembleur, par exemple, vous auriez des instructions indiquant "charger le registre EDX avec la valeur 5", "incrémenter le registre EDX", etc. Avec les CPU modernes qui ont 4 noyaux (ou même plus), au niveau du code machine, est-ce qu'il semble juste qu'il y ait 4 CPU séparés (c'est-à-dire qu'il n'y a que 4 CPU distincts "EDX" registers)?

exactement. Il y a 4 jeux de registres, dont 4 pointeurs d'instructions séparés.

si oui, quand vous dites" incrémenter le registre EDX", qu'est-ce qui détermine quel registre EDX du CPU est incrémenté?

CPU qui a exécuté cette instruction, naturellement. Pensez-y comme 4 microprocesseurs entièrement différents qui partagent simplement le même mémoire.

Existe-t-il un concept de" contexte CPU "ou de" thread " dans l'assembleur x86 maintenant?

Pas de. L'assembleur traduit juste les instructions comme il l'a toujours fait. Pas de changements.

Comment fonctionne la communication/synchronisation entre les noyaux?

Puisqu'ils partagent la même mémoire, c'est surtout une question de la logique du programme. Bien qu'il existe maintenant un mécanisme d'interruption inter-processeur , il n'est pas nécessaire et n'était pas présent à l'origine dans les premiers systèmes dual-CPU x86.

si vous écrivez un système d'exploitation, quel mécanisme est exposé via le matériel pour vous permettre de programmer l'exécution sur différents noyaux?

l'ordonnanceur ne change pas, sauf qu'il est un peu plus attentivement sur les sections critiques et les types de serrures. Avant SMP, le code du noyau appellerait éventuellement scheduler, qui regarderait la file d'attente d'exécution et choisirait un processus à exécuter comme prochain thread. (Processus du noyau ressemblent beaucoup à des threads. Le noyau SMP exécute exactement le même code, un thread à la fois, c'est juste que maintenant le verrouillage de la section critique doit être sûr SMP pour être sûr que deux noyaux ne puissent pas accidentellement choisir le même PID.

S'agit-il d'instructions spéciales privilégiées?

Pas de. Les noyaux fonctionnent tous dans la même mémoire avec les mêmes vieilles instructions.

si vous écriviez un compilateur optimisant/bytecode VM pour un CPU multicore, que devez-vous savoir spécifiquement sur, disons, x86 pour le faire générer du code qui fonctionne efficacement à travers tous les noyaux?

vous exécutez le même code qu'avant. C'est le noyau Unix ou Windows qui devait changer.

vous pouvez résumer ma question comme suit: "quelles modifications ont été apportées au code machine x86 pour prendre en charge la fonctionnalité multi-core?"

rien n'était nécessaire. Les premiers systèmes SMP utilisaient exactement le même jeu d'instructions que les uniprocesseurs. Maintenant, il y a eu beaucoup d'évolution de l'architecture x86 et des millions de nouvelles instructions pour accélérer les choses, mais aucune n'était nécessaire pour SMP.

pour plus d'informations, voir la Spécification Intel multiprocesseur .


mise à jour: toutes les questions de suivi peuvent être répondues en acceptant complètement qu'un CPU n - voie multicore est presque 1 exactement la même chose que n processeurs séparés qui partagent juste la même mémoire. 2 il y avait une question importante qui n'a pas été posée: comment un programme est-il écrit pour fonctionner sur plus d'un noyau pour plus de performance? et la réponse est: il est écrit en utilisant une bibliothèque de thread comme Pthreads. certaines bibliothèques de thread utilisent des "threads verts" qui ne sont pas visibles sur le système D'exploitation, et qui n'obtiendront pas de noyaux séparés, mais tant que la bibliothèque thread utilise les fonctionnalités thread du noyau, votre programme thread sera automatiquement multicore.
1. Pour une compatibilité à rebours, seul le premier noyau démarre à la réinitialisation, et quelques actions de Type driver sont nécessaires pour démarrer les autres.

2. Ils partagent également tous les périphériques, naturellement.
33
répondu DigitalRoss 2016-09-16 23:00:08

chaque noyau s'exécute à partir d'une zone de mémoire différente. Votre système d'exploitation pointera un noyau à votre programme et le noyau exécutera votre programme. Votre programme ne sera pas conscient qu'il y a plus d'un noyau ou sur lequel il exécute.

il n'y a pas non plus d'instruction supplémentaire disponible uniquement pour le système d'exploitation. Ces noyaux sont identiques à simple cœur des puces. Chaque noyau exécute une partie du système D'exploitation qui traitera la communication à les zones mémoire communes utilisées pour l'échange d'information afin de trouver la zone mémoire suivante à exécuter.

il s'agit d'une simplification, mais elle vous donne l'idée de base de la façon dont il est fait. plus sur multicores et multiprocesseurs on Embedded.com a beaucoup d'informations sur ce sujet ... Cette rubrique se compliquer très rapidement!

9
répondu Gerhard 2009-06-11 13:49:35

si vous écrivez une optimisation compilateur / bytecode VM pour un multicore PROCESSEUR, de quoi avez vous besoin de connaître spécifiquement sur, disons, x86 à faire il génère du code qui fonctionne efficacement dans tous les cœurs?

comme quelqu'un qui écrit l'optimisation compilateur/bytecode VMs je peux être en mesure de vous aider ici.

vous n'avez pas besoin de savoir quoi que ce soit spécifiquement sur x86 pour le faire générer du code qui s'exécute efficace dans tous les cœurs.

cependant, vous pouvez avoir besoin de connaître cmpxchg et les amis afin d'écrire le code qui exécute correctement à travers tous les noyaux. La programmation Multicore nécessite l'utilisation de la synchronisation et de la communication entre les fils d'exécution.

vous pourriez avoir besoin de savoir quelque chose sur x86 pour le faire générer du code qui fonctionne efficacement sur x86 en général.

il y a d'autres choses qu'il vous serait utile d'apprendre:

vous devriez en savoir plus sur les facilités que le système D'exploitation (Linux ou Windows ou OSX) fournit pour vous permettre d'exécuter plusieurs threads. Vous devriez en savoir plus sur les API de parallélisation comme OpenMP et Threading Building Blocks, ou OSX 10.6 "Snow Leopard"'s à venir "Grand Central".

vous devriez considérer si votre compilateur devrait être auto-parallelising, ou si l'auteur des applications compilées par votre compilateur il a besoin d'ajouter des appels de syntaxe ou D'API dans son programme pour profiter des noyaux multiples.

9
répondu Alex Brown 2009-06-11 16:42:19

le code d'assemblage se traduira en code machine qui sera exécuté sur un noyau. Si vous voulez qu'il soit multithreaded vous devrez utiliser des primitives de système d'exploitation pour démarrer ce code sur différents processeurs plusieurs fois ou différents morceaux de code sur différents noyaux - chaque noyau exécutera un thread séparé. Chaque thread ne verra qu'un noyau sur lequel il est actuellement exécuté.

5
répondu sharptooth 2009-06-11 13:21:19

cela ne se fait pas du tout dans les instructions de la machine; les noyaux prétendent être des CPU distincts et n'ont pas de capacités spéciales pour parler les uns aux autres. Il y a deux façons de communiquer:

  • ils partagent l'espace d'adresse physique. Le matériel gère la cohérence du cache, donc un CPU écrit à une adresse mémoire qu'un autre lit.

  • contrôleur.) C'est la mémoire mappée dans l'espace d'adresse physique, et peut être utilisé par un processeur de contrôler les autres, les activer ou les désactiver, d'envoyer des interruptions, etc.

http://www.cheesecake.org/sac/smp.html est une bonne référence avec une url idiote.

2
répondu pjc50 2009-10-27 13:56:55

la principale différence entre une application monofiltre et une application multi - filetée est que la première a une pile et la seconde en a une pour chaque filetage. Le Code est généré un peu différemment puisque le compilateur supposera que les données et les registres de segments de pile (ds et ss) ne sont pas égaux. Cela signifie qu'indirectement par le biais des registres ebp et esp que par défaut au registre ss ne sera pas aussi par défaut à ds (parce que ds!= ss). À l'inverse, l'indirection par les autres registres qui par défaut à ds ne sera pas par défaut à ss.

les threads partagent tout le reste, y compris les données et les zones de code. Ils partagent également des routines lib donc assurez-vous qu'ils sont thread-safe. Une procédure qui trie une zone en RAM peut être multi-threadée pour accélérer les choses. Les threads accéderont alors, compareront et ordonneront les données dans la même zone de mémoire physique et exécuteront le même code, mais en utilisant des variables locales différentes pour contrôler leur partie respective du tri. Bien sûr, cela est parce que les threads ont différentes piles où les variables locales sont contenues. Ce type de programmation nécessite une adaptation soigneuse du code afin de réduire les collisions entre les données (dans les caches et la mémoire vive), ce qui se traduit par un code plus rapide avec deux ou plusieurs threads qu'avec un seul. Bien sûr, un non réglé code sera souvent plus rapide avec un processeur qu'avec deux ou plus. Déboguer est plus difficile parce que le point de rupture "int 3" standard ne sera pas applicable puisque vous voulez interrompre un fil spécifique et pas tous. Les points de rupture de registre de débogage ne résolvent pas ce problème non plus, sauf si vous pouvez les régler sur le processeur spécifique exécutant le thread spécifique que vous voulez interrompre.

autre code multi-threadé peut impliquer différents threads fonctionnant dans différentes parties du programme. Ce type de programmation ne nécessite pas le même type d'accord et est donc beaucoup plus facile à apprendre.

1
répondu Olof Forshell 2011-02-22 12:25:43

ce qui a été ajouté sur chaque architecture multiprocessing-capable par rapport aux variantes d'un seul processeur qui sont venus avant eux sont des instructions pour synchroniser entre les cœurs. De plus, vous avez des instructions pour gérer la cohérence du cache, les tampons de rinçage et les opérations de niveau bas similaires qu'un système D'exploitation doit gérer. Dans le cas d'architectures multithreaded simultanées comme IBM POWER6, IBM Cell, Sun Niagara, et Intel "Hyperthreading", vous avez également tendance à voir de nouvelles instructions pour prioriser entre les threads (comme définir les priorités et explicitement céder le processeur quand il n'y a rien à faire).

mais la sémantique de base du fil simple est la même, il vous suffit d'ajouter des installations supplémentaires pour gérer la synchronisation et la communication avec d'autres noyaux.

0
répondu jakobengblom2 2009-08-18 18:20:46