技術共有

高同時実行リアクター サーバー [中]

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

4. プロセス制御とプロセス同期

1.信号

1.1 信号の基本概念

シグナル (シグナル) は、プロセス間でメッセージを送信する方法です。イベントの発生をプロセスに通知するために使用されますが、プロセスにデータを渡すことはできません。

シェルでシグナルが生成される理由は数多くあります。killそしてkillall信号を送信するコマンド:

kill -信号的类型 进程编号
killall -信号的类型 进程名
  • 1
  • 2

1.2 信号の種類

信号名信号値デフォルトの処理アクションシグナリングの理由
SIGHUP1端末がハングするか、制御プロセスが終了する
シギント2キーボード割り込み Ctrl+c
終了3キーボードのエスケープキーが押されたとき
シギル4違法な指導
シグトラップ5ブレークポイント命令
シガバート6abort(3) によって発行されたアボート信号
シグバス7バスエラー
SIGFPE8浮動小数点例外
シグナルキル9kill -9 はプロセスを強制終了します。このシグナルは捕捉および無視できません。
SIGUSR110ユーザー定義信号 1
SIGSEGV11無効なメモリ参照 (範囲外の配列、NULL ポインタ操作)
SIGUSR212ユーザー定義信号 2
SIGPIPE の13読み取りプロセスを行わずにデータをパイプに書き込みます
シガルム14目覚まし時計信号、alarm() 関数によって送信される信号
シグナルターム15終了信号、デフォルトで送信される信号
SIGSTKFLT16スタックエラー
シグナル17B子プロセスの終了時に発行されます
シグナル継続18停止したプロセスを再開する
シグナルストップ19プロセスの停止
シグナルTP20ターミナルでストップキーを押す
署名21バックグラウンドプロセスによる端末の読み取り要求
シグトゥ22バックグラウンドプロセスが端末への書き込みを要求します
シグルグ23B緊急状態の検出 (ソケット)
SIGXCPU24CPU時間制限を超えました
SIGGFSZ の25ファイルサイズ制限を超えました
信号26仮想クロック信号
シグプロフ27クロック信号を解析する
シグウィンチ28Bウィンドウサイズの変更
シグナルポール29Bポーリング(Sys V)
SIGPWR30停電
シグシス31不正なシステムコール

A のデフォルトのアクションはプロセスを終了することです。

B のデフォルトのアクションは、この信号を無視することです。

C のデフォルトのアクションは、プロセスを終了し、カーネル イメージ ダンプを実行することです。

D のデフォルトのアクションはプロセスを停止することですが、停止状態に入ったプログラムは実行を継続できます。

1.3 信号処理

プロセスがシグナルを処理するには 3 つの方法があります。

  1. このシグナルの処理には、システムのデフォルトの操作が使用されます。ほとんどのシグナルのデフォルトの操作は、プロセスを終了することです。
  2. 信号受信後、割り込み処理関数を設定します。
  3. 信号を無視し、何も起こらなかったかのように信号に対して何もしません。

signal()関数は、プログラムが信号を処理する方法を設定できます。

関数宣言:

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 1
  • 2
  • 3
  • 4

パラメータの説明:

  • sig:キャプチャする信号を指定します。
  • func : 信号処理関数へのポインタ。処理関数は、キャプチャされた信号番号である整数パラメータを受け取る必要があります。
  1. SIG_DFL :SIG_DFL マクロは、デフォルトの信号処理方法を表します。使用SIG_DFLとしてsignal関数の 2 番目のパラメーターは、システムのデフォルトの処理方法が信号に使用されることを示します。
  2. SIG_IGN :SIG_IGN マクロは信号を無視することを意味します。使用SIG_IGNとしてsignal関数の 2 番目のパラメーターは、プロセスがシグナルを受信したときにそれを無視し、処理を実行しないことを示します。これにより、特定の状況でプロセスが予期せず終了したり中断されたりするのを防ぐことができます。
  3. SIG_ERR:SIG_ERRマクロはエラーを示すために使用されます。それはそうではありませんsignal関数の 2 番目のパラメータは代わりに次のように使用されます。signal関数の戻り値は、呼び出しが失敗したことを示します。もしsignal関数の呼び出しが失敗した場合は、戻り値が返されます。SIG_ERR 。これは通常、検出と処理に使用されますsignal関数呼び出しでエラーが発生しました。

画像-20240709113614147

画像-20240709113230874

画像-20240709113240944

1.4 シグナルの用途は何ですか?

サービス プログラムはバックグラウンドで実行されており、プロセスを強制終了すると突然終了し、その後の処理が行われないため、サービス プログラムを強制終了することはお勧めできません。

サービス プログラムにシグナルを送信すると、シグナルを受信した後、サービス プログラムは関数を呼び出し、関数内にアフターマス コードを書き込むため、プログラムは計画的に終了できます。

サービス プログラムに 0 のシグナルを送信すると、プログラムが生きているかどうかを検出できます。

画像-20240709135336848

1.5 信号の送信

Linux オペレーティング システムが提供するのは、 kill そしてkillall コマンドはプログラムにシグナルを送信します。プログラム内では、次のコマンドを使用できます。kill() ライブラリ関数は他​​のプロセスにシグナルを送信します。

関数宣言:

int kill(pid_t pid, int sig);
  • 1

kill() 関数はパラメータを受け取りますsig 指定された信号がパラメータに渡されますpid 指定されたプロセス。

パラメータ pid いくつかの状況があります。

  1. pid > 0 シグナルを次のようにプロセスに渡しますpid プロセス。
  2. pid = 0 現在のプロセスと同じプロセス グループ内のすべてのプロセスにシグナルを渡します。これは、親プロセスが子プロセスにシグナルを送信するためによく使用されます。この動作はシステムの実装に依存することに注意してください。
  3. pid < -1 のプロセス グループ ID にシグナルを渡します。|pid| すべてのプロセスの。
  4. pid = -1 シグナルを送信する許可を持つすべてのプロセスにシグナルを渡しますが、シグナルを送信したプロセスには渡しません。

2. プロセスの終了

プロセスを終了するには 8 つの方法があり、そのうち 5 つは通常の終了です。

  1. 存在する main() 機能用return 戻る;
  2. 任意の関数で呼び出される exit() 関数;
  3. 任意の関数で呼び出される _exit() または_Exit() 関数;
  4. 最後のスレッドは、そのスタートアップ ルーチン (スレッドのメイン関数) から次のように開始されます。 return 戻る;
  5. 最後のスレッドで呼び出された pthread_exit() 戻る;

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

  1. 移行 abort() 関数の中止。
  2. 信号が受信されます。
  3. 最後のスレッドがキャンセル要求に応答します。

2.1 プロセスの終了状態

存在する main() 関数では、return 終了ステータスでない場合、戻り値は終了ステータスです。return 声明または電話exit()の場合、プロセスの終了ステータスは 0 になります。

シェルで、プロセスの終了ステータスを表示します。

echo $?
  • 1

プロセスを正常に終了する3つの関数(exit() そして_Exit() ISO Cで規定されており、_exit() POSIX で指定されています):

void exit(int status);
void _exit(int status);
void _Exit(int status);
  • 1
  • 2
  • 3

status プロセスの終了ステータス。

画像-20240709143530327

画像-20240709143615950

2.2 リソース解放の問題

  • return 関数が戻るときに、ローカル オブジェクトのデストラクターが呼び出されるように指定します。main() 機能中return グローバル オブジェクトのデストラクターも呼び出されます。
  • exit() プロセスを終了することを示します。ローカル オブジェクトのデストラクターは呼び出されず、グローバル オブジェクトのデストラクターのみが呼び出されます。
  • _exit() そして_Exit() 直接終了すると、クリーンアップ作業は実行されません。

2.3 プロセスの終了機能

プロセスは利用可能です atexit() 機能登録により終了する機能(最大32個)は、これらの機能が終了します。exit() 自動的に呼び出されます。

int atexit(void (*function)(void));
  • 1

exit() 終了関数が呼び出される順序は登録時とは逆になります。

画像-20240709143824286

画像-20240709143830549

3. 実行可能プログラムを呼び出す

3.1 system() 関数

system()この関数は、プログラムを実行するための簡単なメソッドを提供し、実行する必要があるプログラムとパラメーターを文字列として渡します。system()ただ機能するだけ。

関数宣言:

int system(const char * string);
  • 1

system()さらに厄介なのは関数の戻り値です。

  1. 実行したプログラムが存在しない場合は、system()関数はゼロ以外を返します。
  2. プログラムの実行が成功し、実行したプログラムの実行ステータスが0の場合、system()この関数は 0 を返します。
  3. プログラムの実行が成功し、実行したプログラムの終了ステータスが0以外の場合、system()関数はゼロ以外を返します。

3.2 実行関数ファミリー

exec関数ファミリーは、プロセス内でプログラム (バイナリまたはシェル スクリプト) を呼び出す別の方法を提供します。

exec関数ファミリーは次のように宣言されます。

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 execvpe(const char *file, char *const argv[], char *const envp[]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

知らせ

  1. プログラムの実行が失敗した場合は、直接 -1 が返され、失敗の理由が格納されます。errno真ん中。
  2. 新しいプロセスのプロセス番号は元のプロセスのプロセス番号と同じですが、新しいプロセスは元のプロセスのコード セグメント、データ セグメント、およびスタックを置き換えます。
  3. 実行が成功した場合、関数はメイン プログラムで正常に呼び出されても戻りません。execその後、呼び出されたプログラムが呼び出し元のプログラムを置き換えます。exec関数の後のコードは実行されません。
  4. 実際の開発では、最も一般的に使用されるのはexecl()そしてexecv()、その他はほとんど使用されません。

4. プロセスを作成する

4.1 Linux プロセス 0、1、2

Linux システム全体のすべてのプロセスはツリー構造になっています。

  • **プロセス No.0 (システム プロセス)** は、プロセス No.1 とプロセス No.2 を作成したすべてのプロセスの祖先です。
  • **プロセス No. 1 (systemd)** は、カーネルの初期化とシステム構成の実行を担当します。
  • **プロセス No. 2 (kthreadd)** は、すべてのカーネル スレッドのスケジューリングと管理を担当します。

使用pstree次のコマンドを使用してプロセス ツリーを表示できます。

pstree -p 进程编号
  • 1

4.2 プロセスの識別

各プロセスには、負でない整数で表される一意のプロセス ID があります。プロセス ID は一意ですが、再利用できます。プロセスが終了すると、そのプロセス ID が再利用の候補になります。 Linux は遅延再利用アルゴリズムを使用しているため、新しく作成されたプロセスの ID は、最近終了したプロセスで使用されていた ID とは異なります。これにより、新しいプロセスが同じ ID を使用して終了したプロセスと間違われるのを防ぎます。

プロセスIDを取得する関数:

pid_t getpid(void);    // 获取当前进程的ID。
pid_t getppid(void);   // 获取父进程的ID。
  • 1
  • 2

4.3 fork()関数

既存のプロセスは呼び出すことができますfork()関数は新しいプロセスを作成します。

関数宣言:

pid_t fork(void);
  • 1

によるfork()新しく作成されたプロセスを子プロセスと呼びます。

fork()この関数は 1 回呼び出されますが、2 回戻ります。 2 つの戻り値の違いは、子プロセスの戻り値が 0 であるのに対し、親プロセスの戻り値は新しく作成された子プロセスのプロセス ID であることです。

子プロセスと親プロセスは実行を継続します。fork()その後のコードは、子プロセスは親プロセスのコピーです。子プロセスには、親プロセスのデータ領域、ヒープ、およびスタックのコピーがあります (注: 子プロセスはコピーを所有し、親プロセスと共有されません)。

fork()それ以降、親プロセスと子プロセスの実行順序は不定になります。

画像-20240709221535371

画像-20240709221546617

4.4 fork() の 2 つの使用法

  1. 親プロセスは自分自身をコピーしようとし、親プロセスと子プロセスはそれぞれ異なるコードを実行します。この使用法は、ネットワーク サービス プログラムで非常に一般的です。親プロセスは、クライアントの接続要求を待機し、要求が到着すると呼び出します。fork()、子プロセスにこれらのリクエストを処理させ、親プロセスは次の接続リクエストを待ち続けます。
  2. プロセスは別のプログラムを実行しようとしています。この使用法はシェルでは非常に一般的で、子プロセスは次から始まります。fork()帰ったらすぐに電話exec

4.5 共有ファイル

fork()1 つの特徴は、親プロセスで開かれたファイル記述子が子プロセスにコピーされ、親プロセスと子プロセスが同じファイル オフセットを共有することです。

親プロセスと子プロセスが、いかなる同期も行わずに、同じ記述子が指すファイルに書き込む場合、それらの出力が互いに混合される可能性があります。

画像-20240709222929369

画像-20240709222803641

この時点では、データが 100,000 行しかないことがわかります。

画像-20240709222853769

画像-20240709223236254

この時点で 200,000 行のデータが存在するはずですが、ファイルの書き込み操作がアトミックではないため、2 つのプロセスが同時にファイルの異なる部分を書き込もうとする可能性があります。データが相互に干渉し合うため、書き込みが失敗します。

4.6 vfork() 関数

vfork()関数呼び出しと戻り値は同じですfork()同じですが、セマンティクスが異なります。

vfork()この関数は、目的が次のような新しいプロセスを作成するために使用されます。exec子プロセスがすぐに呼び出しを行うため、親プロセスのアドレス空間をコピーしない新しいプログラムexec , そのため、親プロセスのアドレス空間は使用されません。子プロセスが親プロセスのアドレス空間を使用すると、不明な結果が発生する可能性があります。

vfork()そしてfork()もう 1 つの違いは次のとおりです。vfork()子プロセスが最初に実行されていることを確認し、子プロセスで呼び出します。execまたはexitその後、親プロセスが動作を再開します。

5. ゾンビプロセス

オペレーティング システムでは、ゾンビ プロセスとは、終了したものの親プロセスが終了ステータスをまだ読み取っていない子プロセスを指します。ゾンビ プロセスは実行されていませんが、プロセス テーブル内のエントリを占有しているため、親プロセスがこの情報を読み取るまで、カーネルはプロセスの終了ステータス情報 (プロセス ID、終了ステータスなど) を保存できます。

5.1 ゾンビプロセスの原因

親プロセスが子プロセスより前に終了した場合、子プロセスはプロセス 1 によってホストされます (これは、プロセスをバックグラウンドで実行させる方法でもあります)。

子プロセスが親プロセスより先に終了し、親プロセスが子プロセスの終了情報を処理しない場合、子プロセスはゾンビプロセスになります。

5.2 ゾンビプロセスの害

カーネルは、プロセス番号、終了ステータス、使用された CPU 時間などを含む、各子プロセスのデータ構造を保持します。親プロセスが子プロセスの終了情報を処理すると、カーネルはこのデータ構造を解放します。親プロセスが子プロセスの終了情報を処理しない場合、カーネルはこのデータ構造を解放せず、子プロセスのプロセス番号は常に占有されます。システム内で使用できるプロセス数には限りがあります。ゾンビプロセスが大量に生成されると、使用可能なプロセス数がなくなるため、システムは新しいプロセスを生成できなくなります。

5.3 ゾンビプロセスを回避する方法

  1. SIGCHLD シグナルの処理 : 子プロセスが終了すると、カーネルは親プロセスに SIGCHLD シグナルを送信します。親プロセスが使用する場合signal(SIGCHLD, SIG_IGN)カーネルが子プロセスの終了に関心がないことをカーネルに通知します。カーネルのデータ構造は、子プロセスが終了した直後に解放されます。
  2. 使用wait()/waitpid()関数: 親プロセスは、これらの関数を呼び出して子プロセスの終了を待ち、その終了ステータスを取得することで、子プロセスが占有していたリソースを解放します。
pid_t wait(int *stat_loc); 
pid_t waitpid(pid_t pid, int *stat_loc, int options); 
pid_t wait3(int *status, int options, struct rusage *rusage); 
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
  • 1
  • 2
  • 3
  • 4

戻り値は子プロセスの番号です。

stat_loc 子プロセスの終了に関する情報は次のとおりです。

a) 正常に終了すると、マクロは WIFEXITED(stat_loc) true を返す、マクロWEXITSTATUS(stat_loc) 終了ステータスを取得できます。

b) 異常終了した場合、マクロは WTERMSIG(stat_loc) プロセスを終了するシグナルを取得します。

画像-20240709230911352

画像-20240709231034423

画像-20240709231050581

画像-20240709231124375

画像-20240709231140813

親プロセスがビジー状態の場合は、キャプチャできます。 SIGCHLD Signal、信号処理関数で呼び出されますwait()/waitpid()

画像-20240709231439475

画像-20240709231422927

6.複数のプロセスとシグナル

[プロセス間でシグナルを送信する](##1.5 シグナルを送信する)

マルチプロセスサービスプログラムでは、サブプロセスが終了シグナルを受信すると、サブプロセスは単独で終了します。

親プロセスが終了シグナルを受信した場合、すべての子プロセスに終了シグナルを送信してから、親プロセス自体を終了する必要があります。

画像-20240711222919564

画像-20240711222900141

画像-20240711223111481

7. 共有メモリ

マルチスレッドはプロセスのアドレス空間を共有し、複数のスレッドが同じメモリにアクセスする必要がある場合は、グローバル変数を使用してください。

複数のプロセスでは、各プロセスのアドレス空間は独立しており、共有されません。複数のプロセスが同じメモリにアクセスする必要がある場合、グローバル変数は使用できず、共有メモリのみが使用できます。

共有メモリを使用すると、複数のプロセス (プロセス間の血縁関係は必要ありません) が同じメモリ空間にアクセスできます。これは、複数のプロセス間でデータを共有および転送する最も効果的な方法です。プロセスは共有メモリを自身のアドレス空間に接続できます。あるプロセスが共有メモリ内のデータを変更すると、他のプロセスによって読み取られたデータも変更されます。

共有メモリにはロック メカニズムがありません。つまり、プロセスが共有メモリを読み書きするときに、他のプロセスによる共有メモリの読み書きが妨げられません。共有メモリの読み取り/書き込みをロックしたい場合は、セマフォを使用できます。 。 Linux は、共有メモリを操作するための一連の関数を提供します。

7.1 shmget関数

この関数は共有メモリを作成/取得するために使用されます。

 int shmget(key_t key, size_t size, int shmflg);
  • 1
  • 共有メモリのキー値は整数(typedef unsigned int key_t)、通常は 16 進数で、たとえば 0x5005、異なる共有メモリのキーを同じにすることはできません。
  • サイズ 共有メモリのサイズ (バイト単位)。
  • シュムフグ 共有メモリのアクセス許可は、ファイルのアクセス許可と同じです。たとえば、0666|IPC_CREAT 共有メモリが存在しない場合は作成することを示します。
  • 戻り値: 成功した場合は共有メモリの ID (0 より大きい整数) を返し、失敗した場合 (システムにメモリが不十分で権限がない) は -1 を返します。

画像-20240711224223200

画像-20240711224212293

使使用 ipcs -m システムの共有メモリを表示できます。これには、キー、共有メモリ ID (shmid)、所有者、アクセス許可 (perms)、およびサイズ (バイト) が含まれます。

使使用 ipcrm -m 共享内存id 共有メモリは次のように手動で削除できます。

画像-20240711225202860

注: コンテナーは共有メモリ内のデータ型には使用できません。使用できるのは基本データ型のみです。

7.2 shmat関数

この関数は、共有メモリを現在のプロセスのアドレス空間に接続するために使用されます。

void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 1
  • シュミッド によるshmget() 関数によって返される共有メモリ識別子。
  • シュマドル 共有メモリが現在のプロセスに接続されているアドレスの場所を指定します。通常は 0 を入力して、システムに共有メモリのアドレスを選択させます。
  • シュムフグ フラグビット。通常は 0 で埋められます。

呼び出しが成功した場合は共有メモリの開始アドレスを返し、呼び出しが失敗した場合は戻ります。 (void *)-1

7.3 shmdt関数

この関数は、現在のプロセスから共有メモリを切り離すために使用されます。これは以下と同等です。 shmat() 関数の逆演算。

int shmdt(const void *shmaddr);
  • 1
  • シュマドル shmat() 関数によって返されるアドレス。

呼び出しが成功した場合は 0 を返し、失敗した場合は -1 を返します。

7.4 shmctl関数

この関数は共有メモリを操作するために使用され、最も一般的に使用される操作は共有メモリの削除です。

int shmctl(int shmid, int command, struct shmid_ds *buf);
  • 1
  • シュミッド shmget() 関数によって返される共有メモリ ID。
  • 指示 共有メモリを削除したい場合は、共有メモリの操作手順を記入してください。IPC_RMID
  • バッファ 共有メモリを操作するデータ構造体のアドレス。共有メモリを削除する場合は0を入力します。

呼び出しが成功した場合は 0 を返し、失敗した場合は -1 を返します。

注意、使用 root 作成された共有メモリは、作成された権限に関係なく、一般のユーザーは削除できません。

画像-20240711230653886

画像-20240711230522921

7.5 循環キュー

7.6 共有メモリに基づく循環キュー

呼び出しが成功した場合は 0 を返し、失敗した場合は -1 を返します。

7.4 shmctl関数

この関数は共有メモリを操作するために使用され、最も一般的に使用される操作は共有メモリの削除です。

int shmctl(int shmid, int command, struct shmid_ds *buf);
  • 1
  • シュミッド shmget() 関数によって返される共有メモリ ID。
  • 指示 共有メモリを削除したい場合は、共有メモリの操作手順を記入してください。IPC_RMID
  • バッファ 共有メモリを操作するデータ構造体のアドレス。共有メモリを削除する場合は0を入力します。

呼び出しが成功した場合は 0 を返し、失敗した場合は -1 を返します。

注意、使用 root 作成された共有メモリは、作成された権限に関係なく、一般のユーザーは削除できません。

[外部リンク画像を転送中...(img-v6qW3XRA-1720711279572)]

[外部リンク画像は転送中です...(img-CG0tGAne-1720711279572)]外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします。
外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします。
外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします。

7.5 循環キュー

7.6 共有メモリに基づく循環キュー