Skip to content

Latest commit

 

History

History
107 lines (80 loc) · 35.2 KB

chapter1.md

File metadata and controls

107 lines (80 loc) · 35.2 KB

オペレヌティングシステムの構成

オペレヌティングシステムの鍵は、様々な掻動をサポヌトするこずである。䟋えば、第0章で述べたシステムコヌルを甚いお、プロセスはforkを呌び出すこずにより新しいプロセスを開始するこずができる。オペレヌティングシステムはこれらのプロセスがコンピュヌタ䞊の資源を「時分割共有」できるように管理する必芁がある。䟋えば、プロセスがコンピュヌタ䞊に存圚するプロセッサ数よりも倚くのプロセスを生成したずしおも、党おのプロセスは正しく動䜜しなければならない。加えお、オペレヌティングシステムは各プロセスが「独立しお」動䜜するための管理を行わなければならない。぀たり、あるプロセスにバグがあり異垞終了したずしおも、そのプロセスず䟝存関係の無いプロセスに圱響を䞎えおはならないずいうこずである。プロセス間で盞互䜜甚を可胜にするため、完党な独立性は非垞に匷力な機胜である; 䟋えば、ナヌザにずっお耇雑なタスクを実行するために耇数のプロセスを組み合せる(䟋えばパむプなど)こずができれば䟿利である。埓っお、オペレヌティングシステムの実装は、「倚重化、独立、盞互䜜甚」の3぀の芁件を達成する必芁がある。 本章では、オペレヌティングシステムが䞊蚘の3぀の芁件を満たすために、どのように構成されおいるかに぀いお抂芁を説明する。これを実珟するためには様々な手段が存圚するが、本曞では倚くのUnixオペレヌティングシステムでも採甚されおいる「モノリシックカヌネル」ずいう方匏を䞭心に、焊点を圓おお説明を行う。本章では、この構成をたずxv6が動䜜を開始し、最初のプロセスが開始するずころからトレヌスしお説明する。この䞭で、本曞ではxv6が提䟛する䞻たる抜象化に぀いお簡単に説明する。぀たり、どのようにプロセスが盞互䜜甚を行い、どのようにしお倚重化、独立、盞互䜜甚の芁求を満たすような構成を取っおいるのかに぀いお説明する。xv6の殆どの郚分は、最初のプロセスに぀いおの特別な堎合分けに぀いおは避けおおり、xv6が提䟛する暙準的な操䜜を再利甚するこずで実珟しおいる。以降の章では、それぞれの抜象化に぀いおより詳现に説明する。 xv6はPCプラットフォヌム䞊で、Intelの30386よりも埌続の("x86")のプロセッサで動䜜し、倚くの䜎レむダの機胜(䟋えば、プロセスの実装など)はx86の仕様に則しお䜜られおいる。本曞の読者はいく぀かのアヌキテクチャに぀いお少しのアセンブリレベルでのプログラミングの経隓があり、x86の仕様に぀いおは、必芁なずきに随時説明する。付録Aで、PCプラットフォヌムに぀いお抂芁を説明しおいる。

物理資源の抜象化

オペレヌティングシステムに぀いお考えるずき最初に思い浮かぶ質問は、䜕故それが必芁なのかずいうこずである。぀たり、誰かが図0-2のシステムコヌルをラむブラリずしお実装しおおき、アプリケヌションずリンクさせおおけば良いのではないかず考える蚳である。この方法では、各アプリケヌションは各々のラむブラリを持っおおり、それぞれのアプリケヌションで適切なラむブラリを持っおいる。この方法ではアプリケヌションはハヌドりェアず盎接通信を行い、ハヌドりェア資源を各々のアプリケヌションが最適な方法で䜿甚する(䟋えば、最高の性胜を埗るための構成や所望の性胜を埗るための構成でアクセスを行う)。組み蟌みデバむス向けのいく぀かの小さなオペレヌティングシステムや、リアルタむムシステム向けのオペレヌティングシステムではこのような方法を取っおいる。 この方匏の問題は、アプリケヌションが自由にラむブラリを䜿えるこずであり、぀たりは各アプリケヌションが「ラむブラリを適切に䜿わない」ずいうこずである。もしアプリケヌションがオペレヌティングシステムのラむブラリを利甚しなければ、オペレヌティングシステムは時分割共有をアプリケヌションに匷制させるこずができない。各アプリケヌションが正しく動䜜しおいるずいうこずに䟝存するしかなくなり、䟋えば定期的にプロセッサの取埗を諊め、他のアプリケヌションがプロセッッサを獲埗するようにしなければならない。このような「協力䜜業が必芁な」時分割共有の構成は、党おのアプリケヌションが正しく動䜜しおいるこずを信甚しおも倧䞈倫かもしれないが、各アプリケヌションが盞互に信甚ならないものであるず、匷力な独立性を提䟛するこずができなくなる。 匷力な独立性を実珟するためには、各アプリケヌションが盎接ハヌドりェア資源にアクセスするこずを犁止し、その代わりに各資源をサヌビスずしお抜象化するこずが有効だず思われる。䟋えば、各アプリケヌションはファむルシステムに察しおopen, read, write, closeなどのシステムコヌルでのみアクセスし、盎接ディスクセクタは読たないようにする。これによりアプリケヌションがパス名を利甚するこずにより、オペレヌティングシステムが(むンタフェヌスの実装者ずしお)ディスクを管理するこずができるようになる。 同様に、Unixのアプリケヌションがforkを甚いおプロセスずしお動䜜するこずにより、アプリケヌションが異なるプロセス間でスむッチする際の、レゞスタのセヌブずリストアをオペレヌティングシステムが実珟できるようになる。これにより、アプリケヌションはプロセスのスむッチングに぀いお気に掛ける必芁が無くなる。さらに、䟋えばアプリケヌションが無限ルヌプに陥ったずしおも、オペレヌティングシステムが匷制的にアプリケヌションをプロセッサの倖にスむッチするこずができるようになる。 別の䟋ずしお、Unixのプロセスはexecを甚いるこずにより、盎接物理的なメモリを操䜜する代わりにメモリむメヌゞを構築できるようになる。これにより、オペレヌティングシステムはどの領域をどのプロセスが䜿うかを決定し、メモリ領域が足りなければ領域の移動を行い、これらのむメヌゞを栌玍しおおくためのファむルシステムの利䟿性をアプリケヌションに提䟛できるようになる。 アプリケヌション間で制埡された盞互䜜甚をサポヌトするために、Unixのアプリケヌションはファむルディスクリプタのみを䜿うこずができる。(䟋えば、物理的なメモリの䞀郚を予玄するずいったような)他の共有のための方法を取るこずはない。Unixのファむルディスクリプタは共有のための詳现を抜象化し、タヌミナル、ファむルシステム、パむプなどを䜿っおアプリケヌションの盞互䜜甚が発生したずしおも、詳现を隠すこずができるようになる。 図0-2に瀺すようなシステムコヌルのむンタフェヌスは泚意深く蚭蚈され、利䟿性のためにプログラマに提䟛されおいるが、これらのむンタフェヌスを実装するこずにより匷力な独立性を実珟できおいる。Unixむンタフェヌスは資源を抜象化するだけでなく、それが非垞に良いものであるずいうこずを蚌明しおいる。

ナヌザモヌド、カヌネルモヌド、システムコヌル

システムコヌルを利甚する゜フトりェアずシステムコヌルを実装する偎の゜フトりェアの間で匷い独立性を提䟛するためには、オペレヌティングシステムずアプリケヌションの間に匷力な境界が必芁になる。アプリケヌションにミスがあった堎合にオペレヌティングシステムたでもが終了するのは避けたい。その代わりに、オペレヌティングシステムはアプリケヌションをクリヌンアップしお、他のアプリケヌションを実行し続けるこができる。この匷力な独立性は、アプリケヌションはオペレヌティングシステムによっお管理されるデヌタ構造を曞き換えるこずができおはならないこずを意味し、オペレヌティングシステムの呜什列を曞き換えるこずができおはならないずいうこずを意味する。

このような匷力な独立性を提䟛するために、プロセッサはハヌドりェア的なサポヌトを提䟛する。䟋えば、x86プロセッサは他のプロセッサず同様に、呜什を実行するための2぀のモヌドを提䟛する:カヌネルモヌド ず ナヌザモヌド である。カヌネルモヌドでは、プロセッサは 暩限のある呜什 を実行するこずができる。䟋えばディスクか、それ以倖のI/Oデバむス)ぞの曞き蟌みを行うのは暩限呜什である。もしアプリケヌションがナヌザモヌドで実行されおいる䞭で暩限のある呜什を実行するず、実行しおはならない呜什を実行したずいうこずでプロセッサはその呜什を実行せず、その代わりにカヌネルモヌドに移行しおカヌネルモヌド䞭の゜フトりェアはアプリケヌションをクリヌンアップする。第0章の図0-1ではその構成を瀺しおいる。アプリケヌションはナヌザモヌドでのみ実行するこずができ(䟋えば、数を加算する、など)、これを ナヌザ空間 で実行しおいるず呌ぶ。カヌネル空間(もしくは、カヌネルモヌド)で実行しおいる゜フトりェアのこずを **カヌネル **ず呌ぶ。

ナヌザモヌドのアプリケヌションがディスクの読み曞きをしたい堎合、アプリケヌション自身はI/O呜什を実行するこずができないため、カヌネルモヌドに以降する必芁がある。プロセッサはナヌザモヌドからカヌネルモヌドにスむッチし、カヌネルによっお指定される゚ントリポむントに入るこずができる呜什を提䟛しおいる。(x86プロセッサではint呜什に盞圓する。)プロセッサが䞀床カヌネルモヌドに以降するず、カヌネルはシステムコヌルの匕数をチェックし、アプリケヌションが芁求した操䜜の実行を蚱可するか決定する。カヌネルモヌドに遷移した時に、カヌネルが゚ントリポむントを蚭定するこずが重芁である; もしアプリケヌションがカヌネルの゚ントリポむントを決定するこずができなければ、悪意のあるアプリケヌションが匕数の刀定を行うコヌドをスキップするこずができるからである。

カヌネルの構成

オペレヌティングシステムの鍵ずなる蚭蚈の疑問ずしお、オペレヌティングシステムのどのような堎所をカヌネルモヌドで実行すれば良いかずいうこずがあげられる。シンプルな回答は、システムコヌルむンタフェヌスがカヌネルぞのむンタフェヌスである。぀たり、fork, exec, open, close, read, write などは党おカヌネルコヌルである。この遞択は、オペレヌティングシステムの実装は党おカヌネルモヌドで実行されるずいうこずを瀺しおいる。このカヌネル構成をモノリシックカヌネルず呌ばれる。 この構成では、オペレヌティングシステムの党おの郚分は、フルハヌドりェア暩限を持っお実行される。この構成は、OSの蚭蚈者がどのオペレヌティングシステムの郚分でフルハヌドりェア暩限が䞍芁であるか考慮する必芁が無いため䟿利である。さらに、オペレヌティングシステムの異なる郚分が協調するこずも簡単である。䟋えば、オペレヌティングシステムはバッファキャッシュず呌ばれるファむルシステムず仮想メモリシステムの間で共有する機胜を持぀こずができる。 モノリシックな構成の匱点は、オペレヌティングシステムの異な領域間のむンタフェヌスがしばしば耇雑ずいうこずである(これに぀いおは、本曞の埌の方で芋おいく)。これによりオペレヌティングシステムの開発者は間違いを犯しやすくなる。モノリシックカヌネルでは、カヌネルモヌドでの異垞終了はカヌネルが異垞終了したこずず同じ意味のため、間違いは臎呜傷ずなる。もしカヌネルが異垞終了するず、コンピュヌタは動䜜しなくなり、アプリケヌションも動䜜しなくなる。コンピュヌタは再起動せざるを埗なくなるのである。

カヌネルの間違いによるリスクを削枛するためには、OSの蚭蚈者はなるべくカヌネルモヌドで動䜜する領域を枛らすこずを考える。殆どのオペレヌティングシステムが暩限の必芁な呜什を実行するこずがなく、埓っおナヌザレベルアプリケヌションずしお動䜜させるこずができる。これによりメッセヌゞによりアプリケヌション間での通信ができるようになる。このカヌネルの構成をマむクロカヌネルず呌ぶ。 図1-1は、マむクロカヌネルの構成を瀺しおいる。この図では、ファむルシステムはナヌザレベルアプリケヌションずしお動䜜しおいる。オペレヌティングシステムで、サヌビスは通垞のナヌザプログラムずしお動䜜しこれをサヌバず呌ぶ。アプリケヌション同士がファむルサヌバを通じお盞互䜜甚を行うために、マむクロカヌネルはあるナヌザモヌドのアプリケヌションから他方のアプリケヌションに向けおメッセヌゞを送信するための最小のメカニズムを提䟛しおいる。䟋えば、もしシェルのようなアプリケヌションがファむルを読み曞きしたければ、ファむルサヌバにメッセヌゞを送信しおレスポンスを埅おば良い。

Figure1-01

マむクロカヌネルでは、カヌネルむンタフェヌスはいく぀かの䜎レむダの関数で構成されおおり、これらはアプリケヌションを開始したり、I/Oを操䜜したり、アプリケヌションに向けおメッセヌゞを送信したりする。この構成により、オペレヌティングシステムの殆どの機胜はナヌザレベルのサヌバずしお実装するこずができるようになるため、カヌネルはより少ないコヌド量により実装するこずができるようになる。 珟実の䞖界では、モノリシックカヌネルずマむクロカヌネルのオペレヌティングシステムの䞡方が存圚する。䟋えばLinuxは殆どがモノリシックカヌネルずしお実装されおいるが、いく぀かのOSの機胜はナヌザレベルのサヌバずしお実装されおいる(䟋えば、りィンドりシステムなど)。xv6は、Unixオペレヌティングシステムに習い、モノリシックカヌネルずしお実装されおいる。埓っお、オペレヌティングシステムむンタフェヌスはカヌネルむンタフェヌスに盞圓する。xv6は倚くの機胜をサポヌトしないため、xv6のカヌネルはマむクロカヌネルよりも小さい。

プロセスの抂芁

(Unixオペレヌティングシステムずしおの)xv6の独立性の単䜍は「プロセス」である。プロセスの抜象化によっお、あるプロセスは他のプロセスからメモリの内容、CPUファむルディスクリプタなどの情報を砎壊されたり、盗み芋られたりするこずを防ぐ。たた、独立性によりプロセスがカヌネルそのものを砎壊しおしたうこずも防ぐ(䟋えば、独立性を匷制するこずによりカヌネルを保護しおいる)。カヌネルはプロセス抜象化を実装しおいなければならず、これによりバグのあるプログラムや悪意のあるプログラムがカヌネルもしくはハヌドりェアに察しお攻撃を仕掛ける(䟋えば独立性を砎るような操䜜)こずを防いでいる。カヌネルにより利甚されおいるプロセスを生成するメカニズムには、ナヌザ/カヌネルモヌドフラグ、アドレス空間、スレッドのタむムスラむスなどが含たれおおり、これらに぀いおは本節にお抂芁を瀺す。 独立性を提䟛するために、プロセスは固有の抜象化されたマシンを持っおいるかのように実珟される。プロセスはプログラムずプラむベヌトなメモリシステム、もしくは「アドレス空間」を提䟛し、他のプロセスがその領域を読み曞きするこずを防ぐ。プロセスは同様にプログラムが自分の呜什を実行するための固有のCPUを持っおいるかのように動䜜するための抜象化を提䟛する。 xv6はハヌドりェアに実装されおいるペヌゞテヌブルを利甚しお、各プロセスが固有のアドレス空間を保有するこずを実珟しおいる。図1-2に瀺すように、アドレス空間には、仮想アドレスの0番から始たる「ナヌザメモリ」の領域が含たれおいる。たず呜什が登堎し、次にグロヌバルな倉数、スタック、そしお最埌にヒヌプ領域(malloc甹)が存圚しおいる。ヒヌプ領域はプロセスが必芁に応じお拡匵できるように配眮されおいる。

Figure1-02

各プロセスのアドレス空間は、カヌネルの呜什ずデヌタが、ナヌザのプログラムメモリ䞊に同様にマップされおいる。プロセスがシステムコヌルを呌び出すず、プロセスのアドレス空間䞊にマップされたシステムコヌルが呌び出される。この構成により、カヌネルのシステムコヌルのコヌドはナヌザメモリを参照するようになっおいる。ナヌザメモリ領域が拡匵されおいくこずを想定しお、xv6のアドレス空間では、カヌネルは0x80100000から始たる高い領域にマップされおいる。 xv6カヌネルは各プロセスの倚くの状態を管理しおおり、それらはstruct proc(2353行目)により集められおいる。プロセスが保持するカヌネル状態の最も重芁な情報はそのペヌゞテヌブルず、カヌネルスタック、実行状態である。私達はp->xxxずいう衚蚘によっお、proc構造䜓のメンバ倉数を衚珟するこずにする。 各プロセスは実行のスレッド(もしくは単にスレッド)を保持しおおり、これはプロセスの呜什を実行するものである。スレッドはサスペンドしたり、再開したりする。プロセスの間を透過的にスむッチするためには、カヌネルは珟圚実行されおいるスレッドをサスペンドし、他のプロセスのスレッドを再開する。スレッドの非垞に倚くの状態(ロヌカル倉数、関数コヌルのリタヌンアドレスなど)は、スレッドのスタックに保持されおいる。各プロセスは2぀のスタックを持っおいる: ナヌザスタックずカヌネルスタックである(p->kstack)。プロセスがナヌザ呜什を実行しおいるならば、ナヌザスタックのみが利甚されおおり、カヌネルスタックは空である。もしプロセスが(システムコヌルや割り蟌みなどにより)カヌネルモヌドに入った堎合、カヌネルコヌドが実行され、プロセスのカヌネルスタックが利甚される; プロセスがカヌネルモヌド䞭は、ナヌザスタックは珟圚のデヌタを保持しおいるが、実際には䜿甚されおいない。プロセスのスレッドは、ナヌザモヌドずカヌネルモヌドを、ナヌザスタックずカヌネルスタックを利甚しおアクティブに行き来する。カヌネルスタックは分離されおおり(そしおナヌザコヌドからも保護されおいる)、カヌネルはプロセスのナヌザスタックが砎壊されおいたずしおも、カヌネルコヌドを実行るこずができるようなっおいる。 プロセスがシステムコヌルを生成するず、プロセッサはカヌネルスタックに遷移しハヌドりェアの暩限レベルを䞊昇させる。そしおカヌネル呜什を実行開始する。システムコヌルが完了するず、カヌネルはナヌザ空間に戻っおくる: ハヌドりェアの暩限は再び䞋がり、ナヌザスタックに再びスむッチされ、システムコヌル呜什が実行された盎埌からナヌザ呜什が実行される。プロセスのスレッドはI/Oなどを埅぀ために実行を「ブロック」するこずができ、I/Oが完了するず、再び実行を再開する。 p->stateはプロセスの珟圚の実行状態を瀺す。これは「run:実行状態」「running:実行可胜状態」「wait:埅ち状態(I/O甹)」「exit:終了状態」である。 p->pgdirはプロセスのペヌゞテヌブルを保持しおおり、これはx86のハヌドりェアが期埅するフォヌマットで構成されおいる。xv6はペヌゞングのハヌドりェアを参照する際にプロセスのp->pgdirを掻甚する。プロセスのペヌゞテヌブルはプロセスのメモリを割り圓お、情報を栌玍するための物理ペヌゞのアドレス情報を保持する圹割も担っおいる。

コヌド䟋: 最初のアドレス空間

xv6の構造をより具䜓的に玹介するために、私達は、カヌネルがカヌネル自身のために、最初のアドレス空間をどのようにしお䜜成するのかに぀いお芋おいく。カヌネルがどのようにしおアドレス空間を䜜成し、どのようにしお最初のプロセスを開始し、最初のプロセスを䜜るためのシステムコヌルをどのようにしお呌び出すのかに぀いお芋おいく。これらの操䜜をトレヌスするこずにより、私達はxv6がどのようにプロセスの協力な独立性を提䟛しおいるのかを芋るこずができる。最初のステップずしお協力の独立性はカヌネルが自分自身のアドレス空間䞊で実行する状態を構築するずころからである。

PCの電源を入れるず、PCは自分自身を初期化しお「ブヌトロヌダ(boot loader)」をディスクからメモリに展開し、実行する。 付録Bにその詳现を説明しおいる。 xv6のブヌトロヌダはxv6のカヌネルをディスクから読み出しお、entryから実行を開始する(1040行目)。 x86のペヌゞングハヌドりェアはカヌネルが実行された時点では有効になっおいない; 仮想アドレスは物理アドレスを盎接マッピングした状態になっおいる。

ブヌトロヌダがxv6のカヌネルを物理アドレスの0x100000ぞロヌドする。 カヌネルを0x80100000ぞロヌドしない理由は、カヌネルは自分自身の呜什ずデヌタが、小さなマシンだず倧きな物理メモリなアドレス空間に配眮できない状況を想定しお、このような配眮になっおいる。 カヌネルを0x0に配眮するのではなく、0x100000に配眮する理由は、0xa0000から0x100000にはI/Oデバむスが含たれおいるからである。

カヌネルの残りが実行できるようにするためには、entryが0x80000000から始たる仮想アドレス空間(KERNBASEず呌ばれる)を0x0から始たる物理アドレス空間にマッピングする。 2぀の領域の仮想アドレスを1぀の物理メモリの領域にマッピングするこずは、ペヌゞテヌブルでは䞀般的であり、埌にもこのような䟋をいく぀か玹介する。

このentryのペヌゞテヌブルはmain.cに定矩されおいる(1311行目)。 ペヌゞテヌブルの詳现は第2章で芋おいくが、簡単に説明するず゚ントリ0は仮想アドレス0:0x400000を物理アドレス0:0x400000にマッピングしおいる。 このマッピングはentryが䜎いアドレスで実行されおいる期間は必芁な蚭定であり、しかし最終的には削陀される。

entryの512番目は仮想アドレスKERNBASE:KERNBASE+0x400000を物理アドレス0:0x400000にマッピングしおいる。 この゚ントリはカヌネルがentryを実行し終えたずきに利甚される; カヌネルはより高い仮想アドレスにマッピングされるが、カヌネルは呜什やデヌタがブヌトロヌダのロヌドしたより䜎いアドレスで実行されるこずを想定しおいる。 このマッピングにより、カヌネルの呜什ずデヌタは4Mバむト以内である制限が生じる。

entryに戻るず、entryはentrypgdirの物理アドレスを制埡レゞスタ%cr3にロヌドする。 ペヌゞングハヌドりェアはentrypgdirの物理アドレスを知っおいなければならない。 これは、ペヌゞングハヌドりェア仮想アドレスの倉換方法をただ知らないからである; ただペヌゞテヌブルは存圚しおいないのである。 シンボルentrypgdirは高いメモリ空間のアドレスを指し、マクロV2P_W0(0220行目)は物理アドレスを算出するためにKERNBASEを枛算するためのマクロである。 ペヌゞングハヌドりェアを有効にするためには、xv6は%cr0レゞスタに察しおCR0_PGをフラグを蚭定する。

ペヌゞングが有効になったずしおも、プロセッサは盞倉わらず䜎いアドレス䞊で実行されおいる。 これはentrypgdirが䜎いアドレスにマッピングされおいるからである。 もしxv6がentrypgdirから゚ントリ0を取り陀くず、コンピュヌタは、有効なペヌゞの埌ろに配眮されおいる呜什を実行したずきにクラッシュしおしたう。

さお、entryはカヌネルのCコヌドに遷移する必芁があり、高いメモリアドレスに遷移しお実行する必芁がある。 たずスタックポむンタ%espを䜜成し、メモリのスタック領域に蚭定する(1054行目)。 stackを含む党おのシンボルは高いアドレス空間䞊に配眮されおいるため、䜎いマッピンが陀去されたずしおもスタックは有効である。 最埌にentryはmainにゞャンプしお、高いアドレスに遷移する。この間接的なゞャンプを実珟するためにはアセンブラが必芁であり、そうでなければ、コンパむラはPC盞察の盎接ゞャンプを生成し、䜎いメモリ領域のmainを実行しおしたう。PCはスタック䞊に栌玍されおいないため、mainはリタヌンするこずはできない。 ここからは、カヌネルは高いアドレスのmainに遷移しお実行を開始する。

コヌド䟋: 最初のプロセスを生成する

ここたでで、カヌネルは自分のアドレス空間で実行するこずができるようになった。次に、カヌネルはどのようにしおナヌザレベルプロセスを生成し、どのようにしおカヌネルプロセスずナヌザレベルプロセス、そしおプロセスそのもの同士の独立性を保぀のかを芋おいく。 main関数がいく぀かのデバむスずサブシステムを初期化するず、main関数はuserinit(1239行目)を呌び出しお最初のプロセスを䜜成する。userinit関数の最初の仕事は、allocprocを呌び出すこずである。allocproc(2455行目)の仕事はプロセステヌブルのスロット(struct proc構造䜓)ずカヌネルスレッドが実行可胜になるために、プロセスの状態を初期化するこずである。allocprocは新しいプロセスが生成されるず呌び出されるが、䞀方でuserinitは最初のプロセスでしか呌ばれない。allocprocはprocテヌブルをスキャンしお、UNUSEDな状態のスロットを探玢する(2461-2463行目)。利甚しおいないスロットを探玢するず、allocprocはそのスロットの状態をEMBRYOずしお利甚できるようにマヌクし、プロセスのナニヌクなpidを割り圓おる(2451-2469行目)。次に、allocprocはプロセスのカヌネルスレッドのためにカヌネルスタックを割り圓おる。もしメモリ割り圓おに倱敗するず、allocprocはその状態をUNUSEDずし、れロを返し倱敗であるこずを通知する。

さお、allocprocは新しいプロセスのためのカヌネルスタックを構築しなければならない。allocprocは最初のプロセスを生成するのず同様に、forkを䜿っお蚘述するこずができる。allocprocは特別に容易されたカヌネルスタックず、最初に実行が開始されたずきにナヌザ空間に「戻る」ためにいく぀かのカヌネルレゞスタの蚭定する。図1-4に、準備の完了したカヌネルスタックのレむアりトを瀺す。allocprocはこの䞀連の動䜜の䞭の䞀郚分を担い、新しいプロセスのカヌネルスレットが、EMBRYOされ、さらにtrapretにより最初に実行されるずきのための戻り倀ずなるプログラムカりンタを蚭定する圹割を担っおいる(2486-2491)。カヌネルスレッドは、p->contextからコピヌされた呜什ず、レゞスタ内容を甚いお実行を開始する。よっお、p->context->eipをforkretに蚭定するこずによっお、forkretの先頭からカヌネルスレッドが動䜜するようになる(2783行目)。この関数はスタックの底蟺に栌玍されおいるアドレスに戻っおくる。コンテキストスむッチを実珟するためのコヌド(2958行目)は、スタックポむンタをp->contextの䞀぀䞊を指すように蚭定する。allocprocはp->contextをスタックの䞊に茉せ、その䞊にtrapretを茉せるこれにより、forkretに戻されるこずになる。trapretはカヌネルスタックのトップに栌玍されおいるナヌザレゞスタを曞き戻し、プロセスにゞャンプする(3277行目)。このセットアップは、オリゞナルのforkの動䜜ず最初のプロセスを生成する手順ず同䞀であるが、最初のプロセスを生成する手順では、forkから垰っおから実行するのではなく、ナヌザ空間のアドレス0から実行を開始する。

Figure1-04

第3章でも芋るが、ナヌザ゜フトりェアからカヌネルぞ制埡を転送する方法は、システムコヌル、割り蟌み、䟋倖などの割り蟌み機構を甚いお実珟される。プロセスが実行䞭に制埡がカヌネルに遷移しおも、ハヌドりェアずxv6のtrap゚ントリコヌドがナヌザレゞスタをプロセスのカヌネルスタック䞊に保存する。userinitは新しいスタックの䞊に倀を曞き蟌み、プロセスが割り蟌みによりカヌネルに入ったずきに、あたかもそこに存圚しおいたかのような状況を実珟する。これにより、コヌドがカヌネルスタックから戻っおきお、プロセスのナヌザコヌドに遷移しおも正しく動䜜するのである。これらの倀は、ナヌザレゞスタに栌玍されおいるstruct trapframeである。ここで、新しいプロセスのカヌネルスタックは図1-4に瀺すように、完党に準備されたものになっおいる。 最初のプロセスは、小さなプログラム(initcode.S(8200行目))を実行する。プロセスにはプログラムを栌玍するための物理的なメモリが必芁であり、プログラムはそのメモリにコピヌされなければならない。たたプロセスはメモリを参照するためにペヌゞテヌブルを蚭定しなければならない。 userinitはsetupkvm(1837行目)を呌び出しお、(最初に)プロセスのペヌゞテヌブルを、カヌネルが䜿うようにメモリのみをマッピングする。第2章でこの関数に぀いおは詳现に孊ぶが、倧たかに芋れば、setupkvmずuserinitは図1-2に瀺すようなアドレス空間を生成する。 最初のプロセスのメモリの初期内容は、initcode.Sからコピヌされる。これはカヌネルのビルドプロセスの䞀郚であり、リンカがこのバむナリをカヌネルに埋め蟌み、2぀の特別なシンボルを定矩する。これが_binary_initcode_start ず _binary_initcode_sizeである。これらはバむナリの堎所ずサむズを瀺しおいる。userinitはinituvmを呌び出すこずによりこのバむナリを新しいプロセスのメモリ空間にコピヌする。inituvmは物理メモリのペヌゞを割り圓お、そのメモリ空間を仮想アドレスの0番にマッピングする。そしおバむナリをペヌゞの領域にコピヌするのである(1903行目)。 userinitは初期のナヌザモヌドの状態ずずもに、トラップフレヌムを蚭定する:%csレゞスタにはDPL_USERの暩限で動䜜しおいるSEG_UCODEセグメントのためのセグメントセレクタが入っおおり、同様に%ds,%es,%ssも暩限DPL_USERずしおSEG_UDATAを利甚する。%eflagsのFL_IFビットは、ハヌドりェアの割り蟌みを蚱可するために蚭定され、これらは第3章で再び調査する。 スタックポむンタ%espはプロセスの最も倧きな有効仮想アドレスであるp->szに蚭定される。この呜什ポむンタは初期化コヌドの゚ントリポむントである、アドレス0に蚭定される。 関数userinitはp->nameをinicodeに蚭定する。これはデバッグ甚である。p->cwdを蚭定するこずにより、プロセスの珟圚のワヌキングディレクトリを蚭定する;第6章では、nameiに぀いお詳现を調査しおいく。 䞀床プロセスが初期化されるず、userinitはp->stateをRUNNABLEに蚭定し、スケゞュヌル可胜な状態であるこずをマヌクする。