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