[assembly] 멀티 코어 어셈블리 언어는 어떻게 생겼습니까?

예를 들어 x86 어셈블러를 작성하려면 “EDX 레지스터를 값 5로로드”, “EDX 증가”레지스터 등을 지시하는 지침이 있습니다.

4 개의 코어 (또는 그 이상)가있는 최신 CPU를 사용하면 머신 코드 수준에서 4 개의 개별 CPU가있는 것처럼 보입니까 (즉, 4 개의 “EDX”레지스터 만 있음)? 그렇다면 “EDX 레지스터 증가”라고 말할 때 어떤 CPU의 EDX 레지스터가 증가하는지 결정하는 것은 무엇입니까? x86 어셈블러에 “CPU 컨텍스트”또는 “스레드”개념이 있습니까?

코어 간의 통신 / 동기화는 어떻게 작동합니까?

운영 체제를 작성하는 경우 하드웨어를 통해 어떤 메커니즘이 노출되어 다른 코어에서 실행을 예약 할 수 있습니까? 특별한 특권이 있습니까?

멀티 코어 CPU에 최적화 된 컴파일러 / 바이트 코드 VM을 작성하는 경우 모든 코어에서 효율적으로 실행되는 코드를 생성하려면 x86에 대해 구체적으로 알아야 할 사항은 무엇입니까?

멀티 코어 기능을 지원하기 위해 x86 기계 코드가 어떻게 변경 되었습니까?



답변

이것은 질문에 대한 직접적인 답변은 아니지만 의견에 나타나는 질문에 대한 답변입니다. 본질적으로 문제는 하드웨어가 멀티 스레드 작업을 지원하는 것입니다.

니콜라스 플라이트는 적어도 x86에 대해서는 옳았다 . 다중 스레드 환경 (하이퍼 스레딩, 다중 코어 또는 다중 프로세서)에서 부트 스트랩 스레드 (일반적으로 프로세서 0의 코어 0의 스레드 0)는 address에서 코드 페치를 시작합니다 0xfffffff0. 다른 모든 스레드는 Wait-for-SIPI 라는 특수 절전 상태에서 시작합니다 . 기본 스레드는 초기화의 일부로 SIPI (Startup IPI)라는 APIC를 통해 WFS에있는 각 스레드에 특수한 프로세서 간 인터럽트 (IPI)를 보냅니다. SIPI에는 해당 스레드가 코드 페치를 시작해야하는 주소가 포함되어 있습니다.

이 메커니즘을 통해 각 스레드는 다른 주소에서 코드를 실행할 수 있습니다. 필요한 것은 각 스레드가 자체 테이블과 메시징 대기열을 설정하기위한 소프트웨어 지원입니다. OS는 를 사용 하여 실제 다중 스레드 스케줄링을 수행합니다.

실제 어셈블리에 관한 한, 니콜라스가 쓴 것처럼 단일 스레드 또는 다중 스레드 응용 프로그램의 어셈블리에는 차이가 없습니다. 각 논리 스레드에는 자체 레지스터 세트가 있으므로 다음과 같이 작성하십시오.

mov edx, 0

현재 실행중인 스레드에 대해서만 업데이트 EDX됩니다 . 단일 어셈블리 명령어를 사용하여 다른 프로세서 를 수정할 수있는 방법이 없습니다 . OS에 다른 스레드가 자체적으로 업데이트 할 코드를 실행하도록 지시하려면 일종의 시스템 호출이 필요합니다 .EDXEDX


답변

Intel x86 최소 실행 가능 베어 메탈 예

모든 필수 상용구가있는 실행 가능한 베어 메탈 예 . 모든 주요 부품은 아래에 설명되어 있습니다.

Ubuntu 15.10 QEMU 2.3.0 및 Lenovo ThinkPad T400 실제 하드웨어 게스트 에서 테스트되었습니다 .

325384-056US 2015년 9월 – 인텔 설명서 제 3 권 시스템 프로그래밍 가이드 장 8, 9, 10 커버 SMP.

표 8-1. “브로드 캐스트 INIT-SIPI-SIPI 시퀀스 및 시간 초과 선택”에는 기본적으로 작동하는 예제가 포함되어 있습니다.

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 (사용자 프로그램)에서 이러한 작업의 대부분을 불가능하게합니다.

    따라서 자유롭게 커널을 사용하려면 커널을 작성해야합니다. userland Linux 프로그램이 작동하지 않습니다.

  2. 처음에는 부트 스트랩 프로세서 (BSP)라고하는 단일 프로세서가 실행됩니다.

    IPI (Inter Processor Interrupts) 라는 특수한 인터럽트를 통해 다른 것 (애플리케이션 프로세서 (AP))을 깨워 야합니다 .

    인터럽트는 IRC (Interrupt Command Register)를 통해 APIC (Advanced Programmable Interrupt Controller)를 프로그래밍하여 수행 할 수 있습니다.

    ICR의 형식은 다음과 같습니다. 10.6 “ISSUING INTERPROCESSOR INTERRUPTS”

    IPI는 ICR에 쓰는 즉시 발생합니다.

  3. ICR_LOW는 8.4.4 “MP 초기화 예”에서 다음과 같이 정의됩니다.

    ICR_LOW EQU 0FEE00300H
    

    매직 값 0FEE00300은 표 10-1 “로컬 APIC 레지스터 주소 맵”에 설명 된대로 ICR의 메모리 주소입니다.

  4. 예제에서 가장 간단한 방법이 사용됩니다. 현재 IPC를 제외한 다른 모든 프로세서로 전달되는 브로드 캐스트 IPI를 보내도록 ICR을 설정합니다.

    그러나 그것은 또한 가능하며, 일부에서 권장하는 등의 BIOS에서 특별한 데이터 구조 설정을 통해 프로세서에 대한 정보를 얻기 위해, ACPI 테이블이나 인텔의 MP 구성 테이블 만이 하나씩 필요로하는 사람을 깨워.

  5. XXin 000C46XXH은 프로세서가 실행할 첫 번째 명령어의 주소를 다음과 같이 인코딩합니다.

    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. 프로세서 간 통신을 위해 기본 프로세스에서 스핀 락을 사용하고 두 번째 코어에서 잠금을 수정할 수 있습니다.

    메모리 쓰기가 예를 들어 통해 이루어 지도록해야합니다 wbinvd.

프로세서 간 공유 상태

8.7.1 “논리 프로세서의 상태”는 다음과 같이 말합니다.

다음 기능은 Intel 하이퍼 스레딩 기술을 지원하는 Intel 64 또는 IA-32 프로세서 내 논리 프로세서의 아키텍처 상태의 일부입니다. 기능은 세 그룹으로 세분 될 수 있습니다.

  • 각 논리 프로세서마다 중복
  • 실제 프로세서에서 논리 프로세서가 공유
  • 구현에 따라 공유 또는 복제

각 논리 프로세서에 대해 다음 기능이 복제됩니다.

  • 범용 레지스터 (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 (Machine Check Architecture) MSR (IA32_MCG_STATUS 및 IA32_MCG_CAP MSR 제외)
  • 성능 모니터링 제어 및 카운터 MSR

캐시 공유는 다음에서 논의됩니다.

인텔 하이퍼 스레드가 별도의 코어보다 캐시와 파이프 라인을 공유 할 수 있습니다 /superuser/133082/hyper-threading-and-dual-core-whats-the-difference/995858#995858

리눅스 커널 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를 종료 시키는 세미 호스트 종료 호출 을 수행합니다.

를 사용하여 하나의 CPU로 QEMU를 시작하면 -smp 1시뮬레이션이 스핀 락에서 영원히 중단됩니다.

CPU 1은 PSCI 인터페이스로 깨어났습니다. ARM : 다른 CPU 코어 / AP 시작 / 깨우기 / 연결 및 실행 시작 주소 전달?

또한 업스트림 버전 에는 gem5에서 작동하도록 약간의 조정이있어 성능 특성도 실험 할 수 있습니다.

실제 하드웨어에서 테스트하지 않았기 때문에 이것이 얼마나 휴대 가능한지 잘 모르겠습니다. 다음 Raspberry Pi 참고 문헌이 관심을 가질 수 있습니다.

이 문서는 다음 멀티 코어와 재미있는 일을하는 데 사용할 수있는 ARM 동기화 기본을 사용하는 방법에 대한 몇 가지 지침을 제공합니다 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에서 테스트되었습니다.

보다 편리한 프로그래밍 기능을위한 다음 단계

앞의 예제는 보조 CPU를 깨우고 전용 명령으로 기본 메모리 동기화를 수행합니다. 이는 좋은 시작입니다.

그러나 POSIX 와 같은 멀티 코어 시스템을 쉽게 프로그래밍 pthreads하려면 다음과 같은 관련 주제로 넘어 가야합니다.

  • setup은 인터럽트를 수행하고 타이머를 실행하여 현재 실행할 스레드를 주기적으로 결정합니다. 이것을 선점 형 멀티 스레딩이라고 합니다.

    또한 이러한 시스템은 스레드 레지스터가 시작 및 중지 될 때 저장 및 복원해야합니다.

    비선 점형 멀티 태스킹 시스템을 사용하는 것도 가능하지만 모든 스레드가 (예 : pthread_yield구현으로) 생성되도록 워크로드의 균형을 맞추기가 더 어려워 지도록 코드를 수정해야 할 수도 있습니다 .

    간단한 베어 메탈 타이머 예제는 다음과 같습니다.

  • 메모리 충돌을 다룹니다. 특히 C 또는 다른 고급 언어로 코딩하려면 각 스레드마다 고유 스택 이 필요합니다 .

    스레드가 고정 된 최대 스택 크기를 갖도록 제한 할 수 있지만이를 처리하는 더 좋은 방법 은 효율적인 “무제한 크기”스택을 허용 하는 페이징 입니다.

    다음은 스택이 너무 깊이 커질 경우 날려 버릴 것이라고 순진 aarch64의 baremetal 예제

Linux 커널이나 다른 운영 체제를 사용해야하는 좋은 이유가 있습니다. 🙂

유저 랜드 메모리 동기화 프리미티브

스레드 시작 / 중지 / 관리는 일반적으로 사용자 범위를 벗어나지 만 사용자 랜드 스레드의 어셈블리 명령어를 사용하여 잠재적으로 더 비싼 시스템 호출없이 메모리 액세스를 동기화 할 수 있습니다.

물론 이러한 저수준 프리미티브를 이식 할 수있는 라이브러리를 사용하는 것이 좋습니다. C ++ 표준 자체는 <mutex><atomic>헤더, 특히 로 크게 발전했습니다 std::memory_order. 가능한 모든 메모리 의미를 포함하는지 확실하지 않지만 단지 가능할 수 있습니다.

보다 미묘한 의미론은 잠금없는 데이터 구조 와 관련하여 특히 관련이 있으며 , 이는 특정 경우 성능 이점을 제공 할 수 있습니다. 이를 구현하려면 다양한 유형의 메모리 장벽에 대해 약간 배워야 할 것입니다. https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/

예를 들어 Boost는 https://www.boost.org/doc/libs/1_63_0/doc/html/lockfree.html에 잠금 해제 컨테이너 구현이 있습니다.

이러한 사용자 지침은 Linux futex의 주요 동기화 기본 요소 중 하나 인 Linux 시스템 호출 을 구현하는 데에도 사용됩니다 . man futex4.15는 다음과 같이 읽습니다.

futex () 시스템 호출은 특정 조건이 충족 될 때까지 기다리는 메소드를 제공합니다. 일반적으로 공유 메모리 동기화 컨텍스트에서 블로킹 구성으로 사용됩니다. futex를 사용할 때 대부분의 동기화 작업은 사용자 공간에서 수행됩니다. 사용자 공간 프로그램은 futex () 시스템 호출을 사용합니다. 조건이 true가 될 때까지 프로그램이 더 오랜 시간 동안 차단해야 할 경우입니다. 다른 futex () 연산을 사용하여 특정 조건을 기다리는 프로세스 또는 스레드를 깨울 수 있습니다.

syscall 이름 자체는 “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 prefix / aarch64 LDADD명령이 추가를 원자화했다는 것을 알았 습니다.이를 사용하지 않으면 많은 추가에 대한 경쟁 조건이 있으며 끝의 총 개수는 동기화 된 20000보다 적습니다.

또한보십시오:

Ubuntu 19.04 amd64 및 QEMU aarch64 사용자 모드에서 테스트되었습니다.


답변

내가 알기로, 각 “코어”는 자체 레지스터 세트가있는 완전한 프로세서입니다. 기본적으로 BIOS는 하나의 코어를 실행하면서 시작한 다음 운영 체제는 다른 코어를 초기화하고 코드에서 실행 등을 수행하여 다른 코어를 “시작”할 수 있습니다.

OS에서 동기화를 수행합니다. 일반적으로 각 프로세서는 OS에 대해 서로 다른 프로세스를 실행하므로 운영 체제의 다중 스레딩 기능은 어떤 프로세스가 어떤 메모리에 닿아 야하는지, 메모리 충돌시 수행 할 작업을 결정해야합니다.


답변

비공식 SMP FAQ 스택 오버플로 로고


예를 들어 x86 어셈블러를 작성하려면 예를 들어 “값 5로 EDX 레지스터로드”, “EDX 증가”레지스터 등의 명령이 표시됩니다. 4 코어 (또는 그 이상)의 최신 CPU 사용 머신 코드 레벨에서 4 개의 개별 CPU가있는 것처럼 보입니까 (즉, 4 개의 “EDX”레지스터 만 있음)?

바로 그거죠. 4 개의 개별 명령 포인터를 포함하여 4 개의 레지스터 세트가 있습니다.

그렇다면 “EDX 레지스터 증가”라고 말할 때 어떤 CPU의 EDX 레지스터가 증가하는지 결정하는 것은 무엇입니까?

그 명령을 자연스럽게 실행 한 CPU. 단순히 동일한 메모리를 공유하는 완전히 다른 4 개의 마이크로 프로세서로 생각하십시오.

x86 어셈블러에 “CPU 컨텍스트”또는 “스레드”개념이 있습니까?

아닙니다. 어셈블러는 항상했던 것처럼 명령어를 번역합니다. 거기에 변화가 없습니다.

코어 간의 통신 / 동기화는 어떻게 작동합니까?

그것들은 동일한 메모리를 공유하기 때문에 프로그램 논리의 문제입니다. 현재 프로세서 간 인터럽트 메커니즘이 있지만 필요하지 않으며 최초의 이중 CPU x86 시스템에는 원래 없었습니다.

운영 체제를 작성하는 경우 하드웨어를 통해 어떤 메커니즘이 노출되어 다른 코어에서 실행을 예약 할 수 있습니까?

스케줄러는 실제로 중요한 섹션과 사용 된 잠금 유형에 대해 약간 더 신중하다는 점을 제외하고는 변경되지 않습니다. SMP 이전에 커널 코드는 결국 스케줄러를 호출합니다. 스케줄러는 실행 큐를보고 다음 스레드로 실행할 프로세스를 선택합니다. (커널 프로세스는 스레드와 매우 비슷해 보입니다.) SMP 커널은 한 번에 하나의 스레드와 똑같은 코드를 실행합니다. 두 코어가 실수로 선택되지 않도록하려면 중요한 섹션 잠금이 SMP 안전해야합니다. 동일한 PID.

그것은 특별한 특권 교육인가?

아니요. 코어는 모두 이전 명령과 동일한 메모리에서 모두 실행 중입니다.

멀티 코어 CPU에 최적화 된 컴파일러 / 바이트 코드 VM을 작성하는 경우 모든 코어에서 효율적으로 실행되는 코드를 생성하려면 x86에 대해 구체적으로 알아야 할 사항은 무엇입니까?

이전과 동일한 코드를 실행합니다. 변경해야하는 것은 유닉스 나 Windows 커널입니다.

내 질문을 “멀티 코어 기능을 지원하기 위해 x86 기계 코드가 어떻게 변경 되었습니까?”라고 요약 할 수 있습니다.

아무것도 필요하지 않았습니다. 첫 번째 SMP 시스템은 단일 프로세서와 동일한 명령어 세트를 사용했습니다. 이제는 x86 아키텍처의 진화와 수십억 개의 새로운 명령이있어 더 빠르게 진행할 수 있었지만 SMP 에는 필요 하지 않았습니다 .

자세한 내용은 인텔 멀티 프로세서 사양을 참조하십시오 .


업데이트 : 모든 후속 질문이 단지 완전히 것을 수락하여 답변을 얻을 수 있습니다 N 웨이 멀티 코어 CPU는 거의이다 1 과 정확히 같은 일 N 그냥 같은 메모리를 공유 별도의 프로세서. 2 중요한 질문 이 있습니다. 더 많은 성능을 위해 프로그램이 둘 이상의 코어에서 실행되도록 어떻게 작성 되었습니까? 대답은 Pthreads 와 같은 스레드 라이브러리를 사용하여 작성되었습니다 . 일부 스레드 라이브러리는 OS에 표시되지 않는 “그린 스레드”를 사용하며 별도의 코어를 얻지 못하지만 스레드 라이브러리가 커널 스레드 기능을 사용하는 경우 스레드 프로그램은 자동으로 멀티 코어가됩니다.


1. 이전 버전과의 호환성을 위해 재설정시 첫 번째 코어 만 시작하고 나머지는 실행하기 위해 몇 가지 드라이버 유형 작업을 수행해야합니다.
2. 또한 모든 주변 장치를 자연스럽게 공유합니다.


답변

멀티 코어 CPU에 최적화 된 컴파일러 / 바이트 코드 VM을 작성하는 경우 모든 코어에서 효율적으로 실행되는 코드를 생성하려면 x86에 대해 구체적으로 알아야 할 사항은 무엇입니까?

최적화 컴파일러 / 바이트 코드 VM을 작성하는 사람으로서 여기에서 당신을 도울 수 있습니다.

모든 코어에서 효율적으로 실행되는 코드를 생성하기 위해 x86에 대해 특별히 알 필요는 없습니다.

그러나 모든 코어에서 올바르게 실행되는 코드를 작성하려면 cmpxchg 및 친구에 대해 알아야 할 수도 있습니다 . 멀티 코어 프로그래밍에는 실행 스레드 간 동기화 및 통신을 사용해야합니다.

x86에서 일반적으로 x86에서 효율적으로 실행되는 코드를 생성하려면 x86에 대해 알아야합니다.

배우는 데 도움이 될 다른 것들이 있습니다 :

여러 스레드를 실행할 수 있도록 OS (Linux 또는 Windows 또는 OSX)가 제공하는 기능에 대해 학습해야합니다. OpenMP 및 Threading Building Blocks 또는 OSX 10.6 “Snow Leopard”의 “Grand Central”과 같은 병렬화 API에 대해 배워야합니다.

컴파일러가 자동 병렬화되어야하는지 또는 컴파일러가 컴파일 한 애플리케이션 작성자가 다중 코어를 활용하기 위해 프로그램에 특수 구문 또는 API 호출을 추가해야 하는지를 고려해야합니다.


답변

각 코어는 다른 메모리 영역에서 실행됩니다. 운영 체제는 프로그램의 핵심을 가리키고 핵심은 프로그램을 실행합니다. 프로그램은 하나 이상의 코어가 있거나 어떤 코어에서 실행되고 있는지 인식하지 못합니다.

운영 체제에서만 사용 가능한 추가 지침도 없습니다. 이 코어는 단일 코어 칩과 동일합니다. 각 코어는 운영 체제의 일부를 실행하여 정보 교환에 사용되는 공통 메모리 영역과의 통신을 처리하여 다음에 실행할 메모리 영역을 찾습니다.

이것은 단순화이지만 어떻게 수행되는지에 대한 기본 아이디어를 제공합니다. Embedded.com의 멀티 코어 및 멀티 프로세서 에 대한 자세한 내용은이 항목에 대한 많은 정보를 제공합니다.이 항목은 매우 복잡합니다!


답변

어셈블리 코드는 하나의 코어에서 실행될 기계 코드로 변환됩니다. 멀티 스레드 방식으로 운영하려면 운영 체제 프리미티브를 사용하여 다른 프로세서에서이 코드를 여러 번 시작하거나 다른 코어에서 다른 코드 조각을 시작해야합니다. 각 코어는 별도의 스레드를 실행합니다. 각 스레드는 현재 실행중인 코어를 하나만 볼 수 있습니다.