274

たとえば、昔々、x86 アセンブラーを作成するには、「EDX レジスターに値 5 をロードする」、「EDX レジスターをインクリメントする」などの命令が必要でした。

4 つのコア (またはそれ以上) を持つ最新の CPU では、マシン コード レベルで 4 つの個別の CPU があるように見えます (つまり、4 つの異なる「EDX」レジスタがあるだけですか) ? もしそうなら、「EDXレジスタをインクリメントする」と言うとき、どのCPUのEDXレジスタがインクリメントされるかを決定するものは何ですか? 現在、x86 アセンブラーに「CPU コンテキスト」または「スレッド」の概念はありますか?

コア間の通信/同期はどのように機能しますか?

オペレーティング システムを作成している場合、さまざまなコアでの実行をスケジュールできるようにするために、ハードウェアを介してどのようなメカニズムが公開されていますか? 特別な特権命令ですか?

マルチコア CPU 用の最適化コンパイラ/バイトコード VM を作成している場合、たとえば x86 について、すべてのコアで効率的に実行されるコードを生成するために、具体的に何を知る必要がありますか?

マルチコア機能をサポートするために、x86 マシン コードにはどのような変更が加えられましたか?

4

11 に答える 11

182

これは質問に対する直接の回答ではありませんが、コメントに表示される質問に対する回答です。基本的に、問題はハードウェアがマルチスレッド操作にどのようなサポートを提供するかです。

Nicholas Flyntは、少なくともx86に関しては、それを正しく理解していました。マルチスレッド環境(ハイパースレッディング、マルチコア、またはマルチプロセッサ)では、ブートストラップスレッド(通常はプロセッサ0のコア0のスレッド0)がアドレスからコードのフェッチを開始します0xfffffff0他のすべてのスレッドは、 Wait-for-SIPIと呼ばれる特別なスリープ状態で起動します。初期化の一環として、プライマリスレッドは、SIPI(スタートアップIPI)と呼ばれるAPICを介して特別なプロセッサ間割り込み(IPI)をWFS内の各スレッドに送信します。SIPIには、そのスレッドがコードのフェッチを開始するアドレスが含まれています。

このメカニズムにより、各スレッドは異なるアドレスからコードを実行できます。必要なのは、各スレッドが独自のテーブルとメッセージングキューを設定するためのソフトウェアサポートです。OSはこれらを使用して、実際のマルチスレッドスケジューリングを実行します。

Nicholasが書いたように、実際のアセンブリに関する限り、シングルスレッドまたはマルチスレッドアプリケーションのアセンブリに違いはありません。各論理スレッドには独自のレジスタセットがあるため、次のように記述します。

mov edx, 0

現在実行中EDXのスレッドに対してのみ更新されます。単一のアセンブリ命令を使用して別のプロセッサで変更する方法はありません。独自のを更新するコードを実行するように別のスレッドに指示するようにOSに要求するには、ある種のシステムコールが必要です。EDXEDX

于 2009-06-13T18:09:01.217 に答える
121

Intel x86 最小限の実行可能なベアメタルの例

必要なすべてのボイラープレートを備えた実行可能なベアメタルの例。主要な部分はすべて以下で説明します。

Ubuntu 15.10 QEMU 2.3.0 および Lenovo ThinkPad T400実ハードウェア ゲストでテスト済み。

インテル マニュアル ボリューム 3 システム プログラミング ガイド - 325384-056US 2015 年 9 月では、 8、9、10章で SMP について説明しています。

表 8-1. 「Broadcast INIT-SIPI-SIPI Sequence and Choice of Timeouts」には、基本的に機能する例が含まれています。

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

そのコードで:

  1. ほとんどのオペレーティング システムでは、リング 3 (ユーザー プログラム) からこれらの操作のほとんどが不可能になります。

    したがって、カーネルを自由に操作するには、独自のカーネルを作成する必要があります。ユーザーランドの Linux プログラムは機能しません。

  2. 最初は、ブートストラップ プロセッサ (BSP) と呼ばれる単一のプロセッサが実行されます。

    プロセッサ間割り込み (IPI)と呼ばれる特別な割り込みを介して、他のもの (アプリケーション プロセッサ (AP) と呼ばれる) をウェイクアップする必要があります。

    これらの割り込みは、割り込みコマンド レジスタ (ICR) を介して Advanced Programmable Interrupt Controller (APIC) をプログラミングすることで実行できます。

    ICR の形式は、10.6「ISSUING INTERPROCESSOR INTERRUPTS」に記載されています。

    IPI は、ICR に書き込むとすぐに発生します。

  3. ICR_LOW は、8.4.4「MP 初期化の例」で次のように定義されています。

    ICR_LOW EQU 0FEE00300H
    

    表 10-1「ローカル APIC レジスタ アドレス マップ」に記載されているように、マジック値0FEE00300は ICR のメモリ アドレスです。

  4. この例では、最も単純な方法が使用されています。これは、現在のプロセッサを除く他のすべてのプロセッサに配信されるブロードキャスト IPI を送信するように ICR をセットアップします。

    しかし、 ACPI テーブルや Intel の MP 構成テーブルなどの BIOS による特別なデータ構造のセットアップを通じてプロセッサに関する情報を取得し、必要なものだけを 1 つずつ起動することも可能であり、推奨されています。

  5. XXin000C46XXHは、プロセッサが実行する最初の命令のアドレスを次のようにエンコードします。

    CS = XX * 0x100
    IP = 0
    

    CSはアドレスを 倍する0x10ので、最初の命令の実際のメモリ アドレスは次のようになります。

    XX * 0x1000
    

    したがって、たとえばXX == 1の場合、プロセッサは で開始され0x1000ます。

    次に、そのメモリ位置で実行される 16 ビット リアル モード コードがあることを確認する必要があります。

    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
    

    別の方法として、リンカー スクリプトを使用することもできます。

  6. 遅延ループは、作業を開始するのに厄介な部分です。このようなスリープを正確に実行するための非常に簡単な方法はありません。

    可能な方法は次のとおりです。

    • PIT (私の例で使用)
    • HPET
    • 上記でビジーループの時間を調整し、代わりに使用します

    関連: DOS x86 アセンブリで画面に数字を表示して 1 秒間スリープする方法は?

  7. 0FEE00300H16ビットには高すぎるアドレスに書き込むため、これが機能するには、最初のプロセッサを保護モードにする必要があると思います

  8. プロセッサ間で通信するには、メイン プロセスでスピンロックを使用し、2 番目のコアからロックを変更します。

    などを介して、メモリの書き戻しが確実に行われるようにする必要がありますwbinvd

プロセッサ間の共有状態

8.7.1「論理プロセッサの状態」は次のように述べています。

次の機能は、Intel ハイパースレッディング テクノロジをサポートする Intel 64 または IA-32 プロセッサ内の論理プロセッサのアーキテクチャ状態の一部です。機能は、次の 3 つのグループに分類できます。

  • 論理プロセッサごとに複製
  • 物理プロセッサ内の論理プロセッサで共有
  • 実装に応じて共有または複製

次の機能は、論理プロセッサごとに複製されます。

  • 汎用レジスター (EAX、EBX、ECX、EDX、ESI、EDI、ESP、および EBP)
  • セグメントレジスタ (CS、DS、SS、ES、FS、および GS)
  • EFLAGS および EIP レジスタ。各論理プロセッサの CS および EIP/RIP レジスタは、論理プロセッサによって実行されているスレッドの命令ストリームを指していることに注意してください。
  • x87 FPU レジスタ (ST0 ~ ST7、ステータス ワード、コントロール ワード、タグ ワード、データ オペランド ポインタ、命令ポインタ)
  • MMX レジスタ (MM0 ~ MM7)
  • XMM レジスター (XMM0 ~ XMM7) および MXCSR レジスター
  • 制御レジスタとシステム テーブル ポインタ レジスタ (GDTR、LDTR、IDTR、タスク レジスタ)
  • デバッグ レジスタ (DR0、DR1、DR2、DR3、DR6、DR7) およびデバッグ制御 MSR
  • マシン チェック グローバル ステータス (IA32_MCG_STATUS) およびマシン チェック機能 (IA32_MCG_CAP) MSR
  • サーマル クロック変調および ACPI 電源管理制御 MSR
  • タイム スタンプ カウンター MSR
  • ページ属性テーブル (PAT) を含む、他のほとんどの MSR レジスタ。以下の例外を参照してください。
  • ローカル APIC レジスタ。
  • 追加の汎用レジスタ (R8 ~ R15)、XMM レジスタ (XMM8 ~ XMM15)、制御レジスタ、Intel 64 プロセッサの IA32_EFER。

次の機能は、論理プロセッサによって共有されます。

  • メモリ タイプ レンジ レジスタ (MTRR)

次の機能が共有されるか複製されるかは、実装によって異なります。

  • IA32_MISC_ENABLE MSR (MSR アドレス 1A0H)
  • マシン チェック アーキテクチャ (MCA) MSR (IA32_MCG_STATUS および IA32_MCG_CAP MSR を除く)
  • パフォーマンス監視制御およびカウンター MSR

キャッシュ共有については、次の場所で説明されています。

Intelハイパースレッドは、個別のコアよりもキャッシュとパイプラインの共有が優れています: https://superuser.com/questions/133082/hyper-threading-and-dual-core-whats-the-difference/995858#995858

Linux カーネル 4.2

主な初期化アクションはarch/x86/kernel/smpboot.c.

ARM の最小限の実行可能なベアメタルの例

ここでは、QEMU 用の最小限の実行可能な ARMv8 aarch64 の例を示します。

.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 アップストリーム.

組み立てて実行します。

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

この例では、CPU 0 をスピンロック ループに入れ、CPU 1 がスピンロックを解放したときにのみ終了します。

スピンロックの後、CPU 0は QEMU を終了させるセミホスト終了呼び出しを行います。

を使用して 1 つの CPU だけで QEMU を開始すると-smp 1、シミュレーションはスピンロックで永久にハングアップします。

CPU 1 は PSCI インターフェイスでウェイクアップされます。詳細については、ARM: 他の CPU コア/AP を開始/ウェイクアップ/起動し、実行開始アドレスを渡しますか?

アップストリーム バージョンには、gem5 で動作するようにいくつかの調整が加えられているため、パフォーマンス特性も試すことができます。

実際のハードウェアでテストしていないため、これがどれほど移植性があるかはわかりません。次の Raspberry Pi 参考文献が参考になるかもしれません。

このドキュメントでは、複数のコアで楽しいことを行うために使用できる ARM 同期プリミティブの使用に関するガイダンスを提供します

Ubuntu 18.10、GCC 8.2.0、Binutils 2.31.1、QEMU 2.12.0 でテスト済み。

より便利なプログラミングのための次のステップ

前の例では、セカンダリ CPU をウェイクアップし、専用の命令で基本的なメモリ同期を行っています。これは良い出発点です。

しかし、たとえばPOSIX のようなマルチコア システムをプログラムしやすくするpthreadsには、以下のより複雑なトピックにも入る必要があります。

  • 割り込みをセットアップし、現在どのスレッドを実行するかを定期的に決定するタイマーを実行します。これは、プリエンプティブ マルチスレッドと呼ばれます。

    このようなシステムでは、スレッド レジスタを開始および停止するときに、スレッド レジスタを保存および復元する必要もあります。

    プリエンプティブでないマルチタスク システムを使用することもできますが、その場合、コードを変更してすべてのスレッドが (たとえば、pthread_yield実装で) 生成されるようにする必要があり、ワークロードのバランスを取るのが難しくなります。

    単純化したベア メタル タイマーの例を次に示します。

  • メモリの競合に対処します。特に、 C やその他の高級言語でコーディングする場合は、各スレッドに固有のスタックが必要になります。

    スレッドの最大スタックサイズを固定するように制限することもできますが、これに対処するより適切な方法は、効率的な「無制限のサイズ」スタックを可能にするページングを使用することです。

    これは単純な aarch64 ベアメタルの例で、スタックが深くなりすぎると爆発します。

これらは、Linuxカーネルまたは他のオペレーティングシステムを使用するいくつかの正当な理由です:-)

ユーザーランドのメモリ同期プリミティブ

スレッドの開始/停止/管理は通常、ユーザーランドの範囲を超えていますが、ユーザーランド スレッドからのアセンブリ命令を使用して、潜在的に高価なシステム コールなしでメモリ アクセスを同期することができます。

もちろん、これらの低レベルのプリミティブを移植可能にラップするライブラリを使用することをお勧めします。C++ 標準自体は、<mutex>および<atomic>ヘッダー、特にstd::memory_order. 達成可能なすべての可能なメモリセマンティクスをカバーしているかどうかはわかりませんが、そうかもしれません。

より微妙なセマンティクスは、特定のケースでパフォーマンス上の利点を提供できるロック フリー データ構造のコンテキストに特に関連しています。それらを実装するには、さまざまなタイプのメモリバリアについて少し学ぶ必要があるでしょう: https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/

たとえば、ブーストには、https ://www.boost.org/doc/libs/1_63_0/doc/html/lockfree.html にいくつかのロック フリー コンテナーの実装があります。

futexこのようなユーザーランド命令は、Linuxの主要な同期プリミティブの 1 つであるLinux システム コールの実装にも使用されているようです。man futex4.15 読み取り:

futex() システムコールは、特定の条件が真になるまで待機するメソッドを提供します。これは通常、共有メモリ同期のコンテキストでブロッキング コンストラクトとして使用されます。futex を使用する場合、同期操作の大部分はユーザー空間で実行されます。ユーザー空間プログラムが futex() システム コールを使用するのは、条件が true になるまでプログラムを長時間ブロックする必要がある可能性が高い場合だけです。他の futex() 操作を使用して、特定の条件を待機しているプロセスまたはスレッドをウェイクアップできます。

システムコール名自体は「Fast Userspace XXX」を意味します。

以下は、インライン アセンブリを使用した最小限の役に立たない C++ x86_64 / aarch64 の例です。

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 アップストリーム.

可能な出力:

my_non_atomic_ulong 15264
my_arch_non_atomic_ulong 15267

このことから、x86 LOCK プレフィックス / aarch64LDADD命令が追加をアトミックにしたことがわかります。それがないと、多くの追加で競合状態が発生し、最後の合計カウントは同期された 20000 未満になります。

以下も参照してください。

Ubuntu 19.04 amd64 および QEMU aarch64 ユーザー モードでテスト済み。

于 2015-11-11T13:22:11.783 に答える
47

私が理解しているように、各「コア」は完全なプロセッサであり、独自のレジスタセットがあります。基本的に、BIOS は 1 つのコアが実行されている状態で開始し、オペレーティング システムは他のコアを初期化し、実行するコードを指定するなどして、他のコアを「開始」できます。

同期は OS によって行われます。一般に、各プロセッサは OS に対して異なるプロセスを実行しているため、オペレーティング システムのマルチスレッド機能が、どのプロセスがどのメモリにアクセスするか、およびメモリの衝突が発生した場合に何をすべきかを決定します。

于 2009-06-11T13:21:22.143 に答える
42

非公式 SMP FAQ スタック オーバーフローのロゴ


たとえば、昔々、x86 アセンブラーを作成するには、「EDX レジスターに値 5 をロードする」、「EDX レジスターをインクリメントする」などの命令が必要でした。4 コア (またはそれ以上) を持つ最新の CPU では、マシン コード レベルでは、4 つの個別の CPU があるように見えますか (つまり、4 つの個別の「EDX」レジスタがあるだけですか) ?

丁度。4 つの別個の命令ポインターを含む 4 セットのレジスターがあります。

もしそうなら、「EDXレジスタをインクリメントする」と言うとき、どのCPUのEDXレジスタがインクリメントされるかを決定するものは何ですか?

当然、その命令を実行したCPU。同じメモリを単に共有している 4 つのまったく異なるマイクロプロセッサと考えてください。

現在、x86 アセンブラーに「CPU コンテキスト」または「スレッド」の概念はありますか?

いいえ。アセンブラは、いつものように命令を変換するだけです。変更はありません。

コア間の通信/同期はどのように機能しますか?

それらは同じメモリを共有するため、ほとんどはプログラム ロジックの問題です。現在はプロセッサ間割り込みメカニズムがありますが、これは必須ではなく、最初のデュアル CPU x86 システムにはもともと存在していませんでした。

オペレーティング システムを作成している場合、さまざまなコアでの実行をスケジュールできるようにするために、ハードウェアを介してどのようなメカニズムが公開されていますか?

スケジューラは実際には変更されていませんが、クリティカル セクションと使用されるロックの種類について少し慎重になっています。SMP の前は、カーネル コードが最終的にスケジューラを呼び出し、スケジューラが実行キューを見て、次のスレッドとして実行するプロセスを選択していました。(カーネルへのプロセスはスレッドによく似ています。) SMP カーネルはまったく同じコードを一度に 1 つのスレッドで実行します。2 つのコアが誤って選択しないように、クリティカル セクションのロックを SMP セーフにする必要があるだけです。同じ PID。

特別な特権命令ですか?

いいえ。コアはすべて、同じ古い命令を使用して同じメモリ内で実行されています。

マルチコア CPU 用の最適化コンパイラ/バイトコード VM を作成している場合、たとえば x86 について、すべてのコアで効率的に実行されるコードを生成するために、具体的に何を知る必要がありますか?

前と同じコードを実行します。変更が必要だったのは Unix または Windows カーネルです。

私の質問は、「マルチコア機能をサポートするために x86 マシン コードにどのような変更が加えられましたか?」と要約できます。

何も必要ありませんでした。最初の SMP システムは、ユニプロセッサとまったく同じ命令セットを使用していました。現在、x86 アーキテクチャは大幅に進化し、処理を高速化するために無数の新しい命令が追加されていますが、SMPには必要ありませんでした。

詳細については、Intel マルチプロセッサの仕様を参照してください。


更新: nウェイ マルチコア CPU は、同じメモリを共有するn 個個別のプロセッサとほぼ同じであることを完全に受け入れることで、すべてのフォローアップの質問に答えることができます。2 尋ねられなかった重要な質問がありました:パフォーマンスを向上させるために、複数のコアで実行するようにプログラムを作成するにはどうすればよいでしょうか? その答えは、Pthreads のようなスレッド ライブラリを使用して記述されているということです。一部のスレッド ライブラリは、OS からは見えない「グリーン スレッド」を使用しており、それらは個別のコアを取得しませんが、スレッド ライブラリがカーネル スレッド機能を使用している限り、スレッド化されたプログラムは自動的にマルチコアになります。
1. 下位互換性のために、リセット時に最初のコアのみが起動し、残りのコアを起動するにはいくつかのドライバー タイプの操作を行う必要があります。
2.当然、すべての周辺機器も共有します。

于 2013-02-07T21:22:18.650 に答える
11

マルチコア CPU 用の最適化コンパイラ/バイトコード VM を作成している場合、たとえば x86 について、すべてのコアで効率的に実行されるコードを生成するために、具体的に何を知る必要がありますか?

最適化コンパイラ/バイトコード VM を書いている者として、私はここであなたを助けることができるかもしれません.

すべてのコアで効率的に実行されるコードを生成するために、x86 について特に何も知る必要はありません。

ただし、すべてのコアで正しく実行されるコードを作成するには、cmpxchg とその仲間について知る必要がある場合があります。マルチコア プログラミングでは、実行スレッド間の同期と通信を使用する必要があります。

一般に、x86 で効率的に実行されるコードを生成するには、x86 に関する知識が必要になる場合があります。

他にも、次のことを学ぶと役立ちます。

複数のスレッドを実行できるようにするために、OS (Linux または Windows または OSX) が提供する機能について学ぶ必要があります。OpenMP や Threading Building Blocks などの並列化 API、または OSX 10.6「Snow Leopard」の今後の「Grand Central」について学ぶ必要があります。

コンパイラを自動並列化する必要があるかどうか、またはコンパイラによってコンパイルされたアプリケーションの作成者が、複数のコアを利用するために特別な構文または API 呼び出しをプログラムに追加する必要があるかどうかを検討する必要があります。

于 2009-06-11T13:49:02.810 に答える
9

各コアは、異なるメモリ領域から実行されます。オペレーティング システムはコアをプログラムに向け、コアがプログラムを実行します。プログラムは、複数のコアがあることや、どのコアで実行されているかを認識しません。

また、オペレーティング システムでのみ使用できる追加の命令もありません。これらのコアは、シングル コア チップと同じです。各コアは、次に実行するメモリ領域を見つけるための情報交換に使用される共通メモリ領域への通信を処理するオペレーティング システムの一部を実行します。

これは単純化したものですが、それがどのように行われるかについての基本的な考え方を示しています。Embedded.com のマルチコアとマルチプロセッサの詳細 には、このトピックに関する多くの情報があります... このトピックはすぐに複雑になります!

于 2009-06-11T13:36:39.233 に答える
5

アセンブリ コードは、1 つのコアで実行されるマシン コードに変換されます。マルチスレッドにしたい場合は、オペレーティング システムのプリミティブを使用して、このコードを異なるプロセッサで数回開始するか、異なるコアで異なるコードを開始する必要があります。各コアは個別のスレッドを実行します。各スレッドは、現在実行中の 1 つのコアのみを認識します。

于 2009-06-11T13:21:19.990 に答える
3

マシン命令ではまったく行われません。コアは個別の CPU のふりをしており、互いに通信するための特別な機能はありません。彼らが通信する方法は 2 つあります。

  • それらは物理アドレス空間を共有します。ハードウェアがキャッシュの一貫性を処理するため、1 つの CPU がメモリ アドレスに書き込み、別の CPU が読み取ります。

  • APIC (プログラマブル割り込みコントローラー) を共有します。これは、物理アドレス空間にマップされたメモリであり、1 つのプロセッサが他のプロセッサを制御したり、オンまたはオフにしたり、割り込みを送信したりするために使用できます。

http://www.cheesecake.org/sac/smp.htmlは、ばかげた URL の良いリファレンスです。

于 2009-10-27T13:56:55.950 に答える
1

シングルスレッド アプリケーションとマルチスレッド アプリケーションの主な違いは、前者には 1 つのスタックがあり、後者にはスレッドごとに 1 つのスタックがあることです。コンパイラはデータ レジスタとスタック セグメント レジスタ (ds と ss) が等しくないと想定するため、コードの生成方法は多少異なります。これは、デフォルトで ss レジスターになる ebp および esp レジスターを介したインダイレクションが、デフォルトで ds にもならないことを意味します (ds!=ss のため)。逆に、デフォルトで ds に設定されている他のレジスタを介したインダイレクションは、デフォルトで ss にはなりません。

スレッドは、データおよびコード領域を含む他のすべてを共有します。lib ルーチンも共有するため、スレッドセーフであることを確認してください。RAM 内の領域をソートするプロシージャは、速度を上げるためにマルチスレッド化できます。スレッドは、同じ物理メモリ領域内のデータにアクセス、比較、順序付けし、同じコードを実行しますが、異なるローカル変数を使用してソートのそれぞれの部分を制御します。これはもちろん、ローカル変数が含まれているスレッドのスタックが異なるためです。このタイプのプログラミングでは、コア間のデータ衝突 (キャッシュおよび RAM 内) が減少するようにコードを慎重に調整する必要があります。その結果、スレッドが 1 つだけの場合よりも 2 つ以上のスレッドの方がコードが高速になります。もちろん、チューニングされていないコードは、多くの場合、2 つ以上のプロセッサよりも 1 つのプロセッサの方が高速です。すべてのスレッドではなく特定のスレッドに割り込みたいため、標準の「int 3」ブレークポイントは適用できないため、デバッグはより困難です。割り込みたい特定のスレッドを実行している特定のプロセッサにブレークポイントを設定できない限り、デバッグ レジスタ ブレークポイントもこの問題を解決しません。

他のマルチスレッド コードには、プログラムのさまざまな部分で実行されるさまざまなスレッドが含まれる場合があります。このタイプのプログラミングは、同種のチューニングを必要としないため、はるかに習得が容易です。

于 2011-02-21T23:18:40.770 に答える
0

以前に登場したシングルプロセッサバリアントと比較して、すべてのマルチプロセッシング対応アーキテクチャに追加されたのは、コア間で同期するための命令です。また、キャッシュコヒーレンシ、バッファのフラッシュ、およびOSが処理する必要のある同様の低レベルの操作を処理するための手順があります。IBM POWER6、IBM Cell、Sun Niagara、Intelの「ハイパースレッディング」などの同時マルチスレッディングアーキテクチャの場合、スレッド間で優先順位を付ける新しい命令も表示される傾向があります(優先順位の設定や、何もすることがない場合のプロセッサの明示的な譲歩など)。 。

ただし、基本的なシングルスレッドのセマンティクスは同じです。他のコアとの同期と通信を処理するための機能を追加するだけです。

于 2009-08-18T18:20:46.973 に答える