Çok çekirdekli montaj dili neye benziyor?


243

Bir zamanlar, örneğin x86 derleyicisi yazmak için, "EDX kaydını 5 değerine yükleyin", "EDX artışını artırın" kaydını vb. Belirten talimatlarınız olacaktır.

4 çekirdeğe (veya daha fazlasına) sahip modern CPU'larla, makine kodu düzeyinde sadece 4 ayrı CPU varmış gibi görünüyor (yani sadece 4 farklı "EDX" kaydı var mı?) Eğer öyleyse, "EDX kaydını artır" dediğinizde, hangi CPU'nun EDX kaydının artırıldığını belirleyen nedir? Şimdi x86 derleyicisinde "CPU bağlamı" veya "iş parçacığı" kavramı var mı?

Çekirdekler arasındaki iletişim / senkronizasyon nasıl çalışır?

Bir işletim sistemi yazıyorsanız, farklı çekirdeklerde yürütmeyi zamanlamanızı sağlayan donanım aracılığıyla hangi mekanizma ortaya çıkar? Bazı özel ayrıcalıklı talimatlar var mı?

Çok çekirdekli bir CPU için optimize edici bir derleyici / bayt kodu VM yazıyorsanız, tüm çekirdeklerde verimli çalışan kod üretmesini sağlamak için özellikle x86 hakkında ne bilmeniz gerekir?

Çok çekirdekli işlevselliği desteklemek için x86 makine kodunda ne gibi değişiklikler yapıldı?


2
Burada benzer (aynı olmasa da) bir soru var: stackoverflow.com/questions/714905/…
Nathan Fellman

Yanıtlar:


153

Bu soruya doğrudan bir cevap değildir, ancak yorumlarda görünen bir sorunun cevabıdır. Esasen soru, donanımın çok iş parçacıklı çalışmaya ne destek verdiğidir.

Nicholas Flynt haklıydı , en azından x86 ile ilgili. Çok iş parçacıklı bir ortamda (Hiper iş parçacığı, çok çekirdekli veya çok işlemcili), Bootstrap iş parçacığı (genellikle işlemci 0'da çekirdek 0'da 0 iş parçacığı) adresinden kod getirmeye başlar 0xfffffff0. Diğer tüm evreler SIPI için Bekle adında özel bir uyku durumunda başlar . Başlatma işleminin bir parçası olarak, birincil iş parçacığı, APIC üzerinden WFS'deki her iş parçacığına SIPI (Başlangıç ​​IPI) adı verilen özel bir işlemci arası kesinti (IPI) gönderir. SIPI, bu iş parçacığının kod getirmeye başlaması gereken adresi içerir.

Bu mekanizma, her bir iş parçacığının farklı bir adresten kod yürütmesine izin verir. Tek gereken, her bir iş parçacığının kendi tablolarını ve mesajlaşma kuyruklarını ayarlaması için yazılım desteğidir. İşletim sistemi bunları gerçek çok iş parçacıklı zamanlamayı yapmak için kullanır .

Gerçek derleme söz konusu olduğunda, Nicholas'ın yazdığı gibi, tek dişli veya çok dişli uygulama için derlemeler arasında fark yoktur. Her mantıksal iş parçacığının kendi kayıt kümesi vardır, bu nedenle yazma:

mov edx, 0

yalnızca EDXo anda çalışan iş parçacığı için güncellenir . EDXTek bir montaj talimatı kullanarak başka bir işlemcide değişiklik yapmanın bir yolu yoktur . İşletim sisteminden başka bir iş parçacığının kendisini güncelleyecek kodu çalıştırmasını söylemesini istemek için bir tür sistem çağrısına ihtiyacınız varEDX .


2
Nicholas'ın cevabındaki boşluğu doldurduğunuz için teşekkürler. Sizinkini şimdi kabul edilen cevap olarak işaretlediniz .... ilgilendiğim belirli ayrıntıları veriyor ... bilgilerinizin ve Nicholas'ın bir araya geldiği tek bir cevap olsaydı daha iyi olurdu.
Paul Hollingsworth

3
Bu, iş parçacıklarının nereden geldiği sorusuna cevap vermez. Çekirdekler ve işlemciler donanımsal bir şeydir, ancak bir şekilde iş parçacıklarının yazılımda oluşturulması gerekir. Birincil iş parçacığı SIPI'yi nereye göndereceğini nasıl biliyor? Yoksa SIPI'nin kendisi yeni bir iş parçacığı mı oluşturuyor?
zengin hatırlatıcı

7
@richremer: HW iş parçacıklarını ve SW iş parçacıklarını karıştırıyorsunuz. HW dişi daima mevcuttur. Bazen uyuyor. SIPI'nin kendisi HW iş parçacığını uyandırır ve SW'yi çalıştırmasına izin verir. Hangi HW iş parçacıklarının çalıştığına ve her HW iş parçacığında hangi işlemlerin ve SW iş parçacıklarının çalıştığına karar vermek OS ve BIOS'a bağlıdır.
Nathan Fellman

2
Burada iyi ve özlü bilgi var, ama bu büyük bir konu - bu yüzden sorular oyalanabilir. USB sürücülerden veya "disket" disklerden önyükleme yapan vahşi "tam kemikler" çekirdeklerinin birkaç örneği var - burada gerçekten çok iş parçacıklı C kodu ( github. com.tr / duanev / oz-x86-32-asm-003 ) ancak standart kütüphane desteği yoktur. İstediğinizden biraz daha fazla ama belki de o kalan soruların bazılarını cevaplayabilir.
duanev

87

Intel x86 minimum çalıştırılabilir baremetal örneği

Gerekli tüm kazan plakası ile çalıştırılabilir çıplak metal örneği . Tüm büyük parçalar aşağıda ele alınmıştır.

Ubuntu 15.10 QEMU 2.3.0 ve Lenovo ThinkPad T400 üzerinde test edildi gerçek donanım konuğu .

325384-056US Eylül 2015 - Intel Manuel Cilt 3 Sistem Programlama Kılavuzu bölümlerde 8, 9 ve 10'da kapakları SMP.

Tablo 8-1. "Yayın INIT-SIPI-SIPI Sırası ve Zaman Aşımlarının Seçimi" temel olarak sadece çalışan bir örnek içerir:

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

Bu kodda:

  1. Çoğu işletim sistemi bu işlemlerin çoğunu halka 3'ten (kullanıcı programları) imkansız hale getirecektir.

    Bu nedenle, özgürce oynamak için kendi çekirdeğinizi yazmanız gerekir: bir kullanıcı ülkesi Linux programı çalışmaz.

  2. İlk başta, bootstrap işlemci (BSP) adı verilen tek bir işlemci çalışır.

    Diğerlerini (Uygulama İşlemcileri (AP) olarak adlandırılır) denilen özel kesintilerle uyandırmalıdır. İşlemciler Arası Kesmeler (IPI) .

    Bu kesintiler, Kesinti komut kaydı (ICR) aracılığıyla Gelişmiş Programlanabilir Kesme Denetleyicisi (APIC) programlanarak yapılabilir

    ICR'nin biçimi şu adreste belgelenmiştir: 10.6 "ENTERPROCESSOR KESİNTİLERİN SORUNU"

    IPI, ICR'ye yazdığımız anda olur.

  3. ICR_LOW, 8.4.4 "MP Başlatma Örneği" de şu şekilde tanımlanır:

    ICR_LOW EQU 0FEE00300H
    

    Sihirli değer 0FEE00300, Tablo 10-1 "Yerel APIC Kayıt Adres Haritası" nda belgelendiği gibi ICR'nin bellek adresidir.

  4. Örnekte mümkün olan en basit yöntem kullanılır: ICR'yi, geçerli olan dışındaki tüm diğer işlemcilere gönderilen yayın IPI'lerini gönderecek şekilde ayarlar.

    Ancak, bazıları tarafından, ACPI tabloları veya Intel'in MP yapılandırma tablosu gibi BIOS tarafından ayarlanan özel veri yapıları aracılığıyla işlemciler hakkında bilgi almak ve yalnızca tek tek ihtiyacınız olanları uyandırmak da mümkündür.

  5. XXin 000C46XXH, işlemcinin yürüteceği ilk komutun adresini kodlar:

    CS = XX * 0x100
    IP = 0
    

    CS'nin adresleri ile çarptığını0x10 unutmayın , bu nedenle ilk talimatın gerçek bellek adresi:

    XX * 0x1000
    

    Yani, örneğin XX == 1, işlemci0x1000 .

    Ardından, o bellek konumunda çalıştırılacak 16 bit gerçek mod kodunun olduğundan emin olmalıyız, örneğin:

    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
    

    Bir bağlayıcı komut dosyası kullanmak başka bir olasılıktır.

  6. Gecikme döngüleri çalışmaya başlamak için can sıkıcı bir parçadır: bu tür uykuları tam olarak yapmanın süper basit bir yolu yoktur.

    Olası yöntemler şunları içerir:

    • PIT (örneğimde kullanılır)
    • HPET
    • meşgul döngüsünün süresini yukarıdakilerle kalibre edin ve bunun yerine kullanın

    İlgili: Nasıl ekranda bir sayı görüntülemek ve DOS x86 derleme ile bir saniye uyku?

  7. 0FEE00300H16 bit için çok yüksek olan adrese yazarken bunun için ilk işlemcinin korumalı modda olması gerektiğini düşünüyorum

  8. İşlemciler arasında iletişim kurmak için, ana işlemde bir spinlock kullanabilir ve kilidi ikinci çekirdekten değiştirebiliriz.

    Belleğe geri yazma işleminin yapılmasını sağlamalıyız, örn wbinvd.

İşlemciler arasında paylaşılan durum

8.7.1 "Mantıksal İşlemcilerin Durumu" diyor:

Aşağıdaki özellikler, Intel Hyper-Threading Teknolojisini destekleyen Intel 64 veya IA-32 işlemcilerdeki mantıksal işlemcilerin mimari durumunun bir parçasıdır. Özellikler üç gruba ayrılabilir:

  • Her mantıksal işlemci için çoğaltılmış
  • Fiziksel bir işlemcideki mantıksal işlemciler tarafından paylaşılıyor
  • Uygulamaya bağlı olarak paylaşılan veya çoğaltılan

Her mantıksal işlemci için aşağıdaki özellikler çoğaltılır:

  • Genel amaçlı kayıtlar (EAX, EBX, ECX, EDX, ESI, EDI, ESP ve EBP)
  • Segment kayıtları (CS, DS, SS, ES, FS ve GS)
  • EFLAGS ve EIP kayıtları. Her mantıksal işlemci için CS ve EIP / RIP kayıtlarının, mantıksal işlemci tarafından yürütülen iş parçacığının talimat akışını gösterdiğini unutmayın.
  • x87 FPU kayıtları (ST0 - ST7, durum word'ü, kontrol word'ü, etiket word'ü, veri işlenen işaretçisi ve talimat işaretçisi)
  • MMX kayıtları (MM0 - MM7)
  • XMM kayıtları (XMM0 - XMM7) ve MXCSR kaydı
  • Kontrol kayıtları ve sistem tablosu işaretçi kayıtları (GDTR, LDTR, IDTR, görev kaydı)
  • Hata ayıklama kayıtları (DR0, DR1, DR2, DR3, DR6, DR7) ve hata ayıklama denetimi MSR'leri
  • Makine kontrolü genel durumu (IA32_MCG_STATUS) ve makine kontrolü kapasitesi (IA32_MCG_CAP) MSR'ler
  • Termal saat modülasyonu ve ACPI Güç yönetimi kontrolü MSR'ler
  • Zaman damgası sayacı MSR'leri
  • Sayfa özellik tablosu (PAT) dahil olmak üzere diğer MSR kayıtlarının çoğu. Aşağıdaki istisnalara bakın.
  • Yerel APIC kayıtları.
  • Ek genel amaçlı kayıtlar (R8-R15), XMM kayıtları (XMM8-XMM15), kontrol kaydı, Intel 64 işlemcilerde IA32_EFER.

Aşağıdaki özellikler mantıksal işlemciler tarafından paylaşılır:

  • Bellek tipi aralık kayıtları (MTRR'ler)

Aşağıdaki özelliklerin paylaşılması veya çoğaltılması, uygulamaya özgüdür:

  • IA32_MISC_ENABLE MSR (MSR adresi 1A0H)
  • Makine kontrol mimarisi (MCA) MSR'leri (IA32_MCG_STATUS ve IA32_MCG_CAP MSR'ler hariç)
  • Performans izleme kontrolü ve karşı MSR'ler

Önbellek paylaşımı:

Intel hyperthreads, ayrı çekirdeklerden daha fazla önbellek ve boru hattı paylaşımına sahiptir: /superuser/133082/hyper-threading-and-dual-core-whats-the-difference/995858#995858

Linux çekirdeği 4.2

Ana başlatma eylemi gerçekleşiyor gibi görünüyor arch/x86/kernel/smpboot.c.

ARM minimal çalıştırılabilir baremetal örneği

Burada QEMU için minimal runnable ARMv8 aarch64 örneği sağlıyorum:

.global mystart
mystart:
    /* Reset spinlock. */
    mov x0, #0
    ldr x1, =spinlock
    str x0, [x1]

    /* Read cpu id into x1.
     * TODO: cores beyond 4th?
     * Mnemonic: Main Processor ID Register
     */
    mrs x1, mpidr_el1
    ands x1, x1, 3
    beq cpu0_only
cpu1_only:
    /* Only CPU 1 reaches this point and sets the spinlock. */
    mov x0, 1
    ldr x1, =spinlock
    str x0, [x1]
    /* Ensure that CPU 0 sees the write right now.
     * Optional, but could save some useless CPU 1 loops.
     */
    dmb sy
    /* Wake up CPU 0 if it is sleeping on wfe.
     * Optional, but could save power on a real system.
     */
    sev
cpu1_sleep_forever:
    /* Hint CPU 1 to enter low power mode.
     * Optional, but could save power on a real system.
     */
    wfe
    b cpu1_sleep_forever
cpu0_only:
    /* Only CPU 0 reaches this point. */

    /* Wake up CPU 1 from initial sleep!
     * See:https://github.com/cirosantilli/linux-kernel-module-cheat#psci
     */
    /* PCSI function identifier: CPU_ON. */
    ldr w0, =0xc4000003
    /* Argument 1: target_cpu */
    mov x1, 1
    /* Argument 2: entry_point_address */
    ldr x2, =cpu1_only
    /* Argument 3: context_id */
    mov x3, 0
    /* Unused hvc args: the Linux kernel zeroes them,
     * but I don't think it is required.
     */
    hvc 0

spinlock_start:
    ldr x0, spinlock
    /* Hint CPU 0 to enter low power mode. */
    wfe
    cbz x0, spinlock_start

    /* Semihost exit. */
    mov x1, 0x26
    movk x1, 2, lsl 16
    str x1, [sp, 0]
    mov x0, 0
    str x0, [sp, 8]
    mov x1, sp
    mov w0, 0x18
    hlt 0xf000

spinlock:
    .skip 8

GitHub akış yukarı .

Birleştirin ve çalıştırın:

aarch64-linux-gnu-gcc \
  -mcpu=cortex-a57 \
  -nostdlib \
  -nostartfiles \
  -Wl,--section-start=.text=0x40000000 \
  -Wl,-N \
  -o aarch64.elf \
  -T link.ld \
  aarch64.S \
;
qemu-system-aarch64 \
  -machine virt \
  -cpu cortex-a57 \
  -d in_asm \
  -kernel aarch64.elf \
  -nographic \
  -semihosting \
  -smp 2 \
;

Bu örnekte, CPU 0'ı bir spinlock döngüsüne koyduk ve sadece CPU 1 ile çıkar, spinlock'u serbest bırakır.

Döndürme kilidinden sonra, CPU 0, QEMU'dan çıkmayı sağlayan bir yarı ana bilgisayar çıkış çağrısı yapar.

QEMU'yu yalnızca bir CPU ile başlatırsanız -smp 1, simülasyon sadece sonsuza dek kilitlenir.

CPU 1, PSCI arabirimi ile uyandırılır, daha fazla bilgi için: ARM: Başlat / Uyandır / Diğer CPU çekirdeklerini / AP'lerini getir ve yürütme başlangıç ​​adresini geç?

memba versiyon siz de performans özellikleri ile deneme yapabilecekleri de, bu gem5 üzerinde çalışmasını sağlamak için birkaç ince ayar vardır.

Gerçek donanımda test etmedim, bu yüzden ne kadar taşınabilir olduğundan emin değilim. Aşağıdaki Raspberry Pi bibliyografyası ilgi çekici olabilir:

Bu belge, birden çok çekirdekli eğlenceli şeyler yapmak için kullanabileceğiniz ARM senkronizasyon ilkelerini kullanma konusunda bazı rehberlik sağlar: http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf

Ubuntu 18.10, GCC 8.2.0, Binutils 2.31.1, QEMU 2.12.0'da test edildi.

Daha rahat programlanabilirlik için sonraki adımlar

Önceki örnekler ikincil CPU'yu uyandırır ve özel talimatlarla temel bellek senkronizasyonu yapar, bu da iyi bir başlangıçtır.

Ancak, POSIX gibi çok çekirdekli sistemleri programlamayı kolaylaştırmak için pthreads, aşağıdaki daha ilgili konulara da girmeniz gerekir:

  • kurulum kesintiye uğrar ve şimdi hangi iş parçacığının çalışacağına periyodik olarak karar veren bir zamanlayıcı çalıştırır. Bu, önleyici çoklu kullanım olarak bilinir .

    Bu sistem ayrıca başlatılıp durduğunda iş parçacığı kayıtlarını kaydetmeli ve geri yüklemelidir.

    Önleyici olmayan çoklu görev sistemlerine sahip olmak da mümkündür, ancak bunlar her bir iş parçacığının vermesi için kodunuzu değiştirmenizi gerektirebilir (örneğin bir pthread_yielduygulama ile) ve iş yüklerini dengelemek zorlaşır.

    İşte bazı basit çıplak metal zamanlayıcı örnekleri:

  • hafıza çatışmalarıyla başa çıkmak. Özellikle, C veya diğer üst düzey dillerde kodlamak istiyorsanız , her bir iş parçacığının benzersiz bir yığına ihtiyacı olacaktır .

    Sadece iş parçacıklarını sabit bir maksimum yığın boyutuna sahip olacak şekilde sınırlayabilirsiniz, ancak bununla başa çıkmanın en güzel yolu verimli "sınırsız boyut" yığınlarına izin veren sayfalamadır .

    İşte yığın çok derinleşirse patlayacak saf bir aarch64 baremetal örneği.

Bunlar Linux çekirdeğini veya başka bir işletim sistemini kullanmak için iyi nedenlerdir :-)

Kullanıcı alanı bellek senkronizasyonu ilkelleri

İş parçacığı başlatma / durdurma / yönetimi genellikle kullanıcı alanı kapsamının dışında olsa da, potansiyel olarak daha pahalı sistem çağrıları yapmadan bellek erişimlerini senkronize etmek için kullanıcı alanı iş parçacıklarından gelen montaj talimatlarını kullanabilirsiniz.

Elbette bu düşük seviyeli ilkelleri portatif olarak saran kütüphaneleri kullanmayı tercih etmelisiniz. C ++ standardının kendisi <mutex>ve <atomic>üstbilgilerinde ve özelliklestd::memory_order . Ulaşılabilen tüm olası bellek anlambilimini kapsayıp kapsamadığından emin değilim, ama olabilir.

Daha ince semantikler, belirli durumlarda performans avantajları sunabilen kilitsiz veri yapıları bağlamında özellikle önemlidir . Bunları uygulamak için, farklı bellek engelleri hakkında biraz bilgi edinmeniz gerekecektir: https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/

Örneğin Boost'un kilitsiz konteyner uygulamaları var: https://www.boost.org/doc/libs/1_63_0/doc/html/lockfree.html

Bu tür kullanıcı alanı talimatları, Linux'taki futexana senkronizasyon ilkelerinden biri olan Linux sistem çağrısını uygulamak için de kullanılıyor gibi görünmektedir . man futex4.15 okur:

Futex () sistem çağrısı, belirli bir koşul gerçekleşene kadar beklemek için bir yöntem sağlar. Genellikle paylaşılan bellek senkronizasyonu bağlamında bir engelleme yapısı olarak kullanılır. Futexleri kullanırken, senkronizasyon işlemlerinin çoğu kullanıcı alanında gerçekleştirilir. Bir kullanıcı-alanı programı futex () sistem çağrısını yalnızca, koşul gerçekleşene kadar programın daha uzun bir süre engellenmesi gerektiğinde kullanır. Belirli bir koşulu bekleyen işlemleri veya iş parçacıklarını uyandırmak için diğer futex () işlemleri kullanılabilir.

Sistem çağrısı adının kendisi "Hızlı Kullanıcı Alanı XXX" anlamına gelir.

İşte bu tür talimatların çoğunlukla eğlence için temel kullanımını gösteren satır içi montajlı minimal işe yaramaz C ++ x86_64 / aarch64 örneği:

main.cpp

#include <atomic>
#include <cassert>
#include <iostream>
#include <thread>
#include <vector>

std::atomic_ulong my_atomic_ulong(0);
unsigned long my_non_atomic_ulong = 0;
#if defined(__x86_64__) || defined(__aarch64__)
unsigned long my_arch_atomic_ulong = 0;
unsigned long my_arch_non_atomic_ulong = 0;
#endif
size_t niters;

void threadMain() {
    for (size_t i = 0; i < niters; ++i) {
        my_atomic_ulong++;
        my_non_atomic_ulong++;
#if defined(__x86_64__)
        __asm__ __volatile__ (
            "incq %0;"
            : "+m" (my_arch_non_atomic_ulong)
            :
            :
        );
        // https://github.com/cirosantilli/linux-kernel-module-cheat#x86-lock-prefix
        __asm__ __volatile__ (
            "lock;"
            "incq %0;"
            : "+m" (my_arch_atomic_ulong)
            :
            :
        );
#elif defined(__aarch64__)
        __asm__ __volatile__ (
            "add %0, %0, 1;"
            : "+r" (my_arch_non_atomic_ulong)
            :
            :
        );
        // https://github.com/cirosantilli/linux-kernel-module-cheat#arm-lse
        __asm__ __volatile__ (
            "ldadd %[inc], xzr, [%[addr]];"
            : "=m" (my_arch_atomic_ulong)
            : [inc] "r" (1),
              [addr] "r" (&my_arch_atomic_ulong)
            :
        );
#endif
    }
}

int main(int argc, char **argv) {
    size_t nthreads;
    if (argc > 1) {
        nthreads = std::stoull(argv[1], NULL, 0);
    } else {
        nthreads = 2;
    }
    if (argc > 2) {
        niters = std::stoull(argv[2], NULL, 0);
    } else {
        niters = 10000;
    }
    std::vector<std::thread> threads(nthreads);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i] = std::thread(threadMain);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i].join();
    assert(my_atomic_ulong.load() == nthreads * niters);
    // We can also use the atomics direclty through `operator T` conversion.
    assert(my_atomic_ulong == my_atomic_ulong.load());
    std::cout << "my_non_atomic_ulong " << my_non_atomic_ulong << std::endl;
#if defined(__x86_64__) || defined(__aarch64__)
    assert(my_arch_atomic_ulong == nthreads * niters);
    std::cout << "my_arch_non_atomic_ulong " << my_arch_non_atomic_ulong << std::endl;
#endif
}

GitHub akış yukarı .

Olası çıktı:

my_non_atomic_ulong 15264
my_arch_non_atomic_ulong 15267

Bundan, x86 LOCK öneki / aarch64 LDADDkomutunun ekleme atomunu yaptığını görüyoruz : onsuz birçok eklemde yarış koşulları var ve sondaki toplam sayı senkronize 20000'den daha az.

Ayrıca bakınız:

Ubuntu 19.04 amd64 ve QEMU aarch64 kullanıcı modunda test edilmiştir.


Örneğinizi derlemek için hangi montajcıyı kullanıyorsunuz? GAS, #include(yorum olarak alıyor), NASM, FASM, YASM, AT&T sözdizimini bilmiyor gibi görünüyor , bu yüzden onlar olamaz ... yani ne?
Ruslan

@Ruslan gcc, #includeC ön işlemcisinden geliyor. MakefileSağlananları başlangıç ​​bölümünde açıklanan şekilde kullanın : github.com/cirosantilli/x86-bare-metal-examples/blob/… Bu işe yaramazsa, bir GitHub sorunu açın.
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

x86'da, bir çekirdek kuyrukta çalışmaya hazır başka işlem olmadığını fark ederse ne olur? (zaman zaman atıl bir sistemde olabilir). Çekirdek, yeni bir görev olana kadar paylaşılan bellek yapısında dönüyor mu? (muhtemelen iyi değil, çok fazla güç kullanacaktır) bir kesinti olana kadar uyumak için HLT gibi bir şey mi çağırıyor? (bu durumda kim bu çekirdeği uyandırmaktan sorumlu?)
tigrou

@tigrou emin değilim, ancak Linux uygulamasının bir sonraki (muhtemelen zamanlayıcı) kesintiye kadar, özellikle gücün anahtar olduğu ARM'de bir güç durumuna geçmesini son derece olası görüyorum. Linux çalıştıran bir simülatörün talimat izlemesi ile somut olarak kolayca gözlemlenip gözlemlenemeyeceğini çabucak denemek isterim: github.com/cirosantilli/linux-kernel-module-cheat/tree/…
Ciro Santilli 郝海东 冠状 病法轮功 事件 法轮功

1
Bazı bilgileri (x86 / Windows'a özgü) burada bulabilirsiniz (bkz. "Boşta İş Parçacığı"). TL; DR: CPU'da çalıştırılabilir iş parçacığı yoksa, CPU boş bir iş parçacığına gönderilir. Diğer bazı görevlerle birlikte, nihayetinde kayıtlı güç yönetimi işlemci boşta rutinini çağırır (CPU satıcısı tarafından sağlanan bir sürücü aracılığıyla, örneğin: Intel). Bu, güç tüketimini azaltmak için CPU'yu daha derin bir C durumuna (örneğin: C0 -> C3) geçirebilir.
tigrou

43

Anladığım kadarıyla, her "çekirdek" kendi kayıt kümesiyle eksiksiz bir işlemcidir. Temel olarak, BIOS sizi bir çekirdek çalışırken başlatır ve daha sonra işletim sistemi diğer çekirdekleri başlatarak ve çalıştırılacak koda işaret ederek "başlatabilir" vb.

Senkronizasyon işletim sistemi tarafından yapılır. Genellikle, her işlemci işletim sistemi için farklı bir işlem yürütür, bu nedenle işletim sisteminin çoklu iş parçacığı işlevselliği, hangi işlemin hangi belleğe ve bellek çarpışması durumunda ne yapılacağına karar vermekten sorumludur.


28
hangisi sorun değil: İşletim sistemi bunu yapmak için hangi talimatları kullanabilir?
Paul Hollingsworth

4
Bunun için bir dizi ayrıcalıklı talimat var, ancak uygulama kodu değil, işletim sistemi sorunu. Uygulama kodu çoklu iş parçacığı olmak istiyorsa, "sihir" yapmak için işletim sistemi işlevlerini çağırmak zorundadır.
Sharptooth

2
BIOS genellikle kaç tane çekirdek olduğunu belirler ve istendiğinde bu bilgiyi işletim sistemine iletir. BIOS'un (ve donanımın) farklı bilgisayarlar için donanım özelliklerine (işlemciler, çekirdekler, PCI veri yolu, PCI kartları, fare, klavye, grafik, ISA, PCI-E / X, bellek vb.) Erişimin uyması gereken standartlar vardır. işletim sisteminin bakış açısından aynı görünüyor. BIOS dört çekirdek olduğunu bildirmezse, işletim sistemi genellikle sadece bir tane olduğunu varsayar. Denemek için bir BIOS ayarı bile olabilir.
Olof Forshell

1
Bu harika ve hepsi ya çıplak metal bir program yazıyorsanız ne olacak?
Alexander Ryan Baggett

3
@AlexanderRyanBaggett,? Bu bile ne? Tekrarlamak gerekirse, "İşletim Sistemine bırakın" dediğimizde, sorudan kaçınıyoruz, çünkü soru işletim sisteminin bunu nasıl yapmasıdır? Hangi montaj talimatlarını kullanıyor?
Pacerier

39

Resmi Olmayan SMP SSS yığın taşması logosu


Bir zamanlar, örneğin x86 birleştirici yazmak için, "EDX kaydını 5 değerini yükle", "EDX kaydını artırın" kaydını vb. Belirten talimatlarınız olacaktır. 4 çekirdekli (veya daha fazla) modern CPU'larla , makine kodu düzeyinde sadece 4 ayrı CPU varmış gibi görünüyor (yani sadece 4 farklı "EDX" kaydı var mı?)

Kesinlikle. 4 ayrı komut işaretçisi de dahil olmak üzere 4 kayıt grubu vardır.

Eğer öyleyse, "EDX kaydını artır" dediğinizde, hangi CPU'nun EDX kaydının artırıldığını belirleyen nedir?

Doğal olarak bu talimatı uygulayan CPU. Bunu aynı belleği paylaşan tamamen farklı 4 mikroişlemci olarak düşünün.

Şimdi x86 derleyicisinde "CPU bağlamı" veya "iş parçacığı" kavramı var mı?

Hayır. Montajcı talimatları her zaman olduğu gibi çevirir. Orada değişiklik yok.

Çekirdekler arasındaki iletişim / senkronizasyon nasıl çalışır?

Aynı belleği paylaştıkları için, çoğunlukla program mantığı meselesidir. Şimdi bir işlemci arası kesme mekanizması olmasına rağmen , gerekli değildir ve ilk çift CPU x86 sistemlerinde mevcut değildi.

Bir işletim sistemi yazıyorsanız, farklı çekirdeklerde yürütmeyi zamanlamanızı sağlayan donanım aracılığıyla hangi mekanizma ortaya çıkar?

Zamanlayıcı, kritik bölümler ve kullanılan kilit türleri hakkında biraz daha dikkatli olması dışında aslında değişmez. SMP'den önce, çekirdek kodu sonunda çalışma zamanlayıcısına bakacak ve bir sonraki iş parçacığı olarak çalıştırmak için bir işlem seçecek olan zamanlayıcıyı çağırır. (Çekirdeğe yapılan işlemler, iş parçacıklarına çok benziyor.) SMP çekirdeği aynı kodu, her seferinde bir iş parçacığı çalıştırıyor, sadece iki çekirdeğin yanlışlıkla seçemediğinden emin olmak için kritik bölüm kilidinin SMP güvenli olması gerekiyor aynı PID.

Bazı özel ayrıcalıklı talimatlar var mı?

Hayır. Çekirdeklerin hepsi aynı eski talimatlarla aynı bellekte çalışıyor.

Çok çekirdekli bir CPU için optimize edici bir derleyici / bayt kodu VM yazıyorsanız, tüm çekirdeklerde verimli çalışan kod üretmesini sağlamak için özellikle x86 hakkında ne bilmeniz gerekir?

Önceki kodla aynı kodu çalıştırıyorsunuz. Değiştirilmesi gereken Unix veya Windows çekirdeğidir.

Sorumumu "Çok çekirdekli işlevselliği desteklemek için x86 makine kodunda ne gibi değişiklikler yapıldı?" Şeklinde özetleyebilirsiniz.

Hiçbir şey gerekli değildi. İlk SMP sistemleri, tek işlemcilerle aynı komut setini kullandı. Şimdi, işlerin daha hızlı gitmesi için çok sayıda x86 mimari evrimi ve milyonlarca yeni talimat var, ancak SMP için hiçbir şey gerekli değildi .

Daha fazla bilgi için bkz. Intel Çok İşlemcili Teknik Özellikler .


Güncelleme: tüm takip soruları, n- yollu çok çekirdekli CPU'nun aynı belleği paylaşan n ayrı işlemciyle neredeyse 1 aynı şey olduğunu tamamen kabul ederek cevaplanabilir . 2 Sorulmayan önemli bir soru vardı: bir program daha fazla performans için birden fazla çekirdek üzerinde çalışacak şekilde nasıl yazılır? Ve cevap: Pthreads gibi bir iş parçacığı kütüphanesi kullanılarak yazılmıştır . Bazı iş parçacığı kitaplıkları işletim sistemi tarafından görülmeyen "yeşil iş parçacıkları" kullanır ve bunlar ayrı çekirdek elde etmez, ancak iş parçacığı kitaplığı çekirdek iş parçacığı özelliklerini kullandıkça, iş parçacığı programınız otomatik olarak çok çekirdekli olur.
1. Geriye dönük uyumluluk için, sıfırlama işleminde yalnızca ilk çekirdek başlar ve kalanları ateşlemek için birkaç sürücü tipi işlem yapılması gerekir.
2. Doğal olarak tüm çevre birimlerini de paylaşırlar.


3
Ben her zaman "iş parçacığı" çok çekirdekli işlemci anlamak için beni zorlaştırır bir yazılım kavramı olduğunu düşünüyorum, sorun, nasıl kodları bir çekirdek "çekirdek 2 çalışan bir iş parçacığı oluşturacağım" söyleyebilir? Bunu yapmak için özel bir montaj kodu var mı?
demonguy

2
@demonguy: Hayır, böyle bir şey için özel bir talimat yok. Bir yakınlık maskesi ayarlayarak işletim sisteminden iş parçacığınızı belirli bir çekirdek üzerinde çalıştırmasını istersiniz ("bu iş parçacığı bu mantıksal çekirdek kümesinde çalışabilir" diyor). Tamamen bir yazılım sorunu. Her CPU çekirdeği (donanım iş parçacığı) bağımsız olarak Linux (veya Windows) çalıştırıyor. Diğer donanım iş parçacıklarıyla birlikte çalışmak için, paylaşılan veri yapılarını kullanırlar. Ancak farklı bir CPU üzerinde hiçbir zaman "doğrudan" bir iş parçacığı başlatmazsınız. İşletim sistemine yeni bir iş parçacığı olmasını istediğinizi söylersiniz ve veri yapısında başka bir çekirdekteki işletim sisteminin gördüğü bir not alır.
Peter Cordes

2
Ben os söyleyebilirim, ama os belirli çekirdek üzerine kodları koymak nasıl?
demonguy

4
@demonguy ... (basitleştirilmiş) ... her çekirdek OS görüntüsünü paylaşır ve aynı yerde çalıştırmaya başlar. Yani, 8 çekirdek için, bu çekirdekte çalışan 8 "donanım işlemi" dir. Her biri, işlem tablosunu çalıştırılabilir bir işlem veya iş parçacığı için denetleyen aynı zamanlayıcı işlevini çağırır. (Bu çalışma kuyruğu. ) Bu arada, iş parçacığı olan programlar altta yatan SMP doğasının farkında olmadan çalışır. Onlar sadece çatal (2) falan ve çekirdek çalıştırmak istediklerini bildirin. Temel olarak çekirdek, çekirdeği bulan süreçten ziyade süreci bulur.
DigitalRoss

1
Aslında bir çekirdeği diğerinden kesmeniz gerekmez. Bu konuda Şöyle düşünün: her şey daha önce iletişim kurmak için gerekli olan yazılım mekanizmaları ile gayet tebliğ. Aynı yazılım mekanizmaları çalışmaya devam ediyor. Yani, borular, çekirdek çağrıları, uyku / uyandırma, tüm bu şeyler ... daha önce olduğu gibi çalışıyorlar. Her işlem aynı CPU'da çalışmaz, ancak iletişim için daha önce olduğu gibi aynı veri yapılarına sahiptir. SMP'ye girme çabası çoğunlukla eski kilitlerin daha paralel bir ortamda çalışmasını sağlamakla sınırlıdır.
DigitalRoss

10

Çok çekirdekli bir CPU için optimize edici bir derleyici / bayt kodu VM yazıyorsanız, tüm çekirdeklerde verimli çalışan kod üretmesini sağlamak için özellikle x86 hakkında ne bilmeniz gerekir?

Derleyici / bayt kodu VM'lerini optimize eden bir kişi olarak size burada yardımcı olabilirim.

Tüm çekirdeklerde verimli bir şekilde çalışan kod oluşturmak için x86 hakkında özel bir şey bilmenize gerek yoktur.

Ancak, tüm çekirdeklerde doğru çalışan kod yazmak için cmpxchg ve arkadaşlarınızı bilmeniz gerekebilir . Çok çekirdekli programlama, yürütme konuları arasında senkronizasyon ve iletişim kullanımını gerektirir.

Genel olarak x86 üzerinde verimli bir şekilde çalışan kod üretmesi için x86 hakkında bir şeyler bilmeniz gerekebilir.

Öğrenmeniz için faydalı olabilecek başka şeyler de vardır:

İşletim sisteminin (Linux veya Windows veya OSX) birden çok iş parçacığı çalıştırmanıza izin vermek için sağladığı olanaklar hakkında bilgi edinmelisiniz. OpenMP ve Diş Açma Yapı Taşları veya OSX 10.6 "Snow Leopard" nın yakında çıkacak olan "Grand Central" gibi paralelleştirme API'lerini öğrenmelisiniz.

Derleyicinizin otomatik olarak paralel olup olmadığını veya derleyiciniz tarafından derlenen uygulamaların yazarının birden çok çekirdekten yararlanmak için programına özel sözdizimi veya API çağrıları eklemesi gerekip gerekmediğini düşünmelisiniz.


.NET ve Java gibi birkaç popüler VM'nin ana GC işlemlerinin kilitlerle kaplı ve temel olarak tek başına işlenmiş olmasıyla ilgili bir sorunu yok mu?
Marco van de Voort

9

Her Çekirdek farklı bir bellek alanından yürütülür. İşletim sisteminiz programınıza bir çekirdek gösterecek ve çekirdek programınızı yürütecektir. Programınız, üzerinde çalıştığı birden fazla çekirdek veya hangi çekirdek üzerinde çalıştığının farkında olmayacaktır.

Ayrıca yalnızca İşletim Sistemi için ek bir talimat yoktur. Bu çekirdekler tek çekirdekli yongalarla aynıdır. Her Çekirdek, İşletim Sisteminin yürütülecek bir sonraki bellek alanını bulmak için bilgi alışverişi için kullanılan ortak bellek alanlarıyla iletişim kuracak bir bölümünü çalıştırır.

Bu bir basitleştirmedir, ancak nasıl yapıldığına dair temel fikri verir. Embedded.com'daki çoklu çekirdekler ve çok işlemciler hakkında daha fazla bilgi bu konu hakkında çok fazla bilgiye sahip ... Bu konu çok çabuk karmaşıklaşıyor!


Bence burada çok çekirdekli genel olarak nasıl çalıştığı ve işletim sisteminin ne kadar etkilediği biraz daha dikkatle ayırt edilmelidir. "Her çekirdek farklı bir bellek alanından yürütür" Bence çok yanıltıcı. İlk ve en önemlisi, prensiplerde birden fazla çekirdek kullanmak buna ihtiyaç duymaz ve dişli bir program için iki çekirdeğin iki aynı metin ve veri segmentinde çalışmasını İSTEMEKTEDİR (her çekirdek aynı zamanda yığın gibi bireysel kaynaklara ihtiyaç duyar) .
Volker Stolz

@ShiDoiSi Bu yüzden cevabım "Bu bir basitleştirme" metnini içeriyor .
Gerhard

5

Montaj kodu, bir çekirdek üzerinde çalıştırılacak olan makine koduna çevrilecektir. Çok iş parçacıklı olmasını istiyorsanız, bu kodu farklı işlemcilerde birkaç kez veya farklı çekirdeklerdeki farklı kod parçalarında başlatmak için işletim sistemi ilkellerini kullanmanız gerekir - her çekirdek ayrı bir iş parçacığı yürütür. Her iş parçacığı şu anda yürütmekte olduğu tek bir çekirdeği görür.


4
Ben böyle bir şey söyleyecektim, ama sonra OS nasıl çekirdekleri çekirdek ayırır? Bunu gerçekleştiren bazı imtiyazlı montaj talimatları olduğunu düşünüyorum. Eğer öyleyse, yazarın aradığı cevap budur.
A. Levy

Bunun için bir talimat yok, işletim sistemi zamanlayıcısının görevi budur. Win32'de SetThreadAffinityMask gibi işletim sistemi işlevleri vardır ve kod onları çağırabilir, ancak işletim sistemi şeyleridir ve zamanlayıcıyı etkiler, bir işlemci talimatı değildir.
Sharptooth

2
Bir OpCode olmalıdır, aksi takdirde işletim sistemi de bunu yapamaz.
Matthew Whites

1
Zamanlama için gerçekten bir opcode değil - daha çok işlemci başına işletim sisteminin bir kopyasını alıp bir bellek alanını paylaşıyormuşsunuz gibi; bir çekirdek çekirdeğe her girdiğinde (sistem çağrısı veya kesme), daha sonra hangi iş parçacığının çalıştırılacağına karar vermek için bellekte aynı veri yapılarına bakar.
pjc50

1
@ A.Levy: Bir iş parçacığını, yalnızca farklı bir çekirdekte çalışmasına izin veren bir yakınlık ile başlattığınızda, hemen diğer çekirdeğe geçmez . Bağlamı, normal bir bağlam anahtarı gibi belleğe kaydedildi. Diğer donanım iş parçacıkları, zamanlayıcı veri yapılarına girişini görür ve bunlardan biri sonunda iş parçacığının çalıştırılmasına karar verir. İlk çekirdeğin bakış açısından: paylaşımlı bir veri yapısına yazıyorsunuz ve sonunda başka bir çekirdekteki (donanım iş parçacığı) OS kodu onu fark edip çalıştırıyor.
Peter Cordes

3

Makine talimatlarında hiç yapılmaz; çekirdekler ayrı CPU'lar gibi davranıyor ve birbirleriyle konuşmak için özel yetenekleri yok. İletişim kurmanın iki yolu vardır:

  • fiziksel adres alanını paylaşırlar. Donanım önbellek tutarlılığını işler, böylece bir CPU diğerinin okuduğu bir bellek adresine yazar.

  • bir APIC'yi (programlanabilir kesinti kontrolörü) paylaşırlar. Bu, fiziksel adres alanına eşlenen bellektir ve bir işlemci tarafından diğerlerini kontrol etmek, açmak veya kapatmak, kesintiler göndermek vb. İçin kullanılabilir.

http://www.cheesecake.org/sac/smp.html aptal bir URL ile iyi bir referanstır.


2
Aslında bir APIC paylaşmıyorlar. Her mantıksal CPU'nun kendi bir CPU'su vardır. APIC'ler kendi aralarında iletişim kurarlar, ancak ayrıdırlar.
Nathan Fellman

Tek bir yolla senkronize ederler (iletişim kurmak yerine) ve bu da LOCK önekiyle ("xchg mem, reg" talimatı örtük bir kilit isteği içerir) etkili bir şekilde tüm CPU'lara çalışan kilit pimine çalışır. (aslında herhangi bir veriyolu yönetim aygıtı) veri yoluna özel erişim ister. Sonunda bir sinyal LOCKA (onay) pinine dönerek CPU'ya şimdi veriyoluna özel erişimi olduğunu söyler. Harici cihazlar CPU'nun dahili işleyişinden çok daha yavaş olduğundan, bir LOCK / LOCKA dizisinin tamamlanması için yüzlerce CPU döngüsü gerekebilir.
Olof Forshell

1

Tek ve çok iş parçacıklı bir uygulama arasındaki temel fark, birincinin bir yığını ve ikincisinin her bir iş parçacığı için bir yığını olmasıdır. Derleyici, veri ve yığın segment kayıtlarının (ds ve ss) eşit olmadığını varsayacağından kod biraz farklı üretilir. Bu, ss kaydında varsayılan olan ebp ve esp kayıtları aracılığıyla dolaylı aktarmanın da ds için varsayılan olmayacağı anlamına gelir (çünkü ds! = Ss). Tersine, ds için varsayılan olan ss için varsayılan olmayan diğer kayıtlar aracılığıyla dolaylı.

İş parçacıkları, veri ve kod alanları da dahil olmak üzere her şeyi paylaşır. Ayrıca lib rutinlerini paylaşırlar, bu yüzden iş parçacığı açısından güvenli olduklarından emin olun. RAM'deki bir alanı sıralayan bir prosedür, işleri hızlandırmak için çok iş parçacıklı olabilir. İş parçacıkları daha sonra aynı fiziksel bellek alanındaki verilere erişecek, bunları karşılaştıracak ve sıralayacak ve aynı kodu yürütecek, ancak sıralamanın ilgili kısımlarını kontrol etmek için farklı yerel değişkenler kullanacaktır. Bunun nedeni, iş parçacıklarının yerel değişkenlerin bulunduğu farklı yığınlara sahip olmasıdır. Bu tür programlama, kodun dikkatli bir şekilde ayarlanmasını gerektirir, böylece çekirdekler arası veri çarpışmaları (önbelleklerde ve RAM'de) azaltılır, bu da iki veya daha fazla iş parçacığıyla yalnızca bir tanesinden daha hızlı olan bir kodla sonuçlanır. Elbette, ayarlanmamış bir kod genellikle bir işlemci ile iki veya daha fazla işlemciden daha hızlı olacaktır. Hata ayıklamak daha zordur çünkü standart "int 3" kesme noktası, belirli bir iş parçacığını kesmek istemediğinizden ve tümünü değil istediğinizden geçerli olmayacaktır. Hata ayıklama kayıt kesme noktaları, kesmek istediğiniz belirli iş parçacığını çalıştıran belirli bir işlemcide ayarlayamazsanız bu sorunu çözmez.

Diğer çok iş parçacıklı kodlar, programın farklı bölümlerinde çalışan farklı iş parçacıkları içerebilir. Bu tür programlama aynı tür ayarlama gerektirmez ve bu nedenle öğrenilmesi çok daha kolaydır.


0

Çok işlemcili her mimariye, onlardan önce gelen tek işlemcili varyantlara kıyasla eklenenler, çekirdekler arasında senkronize etme talimatlarıdır. Ayrıca, bir işletim sisteminin uğraşması gereken önbellek tutarlılığı, temizleme tamponları ve benzer düşük seviyeli işlemlerle ilgili talimatlarınız vardır. IBM POWER6, IBM Cell, Sun Niagara ve Intel "Hyperthreading" gibi eşzamanlı çok iş parçacıklı mimariler söz konusu olduğunda, iş parçacıkları arasında öncelik sırasına koymak için yeni talimatlar görme eğilimindesiniz (öncelikleri ayarlamak ve yapacak bir şey olmadığında işlemciyi açıkça sunmak gibi) .

Ancak temel tek iş parçacıklı semantik aynıdır, senkronizasyon ve diğer çekirdeklerle iletişim için ekstra özellikler eklersiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.