我无法找到一种方法将代码从一个位置移动到内存中的其他位置
所以我提出了一些像这样的事情,但它不起作用
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
有没有其他方法可以做到这一点或如何解决这个问题
答案 0 :(得分:3)
段寄存器的大小都是16位。将其与e?x
寄存器进行比较,寄存器的大小为32位。显然,这两个东西的大小不一样,促使汇编程序生成"操作数大小不匹配"错误 - 两个操作数的大小不匹配。
据推测,您希望使用寄存器的低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
prefix添加到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
有点违反直觉,如果你的处理器实现the ERMSB optimization(英特尔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
,除非你能证明不是重大的瓶颈和/或者您希望为不同的处理器提供优化路径。)
答案 1 :(得分:1)
那会有糟糕的表现。 Agner Fog说mov sr, r
在Nehalem上每13个周期有一个吞吐量,并且我猜测如果在最近的CPU上情况更糟,因为细分已经过时了。在Nehalem之后,Agner停止了对段寄存器性能的测试。
你这样做是为了让你复制超过64kiB吗?如果是这样,至少在更改段寄存器之前复制一个完整的64kiB。
我认为您可以使用32位寻址模式以避免混淆段,但是您在16位模式下设置的段隐含地具有"限制" 64k。 (即mov eax, [esi]
在16位模式下可编码,带有操作数大小和地址大小前缀。但是如果esi值大于0xFFFF,我认为违反ds
会出错segment limit。)下面的osdev链接了解更多。
正如Cody所说,使用rep movsd
让CPU使用优化的微编码memcpy
。 (or rep movsb
, but only on CPUs with the ERMSB feature。
在实践中,most CPUs that support ERMSB give the same performance benefit for rep movsd
too,因此最简单的方法就是始终使用rep movsd
。但IvyBridge可能没有。)它比单独的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
,但interrupts will set cs:ip
,因此对于大型平面代码地址空间,只是数据而言,这不方便。