Linuxオペレーティングシステムの研究ノート(2つ)カーネル操作

序文

  上記では、電源ボタンを押してからブートローダーのロードが完了するまでのプロセスを分析しました。ロードが完了したら、Linuxカーネルを正式に起動する必要があります。その前に、リアルモードからプロテクトモードへの切り替えを最初に完了する必要があります。この記事は主に次の部分を分析します

  • 新旧の中断の交互
  • A20を開く
  • メイン機能に入る
  • カーネルの初期化

  実際、さまざまなハードウェアデバイスのチェックなど、プロセス全体にはまだ多くのコンテンツが含まれています。ここでは省略しています。Linuxソースコードの海に飛び込みましょう。

新旧の中断の交互

  明らかに、リアルモードの割り込みはプロテクトモードの割り込みと同じにすることはできないため、古い割り込み(cli)を閉じて新しい割り込み(sti)を確立する必要があります。mainプロテクトモードに対応できる機能を持つ割り込みサービスシステムは、割り込みがオンになる前に再構築されます。そのとき、割り込みに応答するサービスプログラムは、BIOSが提供する割り込みサービスプログラムではなく、システム自体が提供する割り込みサービスプログラムになります。

  cliとstiは、完全な操作プロセスの両端に常に表示されます。その目的は、この期間中に介入が中断されるのを回避することです。次のコードは、オペレーティングシステムを保護モードにする準備をします。リアルモードでの割り込みベクタテーブルとプロテクトモードでの割り込み記述子テーブル(IDT)のハンドオーバは、ここで実行されます想像してみてください。cliがなく、割り込みが発生した場合、ユーザーが誤ってキーボードに触れると、割り込みが割り込まれます。リアルモードの割り込みメカニズムが廃止され、プロテクトモードの割り込みメカニズムが完了していないという恥ずかしい状況に直面する必要があります。その結果、システムがクラッシュします。cliとstiにより、このプロセス中にIDTを完全に作成できるため、IDTの作成が不完全になったり、予期しない割り込みエントリによる新旧の割り込みメカニズムの混合使用を回避したりできます。

#boot/setup.s
……
do _move:
mov es,ax!destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax!source segment
sub di,di
sub si,si
mov cx,#0x8000
rep
movsw
jmp do_move

  上記のコードは主にタスクを完了します。0x10000にあるカーネルプログラムを0x00000にあるメモリアドレスの開始位置にコピーします。前のセクションでは、リアルモードでメモリマップを分析しました。このモードでは、BIOSによって作成された割り込みベクターテーブルとBIOSデータ領域が最初に格納されていました。このコピーアクションは、BIOS割り込みベクターテーブルとBIOSデータ領域を完全にカバーし、存在しなくなります。これの利点は次のとおりです。

  1. BIOS割り込みベクターテーブルの廃止は、BIOSが提供するリアルモードでの割り込みサービスルーチンの廃止と同等です。
  2. サービスの寿命が終了したばかりのプログラムによって占有されていたメモリ空間を取り戻します。
  3. カーネルコードが物理メモリアドレスの最初の自然で有利な位置を占めるようにします。

  現在、重要な役割が表示されようとしています。それらは、割り込み記述子テーブルIDTとグローバル記述子テーブルGDTです。

  • システム内でセグメントレジスタ(セグメント記述子)の内容を格納する唯一の配列であるGDT(グローバル記述子テーブル、グローバル記述子テーブル)は、プログラムと連携して、保護モードでセグメントアドレス指定を実行します。これは、オペレーティングシステムのプロセス切り替えにおいて非常に重要です。すべてのプロセスの合計ディレクトリテーブルとして理解でき、各タスク(タスク)ローカル記述子テーブル(LDT、ローカル記述子テーブル)のアドレスとタスクステータスセグメント(TSS、タスク)構造セグメント)アドレス指定、プロセス内の各セグメントのアドレス指定、フィールド保護、フィールド回復を完了します。GDTRはGDTベースアドレスレジスタであり、プログラムがセグメントレジスタを介してセグメント記述子を参照する場合、GDTエントリを取得する必要があり、GDTRで識別されるエントリはこのエントリです。オペレーティングシステムによってGDTが初期化された後、LGDT(Load GDT)命令を使用してGDTベースアドレスをGDTRにロードできます。

  • IDT(割り込み記述子テーブル)は、リアルモードの割り込みベクトルテーブルと同様に、プロテクトモードですべての割り込みサービスルーチンのエントリアドレスを保存します。IDTR(IDTベースアドレスレジスタ)。IDTの開始アドレスを保存します。

  32ビット割り込みメカニズムと16ビット割り込みメカニズムは、原則としてまったく異なります。最も明白なのは、16ビットの割り込みメカニズムが割り込みベクターテーブルを使用していることです。割り込みベクターテーブルの開始位置は0x00000に固定されていますが、32ビットの割り込みメカニズムは割り込み記述子テーブル(IDT)を使用しています。これは固定されておらず、オペレーティングシステムの設計者が設計要件に従って柔軟に配置でき、その位置はIDTRによってロックされます。GDTは、プロテクトモードでセグメント記述子を管理するデータ構造であり、オペレーティングシステム自体の操作や、管理およびスケジューリングプロセスにとって非常に重要です。

  この時点では、カーネルは実際には実行されておらず、まだプロセスがないため、作成されたGDTの最初の項目は空、2番目の項目はカーネルコードセグメント記述子、3番目の項目はカーネルデータセグメント記述子、残りは空です。IDTは設定されていますが、割り込みは現在クローズされており、割り込みサービスルーチンを呼び出す必要がないため、実際には空のテーブルです。ここに反映されているのは、「十分なデータを取得する」という考え方です。

これら2つのテーブルを作成するプロセスは、2段階のプロセスとして理解できます。

  1. カーネルコードを設計するときに、2つのテーブルが作成され、必要なデータも作成されています。ここでのデータ領域は、カーネルソースコードで形成され、コンパイルされてメモリに直接ロードされるデータ領域です。特殊レジスターの指定は、プログラムのlidtとlgdt命令によって完了します
  2. 特殊レジスター(IDTR、GDTR)がテーブルを指すようにします。

A20

  有効化A20は、前述のreal_to_prot開始によって呼び出されるアイコンアクションlzma_decompress.img です。A20をオンにすると、CPUは32ビットのアドレス指定を実行でき、最大アドレス空間は4 GBになります。図1-19のメモリ範囲の変化に注意してください:5 Fから8 F、つまり0xFFFFFFFF(4 GB)。

  リアルモードでは、プログラムアドレスが0xFFFFFを超えると、CPUは開始アドレスのメモリアドレスに「ロールバック」します(ストリップ
の下位メンバーのアドレス行は20行のみであることに注意してください。0xFFFFF+ 1 = 0x00000、最大のオーバーフロー) 。たとえば、システムセグメントレジスタ(CSなど)の最大許容アドレスは0xFFFFであり、命令ポインタ(IP)の最大許容オフセットも0xFFFFです。この2つによって決定される最大絶対アドレスは0x10FFEFです。つまり、プログラムはリアルモードでの結果のアドレス指定範囲は、1 MBを超える64 KB近くです(特別なアドレス指定要件を持つ一部のプログラムは、この機能を利用します)。このように、ここでA20アドレス行を有効にすることは、リアルモードでCPUアドレッシングの「ロールバック」メカニズムをオフにすることと同じです。以下は、この機能を使用して、A20アドレス行が実際に開いているかどうかを確認することです。ここでのコードは、現時点では実行されませんが、プロテクトモードかどうかを検出するために後続のヘッド実行プロセスで使用されることに注意してください。

#boot/head.s
……
xorl %eax,%eax
1:incl%eax#check that A20 really IS enabled
movl %eax,0x000000#loop forever if it isn't
cmpl %eax,0x100000
je 1b
……

  A20がオンになっていない場合、コンピューターは20ビットアドレッシングモードであり、0xFFFFFを超えるアドレッシングは「ロールバック」する必要があります。特殊なケースとして、0x100000は0x000000にロールバックされます。つまり、アドレス0x100000に格納されている値は、アドレス0x000000に格納されている値とまったく同じでなければなりません。データの一部をメモリロケーション0x000000に書き込んでから、ここでのデータと1 MB(0x100000、リアルモードのアドレス範囲を超えていることに注意)を比較すると、A20アドレスラインが開いているかどうかを確認できます。

メイン機能に入る

  X86システムでは、使用される端末制御チップは8259Aと呼ばれ、このチップはプログラムで制御できる割り込みコントローラーです。1つの8259Aは8レベルのベクトル優先割り込みを管理でき、他の回路を追加することなく、最大で64レベルのベクトル優先割り込みシステムにカスケード接続できます。CPUの保護モードでは、int 0x00〜int 0x1Fは、内部(マスク不可)割り込みおよび異常割り込みとしてIntelによって予約されています。8259Aが再プログラムされていない場合、int 0x00〜int 0x1F割り込みは上書きされます。たとえば、IRQ0(クロック割り込み)は8番目(int 0x08)の割り込みですが、プロテクトモードでは、この割り込み番号はIntelによって予約されている「二重障害」です。したがって、元のIRQ0x00〜IRQ0x0Fに対応する割り込み番号は、8259Aプログラミングを通じて再配布する必要があります。つまり、保護モードでは、IRQ0x00〜IRQ0x0Fの割り込み番号はint 0x20〜int 0x2Fです。

  セットアッププログラムは、次のコードを使用して、CPUの動作モードを保護モードに設定します。ここにCR0レジスタが含まれます。No。0 32ビット制御レジスタ、システム制御フラグを設定します。ビット0はPE(保護モードイネーブル)フラグで、1に設定するとCPUは保護モードで動作し、0に設定するとリアルモードになります。CR0レジスタの0番目のビット(PE)を1に設定します。つまり、プロセッサの動作モードを保護モードに設定します。CPUの動作モードをプロテクトモードに変更する場合、重要な機能は、GDTに従ってプログラムを実行する場所を決定することです。上記のように、GDTはすでにデータを最初に書き込んでいます。これらは、セットアッププログラムからヘッドプログラムへのジャンプを完了するために使用されます。

#boot/setup.s
mov ax,#0x0001!protected mode(PE)bit
lmsw ax!This is it!
jmpi 0,8!jmp offset 0 of segment 8(cs)

  ヘッドプログラムはメインに入る前の最後のステップです。ヘッドは、スペースにカーネルページングメカニズムを作成します。つまり、ページディレクトリテーブル、ページテーブル、バッファ、GDT、IDTを0x000000の場所に作成し、ヘッドプログラムによって実行されたコードによって占有されていたメモリスペースを上書きします。これは、ヘッドプログラムがそれ自体を破棄し、メイン関数が実行を開始することを意味します。特定のページングメカニズムはより複雑であるため、後続のメモリ管理部分で個別に導入される予定です。

  ヘッドはIDTを構築するため、割り込みメカニズムの全体的な構造が最初にセットアップされ(実際の割り込みサービスルーチンはメイン関数でフックされます)、すべての割り込みサービスルーチンは、プロンプト情報の1行のみを表示して戻るサービスルーチンの同じセグメントを指します。プログラミング技術に関しては、この種の初期化操作は、コードまたはデータを意図せずに上書きすることによって引き起こされるロジックの混乱を防ぐだけでなく、開発プロセスでの誤動作をタイムリーに促すこともできます。IDTには256のエントリがあり、そのうち実際に使用されるのは数ダースだけです。未使用の割り込み記述子を誤用する場合、このようなプロンプトメッセージは開発者にエラーに注意するように促すことができます。

  さらに、ヘッドプログラムは既存のGDTを廃止し、カーネルの新しい場所にGDTを再作成する必要があります。GDTの元の場所は、コードの設計時にsetup.sで設定されたデータです。将来、このセットアップモジュールのメモリの場所は、バッファーの設計時に上書きされます。場所が変更されない場合、GDTの内容は将来的に確実にバッファによって上書きされ、システムの動作に影響を及ぼします。このように、将来的にメモリ全体で唯一安全な場所は、head.sが現在ある場所です。

  次のステップは主に含まれています

  1. セグメントレジスタとスタックを初期化する
  2. eflagレジスタとカーネルの初期化されていないデータ領域をクリアします
  3. を呼び出してdecompress_kernel()カーネルイメージ解凍し、0X00100000にジャンプします。
  4. セグメントレジスタは最終値に初期化され、BSSフィールドは0で埋められます
  5. 一時的なカーネルページテーブルを初期化する

  ページングメカニズムが最終的に初期化された後、PG(ページング)フラグは1に設定され、アドレスマッピングモードがページングメカニズムを採用し、最終的にメイン関数にジャンプし、カーネルが初期化を開始します。

カーネルの初期化

  これまでのところ、割り込みを開いていないことに注意してください。新しい割り込みを開く前に、メイン関数を介して一連の初期化を完了し、カーネルが正式に実行されるようにする必要があります。この部分は主に含まれています:

  1. プロセス0のカーネルモードスタックを構築する
  2. eflagsレジスタをクリアする
  3. setup_idt()空の割り込みハンドラでIDTを埋めるための呼び出し
  4. BIOSで取得したパラメーターを最初のページフレームに渡す
  5. レジスターにGDTおよびIDTテーブルを入力する

  これらが完了すると、カーネルが正式に実行され、プロセス0が作成されます。

総括する

  この記事では、リアルモードからプロテクトモードへの切り替えプロセス全体を紹介し、カーネルのロードを完了し、プロセス0を作成するための正式な準備を始めます。フォローアップでは、カーネルを起動してプロセス0、1、2を作成するプロセス全体を引き続き分析します。この記事の紹介中、多くのアセンブリコードと、非常に重要であるが基本的なプロセスの一部ではない知識は無視されます。理解に関心のある人は、記事のリンク、記事の最後のソースコード、および参考資料に基づいて、より詳細な調査と研究を行うことができます。

ソース情報

[1] ガーブ2

[2] syslinux

参照

[1] Linuxインサイド

[2] Linuxカーネルソースコードの深い理解

[3] Linuxカーネル設計の芸術

[4] Geek TimeがLinuxオペレーティングシステムについて語る

おすすめ

転載: blog.csdn.net/u013354486/article/details/105828471
おすすめ