この投稿が非常に遅れたという事実をお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」=「忙しい」。どこでも長い間コードを簡単に修正できます)。