2017-07-08 17 views
1

を与える私はメモリに移動命令がエラー

を他の場所にコードを移動するための方法を考え出す傾けるので、私はこのような方法でいくつかのことを置くが、それは

を動作しません。
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ビットでそれを行うための他の方法を持っているか、どのようにこの問題を解決することができる

答えて

1

これは恐ろしい性能を発揮します。 Agner Fogmov sr, rにはNehalemで13サイクルのスループットで1つがあり、セグメンテーションが時代遅れであるため、それが最近のCPUで悪化していると思います。 Agnerは、Nehalemの後でセグメントレジスタのパフォーマンスとの間でmovのテストを停止しました。

64キロバイトを超えるコピーを作成するにはどうしますか?そうであれば、セグメントレジスタを変更する前に少なくとも完全に64kiBをコピーしてください。

私は、セグメントの混乱を避けるために32ビットアドレッシングモードを使用できますが、16ビットモードで設定するセグメントは暗黙的に64kの「制限」を持っています。 (つまり、mov eax, [esi]は16ビットモードでエンコード可能で、オペランドサイズとアドレスサイズの接頭辞が付きますが、0xFFFF以上の値の場合はdsセグメント制限に違反していると思います)。osdevリンク詳しくは下記をご覧ください。

コーディー氏によれば、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のではないかもしれないが最も簡単ですので。)それは(movロード/ストア別々よりも遅い)別のmovsdの指示よりずっと高速です。一部のCPUでは、SSE 16Bベクタロード/ストアのループがrep movsdとほぼ同じ速度になる場合がありますが、16ビットモードの32BベクタにはAVXを使用できません。


大きなコピーするためのもう1つのオプション:巨大な非現実的なモード

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ではうまく動作しますが、interrupts will set cs:ipではうまくいきます。したがって、大きなフラットコードのアドレス空間ではデータだけでは不都合です。

+0

ありがとう、大きなリアルモードは助けられましたが、私は関数から戻ってきましたが返されません。 – sakura

+0

@sakura:古い16ビットモードにはほとんど関心がありません。私はそれについて、私がこの答えを書くことができた唯一の理由である(それが喜んで助けた、BTW)ということでそれについて読んでいる。私が推奨できるのは、良いデバッガを入手することです。 BOCHSに組み込まれているので、カーネルをシングルステップで実行できます。 –

+1

この質問の詳細については、後で明らかになりましたが、なぜこのコピーを行う前に32ビット保護モードに切り替えるだけではないのだろうかと疑問に思っています。カーネルが最終的にやることになっていると思われます。なぜリアルモードに戻したいのですか? 「非現実的なモード」であっても、私が知る限り、1 MBを超えることはできませんので、これはまさに銀色の弾丸の解決策ではありません。 @sakura –

3

。これを32ビットのサイズのe?xレジスタと比較してください。明らかに、これらの2つのものは同じサイズではなく、2つのオペランドのサイズが一致しない "オペランドサイズの不一致"エラーを生成するようにアセンブラに促します。

おそらく、あなたはレジスタの下位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 

しかしMOVSD命令にREP prefixを追加すると、あなたも、より効率的にこれを行うことができるようになることに注意してください。これは基本的にMOVSDの合計でECX回です。例えば:あなたのプロセッサはthe ERMSB optimization(後でインテルアイビーブリッジなど)を実装している場合あなたができるよう

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 

やや反直感的に、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に固執します。

+0

_kernel_segmentの値は32ビットのアドレスなので、 "mov ds、ax"のような下位16ビットを使用すると問題になります – sakura

+0

なぜ32ビット値ですか?セグメントはわずか16ビットです。まあ、私はあなた自身がセグメント演算を行う必要がありますね。 –

+0

正当な32ビット値「esi、ediおよびecx」は影響を受けませんが、http://thestarman.pcministry.com/asm/debug/Segments.htmlを参照してください。 ECXを1FC00に設定したとしても、ポインターは65536回の繰り返しごとに折り返されます。移動するワードの場合は32768、dwordsを移動する場合は16384になります。セグメントレジスタをそれに応じて調整するロジックがない限り、これを回避する方法はありません。 –