将指令从 eax 移到 es 寄存器会出错

move instruction from eax to es register gives error

我想不出一种方法将代码从内存中的一个位置移动到另一个位置

所以我输入了类似这样的东西,但它不起作用

extern _transfer_code_segment

 extern _kernel_segment

  extern _kernel_reloc


 extern _kernel_reloc_segment

  extern _kernel_para_size


    section .text16



    global transfer_to_kernel




transfer_to_kernel:



    ;cld

    ;
    ; Turn off interrupts -- the stack gets destroyed during this routine.
    ; kernel must set up its own stack.
    ;
    ;cli
    ; stack for only for this function

    push ebp
    mov ebp, esp








    mov eax, _kernel_segment             ; source segment
    mov ebx, _kernel_reloc_segment       ; dest segment
    mov ecx, _kernel_para_size

.loop:



    ; XXX: Will changing the segment registers this many times have
    ; acceptable performance?


    mov ds, eax  ;this the place where the error
    mov es, ebx  ; this to
    xor esi, esi
    xor edi, edi
    movsd
    movsd
    movsd
    movsd
    inc eax
    inc ebx
    dec ecx
    jnz .loop



    leave
    ret

有没有其他方法可以解决这个问题,或者我该如何解决这个问题

段寄存器的大小都是16位。将其与大小为 32 位的 e?x 寄存器进行比较。显然,这两个东西的大小不一样,提示你的汇编程序产生一个 "operand size mismatch" 错误——两个操作数的大小不匹配。

据推测,你想用寄存器的低 16 位初始化段寄存器,所以你会做这样的事情:

mov  ds, ax
mov  es, bx

此外,不,您实际上不需要在循环的每次迭代中初始化段寄存器。您现在所做的是递增 并将偏移量强制为 0,然后复制 4 个 DWORD。您应该做的是不理会该段,只增加 offsetMOVSD 指令隐式执行)。

    mov eax, _kernel_segment             ; TODO: see why these segment values are not
    mov ebx, _kernel_reloc_segment       ;        already stored as 16 bit values
    mov ecx, _kernel_para_size

    mov ds, ax
    mov es, bx

    xor esi, esi
    xor edi, edi

.loop:

    movsd
    movsd
    movsd
    movsd

    dec  ecx
    jnz .loop

但请注意,将 REP prefix 添加到 MOVSD 指令中可以让您更有效地执行此操作。这基本上总共 MOVSDECX 次。例如:

mov ds, ax
mov es, bx
xor esi, esi
xor edi, edi
shl ecx, 2         ; adjust size since we're doing 1 MOVSD for each ECX, rather than 4
rep movsd

有点违反直觉,如果您的处理器实现了 (Intel Ivy Bridge 及更高版本),REP MOVSB 实际上可能比 REP MOVSD 更快,因此您可以:

mov ds, ax
mov es, bx
xor esi, esi
xor edi, edi
shl ecx, 4
rep movsb

最后,虽然您已经在代码中注释掉了 CLD 指令,但您确实需要这样做以确保移动按计划进行。您不能依赖具有特定值的方向标志;您需要自己将其初始化为您想要的值。

(另一种选择是流式传输 SIMD 指令甚至浮点存储,它们都不会关心方向标志。这具有增加内存复制带宽的优势,因为您将执行 64 位、128位,或一次更大的副本,但引入了其他缺点。在内核中,我会坚持使用 MOVSD/MOVSB 除非你能证明这不是一个重要的瓶颈 and/or你想为不同的处理器优化路径。)

那将有可怕的表现。 Agner Fogmov sr, r 在 Nehalem 上每 13 个周期有一个吞吐量,我猜如果有的话,最近的 CPU 会更糟,因为分段已经过时了。 Agner 在 Nehalem 之后停止测试 mov to/from 段寄存器性能。

您这样做是为了让您总共复制超过 64kiB 的文件吗?如果是这样,在更改段寄存器之前至少复制完整的 64kiB。

我认为您可以使用 32 位寻址模式来避免弄乱段,但是您在 16 位模式下设置的段隐式具有 64k 的 "limit"。 (即 mov eax, [esi] 在 16 位模式下是可编码的,具有操作数大小和地址大小前缀。但是 esi 中的值超过 0xFFFF,我认为违反 ds段限制。) 下面的 osdev link 更多。

正如 Cody 所说,使用 rep movsd 让 CPU 使用优化的微编码 memcpy。 (。 实际上,,所以总是使用 rep movsd 可能是最简单的。但 IvyBridge 可能不会。)它 比单独的 movsd 指令快 很多 (比单独的 mov loads/stores 慢)。在某些 CPU 上,使用 SSE 16B 向量 loads/stores 的循环可能几乎与 rep movsd 一样快,但您不能在 16 位模式下将 AVX 用于 32B 向量。


大副本的另一种选择:巨大的虚幻模式

在 32 位保护模式下,您放入段中的值是描述符,而不是实际的段基本身。 mov es, ax 触发 CPU 将该值用作 GDT 或 LDT 的索引,并从那里获取段基数/限制。

如果您在 32 位模式下执行此操作,然后切换回 16 位模式,您将处于巨大的虚幻模式,其段可能大于 64k。段 base/limit/permissions 保持缓存,直到某些东西以 16 位模式写入段寄存器并将其放回通常的 16*seg 和 64k 限制。 (如果我描述正确的话)。有关更多信息,请参阅 http://wiki.osdev.org/Unreal_Mode

那么您可以在 16 位模式下使用 rep movsd 操作数大小和地址大小前缀,这样您就可以一次复制超过 64kiB。

这对 dses 很有效,但是 ,所以这对大平面代码地址 space 不方便,只是数据。