マルチタスク会話の開始

ご挨拶。

この投稿が非常に遅れたという事実をおIびしますが、それより早く書くことはできませんでした。 この号では、システムのマルチタスクについて説明します。



まず、1つの重要な質問を解決します。どのマルチタスクを実装するのでしょうか。 ハードウェアがあり、マルチタスクソフトウェアがあります...

最初はハードウェアの方が優れているように見えますが、結局のところ、Intelは明らかにこのメカニズムを「飛ばす」ようにしようとしましたが、ここには落とし穴があります。 まず、遅いです。 どうやって? インテルのエンジニアにお問い合わせください。 次に、ハードウェアマルチタスクでは、TSS(Task-State Segment)を使用する必要があることを誰もが既に読んでいると思います。TSS(記述子はGDTに格納され、...(ドラムロール)... 8192記述子に対応できます。 これで十分のように思えるかもしれませんが、サーバー上(はい、はい、私たちは夢を見るでしょう)では、これは十分ではないかもしれません。 原則として、これは重要ではありませんが、正直に行います-ソフトウェアマルチタスク。

この号では、タスクを切り替えるメカニズムのみを検討することを提案します。

次に、何をする必要があるかについて話しましょう。

1)TSSに代わる何らかの種類を考え出す。

2)プロセスのアドレス空間を実装する方法を決定します。

3)タスクの切り替えについて考えます。

プリエンプティブマルチタスクを実装しましょう。つまり、これを行います。タイマーティック(デフォルトは毎秒18.2回)に従って、タスクを切り替えます。 TSSの代わりに、プロセスの状態が保存される構造を入力できます。 各プロセスのアドレス空間は静的です(どこから始めなければなりませんか?)。 つまり、大まかに言えば、RAMの一部を取り、それをN個の等しい部分に分割します。

これで実装を開始できます。



最初に、代替TSSを紹介しましょう。 それを誇らしげにTSS_structと呼び、次のようにします。



TSS_struct:

0: privilage level (0|3)

1: ESP (Ring0)

5: CR3

9: EIP

13: EFLAGS

17: EAX

21: ECX

25: EDX

29: EBX

33: ESP (Ring3)

37: EBP

41: ESI

45: EDI

49: ES

51: CS

53: SS

55: DS

57: FS

59: GS

61: LDT_selector

63-256 - free









ここで、タイマーのティックごとに呼び出す関数を実装します。

古いタスクの状態を維持し、別のタスクを見つけて、新しいタスクをロードする必要があります。 TSS_structsをマークするには、各バイトが1つのタスクを示す10バイトを使用します。 スタックに影響する2つの状況を考慮することも価値があります。

1)特権レベルは変更されません。

2)変化しています。

最初のバージョンでは、スタックは次のようになります。



;|EFLAGS

;|CS

;|EIP <---- ESP

;V









第二に、このように:

;|SS

;|ESP

;|EFLAGS

;|CS

;|EIP <---- ESP

;V









割り込みハンドラを実行するために単に必要なレジスタのみが値を変更することに注意してください。

この問題では、Ring0のみを検討することを提案します。 リング(1 | 2 | 3)まだ考慮しておらず、そこでの動作は異なるため、自分自身を制限します。

コードの記述を開始する前に、私が個人的に停止した滑りやすい場所がどこにあるか、そして多くの場合長い間、私に教えてください。

1)最も基本的な方法:スタックにプッシュする戻りアドレスを誤って指定します。

2)EFLAGSでIFフラグ(割り込みフラグ)を設定しないでください。 つまり、マスクされた割り込みは禁止されており、タスクの切り替えを忘れることができます。

3)次のタスクの検索機能を間違えないことが重要です。 そのシンプルさにもかかわらず、そこでカブトムシを繁殖させることができます。 個人的には、結果が真実であることが保証されるように、オルガを介して個別に運転しました。

4)手順から手順へとジャンプせず、呼び出しを通して冷静に行動する場合は、スタックを忘れないでください! 一般的に、スタックは、このビジネスで最も滑りやすい場所です。

次に、コンテキスト切り替え手順の短縮を検討します。 なぜ短縮されたのですか? マルチタスクを表示する必要があるため、ここでは多くのコードを記述しません。 デモがあります。

では、新しいタスクを説明する必要があるという事実から始めましょう。 Ring0で実行されるため、スタックとすべてのセグメントレジスタの値はそのままにしておきましょう。 返すデータを入れるだけです。 これは単なるデモンストレーションです! これには、create_taskという誇らしい名前を付けないでください。 ブート手順の値を入力し、占有されているTSS_structsのビットマップにビットを設定するだけです。 だから:



create_task:

mov ax,20h; - .

mov es,ax

mov [es:100h+9],dword task;EIP

pushfd ;EFLAGS

pop eax

mov [es:100h+13],eax;EFLAGS

mov [es:100h+51],word 8h ;CS – Ring0,



bts word [bysi_TSS_map],6 ;

mov ax,10h;

mov es,ax

ret









ここでは、20hセレクターが使用されます。 TSS_structを保存するためのこの領域があります。 もっと。 なぜ6番目のビットを設定するのですか? そして、ここにキャッチがあります。 すでに実行されているコードも...になるはずです。 タスク。 したがって、この7番目のビットをマークする必要があります。



bts word [bysi_TSS_map],7









それでは、コンテキストを保存および切り替えて、新しいタスクを検索する手順を見てみましょう。

ここではすべてが簡単で簡単です。タスク番号によってTSS_structアドレスを計算し、新しいアドレスを探し、そのTSS_structからデータを読み取り、新しいタスクにジャンプします。

したがって、PITハンドラーからタスクスイッチングプロシージャtask_switchにジャンプします。

task_switch:



mov [temp_1],eax

mov [temp_2],es



xor eax,eax

mov ax,20h

mov es,ax



call calculate_TSS_struct_address ;: EDI –

jmp store_context









格納する変数は次のとおりです。



temp_1 dd 0;EAX

temp_2 dw 0;ES









将来必要になるアドレスを数えます。 そのため、プロシージャの形式で発行します。



calculate_TSS_struct_address:

push eax

push ebx

mov eax,[cur_task_num]; dword' .

mov ebx,100h

mul ebx

pop ebx

mov edi,eax;EDI – TSS_struct

pop eax

ret









次に、コンテキストを保存する手順を検討します。 バナリティ-オフセットに値を入力します。 それだけです



store_context:

;mov eax,cr3 ;

;mov [es:edi+5],eax



pushfd

pop eax

mov [es:edi+13],eax;EFLAGS



mov [es:edi+21],ecx

mov [es:edi+25],edx

mov [es:edi+29],ebx

mov [es:edi+37],ebp

mov [es:edi+41],esi

mov [es:edi+45],edi



mov ax,[temp_2]

mov [es:edi+49],ax;ES



mov eax,[temp_1]

mov [es:edi+17],eax

mov [es:edi+53],ss

mov [es:edi+55],ds

mov [es:edi+57],fs

mov [es:edi+59],gs

;sldt ax ;

;mov [es:edi+61],ax



pop eax

mov [es:edi+9],eax;EIP

;

pop ax

mov [es:edi+51],ax;CS



popfd ;EFLAGS



jmp find_next_task









次の関数は、ビットマップで次の犠牲者を検索し、EDIでそのアドレスを返します。



find_next_task:

xor edx,edx

mov eax,[cur_task_num]

mov ecx,8

div ecx

;EAX -

;EDX - ''

test edx,edx

jnz .norm



mov edi,7

jmp .step



.norm: mov edi,8

.step: sub edi,edx;real bit #

mov edx,edi

mov edi,eax



.cycle:

bt word [bysi_TSS_map+edi],dx

jc .found

cmp dx,0

je .inc_byte

dec dx

jmp .cycle



.inc_byte:

cmp edi,10; - 800- .

je .error

inc edi

mov dx,7

jmp .cycle



.found:

push edx

mov eax,8

mul edi

pop edx



mov di,7

sub di,dx

add eax,edi

mov [cur_task_num],eax

call calculate_TSS_struct_address

jmp load_context



.error:

mov dx,7; .

xor edi,edi

jmp .cycle









残っているのは、コンテキストの読み込みだけです。



load_context:

;mov eax,[es:edi+5];CR3 -

;mov cr3,eax



;mov esp,[es:edi+1];

;mov ss,[es:edi+53]



mov ecx,[es:edi+21]

mov edx,[es:edi+25]

mov ebx,[es:edi+29]

mov ebp,[es:edi+37]

mov esi,[es:edi+41]

mov edi,[es:edi+45]



;mov ds,[es:edi+55]; .

;mov fs,[es:edi+57]

;mov gs,[es:edi+59]



; '' ( iretd)

push dword [es:edi+13];EFLAGS

push word [es:edi+51] ;CS

push dword [es:edi+9];EIP



jmp timer.s_t









ここで注意する価値があります。 記憶しているように、中断中に3つの値がスタックに保存されます(この場合、共有)(特権レベルは変更されません)。 移行の前に、新しいタスクの「座標」をスタックに配置し、PITハンドラーに制御を移してiretdを作成します。 私たちの苦しみは終わった? ほぼ。 新しいタスクのコンテキストでは、stiを実行して割り込みを有効にする必要があります。 以上です。 そして、あなたは恐れていました!

タイマーのハンドラーは次の形式を取ります。



timer:

;........

.s_t:

push ax ; EOI

mov al,20h

out 20h,al

out 0a0h,al

pop ax

iretd ;









次に、タスクを紹介します。

その前に、jmp $最終コードがあることを覚えていますか?

これで、キャラクターを増やすことができます。 視覚的かつ迅速。

前述のタスクは次のように表すことができます



task:

sti

inc byte [gs:0]

jmp task









今、私たちはすべてをまとめて、結果を楽しんでいます。



だから。 ここに紹介記事があり、これで終わりです。

マルチタスクの外観を実装しました。 「タスク」には、独自のスタック、セグメント、およびローカル記述子テーブルがありません...まだやるべきことがたくさんあります。 将来のリリースのトピックは次のとおりです。 私は自分の英語に謝罪します(「bysi」=「忙しい」。どこでも長い間コードを簡単に修正できます)。



All Articles