「ユーザ空間」とは通常 (カーネルに対するという意味) プロセスの実行環境を意味します。「ユーザ空間」プロセスとは実際にユーザによって開始されたプロセスという意味ではありません。なぜなら、標準的なシステムでは通常、ユーザがセッションを開始する前から実行されている複数の「デーモン」(またはバックグラウンド) プロセスが存在するからです。デーモンプロセスもまたユーザ空間プロセスと考えられます。
カーネルはその初期化終了後に、最初のプロセスである init
を開始します。最初のプロセスである init
それ自身が役に立つことはほとんどなく、Unix 系システムは多くの追加的プロセスと一緒に動いています。
第一に、プロセスは自分自身を複製することが可能です (これはフォークとして知られています)。カーネルは新しい (全く同じ) プロセスメモリ空間とそれを使うもう一つ別のプロセスを割り当てます。この時点で、2 つのプロセスの違いは pid だけです。新しいプロセスは通常子プロセスと呼ばれています。そして pid が変わらなかったプロセスは親プロセスと呼ばれています。
しばしば、子プロセスは親プロセスからコピーされたデータを持った状態で、親プロセスから独立して引き続き実行されます。とは言うものの、子プロセスは他のプログラムを実行する場合が多いです。この場合、いくつかの例外を除いて、子プロセスのメモリは単純に新しいプログラムで置き換えられ、新しいプログラムの実行が始まります。このメカニズムを使用して、init プロセス (プロセス ID が 1 のプロセス) は追加的サービスを起動し起動シーケンスの全体を実行します。同時に、
init
の子プロセスの 1 つがログイン機能を提供するユーザ用のグラフィカルインターフェースを開始します (実際のイベントの順番は
第 9.1 節「システム起動」に詳しく書かれています)。
プロセスは自分が開始したタスクを完了したら、終了します。その後、カーネルがこのプロセスに割り当てられたメモリを回収し、カーネルはプロセスに実行時間を与えることを停止します。親プロセスは子プロセスが終了したことについて通知を受けます。このおかげで、親プロセスは子プロセスに委託したタスクの完了を待つことが可能になります。コマンドラインインタプリタ (シェルとして知られています) ではこの挙動がはっきりと見えます。コマンドがシェルに入力された場合、コマンドの実行が終了するまでプロンプトは戻って来ません。多くのシェルでは、コマンドをバックグラウンドで実行することが可能です。これを行うには、コマンドの最後に &
を追加するだけです。この場合、プロンプトはすぐに戻ってきます。バックグラウンドで実行されるコマンドがデータを表示する場合、このやり方は問題を引き起こすかもしれません。
「デーモン」は起動シーケンスによって自動的に開始されるプロセスです。「デーモン」はメンテナンス作業を実行したり他のプロセスにサービスを提供するために (バックグラウンドで) 実行され続けます。ここで実際の「バックグラウンドタスク」はどんなものでも構いませんし、システムの観点から何か特別なタスクを意味しているわけでもありません。「バックグラウンドタスク」は単なるプロセスで、他のプロセスとよく似ており、自分に割り当てられたタイムスライスが来た時に動きます。プロセスの区別は人間の言葉に過ぎません。そして、ユーザと対話せずに実行される (特にグラフィカルインターフェースを持たない) プロセスは「バックグラウンドで実行される」とか「デーモンとして実行される」などと表現されます。
デーモンでも対話型アプリケーションでも、単独のプロセスでは役に立たない場合が多いです。このため、異なるプロセス同士がデータを交換したり、相互に制御し合うためのさまざまな通信方法があります。これを意味する一般的な用語がプロセス間通信 (略して IPC) です。
最も簡単な IPC システムではファイルを使います。データ送信側のプロセスが送信内容をファイルに書き込み (事前にファイル名を決めておく必要があります)、受信側はファイルを開いてその内容を読むだけです。
データをディスクに保存したくないと思っているならば、パイプを使うことが可能です。パイプは 2 つの端を持つ単純なオブジェクトです。そして、片側に書き込まれたデータを逆側から読み出すことが可能です。パイプの一方の端が別のプロセスによって制御されている場合、パイプは単純で便利なプロセス間通信チャネルになります。パイプは 2 種類に分類分けされます。すなわち名前付きパイプと無名パイプに分類分けされます。名前付きパイプはファイルシステム上のエントリによって表現されます (転送されたデータは保存されません)。このため、事前に名前付きパイプの場所がわかっていれば、2 つのプロセスが独立に名前付きパイプを開くことが可能です。通信プロセス同士に関連性がある場合 (たとえば、親と子プロセス)、親プロセスはフォークの前に無名パイプを作成し、子プロセスがこれを継承するだけで済みます。両方のプロセスはパイプを通じてデータを交換することが可能です。ファイルシステムは必要ありません。
しかしながら、すべてのプロセス間通信がデータを移動させるために使われるわけではありません。多くの状況で、送信する必要のある情報は「実行を一時停止」や「実行を再開」などの制御メッセージです。Unix (と Linux) はシグナルとして知られているメカニズムを提供します。このメカニズムを使って、あるプロセスは別のプロセスに対して簡単に特定のシグナルを送信することが可能です (送信するシグナルは事前に定義されたシグナルのリストから選びます)。送信に必要な情報は送信先の pid だけです。
さらに複雑な通信を行うには、プロセスが他のプロセスに対して自分が割り当てられたメモリの一部へのアクセス制限を解除したり、共有したりするメカニズムを使います。これで、プロセス間で共有されたメモリをデータ交換のために使うことが可能になります。
最後に、ネットワーク接続を使ってプロセス同士を通信させることが可能です。そして数千キロ離れた異なるコンピュータで動いているプロセス同士でも通信することが可能です。
典型的な Unix 系システムでは、さまざまなレベルでこれらのメカニズムを使っており、かなり標準的な手法になっています。
関数ライブラリは Unix 系オペレーティングシステムで重要な役割を果たします。関数ライブラリは厳密な意味のプログラムではありません。なぜなら、関数ライブラリ自体は実行できず、標準的なプログラムで使用されるコードの断片に過ぎないからです。よく使われるライブラリの中でも特に以下のものが有名です。
標準 C ライブラリ (glibc)。これにはファイルやネットワーク接続を開く関数やカーネルとの通信を容易にする関数などの基本的な関数が含まれます。
Gtk+ と Qt などのグラフィカルツールキット。これを使うことで、多くのプログラムはツールキットの提供するグラフィカルオブジェクトを再利用することが可能です。
libpng ライブラリ。これを使うことで、PNG フォーマットイメージを読み込み、編集、保存することが可能になります。
これらのライブラリのおかげで、アプリケーションは既存のコードを再利用することが可能です。多くのアプリケーションは同じ関数を再利用できるため、アプリケーションの開発は単純化されます。また、通常それぞれのライブラリは別の人によって開発されているため、アプリケーションの大域的な開発は Unix の歴史的哲学に近いものになります。
さらに、これらのライブラリは「共有ライブラリ」とも呼ばれています。なぜなら、複数のプロセスが同じライブラリを同時に使う場合、カーネルはライブラルの読み込みを 1 回だけで済ませることが可能だからです。プロセスが使うライブラリのコードを何度も読み込むような逆の (仮想的な) 状況に比べて、これはメモリを節約することになります。