能否使用INIT-SIPI-SIPI序列在所有处于实模式的英特尔核心上唤醒它们?

7

我正在使用DOS启动并启动我的应用程序test.exe。该程序以实模式启动BSP(引导处理器)并在FEE0:0000处访问APIC表, 以启用位于偏移量0x0F0的SVI(虚拟中断向量),并使用ICR_low(偏移0x300)和ICR_high(偏移0x310)发送INIT-SIPI-SIPI序列进行初始化。BSP进入循环内部jmp $停止执行,让APs(应用处理器)在地址0000:8000处执行代码并打印一个字符。

由于我没有看到任何AP打印任何内容到显示器上,因此似乎信息没有被发送到AP。

我正在以实模式下使用FreeDos。我使用FASM(flat assembler)进行编译。

我使用了OsDev手册,其中包含了我正在尝试的代码(经过一些修改)以尽可能简单地查看是否能够使其工作。我还参考了Intel编程人员手册、其他规范以及Code Project的教程。

我只是想唤醒APs并执行一些简单的代码。我发现的所有示例都进入虚拟模式、保护模式、长模式或专注于多核处理。我只是编写此代码以了解其工作原理。

我的代码如下:

    format MZ  

    USE16 

    start:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    xor sp, sp
    cld
    ;Clear screen                
       mov ax, 03h
       int 10h
    ;Move payload to the desired address
       mov si, payload
       mov cx, payload_end-payload + 1
       mov bx,es
       mov ax,7c0h
       mov es,ax
       mov di,400h                 ;07c0:400 = 8000h
       rep movsb
       mov es,bx
    ;Enable APIC table
       call enable_lapic
    ; Wakeup the other APs
      ;INIT
       call lapic_send_init
       mov cx, WAIT_10_ms
       call us_wait
      ;SIPI
       call lapic_send_sipi
       mov cx, WAIT_200_us
       call us_wait
      ;SIPI
       call lapic_send_sipi

      ;Jump to the payload
      ;Para teste de acordar nucleos
      jmp 0000h:8000h ;voltar esse depois


    ;Payload é o código que será movido para o endereço físico 0x08000
    payload:
      mov ax, cs
      mov ds, ax
      xor sp, sp
      cld
    ;Only print letter 'A' directly to video memory
      mov cx,0b800h
      mov es,cx
      mov di,00h
      mov al,41h
      stosb
      cli    
      hlt    
    payload_end:

    enable_lapic:
      mov ecx, IA32_APIC_BASE_MSR
      rdmsr
      or ah, 08h ;Enable global APIC flag
      wrmsr
      and ah, 0f0h ; Mask to obtain APIC_Base address
      mov DWORD [APIC_BASE], eax ;Save it
      shr eax,16
      mov bx,fs
      mov fs,ax
      mov ecx, DWORD [fs:APIC_REG_SIV] ;Load value from SIV (FEE0:00F0) to ecx
      or ch, 01h    ;bit8: APIC SOFTWARE enable/disable
      mov DWORD [fs:APIC_REG_SIV], ecx ;Save it
      mov fs,bx
      ret

    IA32_APIC_BASE_MSR = 1bh
    APIC_REG_SIV       = 0f0h
    APIC_REG_ICR_LOW   = 300h
    APIC_REG_ICR_HIGH  = 310h
    APIC_REG_ID        = 20h

    APIC_BASE         dd 00h

    ;CX = Wait (in ms) Max 65536 us (=0 on input)
    us_wait:
      mov dx, 80h               ;POST Diagnose port, 1us per IO
      xor si, si
      rep outsb
      ret
      WAIT_10_ms     = 10000
      WAIT_200_us    = 200

    lapic_send_init:
      mov eax, DWORD [APIC_BASE]
      xor ebx, ebx
      shr eax,16
      mov cx,fs
      mov fs,ax
      mov DWORD [fs:APIC_REG_ICR_HIGH], ebx
      mov ebx, 0c4500h
      mov DWORD [fs:APIC_REG_ICR_LOW], ebx  ;Writing the low DWORD sent the IPI
      mov fs,cx
      ret

    lapic_send_sipi:
      mov eax, DWORD [APIC_BASE]
      xor ebx, ebx
      shr eax,16
      mov cx,fs
      mov fs,ax
      mov DWORD [fs:APIC_REG_ICR_HIGH], ebx
      mov ebx, 0c4608h
      mov DWORD [fs:APIC_REG_ICR_LOW], ebx  ;Writing the low DWORD sent the IPI
      mov fs,cx
      ret

我希望BSP进入一个无限循环,AP在0000:8000执行代码并在视频内存中打印“A”。 2019年11月06日 大家好! 现在我有一段可以访问保护模式的代码。由于我难以转移到非真实模式,所以我决定保持在保护模式下,并通过这种方式启用所有内核。 这是一个简单的代码,但正如Michael Petch所说,我尝试在引导装载程序中完成它。 以下是代码: “格式化二进制为'bin'”
use16

org 0x7C00

boot:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    xor sp, sp

   ;Clear screen
   ; mov ax, 03h
   ; int 10h

   ;Set VGA text mode 3
    mov ax,0x3
    int 0x10

   ;Move payload to the desired address
    mov si, payload
    mov cx, payload_end-payload + 1
    ;mov si,boot2
    ;mov cx,boot2_end-boot2+1
    mov bx,es
    mov ax,7c0h
    mov es,ax
    mov di,400h                 ;07c0:400 = 8000h
    rep movsb
    mov es,bx

    ;jmp 0000h:8000h

    call enableA20Line

    call enterProtectedMode

use32

    ;Enable the APIC
     call enable_lapic

    ;INIT
     call lapic_send_init
     ;mov cx, WAIT_10_ms
     ;call us_wait
     .Verify1:
        PAUSE
        MOV EBX,[APIC_BASE]
        MOV EAX,[EBX+0x300];
        SHR EAX,12
        TEST EAX,1
        JNZ .Verify1
     MOV EDI,[APIC_BASE]
     ADD EDI,0xB0
     MOV dword [EDI],0

    ;SIPI
     call lapic_send_sipi
     ;mov cx, WAIT_200_us
     ;call us_wait
     .Verify2:
        PAUSE
        MOV EBX,[APIC_BASE]
        MOV EAX,[EBX+0x300];
        SHR EAX,12
        TEST EAX,1
        JNZ .Verify2
     MOV EDI,[APIC_BASE]
     ADD EDI,0xB0
     MOV dword [EDI],0

    ;SIPI
     call lapic_send_sipi
     ;mov cx, WAIT_200_us
     ;call us_wait
     .Verify3:
        PAUSE
        MOV EBX,[APIC_BASE]
        MOV EAX,[EBX+0x300];
        SHR EAX,12
        TEST EAX,1
        JNZ .Verify3
     MOV EDI,[APIC_BASE]
     ADD EDI,0xB0
     MOV dword [EDI],0

    ;mov eax,0x8000
    ;jmp DWORD[eax]
    ;jmp boot2
    ;jmp 0x8000
    ;jmp $
    ;cli
    ;hlt
    mov eax,0x000b8010
    mov dword[eax],0e41h
    cli
    hlt

use16

enableA20Line:
    mov ax,0x2401
    int 0x15 ;enable A20 bit
    ret

enterProtectedMode:
    lgdt[gdt_pointer]
    mov eax,cr0
    or eax,0x1 ;set the protected mode bit on special cpu reg CR0
    mov cr0,eax

    jmp CODE_SEG:exit ;long jump to the code segment
    exit:
    ret

gdt_pointer:
    dw gdt_end - gdt_start
    dd gdt_start
CODE_SEG = gdt_code - gdt_start
DATA_SEG = gdt_data - gdt_start

gdt_start:
    dq 0x0        ;NULL segment
gdt_code:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0
gdt_data:
    dw 0xFFFF
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0
gdt_end:

;CX = Wait (in ms) Max 65536 us (=0 on input)
 us_wait:
  mov dx, 80h               ;POST Diagnose port, 1us per IO
  xor si, si
  rep outsb
  ret

  WAIT_10_ms     = 10000
  WAIT_200_us    = 200

use32

enable_lapic:
  mov ecx, IA32_APIC_BASE_MSR
  rdmsr
  or ah, 08h        ;bit11: APIC GLOBAL Enable/Disable
  wrmsr

  and ah, 0f0h
  mov DWORD [APIC_BASE], eax

  mov ecx, DWORD [eax+APIC_REG_SIV]
  ;or ch, 01h                                ;bit8: APIC SOFTWARE enable/disable
  or edx,01FFh
  mov DWORD [eax+APIC_REG_SIV], ecx

  mov DWORD[eax+0B0h],00h
  ret

lapic_send_init:
  mov eax, DWORD [APIC_BASE]

  xor ebx, ebx
  mov DWORD [eax+APIC_REG_ICR_HIGH], ebx

  mov ebx, 0c4500h
  mov DWORD [eax+APIC_REG_ICR_LOW], ebx  ;Writing the low DWORD sent the IPI
  ret

lapic_send_sipi:
  mov eax, DWORD [APIC_BASE]

  xor ebx, ebx
  mov DWORD [eax+APIC_REG_ICR_HIGH], ebx

  mov ebx, 0c4608h
  mov DWORD [eax+APIC_REG_ICR_LOW], ebx  ;Writing the low DWORD sent the IPI
  ret

 IA32_APIC_BASE_MSR = 1bh

 APIC_REG_SIV       = 0f0h

 APIC_REG_ICR_LOW   = 300h
 APIC_REG_ICR_HIGH  = 310h

 APIC_REG_ID        = 20h

 APIC_BASE        dd  00h



boot2:
    mov ax,DATA_SEG
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax

    mov esi,hello2
    mov ebx,0b8000h
    .loop:
        lodsb
        or al,al
        jz halt
        or eax,0x0100
        mov word[ebx],ax
        add ebx,2
        jmp .loop
halt:
    cli
    hlt
    hello2: db "Hello world!",0
boot2_end:

use16

payload:
    mov ax,cs
    mov ds,ax
    xor sp,sp

    mov ax,0b800h
    mov es,ax
    mov di,20h
    mov ax,0e45h
    mov [es:di],al

    cli
    hlt
    ;jmp $

payload_end:

times 510 - ($-$$) db 0 ; pad remaining 510 bytes with zeroes
dw 0xaa55 ; magic bootloader magic - marks this 512 byte sector bootable!" 

现在我正在寻找一个延迟程序来发送init和sipi消息。我认为这是问题所在,因为它还没有起作用。
BSP在位置10打印字母“A”,其他人应该在位置20打印另一个字母,但只有“A”被打印出来。
你有什么想法可以帮助我解决这个问题吗?
提前感谢您。
注意:现在我学会了如何使用“qemu”仿真器,并且我正在其中模拟所有内容。
第二次编辑:代码已经起作用。我正在使用只有1个核心的qemu仿真器。当我使用2个或更多核心时,代码才能正常工作!
您需要使用“qemu-system-x86_64 -cpu 486 -smp 2'path'”(不带引号)。
2019年12月06日 我尝试在真实计算机上运行它,但它只能做重置循环。 有人有什么线索吗?
2019年06月14日 您好!我又来了! 我处理了关于DOS内部的线性寻址的大问题,并通过先前的.exe程序将kernel.bin(发送INIT-SIPI-SIPI的程序)复制到0xXXXXXXXX地址中来解决它。我在kernel.bin中放置了“org 0xXXXXXXXX”,现在我不需要解决我使用的所有指针。现在INIT-SIPI-SIPI序列正在工作。
链接: 我需要做的另一件事是在退出程序之前退出保护模式。如果我不这样做,DOS会崩溃。因此,我使用上面的链接来解决线性寻址问题(通过将大部分代码复制到已知的内存位置),并将控制权返回给DOS。
这很有趣,因为我将AP核心置于屏幕上打印“Hello from another core”的循环中,而BSP会退出程序并返回dos。无论你做什么,消息都无法被清除。
我现在将致力于编写一个简单的跳板代码,将核心放置在不同的位置并执行4个计数器程序。这是唤醒核心并给它们一些工作的函数的开始。之后,我将实现MP和MDAT表检测以正确地完成操作。
谢谢!

1
我想我知道你在这里尝试做什么。虽然这里有多个问题,但最主要的问题是你如何处理APIC_BASE。APIC_BASE实际上是一个32位线性地址(通常在0xFEE000F0处),位于内存顶部的4GiB障碍以下。你正在将此地址视为实模式段:偏移量对,只能寻址前约1MiB的内存。FEE0:00F0和0xFEE000F0不同。FEE0:00F0是物理(线性)地址(0xFEE0<<4)+0x00F0=0xFEEF0。显然,0xFEE000F0和0xFEEF0不是内存中相同的物理地址。 - Michael Petch
看起来你这样做是为了避免进入虚幻模式。不幸的是,要访问APIC_BASE所在的内存,它远高于1MiB区域,你需要像进入虚幻模式那样做一些事情,以允许一个段(在这种情况下是FS)访问整个4GiB而不是正常的64KiB限制。这使得实模式程序可以访问整个4GiB的内存地址空间,包括APIC_BASE所在的位置。 - Michael Petch
由于APIC_BASE理论上可以在内存的任何位置,因此应启用A20门。 - Michael Petch
1
我错过了APIC基地址是绝对地址这一点,因为我读取APIC_BASE_MSR的方式是在实模式下返回FEE00000,我假设它是一个段:偏移地址。现在我知道为什么它不起作用了。我需要进入保护模式或非真实模式才能这样做。我正在使用FASM进行简单测试。现在我认为它会更简单,因为我将在OpenWatcom 32位受保护模式下为DOS使用此代码。也许我不需要关心GDT或段,可以访问所有4GB内存。我会尝试一下,如果成功了,我会带着代码回来。现在我正在研究精密计时器。 - vitor alho
在 enable_lapic 的第二个清单中,似乎将 1ffh “或” 到了 edx 中,然后 edx 并没有被用于任何事情。看起来这是一个笔误,应该是将其“或”到 ecx 中。 - trindflo
显示剩余5条评论
2个回答

4
能否在实模式下使用INIT-SIPI-SIPI序列唤醒所有核心?
是的(也许)。有两种选择:
a)如果CPU支持x2APIC,则可以启用它并使用MSR发送INIT-SIPI-SIPI序列(无需访问内存映射寄存器,因为这些地址在实模式下无法访问)。
b)对于xAPIC;可能可以更改本地APIC使用的地址(通过写入APIC_BASE MSR),以便在实模式下访问。但是,这需要极度小心,因为本地APIC不应放置在任何已经使用的地方,并且你能够在实模式下访问的所有空间很可能已经被占用。为了解决这个问题,你可能需要“芯片组特定”的代码来修改访问路径(到RAM,PCI总线等),然后是重新配置MTRRs的代码以适应。APIC_BASE MSR也有一点“CPU特定”(在80486上不存在,在其他厂商的CPU上可能不存在)。注意:我不认为这是一个明智或实际的选择(特别是对于需要在多台计算机上工作的代码)。
注:你应该只启动固件表示存在的CPU(不应向故障和禁用的CPU广播INIT-SIPI-SIPI序列);并且很可能无法在实模式下访问ACPI表(需要找出哪些CPU存在)。由于这个原因(因为没有使用保护模式启动其他CPU没有任何意义),我的答案应该被认为是“仅用于学术目的”。

仅作为一个侧记:如果使用QEMU/KVM进行测试,您可以编写APIC基址,您可以读取修改后的APIC基址,但是APIC基址实际上并没有在内存中重新映射。当然,最终结果可能不会符合您的期望。 - Michael Petch

2
当我第一次遇到这个问题时,我知道问题的一部分在于DOS增加了代码复杂性,需要使用线性地址。我建议将其作为引导加载程序来测试,以消除DOS环境的复杂性。传统的BIOS引导加载程序总是将代码放置在物理地址0x07c00处。在实模式下,物理地址和线性地址是相同的。只要您的引导加载程序在启动时将段设置为0x0000,并使用org 0x7c00指令,所有内存引用都将相对于内存开头。0x0000:0x7c00的段:偏移量对=物理地址(0x0000<<4)+0x07c00。
准确知道程序在物理内存中的位置很重要,因为LGDT指令是少数几个需要加载需要线性地址信息的指令之一:
将源操作数中的值加载到全局描述符表寄存器(GDTR)或中断描述符表寄存器(IDTR)中。源操作数指定一个6字节的内存位置,其中包含全局描述符表(GDT)的基址(一个线性地址)和限制(表的大小,以字节为单位)。
您的代码将GDT记录定义为:
gdt_pointer:
    dw gdt_end - gdt_start
    dd gdt_start

在使用org 0x7c00的引导加载程序中,dd gdt_start将填充gdt_start的偏移量。这将是一个地址为0x7cxx的值,其中xx是距离gdt_start所在的引导加载程序原点的开始处一定距离的位置。这里的gdt_start的值也恰好与线性地址相同!

在DOS中如何不同?

以下信息假定您已修改了程序,使其不再具有org 0x7c00,不再具有填充的512字节(和引导扇区标识),并且文件的顶部行现在是format MZ,用于DOS可执行文件。

DOS的问题在于,程序生成的偏移量是相对于DOS加载代码和数据的段的开始位置的。每次运行程序时,这些段可能会因内存中存在的内容而有所不同。在汇编时,我们不知道代码加载到内存的位置,因此在程序由DOS加载并运行时,我们无法确定物理(线性)地址。这与始终加载到相同物理地址的引导加载程序不同。

为什么这很重要?当FASM为您的MZ(DOS)EXE程序生成代码时,生成的偏移量将相对于DOS将我们加载到的段的开始位置。如果gdt_start相对于段的开始位置的偏移量为0x60(例如),则GDT指针dd gdt_start将填充值0x60。由于LGDT指令将其视为线性地址,它告诉LGDT GDT本身位于线性(物理)地址0x00000060处。这是中断表中间的一个地址,而不是我们程序中的地址!在进入保护模式后重新加载段的第一次,处理器将在错误的内存位置查找GDT,读取虚假的描述符表,并且很可能会崩溃(三重故障/重新启动)。实际上,当您执行jmp CODE_SEG:exit时,它会加载作为索引进入虚假GDT的CS选择器,从而导致崩溃。

如果DOS从0x1230段开始加载你的程序(以此类推),而GDT在程序的偏移量0x60处,则内存中GDT的线性地址(物理)实际上是(0x1234 << 4)+ 0x60 = 0x123a0。当程序开始运行时,您需要确定DOS将程序加载到哪个段,并执行此计算并更新“gdt_pointer”结构中的GDT地址。使用FASM创建没有“segment”指令的DOS程序将所有代码和数据放置在同一段中。您可以通过检索“CS”的值来获取段,并将该值向左移动4位,然后加上汇编器中存储的“gdt_pointer”的偏移量。当您将“CS”加载到其他寄存器中时,可以在代码开头执行此操作。它必须在设置“DS”之后完成。
mov eax, cs
mov ds, ax
mov es, ax

mov ebx, eax
shl ebx, 4
add [gdt_pointer+2], ebx

; mov ss, ax
; xor sp, sp

我已经删除了设置SS:SP的步骤,因为当DOS加载我们的程序时,它已经为我们设置好了。我将CS移动到EAX中,这样EAX的高16位将为零,从而简化了计算的代码。我们将EAX复制到EBX,将该值左移4位(相当于乘以十进制的16)并直接加到gdt_pointer(gdt_pointer+2是存储GDT偏移量的位置)的GDT偏移部分。汇编器会将gdt_start的偏移存储在gdt_pointer+2处,我们正在调整它成为线性地址。
如果您汇编并运行此代码,它将崩溃!
我使用线性地址设置了GDT - 现在怎么办?
GDT不是您的代码中唯一需要像GDT一样进行修正的地址。考虑跳转到保护模式:
jmp CODE_SEG:exit ;long jump to the code segment
exit:
标签相对于我们加载的段的开头。 CODE_SEG 选择器指向具有基址0x00000000的4GiB平面代码描述符。假设Exit 偏移量很小,例如0xf5。FAR JMP 将转到CODE_SEG: 0xf5,它是内存地址0x000000f5,而这不是我们加载的位置。有许多解决方法,但大多数涉及 FAR JMP 到我们必须在运行时计算得出的固定地址。一种机制是在 GDT 代码描述符中使用非零基址,但该选项超出了本回答的范围。最容易理解的方法是在内存中创建一个6字节的指针(32位偏移量和16位段),并执行间接 FAR JMP。我们可以像处理gdt_start一样修复exit的偏移量。此时,我会将exit 重命名为有意义的名称,如pmode
要进行修复,我们可以像gdt_pointer的修复一样,在开始时完成。现在的起始代码看起来可能是这样的:
mov eax, cs
mov ds, ax
mov es, ax

mov ebx, eax
shl ebx, 4
add [gdt_pointer+2], ebx
add [pmode_farptr], ebx

; mov ss, ax
; xor sp, sp

在引导加载程序的同一区域,您有gdt_pointer结构,您需要添加一个新的pmode_farptr结构,其代码如下:

在引导加载程序的同一区域,您有gdt_pointer结构,您需要添加一个新的pmode_farptr结构,其代码如下:
gdt_pointer:
    dw gdt_end - gdt_start
    dd gdt_start
CODE_SEG = gdt_code - gdt_start
DATA_SEG = gdt_data - gdt_start

pmode_farptr:
    dd pmode      ; Offset of pmode label
    dw CODE_SEG   ; Segment to use

现在可以这样间接地进行FAR JMP:

最初的回答:

    jmp fword [pmode_farptr];long jump to the code segment
                            ;indirectly through 6 byte (fword)
                            ;pointer at pmode_farptr
pmode:
    ret

我已经修复了我的FAR JMP,但它仍然崩溃!

问题出在FAR JMP之后的操作,现在是:


    jmp fword [pmode_farptr];long jump to the code segment
                            ;indirectly through 6 byte (fword)
                            ;pointer at pmode_farptr
pmode:
    ret

在标签pmode处,您现在处于32位保护模式。虽然有ret,但您尚未设置SS以指向有效的数据描述符,也没有设置ESP堆栈指针,也没有设置其他段寄存器!即使您在pmode之后设置堆栈指向与实模式堆栈相同的位置,堆栈上的返回地址仍将是一个问题。当执行call enterProtectedMode时,在堆栈上推了一个2字节的NEAR返回地址。我们现在处于32位保护模式,其中NEAR地址为4个字节。最简单的方法是放弃ret并将pmode标签移到您已经拥有的32位模式代码中。这段代码:
    call enterProtectedMode

use32

    ;Enable the APIC
     call enable_lapic

Can now become:

    call enterProtectedMode

use32
pmode:
    movzx esp, sp           ; Extend SP to ESP zero extending upper bits
    mov eax, ss
    shl eax, 4
    add esp, eax            ; ESP is now the linear address of original SS:SP pointer
    mov ax, DATA_SEG        ; Reload segment register with 32-bit flat
                            ; flat data selector
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ...

注意: 在jmp fword [pmode_farptr]之后,删除pmode标签和ret,因为它们不再需要。


远离完美的情况

在进行上述更改后,您应该能够处理APIC代码。这段代码有许多缺点。请参见Brendan关于APIC特定问题的回答,但除此之外,还应该解决以下几个问题:

  • 您的代码盲目地将AP(应用程序处理器)负载复制到0x8000。如果DOS堆栈在那个区域内会发生什么?虽然这种情况非常不可能,但如果堆栈与代码发生冲突,程序很可能会崩溃。
  • 负载中的代码被复制到0x8000。虽然这对于简单的负载代码有效,但如果您在负载中添加数据,则所有生成的数据偏移量都将是错误的。您的负载最初是整个程序的一部分,并且从段的开头开始并不是从0x0000偏移。
  • 在负载中,您将CS复制到DS,但尚未设置CS。将CS复制到DS不会产生您预期的效果,但它不会影响您编写的基本负载,因为该负载并不依赖于DS的特定值 - 因此无关紧要。
  • AP代码没有办法与引导处理器(BSP)通信,因为AP不知道DOS将原始程序加载到内存的位置。这也将严重限制负载代码的功能。使用引导程序,您知道始终可以访问引导程序和BSP数据,因为您知道该数据相对于0x0000:0x7c00在内存中。
  • 这些观察结果有效地总结为:负载代码可能无法执行任何复杂操作,并且尝试执行此操作将导致意外行为。您可以修改代码以处理所有这些缺陷,但这超出了此答案的范围。

Michael,非常感谢你详细的解释。我在DOS内部的线性寻址方面遇到了麻烦,但是你在另一篇帖子中编写的代码帮助了我很多! 我通过使用一个程序将kernel.bin加载到已知地址来解决线性寻址问题,并在其中使用org指令。 我仍在努力工作,并将带着我的结果回来。 - vitor alho
@vitoralho:虽然这可能没有回答你的问题(在你开始之前你可能不知道),但你可以考虑给这个答案点赞。如果以后你发现这个答案真的是解决问题的方法,你应该考虑接受它作为答案。关于所有这些(以及它的工作原理)的更多信息可以在这里找到:https://meta.stackexchange.com/a/5235/271768 - Michael Petch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接