我想不出将代码从一个位置移动到内存中另一个位置的方法
所以我以某种方式放了一些这样的东西,但它不起作用
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
指令中将使您能够更有效地执行此操作。这基本上总共MOVSD
ECX
次。例如:
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, r
Nehalem 上每 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。
这适用于ds
和es
,但中断会设置cs:ip
,所以这对于大平面代码地址空间不方便,只是数据。