6

私はカーネルの設計に取り組んでおり、ページングに関していくつか質問があります。

これまでのところ、私が持っている基本的な考え方は次のとおりです。各プログラムは、プログラムが呼び出すことができるカーネル関数用に予約したセクションを差し引いた、独自の (またはそう考える) 4G のメモリを取得します。そのため、OS は、プログラムが操作中に使用する必要があるページをメモリにロードする何らかの方法を見つける必要があります。

ここで、メモリとプロセッサ時間が無限にあると仮定すると、存在しない (またはスワップアウトされた) ページのページ フォールトを使用して、プログラムが書き込みまたは読み取りを行った任意のページをロード/割り当てることができたので、OSしかし、現実の世界では、このプロセスを最適化する必要があります。これにより、プログラムがこれまでに触れたすべてのメモリを常に消費することはありません。

だから私の質問は、OSは一般的にこれをどのように行うのですか? 私の最初の考えは、プログラムがページを設定/解放するために呼び出す関数を作成することです。これは、それ自体でメモリを管理できますが、プログラムは通常これを行いますか、それともコンパイラは自由に統治できると想定しますか? また、コンパイラは、かなり大きなメモリ セグメントを割り当てる必要がある状況をどのように処理しますか? Xページを順番に渡そうとする関数を提供する必要がありますか?

これは明らかに言語固有の質問ではありませんが、私は標準 C に部分的であり、C++ が得意なので、コード例をそれまたはアセンブリのいずれかにしたいと思います。(アセンブルは必要ありません。できるだけ多くの C コードで動作するようにし、最後のステップとして最適化するつもりです。)

同様に答えやすいもう 1 つのこと: プログラムが呼び出す必要があるカーネル関数を一般的にどのように処理しますか? プログラムが呼び出すことができるほとんどの基本的な関数/プロセス固有のメモリを含むメモリのセット領域(仮想空間の終わりに向かって考えていた)を持つだけで問題ありませんか? そこからの私の考えは、プログラムが何か重要なことをする必要があるときに、カーネル関数に非常に凝ったことをさせ、ページをスワップアウトすることです (プログラムが自分のスペースで機密性の高いカーネル関数を認識できないようにするため)。この時点でセキュリティに焦点を当てます。

だから私は、詳細よりも一般的なデザインのアイデアについてもっと心配していると思います. カーネルを GCC と完全に互換性のあるものにしたいと考えており、通常のプログラムが必要とするすべてのものを提供できるようにする必要があります。

アドバイスをありがとう。

4

2 に答える 2

13

これらすべての質問に対する適切な出発点は、Unix がどのようにそれを行っているかを調べることです。「UNIX を理解していない人は、UNIX を再発明する運命にある」という有名な言葉があります。

まず、カーネル関数の呼び出しについて。プログラムが呼び出すことができる場所に単に関数を配置するだけでは十分ではありません。プログラムはおそらく「ユーザー モード」(IA-32 ではリング 3) で実行され、カーネルは「カーネル モード」(通常はリング 0) で実行される必要があるためです。 IA-32 で) その特権操作を実行します。両方のモード間の移行をどうにかして行う必要があり、これは非常にアーキテクチャ固有です。

IA-32 では、IDT のゲートをソフトウェア割り込みと共に使用するのが伝統的な方法です (Linux は int 0x80 を使用します)。新しいプロセッサには、他の (より高速な) 方法があり、使用可能な方法は、CPU が AMD 製か Intel 製か、および特定の CPU モデルによって異なります。この変化に対応するために、最近の Linux カーネルは、カーネルによってマッピングされたコードのページを、すべてのプロセスのアドレス空間の先頭に使用します。そのため、最近の Linux では、システム コールを実行するには、このページで関数を呼び出します。これにより、カーネル モードに切り替えるために必要なことは何でも実行されます (カーネルにはそのページの複数のコピーがあり、どのコピーを使用するかを選択します)。プロセッサの機能に応じて、起動時に)。

さて、メモリ管理。これは大きなテーマです。あなたはそれについて大きな本を書くことができますが、主題を疲れさせることはありません.

メモリには少なくとも2 つのビューがあることに注意してください。物理ビュー (ページの実際の順序で、ハードウェア メモリ サブシステムや多くの場合、外部周辺機器に表示されます) と論理ビュー (ページの順序) です。 CPU 上で動作するプログラムから見られる)。両方を混同するのは非常に簡単です。物理ページを割り当て、それらをプログラムまたはカーネル アドレス空間の論理アドレスに割り当てます。1 つの物理ページは複数の論理アドレスを持つことができ、異なるプロセスで異なる論理アドレスにマップできます。

カーネル メモリ (カーネル用に予約) は通常、すべてのプロセスのアドレス空間の先頭にマップされます。ただし、カーネル モードでのみアクセスできるように設定されています。メモリのその部分を隠すための巧妙なトリックは必要ありません。ハードウェアがアクセスをブロックするすべての作業を行います (IA-32 では、ページ フラグまたはセグメント制限によって行われます)。

プログラムは、いくつかの方法で残りのアドレス空間にメモリを割り当てます。

  • メモリの一部は、カーネルのプログラム ローダーによって割り当てられます。これには、プログラム コード (または「テキスト」)、プログラムの初期化されたデータ (「データ」)、プログラムの初期化されていないデータ (「bss」、ゼロで埋められた)、スタック、およびいくつかのオッズ アンド エンドが含まれます。どのくらいの量を割り当てるか、どこに、何を初期コンテンツにするか、どの保護フラグを使用するか、およびその他のいくつかの事項は、ロードされる実行可能ファイルのヘッダーから読み取られます。
  • 伝統的に Unix では、拡張および縮小できるメモリ領域があります (その上限はbrk()システム コールで変更できます)。これは伝統的にヒープによって使用されます (malloc()インターフェイスの 1 つである C ライブラリのメモリ アロケータがヒープを担当します)。
  • 多くの場合、カーネルにファイルをアドレス空間の領域にマップするように依頼できます。その領域への読み取りと書き込みは、(ページング マジックを介して) バッキング ファイルに送信されます。これは通常 と呼ばれmmap()ます。anonymousmmapを使用すると、どのファイルにも依存しないアドレス空間の新しい領域を割り当てることができますが、それ以外は同じように動作します。カーネルのプログラム ローダーは、多くの場合mmap、プログラム コードの一部を割り当てるために使用します (たとえば、プログラム コードは実行可能ファイル自体によってバックアップできます)。

割り当てられていない (またはカーネル用に予約されている) アドレス空間の領域へのアクセスはエラーと見なされ、Unix ではシグナルがプログラムに送信されます。

コンパイラは、メモリを静的に (実行可能ファイルのヘッダーで指定することにより、カーネルのプログラム ローダーがプログラムのロード時にメモリを割り当てます)、または動的に (言語の標準ライブラリで関数を呼び出すことにより割り当てます。 C 言語標準ライブラリであり、カーネルを呼び出してメモリを割り当て、必要に応じて再分割します)。

これらすべての基本を学ぶ最善の方法は、オペレーティング システムに関する数冊の本のうちの 1 つを読むことです。特に、例として Unix バリアントを使用している本を読んでください。StackOverflow の回答よりも詳細に説明します。

于 2008-11-07T04:18:17.813 に答える
6

この質問に対する答えは、アーキテクチャに大きく依存します。x86について話していると仮定します。x86 では、カーネルは通常、一連のシステム コールを提供します。システム コールは、カーネルへの事前定義されたエントリ ポイントです。ユーザー コードは、これらの特定のポイントでのみカーネルに入ることができるため、カーネルはユーザー コードとの対話方法を慎重に制御します。

x86 では、システム コールを実装する方法が 2 つあります。割り込みを使用する方法と、sysenter/sysexit 命令を使用する方法です。割り込みを使用すると、カーネルは、カーネルへの可能なエントリ ポイントを定義する割り込み記述子テーブル(IDT) をセットアップします。その後、ユーザー コードはint命令を使用してソフト割り込みを生成し、カーネルを呼び出すことができます。割り込みは、ハードウェアによって生成することもできます (いわゆるハード割り込み)。これらの割り込みは、通常、ソフト割り込みとは区別する必要がありますが、そうである必要はありません。

sysenter および sysexit 命令は、割り込みの処理が遅いため、システム コールを実行する高速な方法です。私はそれらの使用に慣れていないので、それらがあなたの状況にとってより良い選択であるかどうかについてコメントすることはできません.

どちらを使用する場合でも、システム コール インターフェイスを定義する必要があります。割り込みを生成すると、スタックがカーネル スタックに切り替えられるため、システム コールの引数をスタックではなくレジスタに渡すことをお勧めします。これは、ほぼ確実に、システム コールを実行するユーザー モード エンドと、システム コール引数を収集してレジスタを保存するカーネル エンドの両方で、いくつかのアセンブリ言語スタブを記述する必要があることを意味します。

すべてが整ったら、ページ フォールトの処理について考え始めることができます。ページ フォールトは事実上、別の種類の割り込みです。ユーザー コードがページ テーブル エントリのない仮想アドレスにアクセスしようとすると、割り込み 14 が生成され、エラー コードとしてフォールト アドレスも取得されます。カーネルはこの情報を取得し、不足しているページをディスクから読み込み、ページ テーブル マッピングを追加して、ユーザー コードに戻ることを決定できます。

MIT オペレーティング システムクラスの資料のいくつかを参照することを強くお勧めします。参照セクションをチェックしてください。良いものがたくさんあります。

于 2008-11-07T03:22:24.820 に答える