Linux Cシステムプログラミング(07)プロセス管理プロセス制御

1プロセス識別子

1.1実際のユーザー/ユーザーグループ、有効なユーザー/ユーザーグループ

Linux / Unixのプロセスでは、複数のユーザーIDとユーザーグループIDが含まれます。

  1. 実際のユーザーIDと実際のユーザーグループID:私が誰であるかを特定します(これは変な哲学的問題であり、哲学者を殺すのは難しいと言われています)。つまり、ログインユーザーのuidとgidです。たとえば、私のLinuxはtaskillerでログインします。Linuxで実行されているすべてのコマンドの実際のユーザーIDはtaskillerのuidであり、実際のユーザーグループIDはtaskillerのgidです(idコマンドを使用して表示できます) 。
  2. 有効なユーザーIDと有効なユーザーグループID:プロセスは、リソースへのアクセスを決定するために使用されます。一般に、実効ユーザーIDは実際のユーザーIDと等しく、実効ユーザーグループIDは実際のユーザーグループIDと等しくなります。設定ユーザーID(SUID)ビットが設定されている場合、実効ユーザーIDは、実際のユーザーIDではなく、ファイル所有者のuidに等しくなります。同様に、設定ユーザーグループID(SGID)ビットが設定されている場合、実効ユーザーグループIDは、実際のユーザーグループIDではなく、ファイル所有者のgidと同じです。

1.2プロセスID

プロセスの基本的な属性は各人のID番号に似ており、プロセスIDによってプロセスを正確に判別でき、プログラムに複数のプロセス識別子を対応させることができます。

1.3プロセス中の重要なID値

各プロセスには6つの重要なID値があります。つまり、プロセスID、親プロセスID、実効ユーザーID、実効グループID、実際のユーザーID、実際のユーザーグループIDです。これらの6つのIDは、カーネルのデータ構造に格納されます。ときどきユーザーがこれらのIDを必要とします。Linuxでは、getpid関数とgetppid関数を使用して、プロセスIDとプロセスの親プロセスIDを取得します。関数のプロトタイプは次のとおりです。

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);

詳細については、linux関数リファレンスマニュアルを参照してくださいLinuxでは、getuid関数とgeteuid関数を使用して、プロセスの実際のユーザーと有効なユーザーを取得します。関数のプロトタイプは次のとおりです。

#include <unistd.h>
#include <sys/types.h>
uid_t getuid(void);
uid_t geteuid(void);

詳細については、linux関数リファレンスマニュアルを参照してくださいLinuxでは、getgid関数とgetegid関数を使用して、プロセスの実際のユーザーグループIDと実効ユーザーグループIDを取得します。関数のプロトタイプは次のとおりです。

#include <unistd.h>
#include <sys/types.h>
gid_t getgid(void);
gid_t getegid(void);

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

  1. プロセスIDと親プロセスIDの2つの識別子は変更できません。他の4つのIDは適切な状況で変更できます。
  2. 一般的なプロセスでは、実際のユーザーIDと実効ユーザーIDは同じですが、一部の特別な場合のみ異なります。    

2プロセス操作

2.1 fork関数はプロセスを作成します

プロセスはシステムの基本的な実行単位です。Linuxシステムでは、任意のユーザープロセスが子プロセスを作成できます。作成後、子プロセスはシステムに存在し、親プロセスから独立しています。子プロセスはシステムスケジューリングを受け入れ、システムリソースを割り当てることができます。システムは、その存在を検出して、親プロセスと同じ力を与えることもできます。(注:Linuxでは、すべてのプロセスはプロセス番号0を除く他のプロセスによって作成されます)
Linuxでは、fork関数を使用して新しいプロセスを作成します。fork関数のプロトタイプ:

#include <unistd.h>
pid_t fork(void);
函数执行成功有两个返回值;为0,表示子进程;为正数,表示父进程。失败则返回-1。

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

  1. 通常の状況では、プログラムが実行されている場合、親プロセスまたは子プロセスが最初に実行されることは保証されず、プロセスが実行されていることを確認するために追加の操作を実行する必要があります。
  2. fork関数の場合、親プロセスと子プロセスはコードセグメントを共有しますが、データセグメントやスタックセグメントなどの他のリソースは、親プロセスから完全にコピーされます。
  3. 子プロセスが親プロセスを継承すると、ファイルロック、未処理のアラーム信号、保留中の信号は継承されません。
  4. 現在のLinuxカーネルは、子プロセスが親プロセスの前にリソースをコピーするときにfork関数を実装することがよくあります。子プロセスがこれらのコンテンツを変更すると、コピーが発生し、カーネルは子プロセスにプロセススペースを割り当てて親プロセスのコンテンツをコピーします、次の操作に進みます。これは実際には、書き込み時操作の重要な現れです。

フォーク機能のエラー状況:

  1. システム内のプロセス数が、システムで指定された制限を超えています。    
  2. fork関数を呼び出すユーザープロセスが多すぎます。

2.2 vforkがプロセスを作成する

Linuxは、fork関数の関数と同様の関数vfork関数を提供します。それらの違いは次のとおりです。

  1. forkは、親プロセスをコピーする子プロセスのデータセグメントとコードセグメントです。vforkは、子プロセスと親プロセスで共有されるデータセグメントです。 
  2. forkは親プロセスと子プロセスの実行順序が不明確です。vforkは、子プロセスが最初に実行されることを保証します。execまたはexitを呼び出す前に、データは親プロセスと共有されます。execまたはexitを呼び出した後、親プロセスは実行するようにスケジュールされます。 
  3. Vforkは、子プロセスが最初に実行されるようにし、親プロセスは、execまたはexitを呼び出した後に実行されるようにスケジュールされる場合があります。子プロセスがこれら2つの関数を呼び出す前に親プロセスのさらなるアクションに依存している場合、デッドロックが発生します。

vfork関数のプロトタイプ:

#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
函数执行成功有两个返回值;为0,表示子进程;为正数,表示父进程。失败则返回-1。

詳細については、linux関数リファレンスマニュアルを参照してください注:vfork関数の場合、通常はmain以外の関数を呼び出さないでください。その理由は、子プロセスが親プロセスの前に実行され、スタックフレームが上書きされるためです。最後に、親プロセスが動作すると、セグメンテーション違反が発生します。つまり、親プロセスに対する子プロセスの影響は非常に大きくなります。

2.3プロセスを終了する

Linuxでプロセスを終了するには、通常、exit関数を使用します。出口関数のプロトタイプ:

#include <unistd.h>
void exit(int status);
参数status:表示的是进程退出的状态,这个状态值是一个整型。在shell中可以检查到退出的状态。正常退出,exit中的参数为0,异常退出为非0。

詳細については、linux関数リファレンスマニュアルを参照してくださいerrno変数をパラメーターとして使用して出口関数に渡すことができるため、プログラムの終了後にプログラムの終了の理由を確認できます。つまり、シェルでエラーの原因を特定できます。    

出口関数は、実際にはLinuxシステムによって呼び出された_exit関数をカプセル化します。2つの主な違いは、出口関数がユーザースペースでアフターケア作業(ディスクへのコンテンツの同期、ユーザーバッファーのクリアなど)を実行し、カーネルを入力して解放することです。ユーザープロセスのアドレス空間。_exit関数は、直接カーネルに入り、ユーザーのアドレス空間を解放します。ユーザー空間のバッファーの内容はすべて失われます。

2.4プロセス所有者の設定

各プロセスには、実際のユーザーIDと実効ユーザーIDの2つのユーザーIDがあります。Linuxでsetuidを使用して、プロセスの実際のユーザーIDと実効ユーザーIDを変更します。setuid関数のプロトタイプ:

#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid);
参数uid:改变后的新用户ID;函数执行成功返回0,失败返回-1。

詳細については、linux関数リファレンスマニュアルを参照してくださいプロセスの実際のユーザーIDと実効ユーザーIDを変更できるのは、2種類のユーザーだけです。ユーザーと、プロセスの実際のユーザーIDに等しいユーザーをフォローします。
一般的な状況では、プロセスには特定の権限が必要です。そのような権限を持つユーザーIDに実効ユーザーIDを設定します。プロセスがそのような権限を必要としない場合、プロセスはその実効ユーザーIDを復元して権限を復元します。関数seteuid(uid_t euid)の場合、有効なユーザーIDのみが変更されます。同じ一連の関数には、グループIDが影響を受けることを除いて、それぞれsetuid関数とseteuid関数に似ているsetgid関数とsetegid関数もあります。

2.5複数のプロセスのデバッグ

gdbでマルチプロセスをデバッグする方法は2つあります。
@ 1トラッキングストリームを設定します。設定方法は次のとおりです
。set follow-fork-mode [親|子]
追跡する1つのプロセスを選択すると、他のプロセスは影響を受けません。次に、サブプロセスコードにブレークポイントを設定します。
fork関数の後にプロセスのテストを切断する場合は、次のコマンドを使用します。

    set detach-on-fork [on,off]
    #若选中on,则断开调试follow-fork-mode指定的进程。
    #若选中off,gdb将控制父进程和子进程

@ 2 attachコマンドを使用する:gdbデバッガーのattachコマンドは、すでに実行中のプログラムをデバッグできますプロセスがfork関数を呼び出した後、attachコマンドを使用してサブプロセスをデバッグできます。前提は子プロセスのプロセスIDを知っており、子プロセスはデバッグの開始を待つことができるため、attachコマンドを使用するときに補助コードを追加します。


3実行手順

3.1 execファミリ関数

exec関数を使用して、Linux環境で新しいプログラムを実行します。この関数は、ファイルシステムで指定されたパスにあるファイルを検索し、ファイルの内容をexec関数のアドレススペースにコピーして、元のプロセスの内容を置き換えます。プロセスのコードセグメントとデータセグメントが置き換えられたことを除いて、プロセス空間の内容。(注:exec関数は新しいプロセスを作成しません。プロセスの内容は変更されていますが、プロセスIDは変更されておらず、プロセスのままです。)
6つのexecファミリー関数があります。

  1. 接尾辞lはリストを示し、実行プログラムのコマンドラインパラメーターがリストで提供され、NULLで終了することを示しますが、パラメーターの数は制限されません。パラメータのコンマ区切りリストを受け取ります。
  2. ベクトルには接尾辞vが付き、実行プログラムのコマンドラインパラメータが2次元配列の形式で提供されることを示します。NULLで終わる文字列の配列へのポインタを受け取ります。
  3. 接尾辞eは環境を表し、新しいプログラムに渡される環境変数のリストを表します。このリストは2次元配列で、各行は環境変数です。
  4. 末尾がpのexec関数は、最初のパラメーターが完全なパス名ではなくプログラム名であることを示します。これには、PATH環境変数とこのパラメーターを完全なパスに組み合わせる必要があります。

execファミリー関数のプロトタイプは次のとおりです。

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *filename, char *const argv[], char *const envp[]);

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

3.2プログラムで
シェルコマンドを実行するLinuxでシステムコマンドを使用してシェルコマンドを呼び出します。システム関数のプロトタイプは次のとおりです。

#include <stdlib.h>
int system(const char *command);

詳細については、linux関数リファレンスマニュアルを参照してくださいパラメータコマンドは実行されるコマンドです。関数の戻り値はより複雑です。実際、システム関数は、fork、exec、およびwaitpidの3つのシステムコールをカプセル化します。戻り値についても、これらのシステムコールの状況に応じて説明する必要があります。

  1. fork関数とwaitpid関数が実行に失敗した場合、システム関数は-1を返します。
  2. exec関数の実行に失敗した場合、関数はファイルが実行可能ではないことを返します。
  3. 3つの関数がすべて正常に実行されると、システム関数は実行プログラムの終了状態に戻ります。
  4. パラメータコマンドの値がNULLの場合、システム関数は1を返します。実際、これはシステムがシステム関数をサポートしているかどうかをテストするために使用できます。

システム関数を使用するには、要件分析を注意深く検討する必要があります。一般に、システムには次の利点があります。

  1. システム関数は、エラー処理操作を追加します。
  2. システム関数は信号処理操作を追加します
  3. システム関数は待機関数を呼び出して、ゾンビプロセスがないことを確認します。

4関係演算

子プロセスの場合、終了時の状態は親プロセスが取得できます。プロセス起動情報を取得する操作をリレーショナル操作といいます。Linuxカーネルは、プロセスID、プロセスの終了ステータス、プロセス統計など、終了した子プロセスごとに一定量の情報を保存します。この情報は親プロセスによって取得され、それに応じて処理されます

4.1プロセスの終了を待つ2つの関数

Linuxでwait関数とwaitpid関数を使用して、子プロセスの統計を取得します。waitおよびwaitpid関数のプロトタイプ:

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

詳細については、linux関数リファレンスマニュアルを参照してください
waitpidおよびwait関数と比較して、次の3つの点があります。

  1. waitpid関数は、子プロセスを指定できます。
  2. waitpid関数は、ブロックせずにプロセスを待機できます。
  3. waitpid関数はジョブ制御をサポートしています。

4.2ゾンビプロセス

子プロセスが終了すると、プロセスの終了ステータス情報がカーネルに格納されます。このとき、親プロセスは待機関数を呼び出さずに処理します。子プロセスのプロセスIDもシステムプロセスリストに保存されます。このときのプロセスは呼び出されますゾンビプロセス。ゾンビプロセスはシステムにとって大きな脅威であり、システムリソースを消費しますが、何もしません。ゾンビプロセスを作成するには、最初にfork関数を呼び出します。親プロセスには待機関数は必​​要ありません。ゾンビプロセスを表示すると、Zと表示されます。

ゾンビプロセスを解決する方法:

  1. 親プロセスは、waitやwaitpidなどの関数を介して子プロセスが終了するのを待ちます。これにより、親プロセスがハングします。
  2. 親プロセスがビジー状態の場合は、シグナル関数を使用してSIGCHLDのハンドラーをインストールできます。これは、子プロセスが終了した後、親プロセスがシグナルを受け取り、ハンドラーでwaitを呼び出してリサイクルできるためです。
  3. 親プロセスが子プロセスがいつ終了するかを気にしない場合は、シグナル(SIGCHLD、SIG_IGN)を使用して、子プロセスの終了に関心がないことをカーネルに通知できます。その後、子プロセスが終了すると、カーネルはリサイクルして親プロセスにシグナルを送信しなくなります。 。
  4. 2回フォークし、親プロセスは子プロセスをフォークしてから作業を続けます。子プロセスはグランドプロセスをフォークして終了し、グランドプロセスはinitに引き継がれます。グランドプロセスが終了すると、initはリサイクルされます。ただし、子プロセスのリサイクルは自分で行う必要があります。

4.3出力プロセス統計

wait3関数とwait4関数は、基本的にwait関数とwaitpid関数と同等ですが、wait3関数とwait4関数もより詳細な情報を取得できる点が異なります。この情報のほとんどはカーネルに関するものであり、構造を通じてユーザーに返されます。wait3およびwait4関数のプロトタイプは次のとおりです。

#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
pid_t wait3(int *status, int options,struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);

詳細については、linux関数リファレンスマニュアルを参照してくださいより詳細な情報を取得するには、値の結果パラメーターを使用する必要があります。対応する構造はrusageです。     

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

おすすめ

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