将指令从EAX移动到ES寄存器会出错



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

所以我以某种方式放了一些这样的东西,但它不起作用

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寄存器进行比较。显然,这两个东西的大小不同,提示汇编程序生成"操作数大小不匹配"错误 - 两个操作数的大小不匹配。

据推测,您希望使用寄存器的低 16 位初始化段寄存器,因此您可以执行以下操作:

mov  ds, ax
mov  es, bx

另外,不,您实际上不需要在循环的每次迭代中初始化段寄存器。您现在要做的是递增并将偏移量强制为 0,然后复制 4 个 DWORD。您应该做的是保留段,而只是增加偏移量(MOVSD指令隐式地这样做(。

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前缀添加到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

有点违反直觉的是,如果您的处理器实现了 ERMSB 优化(英特尔 Ivy 桥及更高版本(,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,除非你能证明这不是一个重大瓶颈和/或你想为不同的处理器优化路径。

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

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

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

正如科迪所说,使用rep movsd让CPU使用优化的微编码memcpy。 (或rep movsb,但仅在具有 ERMSB 功能的 CPU 上。 实际上,大多数支持 ERMSB 的 CPU 也为rep movsd提供了相同的性能优势,因此始终使用rep movsd可能是最简单的。 但常春藤桥可能不会。 它比单独的movsd指令快得多(这比单独的mov加载/存储慢(。 在某些 CPU 上,具有 SSE 16B 矢量加载/存储的循环可能几乎与rep movsd一样快,但不能在 16 位模式下将 AVX 用于 32B 矢量。

大型

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

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

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

然后,您可以在 16 位模式下使用带有操作数大小和地址大小前缀的rep movsd,以便您可以一次性复制超过 64kiB。

这适用于dses,但中断会设置cs:ip,所以这对于大平面代码地址空间不方便,只是数据。

最新更新