Ngôn ngữ lắp ráp đa lõi trông như thế nào?


243

Ngày xưa, để viết trình biên dịch x86, chẳng hạn, bạn sẽ có các hướng dẫn ghi "tải thanh ghi EDX với giá trị 5", "tăng thanh ghi EDX", v.v.

Với các CPU hiện đại có 4 lõi (hoặc thậm chí nhiều hơn), ở cấp mã máy có trông giống như có 4 CPU riêng biệt (tức là chỉ có 4 thanh ghi "EDX" riêng biệt)? Nếu vậy, khi bạn nói "tăng thanh ghi EDX", điều gì xác định thanh ghi EDX của CPU nào được tăng lên? Bây giờ có khái niệm "bối cảnh CPU" hay "luồng" trong trình biên dịch x86 không?

Làm thế nào để giao tiếp / đồng bộ giữa các lõi hoạt động?

Nếu bạn đang viết một hệ điều hành, cơ chế nào được bộc lộ qua phần cứng để cho phép bạn lên lịch thực hiện trên các lõi khác nhau? Đây có phải là một số hướng dẫn đặc biệt riêng tư?

Nếu bạn đang viết một trình biên dịch tối ưu hóa / VM mã byte cho CPU đa lõi, bạn cần biết cụ thể về x86 để làm cho nó tạo mã chạy hiệu quả trên tất cả các lõi?

Những thay đổi nào đã được thực hiện đối với mã máy x86 để hỗ trợ chức năng đa lõi?


2
Có một câu hỏi tương tự (mặc dù không giống hệt nhau) ở đây: stackoverflow.com/questions/714905/ Kẻ
Nathan Fellman

Câu trả lời:


153

Đây không phải là câu trả lời trực tiếp cho câu hỏi, nhưng nó là câu trả lời cho câu hỏi xuất hiện trong các bình luận. Về cơ bản, câu hỏi là những gì phần cứng hỗ trợ cho hoạt động đa luồng.

Nicholas Flynt đã đúng , ít nhất là về x86. Trong môi trường đa luồng (Siêu phân luồng, đa lõi hoặc đa bộ xử lý), luồng Bootstrap (thường là luồng 0 trong lõi 0 trong bộ xử lý 0) khởi động tìm nạp mã từ địa chỉ 0xfffffff0. Tất cả các luồng khác bắt đầu trong trạng thái ngủ đặc biệt gọi là Wait-for-SIPI . Là một phần của quá trình khởi tạo, luồng chính gửi một ngắt liên bộ xử lý (IPI) đặc biệt qua APIC được gọi là SIPI (Startup IPI) tới mỗi luồng trong WFS. SIPI chứa địa chỉ mà luồng đó sẽ bắt đầu tìm nạp mã.

Cơ chế này cho phép mỗi luồng thực thi mã từ một địa chỉ khác nhau. Tất cả những gì cần thiết là hỗ trợ phần mềm cho mỗi luồng để thiết lập các bảng và hàng đợi nhắn tin của riêng nó. HĐH sử dụng chúng để thực hiện lập lịch đa luồng thực tế.

Theo như lắp ráp thực tế, như Nicholas đã viết, không có sự khác biệt giữa các hội đồng cho một ứng dụng đa luồng hoặc đa luồng. Mỗi luồng logic có tập thanh ghi riêng, do đó, viết:

mov edx, 0

sẽ chỉ cập nhật EDXcho chủ đề hiện đang chạy . Không có cách nào để sửa đổi EDXtrên bộ xử lý khác bằng một lệnh lắp ráp duy nhất. Bạn cần một số loại cuộc gọi hệ thống để yêu cầu HĐH nói với một luồng khác để chạy mã sẽ tự cập nhật EDX.


2
Cảm ơn vì đã lấp đầy khoảng trống trong câu trả lời của Nicholas. Đã đánh dấu câu trả lời của bạn là câu trả lời được chấp nhận ngay bây giờ .... đưa ra các chi tiết cụ thể mà tôi quan tâm ... mặc dù sẽ tốt hơn nếu có một câu trả lời duy nhất có thông tin của bạn và tất cả của Nicholas được kết hợp.
Paul Hollingsworth

3
Điều này không trả lời câu hỏi chủ đề đến từ đâu. Lõi và bộ xử lý là một thứ phần cứng, nhưng bằng cách nào đó, các luồng phải được tạo ra trong phần mềm. Làm thế nào để chủ đề chính biết nơi gửi SIPI? Hay chính SIPI tạo ra một chủ đề mới?
người làm giàu giàu có

7
@richremer: Có vẻ như bạn đang nhầm lẫn giữa các chủ đề CT và chủ đề SW. Chủ đề CT luôn tồn tại. Đôi khi nó ngủ. Bản thân SIPI đánh thức luồng CTNH và cho phép nó chạy SW. Tùy thuộc vào HĐH và BIOS để quyết định các luồng CTNH nào chạy, và các tiến trình và luồng SW nào chạy trên mỗi luồng CTNH.
Nathan Fellman

2
Rất nhiều thông tin hay và súc tích ở đây, nhưng đây là một chủ đề lớn - vì vậy các câu hỏi có thể kéo dài. Có một vài ví dụ về các hạt nhân "xương trần" hoàn chỉnh trong tự nhiên khởi động từ các ổ USB hoặc đĩa "mềm" - đây là phiên bản x86_32 được viết bằng trình biên dịch sử dụng bộ mô tả TSS cũ thực sự có thể chạy mã C đa luồng ( github. com / duanev / oz-x86-32-asm-003 ) nhưng không có hỗ trợ thư viện tiêu chuẩn. Khá nhiều hơn một chút so với bạn yêu cầu nhưng nó có thể trả lời một số câu hỏi còn sót lại.
duanev

87

Ví dụ về bar baral chạy được tối thiểu Intel x86

Ví dụ kim loại trần có thể chạy được với tất cả các mẫu nồi hơi cần thiết . Tất cả các phần chính được bảo hiểm dưới đây.

Đã thử nghiệm trên Ubuntu 15.10 QEMU 2.3.0 và Lenovo ThinkPad T400 khách phần cứng thực sự .

Các Intel Manual Tập 3 Hệ thống Lập trình Hướng dẫn - 325384-056US Tháng Chín 2015 bìa SMP trong chương 8, 9 và 10.

Bảng 8-1. "Trình tự phát sóng INIT-SIPI-SIPI và lựa chọn thời gian chờ" chứa một ví dụ về cơ bản chỉ hoạt động:

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

Trên mã đó:

  1. Hầu hết các hệ điều hành sẽ làm cho hầu hết các hoạt động đó không thể thực hiện được từ vòng 3 (chương trình người dùng).

    Vì vậy, bạn cần phải viết kernel của riêng mình để chơi tự do với nó: một chương trình Linux dành cho người dùng sẽ không hoạt động.

  2. Lúc đầu, một bộ xử lý duy nhất chạy, được gọi là bộ xử lý bootstrap (BSP).

    Nó phải đánh thức những cái khác (được gọi là Bộ xử lý ứng dụng (AP)) thông qua các ngắt đặc biệt gọi là Ngắt bộ xử lý liên bộ (IPI) .

    Những ngắt đó có thể được thực hiện bằng cách lập trình Bộ điều khiển ngắt lập trình nâng cao (APIC) thông qua thanh ghi lệnh ngắt (ICR)

    Định dạng của ICR được ghi lại tại: 10.6 "PHÁT HÀNH NỘI DUNG INTERPROCESSOR"

    IPI xảy ra ngay khi chúng tôi viết thư cho ICR.

  3. ICR_LOW được định nghĩa tại 8.4.4 "Ví dụ khởi tạo MP" là:

    ICR_LOW EQU 0FEE00300H
    

    Giá trị ma thuật 0FEE00300là địa chỉ bộ nhớ của ICR, như được ghi trong Bảng 10-1 "Bản đồ địa chỉ đăng ký APIC cục bộ"

  4. Phương pháp đơn giản nhất có thể được sử dụng trong ví dụ: nó thiết lập ICR để gửi IPI quảng bá được gửi đến tất cả các bộ xử lý khác ngoại trừ bộ xử lý hiện tại.

    Nhưng một số cũng có thể, và được một số người khuyên dùng , để có được thông tin về bộ xử lý thông qua các cấu trúc dữ liệu đặc biệt được thiết lập bởi BIOS như bảng ACPI hoặc bảng cấu hình MP của Intel và chỉ đánh thức từng cái bạn cần.

  5. XXtrong 000C46XXHmã hóa địa chỉ của lệnh đầu tiên mà bộ xử lý sẽ thực thi như:

    CS = XX * 0x100
    IP = 0
    

    Hãy nhớ rằng CS nhân nhiều địa chỉ theo0x10 , vì vậy địa chỉ bộ nhớ thực của lệnh đầu tiên là:

    XX * 0x1000
    

    Vì vậy, nếu ví dụ XX == 1, bộ xử lý sẽ bắt đầu tại 0x1000.

    Sau đó, chúng tôi phải đảm bảo rằng có mã chế độ thực 16 bit được chạy tại vị trí bộ nhớ đó, ví dụ:

    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
    

    Sử dụng một tập lệnh liên kết là một khả năng khác.

  6. Các vòng lặp trì hoãn là một phần khó chịu để làm việc: không có cách nào siêu đơn giản để thực hiện những giấc ngủ như vậy một cách chính xác.

    Các phương pháp có thể bao gồm:

    • Thuế TNCN (được sử dụng trong ví dụ của tôi)
    • HPET
    • hiệu chỉnh thời gian của một vòng lặp bận rộn với ở trên và sử dụng nó để thay thế

    Liên quan: Làm thế nào để hiển thị một số trên màn hình và ngủ trong một giây với lắp ráp DOS x86?

  7. Tôi nghĩ rằng bộ xử lý ban đầu cần ở chế độ được bảo vệ để nó hoạt động khi chúng ta ghi vào địa chỉ 0FEE00300Hquá cao cho 16 bit

  8. Để giao tiếp giữa các bộ xử lý, chúng ta có thể sử dụng một spinlock trên quy trình chính và sửa đổi khóa từ lõi thứ hai.

    Chúng ta nên đảm bảo rằng bộ nhớ ghi lại được thực hiện, ví dụ như thông qua wbinvd.

Trạng thái chia sẻ giữa các bộ xử lý

8.7.1 "Trạng thái của bộ xử lý logic" cho biết:

Các tính năng sau đây là một phần của trạng thái kiến ​​trúc của bộ xử lý logic trong bộ xử lý Intel 64 hoặc IA-32 hỗ trợ Công nghệ siêu phân luồng của Intel. Các tính năng có thể được chia thành ba nhóm:

  • Nhân đôi cho mỗi bộ xử lý logic
  • Được chia sẻ bởi các bộ xử lý logic trong một bộ xử lý vật lý
  • Chia sẻ hoặc nhân đôi, tùy thuộc vào việc thực hiện

Các tính năng sau được nhân đôi cho mỗi bộ xử lý logic:

  • Các thanh ghi mục đích chung (EAX, EBX, ECX, EDX, ESI, EDI, ESP và EBP)
  • Các thanh ghi phân đoạn (CS, DS, SS, ES, FS và GS)
  • Các thanh ghi EFLAGS và EIP. Lưu ý rằng các thanh ghi CS và EIP / RIP cho mỗi bộ xử lý logic trỏ đến luồng lệnh cho luồng được xử lý bởi bộ xử lý logic.
  • Các thanh ghi x87 FPU (ST0 đến ST7, từ trạng thái, từ điều khiển, từ thẻ, con trỏ toán hạng dữ liệu và con trỏ lệnh)
  • Các thanh ghi MMX (MM0 đến MM7)
  • Các thanh ghi XMM (XMM0 đến XMM7) và thanh ghi MXCSR
  • Thanh ghi điều khiển và thanh ghi con trỏ bảng hệ thống (GDTR, LDTR, IDTR, thanh ghi tác vụ)
  • Các thanh ghi gỡ lỗi (DR0, DR1, DR2, DR3, DR6, DR7) và các MSR kiểm soát gỡ lỗi
  • Kiểm tra trạng thái toàn cầu của máy (IA32_MCG_STATUS) và khả năng kiểm tra máy (IA32_MCG_CAP) MSR
  • Điều chế đồng hồ nhiệt và các MSR điều khiển quản lý nguồn ACPI
  • MSRs tem thời gian
  • Hầu hết các thanh ghi MSR khác, bao gồm bảng thuộc tính trang (PAT). Xem các ngoại lệ dưới đây.
  • Đăng ký APIC địa phương.
  • Các thanh ghi mục đích chung bổ sung (R8-R15), các thanh ghi XMM (XMM8-XMM15), thanh ghi điều khiển, IA32_EFER trên bộ xử lý Intel 64.

Các tính năng sau được chia sẻ bởi các bộ xử lý logic:

  • Các thanh ghi phạm vi loại bộ nhớ (MTRR)

Cho dù các tính năng sau được chia sẻ hoặc sao chép là dành riêng cho việc triển khai:

  • IA32_MISC_ENABLE MSR (địa chỉ MSR 1A0H)
  • Kiến trúc kiểm tra máy (MCA) MSR (ngoại trừ các MSR IA32_MCG_STATUS và IA32_MCG_CAP)
  • Kiểm soát giám sát hiệu suất và MSRs truy cập

Chia sẻ bộ nhớ cache được thảo luận tại:

Các siêu phân luồng của Intel có bộ nhớ cache và chia sẻ đường ống lớn hơn các lõi riêng biệt: /superuser/133082/hyper-threading-and-dual-core-whats-the-difference/995858#995858

Nhân Linux 4.2

Các hành động khởi tạo chính dường như là tại arch/x86/kernel/smpboot.c.

ARM ví dụ tối thiểu có thể chạy được

Ở đây tôi cung cấp một ví dụ ARMv8 aarch64 có thể chạy tối thiểu cho QEMU:

.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 ngược dòng .

Lắp ráp và chạy:

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

Trong ví dụ này, chúng tôi đặt CPU 0 trong một vòng lặp spinlock và nó chỉ thoát khi CPU 1 giải phóng spinlock.

Sau spinlock, CPU 0 sau đó thực hiện lệnh gọi semihost khiến QEMU thoát.

Nếu bạn khởi động QEMU chỉ với một CPU -smp 1, thì mô phỏng chỉ bị treo mãi mãi trên spinlock.

CPU 1 được đánh thức với giao diện PSCI, chi tiết hơn tại: ARM: Start / Wakeup / Đưa các lõi CPU / AP khác và vượt qua địa chỉ bắt đầu thực hiện?

Các phiên bản thượng nguồn cũng có một vài điều chỉnh để làm cho nó làm việc trên gem5, vì vậy bạn có thể thử nghiệm với các đặc tính hiệu suất là tốt.

Tôi chưa thử nghiệm nó trên phần cứng thực sự, vì vậy và tôi không chắc nó có khả năng di động như thế nào. Thư mục Raspberry Pi sau đây có thể được quan tâm:

Tài liệu này cung cấp một số hướng dẫn về cách sử dụng các nguyên hàm đồng bộ hóa ARM mà sau đó bạn có thể sử dụng để thực hiện những điều thú vị với nhiều lõi: http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_syn syncization_primologists.pdf

Đã thử nghiệm trên Ubuntu 18.10, GCC 8.2.0, Binutils 2.31.1, QEMU 2.12.0.

Các bước tiếp theo để lập trình thuận tiện hơn

Các ví dụ trước đánh thức CPU thứ cấp và thực hiện đồng bộ hóa bộ nhớ cơ bản với các hướng dẫn chuyên dụng, đây là một khởi đầu tốt.

Nhưng để làm cho các hệ thống đa lõi dễ lập trình, ví dụ như POSIX pthreads , bạn cũng cần phải đi sâu vào các chủ đề liên quan sau:

  • thiết lập ngắt và chạy bộ định thời định kỳ quyết định chủ đề nào sẽ chạy ngay bây giờ. Điều này được gọi là đa luồng ưu tiên .

    Hệ thống như vậy cũng cần lưu và khôi phục các thanh ghi luồng khi chúng được khởi động và dừng.

    Cũng có thể có các hệ thống đa nhiệm không ưu tiên, nhưng các hệ thống đó có thể yêu cầu bạn sửa đổi mã của mình để mọi luồng đều mang lại (ví dụ như với pthread_yieldviệc triển khai) và việc cân bằng khối lượng công việc trở nên khó khăn hơn.

    Dưới đây là một số ví dụ hẹn giờ kim loại trần đơn giản:

  • đối phó với xung đột bộ nhớ. Đáng chú ý, mỗi luồng sẽ cần một ngăn xếp duy nhất nếu bạn muốn mã bằng C hoặc các ngôn ngữ cấp cao khác.

    Bạn chỉ có thể giới hạn các luồng để có kích thước ngăn xếp tối đa cố định, nhưng cách tốt hơn để xử lý vấn đề này là phân trang cho phép ngăn xếp "kích thước không giới hạn" hiệu quả.

    Dưới đây là một ví dụ aarch64 ngây thơ sẽ nổ tung nếu ngăn xếp phát triển quá sâu

Đó là một số lý do tốt để sử dụng nhân Linux hoặc một số hệ điều hành khác :-)

Nguyên thủy đồng bộ hóa bộ nhớ người dùng

Mặc dù bắt đầu / dừng / quản lý luồng thường vượt quá phạm vi người dùng, tuy nhiên bạn có thể sử dụng các hướng dẫn lắp ráp từ các luồng của người dùng để đồng bộ hóa truy cập bộ nhớ mà không cần các cuộc gọi hệ thống đắt tiền hơn.

Tất nhiên bạn nên thích sử dụng các thư viện bao bọc một cách hợp lý các nguyên thủy cấp thấp này. Các tiêu chuẩn riêng của mình C ++ đã có những bước tiến lớn trên <mutex><atomic>tiêu đề, và đặc biệt với std::memory_order. Tôi không chắc liệu nó có bao gồm tất cả các ngữ nghĩa bộ nhớ có thể đạt được hay không, nhưng nó chỉ có thể.

Các ngữ nghĩa tinh tế hơn có liên quan đặc biệt trong bối cảnh khóa cấu trúc dữ liệu miễn phí , có thể mang lại lợi ích hiệu suất trong một số trường hợp nhất định. Để thực hiện những điều đó, bạn có thể sẽ phải tìm hiểu một chút về các loại rào cản bộ nhớ khác nhau: https://preshing.com/20120710/memory-barrier-are-like-source-control-operations/

Ví dụ, Boost có một số triển khai vùng chứa miễn phí khóa tại: https://www.boost.org/doc/libs/1_63_0/doc/html/lockfree.html

Các hướng dẫn về vùng người dùng như vậy cũng dường như được sử dụng để thực hiện lệnh futexgọi hệ thống Linux , đây là một trong những nguyên tắc đồng bộ hóa chính trong Linux. man futex4,15 lần đọc:

Cuộc gọi hệ thống futex () cung cấp một phương thức để đợi cho đến khi một điều kiện nhất định trở thành đúng. Nó thường được sử dụng như một cấu trúc chặn trong bối cảnh đồng bộ hóa bộ nhớ chia sẻ. Khi sử dụng Futexes, phần lớn các hoạt động đồng bộ hóa được thực hiện trong không gian người dùng. Một chương trình không gian người dùng chỉ sử dụng lệnh gọi hệ thống futex () khi có khả năng chương trình đó phải chặn trong một thời gian dài hơn cho đến khi điều kiện trở thành đúng. Các hoạt động futex () khác có thể được sử dụng để đánh thức mọi tiến trình hoặc luồng đang chờ một điều kiện cụ thể.

Bản thân tên tòa nhà có nghĩa là "Không gian người dùng nhanh XXX".

Dưới đây là một ví dụ C ++ x86_64 / aarch64 vô dụng tối thiểu với lắp ráp nội tuyến minh họa việc sử dụng cơ bản các hướng dẫn như vậy chủ yếu để giải trí:

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 ngược dòng .

Sản lượng có thể:

my_non_atomic_ulong 15264
my_arch_non_atomic_ulong 15267

Từ đó, chúng ta thấy rằng lệnh x86 LOCK tiền tố / aarch64 LDADDđã tạo ra nguyên tử bổ sung: không có nó, chúng ta có các điều kiện chạy đua trên nhiều bổ sung và tổng số ở cuối là ít hơn 20000 được đồng bộ hóa.

Xem thêm:

Đã thử nghiệm trong Ubuntu 19.04 amd64 và với chế độ người dùng QEMU aarch64.


Trình biên dịch nào bạn sử dụng để biên dịch ví dụ của bạn? GAS dường như không thích #include(lấy nó làm nhận xét), NASM, FASM, YASM không biết cú pháp AT & T để nó không thể là họ ... vậy đó là gì?
Ruslan

@Ruslan gcc, #includeđến từ bộ tiền xử lý C. Sử dụng phần Makefileđược cung cấp như được giải thích trong phần bắt đầu: github.com/cirosantilli/x86-bare-metal-examples/blob/ trộm Nếu điều đó không hiệu quả, hãy mở một vấn đề GitHub.
Ciro Santilli 郝海东 冠状 病 事件

trên x86, điều gì xảy ra nếu một lõi nhận ra không còn quá trình nào sẵn sàng để chạy trong hàng đợi? (điều này có thể xảy ra theo thời gian trên một hệ thống nhàn rỗi). Liệu spinlock lõi trên cấu trúc bộ nhớ chia sẻ cho đến khi có một nhiệm vụ mới? (có lẽ không tốt là nó sẽ sử dụng nhiều năng lượng) nó có gọi một cái gì đó như HLT để ngủ cho đến khi có một sự gián đoạn? (trong trường hợp đó ai chịu trách nhiệm đánh thức cốt lõi đó?)
tigrou

@tigrou không chắc chắn, nhưng tôi thấy rất có khả năng việc triển khai Linux sẽ khiến nó ở trạng thái nguồn cho đến khi ngắt (có khả năng là bộ đếm thời gian) tiếp theo, đặc biệt là trên ARM nơi có nguồn điện. Tôi sẽ cố gắng nhanh chóng để xem liệu điều đó có thể được quan sát một cách dễ dàng bằng dấu vết hướng dẫn của một trình giả lập chạy Linux hay không, đó có thể là: github.com/cirosantilli/linux-kernel-module-cheat/tree/ tựa
Ciro Santilli 郝海东 冠状六四 事件

1
Một số thông tin (cụ thể cho x86 / Windows) có thể được tìm thấy ở đây (xem "Chủ đề nhàn rỗi"). TL; DR: khi không có luồng có thể chạy trên CPU, CPU được gửi đến một luồng không hoạt động. Cùng với một số tác vụ khác, cuối cùng nó sẽ gọi thói quen nhàn rỗi của bộ xử lý quản lý năng lượng đã đăng ký (thông qua trình điều khiển được cung cấp bởi nhà cung cấp CPU, ví dụ: Intel). Điều này có thể chuyển CPU sang trạng thái C sâu hơn (ví dụ: C0 -> C3) để giảm mức tiêu thụ điện năng.
tigrou

43

Theo tôi hiểu, mỗi "lõi" là một bộ xử lý hoàn chỉnh, với bộ đăng ký riêng. Về cơ bản, BIOS khởi động bạn với một lõi đang chạy và sau đó hệ điều hành có thể "khởi động" các lõi khác bằng cách khởi tạo chúng và chỉ chúng vào mã để chạy, v.v.

Đồng bộ hóa được thực hiện bởi hệ điều hành. Nói chung, mỗi bộ xử lý đang chạy một tiến trình khác nhau cho HĐH, do đó, chức năng đa luồng của hệ điều hành có nhiệm vụ quyết định tiến trình nào sẽ chạm vào bộ nhớ nào và phải làm gì trong trường hợp va chạm bộ nhớ.


28
Điều này đặt ra câu hỏi mặc dù: Hướng dẫn nào có sẵn cho hệ điều hành để làm điều này?
Paul Hollingsworth

4
Có một bộ hướng dẫn được mã hóa cho điều đó, nhưng đó là vấn đề của hệ điều hành, không phải mã ứng dụng. Nếu mã ứng dụng muốn được đa luồng, nó phải gọi các chức năng của hệ điều hành để thực hiện "phép thuật".
sharptooth

2
BIOS thường sẽ xác định có bao nhiêu lõi khả dụng và sẽ chuyển thông tin này cho HĐH khi được hỏi. Có các tiêu chuẩn mà BIOS (và phần cứng) phải tuân thủ để truy cập vào các đặc điểm phần cứng (bộ xử lý, lõi, bus PCI, thẻ PCI, chuột, bàn phím, đồ họa, ISA, PCI-E / X, bộ nhớ, v.v.) cho các PC khác nhau nhìn giống như quan điểm của hệ điều hành. Nếu BIOS không báo cáo rằng có bốn lõi, HĐH thường sẽ cho rằng chỉ có một lõi. Thậm chí có thể có một thiết lập BIOS để thử nghiệm.
Olof Forshell

1
Điều đó thật tuyệt và nếu bạn đang viết một chương trình kim loại trần thì sao?
Alexander Ryan Baggett

3
@AlexanderRyanBaggett ,? Thậm chí là gì? Xin nhắc lại, khi chúng ta nói "hãy để nó cho HĐH", chúng ta đang tránh câu hỏi bởi vì câu hỏi là làm thế nào để HĐH làm điều đó? Nó sử dụng hướng dẫn lắp ráp nào?
Pacerier

39

Câu hỏi thường gặp về SMP không chính thức logo chồng tràn


Ngày xưa, để viết trình biên dịch x86, chẳng hạn, bạn sẽ có các hướng dẫn ghi "tải thanh ghi EDX với giá trị 5", "tăng thanh ghi EDX", v.v. Với các CPU hiện đại có 4 lõi (hoặc thậm chí nhiều hơn) , ở cấp mã máy, có vẻ như có 4 CPU riêng biệt (tức là chỉ có 4 thanh ghi "EDX" riêng biệt)?

Chính xác. Có 4 bộ thanh ghi, trong đó có 4 con trỏ lệnh riêng biệt.

Nếu vậy, khi bạn nói "tăng thanh ghi EDX", điều gì xác định thanh ghi EDX của CPU nào được tăng lên?

CPU thực hiện lệnh đó, một cách tự nhiên. Hãy nghĩ về nó như 4 bộ vi xử lý hoàn toàn khác nhau chỉ đơn giản là chia sẻ cùng một bộ nhớ.

Bây giờ có khái niệm "bối cảnh CPU" hay "luồng" trong trình biên dịch x86 không?

Không. Trình biên dịch chỉ dịch các hướng dẫn như mọi khi. Không có thay đổi ở đó.

Làm thế nào để giao tiếp / đồng bộ giữa các lõi hoạt động?

Vì chúng có chung bộ nhớ, nên chủ yếu là logic chương trình. Mặc dù hiện tại có một cơ chế ngắt giữa các bộ xử lý , nhưng nó không cần thiết và ban đầu không có trong các hệ thống x86 CPU kép đầu tiên.

Nếu bạn đang viết một hệ điều hành, cơ chế nào được bộc lộ qua phần cứng để cho phép bạn lên lịch thực hiện trên các lõi khác nhau?

Bộ lập lịch thực sự không thay đổi, ngoại trừ việc nó cẩn thận hơn một chút về các phần quan trọng và các loại khóa được sử dụng. Trước SMP, mã hạt nhân cuối cùng sẽ gọi bộ lập lịch, sẽ xem xét hàng đợi chạy và chọn một quy trình để chạy như luồng tiếp theo. . cùng một PID.

Đây có phải là một số hướng dẫn đặc quyền đặc biệt?

Không. Các lõi chỉ chạy trong cùng một bộ nhớ với cùng các hướng dẫn cũ.

Nếu bạn đang viết một trình biên dịch tối ưu hóa / VM mã byte cho CPU đa lõi, bạn cần biết cụ thể về x86 để làm cho nó tạo mã chạy hiệu quả trên tất cả các lõi?

Bạn chạy mã giống như trước. Đó là nhân Unix hoặc Windows cần thay đổi.

Bạn có thể tóm tắt câu hỏi của tôi là "Những thay đổi nào đã được thực hiện đối với mã máy x86 để hỗ trợ chức năng đa lõi?"

Không có gì là cần thiết. Các hệ thống SMP đầu tiên đã sử dụng cùng một bộ hướng dẫn chính xác như các bộ xử lý đơn. Bây giờ, đã có rất nhiều sự phát triển kiến ​​trúc x86 và hàng trăm hướng dẫn mới để làm cho mọi thứ nhanh hơn, nhưng không cần thiết cho SMP.

Để biết thêm thông tin, hãy xem Thông số kỹ thuật đa bộ xử lý của Intel .


Cập nhật: tất cả các câu hỏi tiếp theo có thể được trả lời bằng cách chỉ hoàn toàn chấp nhận rằng một n CPU đa lõi hai chiều gần như là 1 chính xác những điều tương tự như n vi xử lý riêng biệt mà chỉ chia sẻ bộ nhớ tương tự. 2 Có một câu hỏi quan trọng không được đặt ra: làm thế nào một chương trình được viết để chạy trên nhiều lõi để có hiệu suất cao hơn? Và câu trả lời là: nó được viết bằng thư viện luồng như Pthreads. Một số thư viện luồng sử dụng "luồng xanh" mà HĐH không thể nhìn thấy và những luồng đó sẽ không có lõi riêng, nhưng miễn là thư viện luồng sử dụng các tính năng luồng của nhân thì chương trình luồng của bạn sẽ tự động được đa lõi.
1. Để tương thích ngược, chỉ lõi đầu tiên khởi động khi thiết lập lại và một vài điều thuộc loại trình điều khiển cần được thực hiện để kích hoạt những cái còn lại.
2. Họ cũng chia sẻ tất cả các thiết bị ngoại vi, một cách tự nhiên.


3
Tôi luôn nghĩ rằng "luồng" là một khái niệm phần mềm, điều này khiến tôi khó hiểu về bộ xử lý đa lõi, vấn đề là, làm thế nào các mã có thể nói với lõi "Tôi sẽ tạo ra một luồng chạy trong lõi 2"? Có bất kỳ mã lắp ráp đặc biệt để làm điều đó?
demonguy

2
@demonguy: Không, không có hướng dẫn đặc biệt cho bất cứ điều gì như thế. Bạn yêu cầu HĐH chạy luồng của bạn trên một lõi cụ thể bằng cách đặt mặt nạ ái lực (có nội dung "luồng này có thể chạy trên tập hợp các lõi logic này"). Đó hoàn toàn là một vấn đề phần mềm. Mỗi lõi CPU (luồng phần cứng) được chạy độc lập Linux (hoặc Windows). Để làm việc cùng với các luồng phần cứng khác, họ sử dụng các cấu trúc dữ liệu được chia sẻ. Nhưng bạn không bao giờ "trực tiếp" bắt đầu một luồng trên một CPU khác. Bạn nói với HĐH mà bạn muốn có một luồng mới và nó ghi chú trong cấu trúc dữ liệu mà HĐH trên lõi khác nhìn thấy.
Peter Cordes

2
Tôi có thể nói với os nó, nhưng làm thế nào os đưa mã vào lõi cụ thể?
demonguy

4
@demonguy ... (đơn giản hóa) ... mỗi lõi chia sẻ hình ảnh HĐH và bắt đầu chạy nó ở cùng một nơi. Vì vậy, đối với 8 lõi, đó là 8 "quy trình phần cứng" đang chạy trong kernel. Mỗi người gọi cùng một hàm lập lịch kiểm tra bảng quy trình cho một tiến trình hoặc luồng có thể chạy được. (Đó là hàng đợi chạy. ) Trong khi đó, các chương trình với các luồng hoạt động mà không nhận thức được bản chất SMP cơ bản. Họ chỉ cần rẽ nhánh (2) hoặc một cái gì đó và cho kernel biết họ muốn chạy. Về cơ bản, lõi tìm thấy quy trình, thay vì quá trình tìm lõi.
DigitalRoss

1
Bạn thực sự không cần phải làm gián đoạn một lõi từ một lõi khác. Hãy suy nghĩ về nó theo cách này: mọi thứ bạn cần để giao tiếp trước đây đều được truyền đạt tốt với các cơ chế phần mềm. Các cơ chế phần mềm tương tự tiếp tục hoạt động. Vì vậy, đường ống, cuộc gọi hạt nhân, giấc ngủ / thức dậy, tất cả những thứ đó ... chúng vẫn hoạt động như trước đây. Không phải mọi quá trình đều chạy trên cùng một CPU nhưng chúng có cùng cấu trúc dữ liệu để liên lạc như trước đây. Nỗ lực trong việc đi SMP chủ yếu giới hạn để làm cho các khóa cũ hoạt động trong một môi trường song song hơn.
DigitalRoss

10

Nếu bạn đang viết một trình biên dịch tối ưu hóa / VM mã byte cho CPU đa lõi, bạn cần biết cụ thể về x86 để làm cho nó tạo mã chạy hiệu quả trên tất cả các lõi?

Là một người viết trình tối ưu hóa trình biên dịch / mã byte, tôi có thể giúp bạn ở đây.

Bạn không cần biết gì cụ thể về x86 để làm cho nó tạo mã chạy hiệu quả trên tất cả các lõi.

Tuy nhiên, bạn có thể cần biết về cmpxchg và bạn bè để viết mã chạy chính xác trên tất cả các lõi. Lập trình đa lõi yêu cầu sử dụng đồng bộ hóa và giao tiếp giữa các luồng thực thi.

Bạn có thể cần biết một vài điều về x86 để làm cho nó tạo mã chạy hiệu quả trên x86 nói chung.

Có những thứ khác sẽ hữu ích cho bạn để học:

Bạn nên tìm hiểu về các phương tiện mà HĐH (Linux hoặc Windows hoặc OSX) cung cấp để cho phép bạn chạy nhiều luồng. Bạn nên tìm hiểu về các API song song hóa như OpenMP và Khối xây dựng luồng, hoặc OSX 10.6 "Snow Leopard" sắp ra mắt "Grand Central".

Bạn nên xem xét liệu trình biên dịch của bạn có nên tự động song song hay không, nếu tác giả của các ứng dụng được biên dịch bởi trình biên dịch của bạn cần thêm cú pháp hoặc các lệnh gọi API đặc biệt vào chương trình của anh ta để tận dụng nhiều lõi.


Không có một số máy ảo phổ biến như .NET và Java có vấn đề là quy trình GC chính của chúng được bao phủ trong các khóa và về cơ bản là đơn lẻ?
Marco van de Voort

9

Mỗi lõi thực thi từ một vùng nhớ khác nhau. Hệ điều hành của bạn sẽ trỏ một lõi vào chương trình của bạn và lõi đó sẽ thực thi chương trình của bạn. Chương trình của bạn sẽ không nhận thức được rằng có nhiều hơn một lõi hoặc trên lõi mà nó đang thực thi.

Cũng không có hướng dẫn bổ sung chỉ có sẵn cho Hệ điều hành. Các lõi này giống hệt với chip lõi đơn. Mỗi Lõi chạy một phần của Hệ điều hành sẽ xử lý giao tiếp đến các vùng nhớ chung được sử dụng để trao đổi thông tin để tìm vùng nhớ tiếp theo để thực thi.

Đây là một sự đơn giản hóa nhưng nó cung cấp cho bạn ý tưởng cơ bản về cách nó được thực hiện. Thông tin thêm về đa lõi và đa bộ xử lý trên Embedded.com có ​​nhiều thông tin về chủ đề này ... Chủ đề này trở nên phức tạp rất nhanh!


Tôi nghĩ người ta nên phân biệt kỹ hơn một chút ở đây về cách thức hoạt động của đa lõi nói chung và mức độ ảnh hưởng của hệ điều hành. "Mỗi lõi thực thi từ một vùng nhớ khác nhau" theo quan điểm của tôi là quá sai lệch. Trước hết, sử dụng nhiều lõi trong các nguyên tắc không cần điều này và bạn có thể dễ dàng thấy rằng đối với chương trình luồng, bạn MUỐN hai lõi làm việc trên cùng một phân đoạn văn bản và dữ liệu (trong khi mỗi lõi cũng cần các tài nguyên riêng lẻ như ngăn xếp) .
Volker Stolz

@ShiDoiSi Đó là lý do tại sao câu trả lời của tôi chứa văn bản "Đây là một sự đơn giản hóa" .
Gerhard

5

Mã lắp ráp sẽ dịch thành mã máy sẽ được thực thi trên một lõi. Nếu bạn muốn nó được đa luồng, bạn sẽ phải sử dụng các nguyên hàm của hệ điều hành để khởi động mã này trên các bộ xử lý khác nhau vài lần hoặc các đoạn mã khác nhau trên các lõi khác nhau - mỗi lõi sẽ thực thi một luồng riêng. Mỗi luồng sẽ chỉ nhìn thấy một lõi mà nó hiện đang thực thi.


4
Tôi sẽ nói một cái gì đó như thế này, nhưng sau đó hệ điều hành phân bổ các luồng cho lõi như thế nào? Tôi tưởng tượng có một số hướng dẫn lắp ráp đặc quyền thực hiện điều này. Nếu vậy, tôi nghĩ đó là câu trả lời mà tác giả đang tìm kiếm.
A. Levy

Không có chỉ dẫn nào cho điều đó, đó là nhiệm vụ của bộ lập lịch hệ điều hành. Có các chức năng hệ điều hành như SetThreadAffinityMask trong Win32 và mã có thể gọi chúng, nhưng đó là công cụ hệ điều hành và ảnh hưởng đến trình lập lịch biểu, đó không phải là hướng dẫn của bộ xử lý.
sharptooth

2
Phải có OpCode nếu không hệ điều hành cũng không thể làm được.
Matthew Whited

1
Không thực sự là một opcode để lập lịch - giống như bạn nhận được một bản sao của HĐH cho mỗi bộ xử lý, chia sẻ một không gian bộ nhớ; Bất cứ khi nào một lõi nhập lại kernel (syscall hoặc interrupt), nó sẽ xem xét các cấu trúc dữ liệu tương tự trong bộ nhớ để quyết định luồng nào sẽ chạy tiếp theo.
pjc50

1
@ A.Levy: Khi bạn bắt đầu một chuỗi với một mối quan hệ chỉ cho phép nó chạy trên một lõi khác, nó sẽ không ngay lập tức chuyển sang lõi khác. Nó có bối cảnh được lưu vào bộ nhớ, giống như một công tắc ngữ cảnh bình thường. Các luồng phần cứng khác nhìn thấy mục nhập của nó trong cấu trúc dữ liệu của bộ lập lịch và một trong số chúng cuối cùng sẽ quyết định rằng nó sẽ chạy luồng. Vì vậy, từ quan điểm của lõi đầu tiên: bạn viết vào cấu trúc dữ liệu được chia sẻ và cuối cùng mã hệ điều hành trên lõi khác (luồng phần cứng) sẽ chú ý đến nó và chạy nó.
Peter Cordes

3

Nó hoàn toàn không được thực hiện trong hướng dẫn máy; các lõi giả vờ là các CPU riêng biệt và không có bất kỳ khả năng đặc biệt nào để nói chuyện với nhau. Có hai cách họ giao tiếp:

  • họ chia sẻ không gian địa chỉ vật lý. Phần cứng xử lý sự kết hợp bộ đệm, do đó, một CPU ghi vào địa chỉ bộ nhớ mà người khác đọc.

  • họ chia sẻ một APIC (bộ điều khiển ngắt lập trình). Đây là bộ nhớ được ánh xạ vào không gian địa chỉ vật lý và có thể được sử dụng bởi một bộ xử lý để điều khiển các bộ xử lý khác, bật hoặc tắt, gửi các ngắt, v.v.

http://www.cheesecake.org/sac/smp.html là một tài liệu tham khảo tốt với một url ngớ ngẩn.


2
Thực tế họ không chia sẻ một APIC. Mỗi CPU logic có một cái riêng. Các APIC liên lạc với nhau, nhưng chúng là riêng biệt.
Nathan Fellman

Chúng đồng bộ hóa (chứ không phải giao tiếp) theo một cách cơ bản và đó là thông qua tiền tố LOCK (hướng dẫn "xchg mem, reg" chứa một yêu cầu khóa ẩn) chạy đến chốt khóa chạy đến tất cả các bus một cách hiệu quả cho chúng biết rằng CPU (thực ra là bất kỳ thiết bị làm chủ xe buýt nào) muốn truy cập độc quyền vào xe buýt. Cuối cùng, một tín hiệu sẽ trở về chân LOCKA (xác nhận) cho CPU biết rằng giờ đây nó có quyền truy cập độc quyền vào xe buýt. Vì các thiết bị bên ngoài chậm hơn nhiều so với hoạt động bên trong của CPU, nên chuỗi LOCK / LOCKA có thể cần nhiều hàng trăm chu kỳ CPU để hoàn thành.
Olof Forshell

1

Sự khác biệt chính giữa một ứng dụng đơn và đa luồng là cái trước có một ngăn xếp và cái sau có một ngăn cho mỗi luồng. Mã được tạo ra hơi khác một chút vì trình biên dịch sẽ cho rằng các thanh ghi phân đoạn dữ liệu và ngăn xếp (DS và ss) không bằng nhau. Điều này có nghĩa là việc xác định thông qua các thanh ghi ebp và đặc biệt mặc định cho thanh ghi ss cũng sẽ không được mặc định là DS (vì ds! = Ss). Ngược lại, việc chuyển hướng thông qua các thanh ghi khác mặc định thành DS sẽ không mặc định là ss.

Các chủ đề chia sẻ mọi thứ khác bao gồm các khu vực dữ liệu và mã. Họ cũng chia sẻ các thói quen lib để đảm bảo rằng chúng an toàn cho chuỗi. Một thủ tục sắp xếp một khu vực trong RAM có thể được đa luồng để tăng tốc mọi thứ. Các luồng sau đó sẽ được truy cập, so sánh và sắp xếp dữ liệu trong cùng một vùng nhớ vật lý và thực thi cùng một mã nhưng sử dụng các biến cục bộ khác nhau để kiểm soát phần tương ứng của chúng. Tất nhiên điều này là do các luồng có các ngăn xếp khác nhau trong đó các biến cục bộ được chứa. Kiểu lập trình này yêu cầu điều chỉnh mã cẩn thận để các xung đột dữ liệu giữa lõi (trong bộ nhớ cache và RAM) được giảm xuống, từ đó dẫn đến một mã nhanh hơn với hai hoặc nhiều luồng so với chỉ một. Tất nhiên, một mã không được điều chỉnh thường sẽ nhanh hơn với một bộ xử lý so với hai hoặc nhiều hơn. Để gỡ lỗi khó khăn hơn vì điểm dừng "int 3" tiêu chuẩn sẽ không được áp dụng vì bạn muốn làm gián đoạn một luồng cụ thể và không phải tất cả chúng. Điểm dừng đăng ký gỡ lỗi cũng không giải quyết được vấn đề này trừ khi bạn có thể đặt chúng trên bộ xử lý cụ thể thực thi luồng cụ thể mà bạn muốn ngắt.

Mã đa luồng khác có thể liên quan đến các luồng khác nhau chạy trong các phần khác nhau của chương trình. Kiểu lập trình này không yêu cầu cùng loại điều chỉnh và do đó dễ học hơn nhiều.


0

Những gì đã được thêm vào trên mọi kiến ​​trúc có khả năng đa xử lý so với các biến thể của bộ xử lý đơn đi trước chúng là các hướng dẫn để đồng bộ hóa giữa các lõi. Ngoài ra, bạn có các hướng dẫn để xử lý sự đồng bộ bộ đệm, xóa bộ đệm và các hoạt động cấp thấp tương tự mà HĐH phải xử lý. Trong trường hợp các kiến ​​trúc đa luồng đồng thời như IBM POWER6, IBM Cell, Sun Niagara và Intel "Hyperthreading", bạn cũng có xu hướng xem các hướng dẫn mới để ưu tiên giữa các luồng (như đặt ưu tiên và cho năng suất rõ ràng của bộ xử lý khi không có gì để làm) .

Nhưng ngữ nghĩa đơn luồng cơ bản là như nhau, bạn chỉ cần thêm các tiện ích bổ sung để xử lý đồng bộ hóa và giao tiếp với các lõi khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.