多核汇编语言的外观如何?
例如,曾几何时,为了编写x86汇编程序,您将得到说明“将EDX寄存器载入值5”,“递增EDX”寄存器等。
使用具有4个内核(甚至更多)的现代CPU,在机器代码级别,它看起来好像有4个独立的CPU(即只有4个不同的“EDX”寄存器)? 如果是这样,当你说“递增EDX寄存器”时,决定哪个CPU的EDX寄存器递增? 现在x86汇编器中是否存在“CPU上下文”或“线程”概念?
内核之间的通信/同步如何工作?
如果你正在编写一个操作系统,那么通过硬件公开哪些机制让你能够在不同的内核上安排执行? 这是一些特殊的特权指令吗?
如果您正在为多核CPU编写一个优化的编译器/字节码虚拟机,那么您需要具体了解x86,才能生成可在所有内核中高效运行的代码?
对x86机器代码进行了哪些更改以支持多核功能?
这不是对问题的直接回答,但它是对评论中出现的问题的回答。 本质上,问题是硬件支持多线程操作。
Nicholas Flynt说得没错,至少在x86方面。 在多线程环境(超线程,多核或多处理器)中,引导线程(通常处理器0中的核0中的线程0)启动从地址0xfffffff0
获取代码。 所有其他线程都以特殊的睡眠状态启动,称为Wait-for-SIPI。 作为初始化的一部分,主线程通过APIC向WFS中的每个线程发送称为SIPI(启动IPI)的特殊处理器间中断(IPI)。 SIPI包含该线程开始获取代码的地址。
这种机制允许每个线程从不同的地址执行代码。 所有需要的是为每个线程建立自己的表和消息队列的软件支持。 操作系统使用这些来执行实际的多线程调度。
就实际装配而言,正如尼古拉斯所写的那样,单个线程或多线程应用程序的程序集没有区别。 每个逻辑线程都有自己的寄存器集,所以写入:
mov edx, 0
只会为当前正在运行的线程更新EDX
。 使用单个汇编指令无法在另一个处理器上修改EDX
。 您需要某种系统调用来要求操作系统告诉另一个线程运行将更新其自己的EDX
代码。
据我所知,每个“核心”是一个完整的处理器,拥有自己的寄存器集。 基本上,BIOS在您运行一个内核时启动您,然后操作系统可以通过初始化它们并指向代码运行等来“启动”其他内核。
同步由OS完成。 一般来说,每个处理器都为操作系统运行不同的进程,因此操作系统的多线程功能负责决定哪个进程接触哪个内存,以及如何处理内存冲突。
最小的可运行Intel x86裸机示例
可运行的裸机示例包含所有必需的样板。 所有主要部分如下所示。
在Ubuntu 15.10 QEMU 2.3.0和Lenovo ThinkPad T400上测试。
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
在该代码上:
大多数操作系统将使环3(用户程序)中的大部分操作无法进行。
所以你需要编写自己的内核来随意使用它:一个用户级的Linux程序将不起作用。
起初,单个处理器运行,称为引导处理器(BSP)。
它必须通过称为互操作处理器中断(IPI)的特殊中断唤醒其他处理器(称为应用处理器(AP))。
这些中断可以通过编程高级可编程中断控制器(APIC)通过中断命令寄存器(ICR)
ICR的格式记录在:10.6“发布代理程序中断”
一旦我们写入ICR,IPI就会发生。
ICR_LOW在8.4.4“MP初始化示例”中定义为:
ICR_LOW EQU 0FEE00300H
魔术值0FEE00300
是ICR的内存地址,如表10-1“本地APIC寄存器地址映射”
在这个例子中使用了最简单的方法:它将ICR设置为发送广播IPI,并将其发送到除当前处理器之外的所有其他处理器。
但也有可能,也有人建议通过BIOS设置的特殊数据结构(如ACPI表或英特尔MP配置表)获取有关处理器的信息,并且只唤醒您需要的那些处理器。
000C46XXH
XX
编码处理器将执行的第一条指令的地址:
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
使用链接脚本是另一种可能。
延迟循环是一个烦人的工作部分:没有超级简单的方法来精确地做这样的睡眠。
可能的方法包括:
相关:如何在屏幕上显示一个数字,并使用DOS x86汇编睡眠一秒钟?
我认为最初的处理器需要处于保护模式下才能工作,因为我们在写入地址为0FEE00300H
工作,这对于16位
为了在处理器之间进行通信,我们可以在主进程上使用自旋锁,并从第二个内核修改锁。
我们应该确保内存回写完成,例如通过wbinvd
。
处理器之间共享状态
8.7.1“逻辑处理器的状态”说:
以下功能是支持英特尔超线程技术的英特尔64或IA-32处理器内逻辑处理器体系结构状态的一部分。 这些功能可以细分为三组:
每个逻辑处理器都具有以下功能:
逻辑处理器共享以下功能:
以下功能是共享的还是重复的是特定于实现的:
缓存共享的讨论如下:
英特尔超线程比单独的内核具有更高的缓存和管道共享:https://superuser.com/questions/133082/hyper-threading-and-dual-core-whats-the-difference/995858#995858
Linux内核4.2
主要的初始化操作似乎在arch/x86/kernel/smpboot.c
。
上一篇: What does multicore assembly language look like?
下一篇: Probably heap corruption, but could it be something else?