Linux Cシステムプログラミング(06)プロセス管理プロセス環境

1プロセスの起動と終了

1.1プロセス

プログラムの開始->プログラムのロードとアドレスの割り当て->プログラムの終了

@ 1プログラムの開始:バイナリファイルの場合:

  1. ファイルがusr / binまたは/ binフォルダーのPATH環境変数で指定されたアドレスにある場合は、バイナリファイルの名前を直接入力します
  2. PATH環境変数の下にない場合は、プログラムのパスとプログラム名(たとえば、。/ hello)を使用する方法も受け入れられます。
  3. PATH環境変数の下にない場合にバイナリ名を直接入力してファイルを実行する場合は、PATH環境変数にプログラムパスを追加し、バイナリファイルをPATH環境変数のディレクトリに移動します。

@ 2プログラムのロード、アドレスの割り当て:

ロードの簡単なプロセスは次のとおりです。

  1. ターゲットファイルから十分なヘッダー情報を読み取り、必要なアドレススペースの量を確認します。
  2. アドレス空間を割り当てるターゲットコードの形式に独立したセグメントがある場合は、アドレス空間を独立したセグメントに分割します。
  3. プログラムをアドレス空間のセグメントに読み込みます。
  4. プログラムの最後のbssセグメントスペースを0で埋めます(仮想メモリシステム部門がこれを自動的に行う場合)。
  5. スタックを作成します(アーキテクチャで必要な場合)。
  6. プログラムパラメータ、環境変数などの動作情報を設定します。
  7. プログラムの実行を開始し、_startエントリからmainを見つけて、プログラムの実行を順次開始します。

@ 3プログラム出口:

終了するには3つの方法があります。

  1. プロセスは自発的に終了します。これは、exit関数とreturn関数に反映されます。終了するときは、プロセスによって割り当てられたリソース(アドレススペース、ファイル記述子など)を再利用する必要があります。オペレーティングシステムは、後で各リソースを処理します。    
  2. プロセスは終了するシグナルを受け取りました。この状況は非常に一般的であり、多くの場合、子プロセスでの親プロセスの終了プロセスです。この操作は、実際には親プロセスが終了シグナルを子プロセスに送信するものであり、子プロセスもシグナルの受信後に自発的に終了します。
  3. 例外の原因となった操作を実行した後、プロセスは終了しました。上記の両方のケースは、プログラムが予期したとおりに終了し、異常な操作はプログラムを準備せずに終了しました。現時点では、オペレーティングシステムはそのリソースを再利用しますが、これらのリソースの余波に対処できない場合があります。例外は、実際にはプロセスに送信される特別なシグナルですが、シグナルを送信するのはプロセスではなくオペレーティングシステム自体です。

1.2プロセス終了処理機能

Linux環境では、プロセスの終了時にユーザー定義関数のいくつかを呼び出すことができます。これらの関数は終了処理関数と呼ばれます。Linuxでは、このようなプロセスの終了処理関数を32個まで設定できると規定しています。Linuxでは、atexit関数を使用してプロセス終了処理関数を設定します。atexit関数のプロトタイプ:

#include <stdlib.h>
int atexit(void (*function)(void));

詳細については、linux関数リファレンスマニュアルを参照してください注:

  1. 関数の実行が成功すると0が返され、失敗するとゼロ以外の値が返されます。(atexit関数が失敗した場合、-1を返さないことに注意してください)
  2. 設定すると、プロセス終了処理関数の呼び出し順序が逆になります。(スタック構造と同様に、最後の呼び出しが最初に終了します)         
  3. 実際、プロセス終了機能は、プロセスの最後に実行される補助操作です。

2 Linuxプロセスのメモリ管理

2.1ビッグエンディアンとリトルエンディアン

一般的なPCはリトルエンディアン構造を使用していますが、サーバーは一般にビッグエンディアン構造を使用しています。このデータストレージの違いはオペレーティングシステムが原因ではなく、ビッグエンディアンとリトルエンディアンがCPUアーキテクチャに反映されています。一般に、プログラミングする場合は、ビッグエンディアンかリトルエンディアンかを判断し、操作してください。

リトルエンディアン:高い値と低い値は高いビットを格納し、低いアドレスは低いビットを格納します。ビッグエンディアン:リトルエンディアンの逆。

2.2コードセクション、データセクション、バッファセクション

@ 1コードセグメント:通常、書き込み操作は許可されておらず、属性は読み取り専用です。プログラムは、プログラムをアップグレードする場合を除いて、ほとんどの場合、コードセグメントを変更する必要はありません。サーバーの場合、プログラムを停止せずにコードセグメントの一部の置換を完了する必要があります。以前は、コードセグメントは一般的に直接記述され、直接置き換えられていましたが、リスクも大きかったです。現在、この問題は一般的に共有ライブラリを使用することで解決されています。

@ 2データセグメント:

  1. 初期化データセクション(.data):プログラムで初期値が明確に指定されているグローバル変数と静的変数が含まれています。
  2. ブロックストレージセグメント(.bss):このセグメントに保存されるデータは通常、明確な初期値を持たないグローバル変数と静的変数です。

@ 3 bssセクションの内容はプログラムファイルの一部ではありません。つまり、バイナリファイルには含まれていませんが、外部メモリに保存されています。システムは、メモリ内のbssセクションの一部の情報のみをマークします(初期化変数サイズ) 、属性など);プログラムの実行時にbssセクションのコンテンツを見つけるため。グローバル変数/静的変数自体に特定の値があり、この値が0 / NULLの場合、コンパイラーはその内容をデータセクションではなくbssセクションに書き込みます。

2.3スタックとヒープ

自動変数を保存するには3つの方法があります。

  1. bssセクション:静的ローカル変数
  2. レジスター:レジスター変数
  3. スタック:一般的な自動変数

プログラミングで最も一般的な間違いは、関数の戻り値としてローカル変数へのポインターを返すことです。ポインタが指すコンテンツはまだスタックフレームにあるため、関数は単にそのアドレスを返します。したがって、スタックフレームが別の関数によって上書きされた場合、返されたポインタが指すメモリ領域の値は無効になります。

ヒープ領域は通常、ユーザーアプリケーションを格納するためのメモリ領域であり、ヒープでの操作は多くの場合mallocです。スタックとヒープの場所は多くの場合相対的ですが、具体的な割り当てはプロセッサのストレージ構造によって異なり、ビッグエンディアンとリトルエンディアンの違いは似ています。

2.4定数ストレージ

単純な定数の場合、単純な変数の長さが固定されているため、コードセグメントに格納されます。これにより、命令のフェッチ速度が向上し、プログラムの効率も向上します。しかし、それは文字列などの複雑な定数であり、その長さは不定です。文字列がコードセグメントに格納されている場合、コードセグメントは非常に大きくなり、コードをバッファ処理に読み込むことはプロセッサにとって助長されず、プログラムに大きな影響を与えます。実行効率。したがって、最後に、文字列を格納するための個別のセグメントが格納されます。

2.5動的メモリ管理

システムはmem_control_block構造体を使用して、割り当てられたすべてのメモリブロックを管理します。構造体は次のとおりです。

    struct mem_control_block{
      int is_available;     //该块是否可用
      int size;               //块的大小
    }

この構造により、malloc関数を簡単に実装でき、全体のプロセスは次のとおりです。

malloc関数は、ユーザーが割り当てるバイト数を「メモリ制御ブロック」のサイズに最初に追加して、割り当てる必要がある実際のバイト数を取得します。

  1. 次に、ヒープ内のすべてのメモリブロックを順番に繰り返します。ブロックが使用可能であり、実際に必要なバイト数より大きい場合、メモリブロックの最初のアドレスが返され、ブロックが使用可能に設定されます。それ以外の場合は、次のメモリブロックを試します。
  2. すべてのメモリブロックが条件を満たさない場合、sbrk関数が呼び出され(sbrk関数が失敗した場合、システムに使用可能なメモリがなく、malloc関数はNULLを返します)、メモリのブロックはオペレーティングシステムを通じて割り当てられます。malloc関数は、ヒープ内のこのメモリを拡張します。これは、ヒープの増加と同等です。
  3. このメモリブロックの「メモリ制御構造」をスキップして、メモリの最後のブロックの最後のアドレスをリセットします。

フリー機能に関する注意事項:フリー機能の主な役割は、メモリ制御ブロックを使用可能に設定することです。malloc関数が次に呼び出されたときに、メモリブロックを割り当て可能なブロックとして割り当てることができます。したがって、free関数を呼び出した後、メモリブロックの内容はすぐには消えませんが、これはこの内容がオペレーティングシステムによって保護されなくなったためです。有効時間もランダムです。


3シェル環境

コマンドラインパラメータと環境変数の両方が親プロセスから取得され、それらはさまざまな方法で取得されます。
コマンドラインパラメーターはメイン関数のパラメーターとして新しいプロセスに転送され、環境変数は新しいプロセスによってグローバル変数として使用されます。

3.1コマンドラインパラメーターとアプリケーション

argc:命令行参数的个数;
argv:指向参数的各个指针所构成的数组;

ここでのArgv [0]は、実行可能プログラムのファイル名だけでなく、実行可能プログラムのパス名全体を表します。(パス名からファイル名を取得するには、対応する文字処理が必要です); argv [argc]はNULLでなければなりません。

3.2環境変数

各プログラムには環境変数テーブルがあり、コマンドラインパラメータと同様に、環境変数テーブルもポインタの配列です。対応するヘッダーファイルをインクルードし、プログラムにextern char ** environを書き込みます。environ[i]を読み取り、environ [i] = NULLになるまでループすることにより、環境変数テーブル、つまり各環境変数の値を取得できます。

注:他のプロセスには影響しないため、このプロセスで環境変数を変更しても意味がありません。

環境変数の設定、取得、削除のプロトタイプは次のとおりです。

#include <stdlib.h>
char *getenv(const char *name);//获取环境变量,成功则返回环境变量的值,失败则返回NULL。
int put(char* str);            //将 name==value的字符串放进环境表,如果原来有值则覆盖。
int setenv(const char *name, const char *value, int overwrite);//设置环境变量,这里第3个参数rewrite的值为0则:不修改原来的值;非0值则:修改原来的值。
int unsetenv(const char *name);//删除一个环境变量的值,成功返回0,失败返回-1。
int clearenv();         //此函数会将整个environ这个指针置为NULL,成功返回0,失败返回-1。

詳細については、linux関数リファレンスマニュアルを参照してくださいこれらの環境変数を操作する上記の関数は、自身のプロセスと子プロセスにのみ影響し、親プロセスには影響しません。

3.3プロセスの終了ステータスを取得する

$?これは、Linuxシェルの組み込み変数であり、最後に実行されたプログラムの戻り値を保持します。3つの状況があります。

  1. プログラムのメイン関数が終了し、メイン関数の戻り値が$?に保存されます。
  2. exit関数を呼び出して、プログラムの実行中に操作を終了します。$?出口関数のパラメーターを保存します。
  3. プログラムは異常終了し、異常なエラーのエラー番号が$?に保存されます。

注:

  1. プログラムが正しく実行されない場合、$?組み込み変数の値は1です。そのため、コードを記述するときにコードに問題がない場合は、1を返さないでください(exit(1)またはreturn(1))。不要な混乱を招かないように。
  2. メイン関数が指定された値を返さない場合、$?の値はランダムではありません。覚えておいてください!
  3. $に組み込まれた変数の値は?プロセスが終了した後、Linuxシェルでは実際にはeaxレジスターの値です(X86アーキテクチャーでのみ)、Linuxシステムはeaxを使用してこのアーキテクチャーの各関数を保存していることがわかります。戻り値。この値は、システムによって異なります。

3.4 errnoを使用したプログラムのデバッグ

プログラムをデバッグする方法はいくつかあります。

  • デバッガーを使用する
  • プログラムで出力関数を直接使用して、デバッグ情報を出力します
  • 標準エラーファイルを表示する
  • プログラムが異常な場合に書き込まれるログ

Linuxでシステムコールを実行すると、いくつかのエラーが発生します。これらのシステムコールの戻り値を確認するだけでは不十分です。開発者は、より詳細な情報を必要とすることがよくあります。C言語はグローバル変数errnoを提供し、それを使用すると、ヘッダーファイル<errno.h>が追加されます。このグローバル変数は、戻り値情報が不十分であるという欠点を補います。
errnoが0の場合はエラーなし、エラーが発生した場合はエラー番号が出力されます。これを使用する場合は、グローバル変数であるため、最初に0にクリアする必要があります。

3.5出力エラーの原因

errnoは単なる整数値です。知るためにテーブルを参照する必要があります。エラーをより簡単に見つけるには、2つの関数を使用できます。これらの2つの関数は、エラー番号を情報変換に提供します:strerrorおよびperror。strerror関数のプロトタイプ:

#include <string.h>
char *strerror(int errnum);

詳細については、linux関数リファレンスマニュアルを参照してくださいperror関数のプロトタイプ:

#include <stdio.h>
void perror(const char *s);

詳細については、linux関数リファレンスマニュアルを参照してください

注:この文字列に「\ n」を追加しないでください。システムが自動的に追加します。これの利点は、渡されるパラメーターが1つ少ないことです。欠点は、perrorがバッファーされないこと、および副作用のある関数であることです。その関数は、関数呼び出しに最も近いシステム関数のエラーの原因を出力することです。    

4グローバルジャンプ

gotoステートメントは、関数内でのみジャンプできるステートメントです。つまり、そのようなジャンプはローカルであり、グローバルジャンプの場合、gotoステートメントは無力です。グローバルジャンプを行うには、そのようなグローバルジャンプステートメントが必要です。

Linuxでは、setjump関数とlongjump関数を使用してグローバルジャンプを実行します。このジャンプのアイデアは、最初にジャンプポイントを設定し、現在の関数呼び出しスタックフレームを保存することです。プログラムがグローバルジャンプを実行してジャンプポイントに戻ると、レポートのスタックフレームを使用して既存のスタックフレームが上書きされ、関数スタックフレームが復元されます。Linuxでは、jmp_buf構造体を使用して現在のスタックフレームを保存し、ジャンプ時にスタックフレームを構造体に復元します。Linuxでは、setjmp関数を使用してグローバルジャンプポイントを設定します。関数のプロトタイプは次のとおりです。

#include <setjmp.h>
int setjmp(jmp_buf env);

詳細については、linux関数リファレンスマニュアルを参照してくださいLinuxでは、longjmp関数を使用してグローバルジャンプを実行しているようです。関数のプロトタイプは次のとおりです。

#include <setjmp.h>
void longjmp(jmp_buf env, int val);

詳細については、linux関数リファレンスマニュアルを参照してくださいグローバルジャンプを使用すると、プログラムの構造がより適切に制御され、コードがコンパクトになります。グローバルジャンプの使用は比較的高度なアプリケーションであり、グローバルジャンプはオペレーティングシステムの支援を必要としますが、ローカルジャンプはそうではありません。ローカルジャンプは言語レベルで実装されており、gotoはCでは単なるキーワードであることに注意してください。

元の記事289件を公開 賞賛された47件 30,000回以上の閲覧

おすすめ

転載: blog.csdn.net/vviccc/article/details/105152197