以下は、特定の実装ではなく、一般的なプログラミングの概念に基づく一般的な説明と要約です。
への呼び出しprintf
は、通常のサブルーチン呼び出しで始まります。カーネル モードは関係ありません。大部分printf
は通常のコードであり、C で記述できます。printf
コード自体の大部分は、フォーマット文字列の解釈、引数の書き込み文字列への変換、およびそれらの文字列の出力ファイルへの書き込みに関係しています。この作業の多くは、数値 (またはのようなオブジェクト) を数値 (数値を表す文字列)printf
に変換するサブルーチンなどを呼び出すサブルーチンによって行われます。int
float
printf
または関連するルーチンを呼び出しmalloc
て、文字列を準備するバッファのメモリを取得する可能性もあります。malloc
この回答で呼び出しについて説明することは控えます。
フォーマット文字列の解釈、引数の変換、および文字列の書き込み準備のすべての作業は C で行うことができますが、高品質のライブラリでは、速度や効率のために、アセンブリ言語を含むさまざまなターゲット固有の最適化が使用される場合があります。
ある時点で、printf
出力する文字列がある場合、ルーチンを呼び出して文字列を に書き込みますstdout
。これは、fwrite
または類似のサブルーチンである可能性があります。議論のために、私はそれが であると仮定しますfwrite
。
通常、ストリームはバッファリングされます。そのため、 をprintf
呼び出すとfwrite
、fwrite
バッファがどれだけいっぱいかをチェックします。からの新しい文字列printf
がバッファに収まる場合、fwrite
単に文字列をバッファに追加して戻ります。バッファーがいっぱいの場合は、fwrite
別のルーチンを呼び出して、実際にバッファーの内容をファイルに書き込みます。(通常、これには、入ってくる文字列の一部でバッファを埋め、バッファをファイルに書き込み [そしてバッファを空にマークする]、そして入ってくる文字列の残りを新しく空になったバッファにコピーすることが含まれます。)状況に応じて、着信文字列内の改行文字を検出するなど、バッファーの書き込みをトリガーします。
fwrite
バッファを書き込むために、システムルーチンを呼び出すとしましょうwrite
。の表面write
はライブラリ ルーチンです。fwrite
call への通常のサブルーチン呼び出しを実行しますwrite
。システムルーチンには通常のサブルーチンの一部がありますが、重要な作業を行う必要がある場合は、ある種のシステムコール命令 (トラップと呼ばれることもあります) があります。
システムコール命令を実行すると、プロセッサはいくつかのことを行います。指定された場所にプロセッサ レジスタを保存します。これには、ユーザー・プロセスの状態を記述する汎用レジスターと特殊レジスターの両方が含まれます。次に、プロセッサはカーネル モードに切り替わります。これには通常、新しい実行状態が特権 (特殊なプロセッサ レジスタの変更、特殊な命令の実行などが許可されている) であることを示すビットの設定と、他の場所からのレジスタのロード、または既知のレジスタへの設定が含まれます。値。特に、プログラム カウンター (プロセッサが実行する命令を読み取る場所) は、オペレーティング システムがシステム コールを処理するコードを持っている特定の場所を指すように設定されます。
これで、プロセッサはカーネル モードで実行されます。通常、この時点でのプロセッサの仕事は、できるだけ早くカーネル モードを終了して、プロセス間のタイム シェアリングを再開し、他の作業の準備を整えることです。さらに、最新のオペレーティング システムには多くのレイヤーが存在するため、現時点で何が起こっているのかを正確に述べるのは困難です。
1 つのシナリオとして、システム コール ハンドラ (システム コールが発生したときに呼び出されるソフトウェア) が、ユーザー プロセスの保存されたレジスタとメモリを読み取り、プロセスが何を要求したかを判断します。各システムでは、システム コールにパラメータを渡すいくつかの方法が指定されています。たとえば、特定のレジスタには、要求が何であるかを示す数値が含まれている場合があり (0 は書き込みを意味し、1 は読み取りを意味し、2 は現在の時刻を取得することを意味し、3 はメモリ マップの変更を意味するなど)、各要求には特定のパラメーターが渡されます。他のレジスタまたはメモリ内 (1 つのレジスタにはメモリ内のアドレスが含まれ、別のレジスタには書き込む長さが含まれる場合があります)。
そのため、システム コール ハンドラは、どのようなリクエストが行われているかを把握し、それを処理するコードにディスパッチします。これには、要求のパラメーターを収集し、それらを実行する作業の記述に形成し、その作業をキューに入れ、システム コール ハンドラーを離れることが含まれる場合があります。
実行すべき作業がある間、オペレーティング システムはおそらくユーザー プロセスに戻りません。前述したように、最新のオペレーティング システムには多くの層があります。デバイス ドライバー、カーネル拡張機能、マイクロカーネル、オペレーティング システム内のソフトウェアのライブラリなどがあります。ただし、オペレーティング システムは組織化されており、ある時点で、システム コールによって要求された作業を実行することを決定します。
標準出力への書き込みの場合、作業は「デバイス」の作業を処理するソフトウェアの名前である「デバイス ドライバ」に送信されます。もともと、デバイスはシステムに接続されたハードウェアの一部でした。デバイス ドライバは、書き込まれるデータをメモリ内の特別な場所にコピーし、(特別な命令を使用して) デバイスにコマンドを発行して、そのデータをメモリから読み取り、デバイスが送信する場所 (端末、ディスク ドライブ) に送信します。 、 なんでもいい)。デバイス ドライバーの別の部分は、作業が完了したときに呼び出されるルーチンです。(この呼び出しはシステム コールに似ていますが、通常は割り込みと呼ばれます。)作業が完了すると、デバイス ドライバーはオペレーティング システムの他の部分にメッセージを返します。
現在、多くの「デバイス」は、仮想デバイスを実装するソフトウェアです。ユーザープロセスの標準出力は、おそらくある種の疑似端末です。その擬似端末には実際のハードウェア端末がないため、他のソフトウェアに助けを求めて書き込み要求を処理する必要があります。
擬似端末がグラフィック ディスプレイ上の端末ウィンドウの一部である場合、端末ウィンドウを実装するソフトウェアがいくつかあります。そのソフトウェアは、標準出力に書き込まれるテキストを受け取り、ウィンドウ内のどこに配置するかを決定し、他のソフトウェアを呼び出して、文字をウィンドウ内のピクセル単位の変化に変換します。つまり、一部のソフトウェアは文字を読み取り、いくつかのテーブルやその他のデータ (書体の説明など) で文字の説明を検索し、それらの文字をイメージ バッファーに描画します。
画像バッファーの準備ができると、画像バッファーをディスプレイに書き込むために、さらにソフトウェアが呼び出されます。この場合も、データを別のデバイス ドライバに渡す必要があります。最終的に、実際のハードウェア デバイスに到達し、データが取得されてディスプレイに表示されます。
まとめると、巨大な一連のイベントがあります。データは複数のレイヤーを行き来し、複数の異なるユーザー プロセス、複数のデバイス ドライバー、および多くのソフトウェア ライブラリが関与する可能性があります。プロセス全体を包括的に把握することは困難です。一般に、プロセス全体を一度に理解しようとするのではなく、各ステップについて個別に学習します。たとえば、私のキャリアの中で、システム コール命令の詳細に対処しなければならないことがありました。しかし、私のシステム全体がどのように機能しているかを考えるとき、それらの通信がどのように機能するかの詳細については考えずに、相互に通信するより大きなレベルのプロセスについて考えます。