Linuxプロセススレッドとスケジューリングの詳細な説明

Linuxプロセススレッドとスケジューリング

1プロセスコンセプト

1.1プロセスとスレッドの
定義オペレーティングシステム古典的な定義:
プロセス:リソース割り当てユニット。
スレッド:スケジューリングユニット。
オペレーティングシステムは、PCB(プロセス制御ブロック、プロセス制御ブロック)を使用してプロセスを記述します。LinuxのPCBはtask_struct構造体です。

1.2プロセスのライフサイクル
1.2.1プロセスの状態
R、TASK_RUNNING:準備完了状態または実行中の状態。プロセスは実行の準備ができていますが、必ずしもCPUを占有しているとは限りません
。TASK_INTERRUPTIBLE:浅いスリープ、リソースの待機、シグナルに応答できます。プロセスがアクティブにスリープ状態に入る
D、TASK_UNINTERRUPTIBLE:ディープスリープ、リソースを待機中、シグナルに応答しない、典型的なシナリオは、プロセスがセマフォをブロックすることです
Z、TASK_ZOMBIE:ゾンビ状態、プロセスは終了または終了しましたが、親プロセスまだわからない、リサイクルがないときの状態
T、TASK_STOPED:停止、デバッグ状態、SIGSTOPシグナルを受信した後にプロセスがハングする
ここに画像の説明を挿入

1.2.2プロセスの作成と終了に関連するAPI1
)system()
はシェルを呼び出して新しいプロセスを開始します
2)exec()
は現在のプロセスイメージを置き換えることで新しいプロセスを開始します
3)fork()
は現在のプロセスイメージをコピーして開始します新しいプロセスである子プロセスのfork()は0を返し、親プロセスのfork()は子プロセスIDを返します。
4)wait()
親プロセスは一時停止され、子プロセスの終了を待ちます。
5)孤立プロセスとゾンビプロセス
孤立プロセス:1つ以上の子プロセスがまだ実行されている間に親プロセスが終了すると、それらの子プロセスは孤立プロセスになります。孤立プロセスはinitプロセス(プロセス番号は1)によって採用され、initプロセスはそれらの状態収集作業を完了します。孤立したプロセスはリソースを浪費しません。
ゾンビプロセス:プロセスはforkを使用して子プロセスを作成します。子プロセスが終了し、親プロセスがwaitまたはwaitpidを呼び出して子プロセスのステータス情報を取得しない場合、子プロセスのプロセス記述子は引き続きシステム。この種のプロセスはゾンビプロセスと呼ばれます。ゾンビプロセスはシステムリソースを浪費します(プロセス記述子task_structが存在し、プロセスによって占有されているリソースがリサイクルされ、メモリリークはありません。実際、システムリソースは基本的に浪費されません。SongBaohuaのコースを参照してください)。
ゾンビプロセスの回避:
ゾンビプロセスの原因:
1。子プロセスが終了した後、SIGCHLDシグナルを親プロセスに送信すると、親プロセスはデフォルトでそれを無視します
。2。親プロセスはwait()またはwaitpid()を呼び出しません。子プロセスの終了を待ちます。
ゾンビプロセスを回避する方法:
1。親プロセスはwait()またはwaitpid()を呼び出して、子プロセスの終了を待機します。このように、親プロセスは通常、待機時にブロックされ、他の処理を実行できません。
2. SIGCHLDシグナルをキャッチし、シグナル処理関数で待機関数を呼び出します。この処理により、1で説明した問題を回避できます。
3.フォークを2回実行すると、親プロセスが息子プロセスを作成し、息子プロセスが孫プロセスを作成し、次に息子プロセスが自殺し、孫プロセスが孤立プロセスになり、initプロセスに採用されます。
1.3プロセス間通信
1)
信号ここでの信号はイベントを指します。たとえば、CTRL-Cキーの組み合わせを押すと、SIGINTシグナルが送信されます。このシグナルは、プロセスでキャプチャされ、それに応じて処理されます。
2)パイプラインPIPE
はファイルであり、パイプラインの操作はファイルの操作と同様です。
popen()関数はfopen()関数に似ており、オブジェクトポインタを返します。
pipe()関数はopen()関数に似ており、オブジェクト記述子を返します。
パイプラインは、相対プロセス(同じ親プロセスによって作成された関連プロセス)間のデータ送信用です。
3)名前付きパイプFIFO
名前付きパイプは、親族関係のないプロセス間通信に使用できます。
mkfifo()/ mknod()は、ファイルシステムにパスと名前のファイルを作成します。このパイプファイルを通常のファイルとして使用するだけで、プロセス間通信を実現できます。
4)セマフォ
セマフォ、メッセージキュー、および共有メモリは、System VIPCメカニズムです。
クリティカルエリア:一度に1つのプロセスからのみアクセスできるコードエリア。
セマフォ:ほとんどのプロセス間通信にはバイナリセマフォのみが必要であるため、ここではバイナリセマフォについてのみ説明します。クリティカルセクションに入る前に、P操作を実行します(セマフォが1より大きい場合は、1を引いてクリティカルセクションに入ります。そうでない場合、プロセスは中断されます)。クリティカルセクションを出るときは、V操作を実行します(プロセスが一時停止されるのを待って、それを起こしてください、さもなければセマフォプラス1)。
ミューテックス:ミューテックスセマフォは、バイナリセマフォのサブセットです。
5)メッセージキュー
名前付きパイプに似ていますが、パイプを開閉する複雑な操作を考慮する必要はありません。メッセージキューは、プロセスとは独立して存在します。
6)共有メモリ
通信する必要のあるプロセスは、データ交換のためにメモリの一部を共有します。

[記事のメリット] C / C ++ Linuxサーバーアーキテクトの学習資料とグループ832218493(C / C ++、Linux、golangテクノロジー、Nginx、ZeroMQ、MySQL、Redis、fastdfs、MongoDB、ZK、ストリーミングメディア、CDN、P2P、K8S、 Docker、TCP / IP、coroutine、DPDK、ffmpegなど)

ここに画像の説明を挿入

2プロセススレッドの実現の本質

Linuxスケジューラーは、実際にスケジューリングのためにtask_structを認識します。
プロセススレッドに関係なく、最下層はtask_structに対応します。プロセスとスレッドの違いは、共有リソースの量です。2つのプロセスはリソースをまったく共有せず、すべてのリソースは2つのスレッド間で共有されます。

2.1 fork()
がforkを実行した、親プロセスのtask_struckが子プロセスにコピーされます。最初は、親プロセスと子プロセスのリソースはまったく同じですが、2つの異なるコピーであるため、変更を加えると、分割する2つ。
ここに画像の説明を挿入

親プロセスと子プロセスは、COW(コピーオンライト、コピーオンライト)テクノロジを使用してメモリリソース(mm)を管理します。

1 \フォーク前は、メモリ領域の一部が物理アドレスと仮想アドレスに対応し、メモリ領域の許可はRWです。

2 \フォーク後、親プロセスと子プロセスから見たメモリ領域の仮想アドレスと物理アドレスは同じであり、親プロセスと子プロセスは実際には同じ物理メモリを使用します。メモリコピーは発生せず、動作します。システムはこのメモリ領域の権限をROに変更します。

3 \親プロセスまたは子プロセスがメモリ領域に書き込むと、PageFaultがトリガーされます。オペレーティングシステムはこの時点でメモリ領域をコピーします。親プロセスと子プロセスから見た仮想アドレスは同じですが、物理アドレスはすでに異なります。 。各プロセスの物理アドレスへの仮想アドレスのマッピングは、MMU(メモリ管理ユニット、メモリ管理ユニット)によって管理されます。

ここに画像の説明を挿入

フォークはMMUを搭載したCPUで実行されます。

2.2 vfork()
ここに画像の説明を挿入

MMUのないCPUの場合、COWは適用できず、フォークはサポートできません。
MMUのないCPUはvforkを使用してプロセスを作成し、親プロセスは子プロセスが終了または実行されるまでブロックします。
vforkとforkの本質的な違いは、vforkの親プロセスと子プロセスが同じメモリ領域を共有することです。

2.3 pthread_create()

ここに画像の説明を挿入

Linuxスレッドは本質的にプロセスですが、プロセス間のリソース共有とは異なります。上の図に示すように、すべてのリソースはスレッド間で共有されます。
各スレッドには独自のtask_structがあるため、各スレッドはCPUによってスケジュールできます。同じプロセスリソースが複数のスレッド間で共有されます。これらの2つのポイントは、スレッドの定義を満たしています。
これは、Linuxがプロセスを使用してスレッドを実装する方法であるため、スレッドは軽量プロセスとも呼ばれます。

2.4PIDおよびTGID

ここに画像の説明を挿入

POSIXでは、同じプロセスの複数のスレッドがプロセスIDを取得して、一意のID値を取得する必要があります。
Linuxでの同じプロセスのマルチスレッドでは、カーネルの観点から、各スレッドは実際にはPIDを持っていますが、ユーザースペースでは、getpidは一意の値を返す必要があります。Linuxは小さなトリックを使用してTGIDの概念であるTGIDを導入します。 getpid()値によって返されます。
プロセスの観点から見た最上位のコマンド:

パラメータなしのtopコマンド(デフォルト)は、プロセスによるシングルコアCPUの使用率を示します。たとえば、プロセスには3つのスレッドがあり、メインスレッドはスレッド1とスレッド2を作成し、スレッド1とスレッド2の両方が呼び出します。一方、デュアルコアCPUの場合、スレッド1とスレッド2はそれぞれ1つのコアを使用し、占有率は100%です。topコマンドで表示されるプロセスのCPU使用率は200%で、プロセスIDははメインスレッドのPIDです(つまりTGID)。


スレッドの観点からのtopコマンド:top -Hコマンドは、スレッドの観点からCPU占有率を表示します。上記の例では、スレッド1の占有率が100%であり、スレッド2の占有率が100%。
スレッドのPIDは、ユーザースペースのプロセスIDを参照し、値はTGIDです。特に指摘されている場合、カーネルスペースのスレッドのPIDは、カーネルのtask_struct内のスレッドの一意のPIDを参照します。

3プロセススケジューリング

ここに画像の説明を挿入

3.1リアルタイムプロセススケジューリング
SCHED_FIFO:異なる優先度は、高い優先度に従ってスリープ状態になり、次に低い優先度で実行されます。同じ優先度が先入れ先出しです。
SCHED_RR:異なる優先度は、高い優先度に従ってスリープ状態になり、次に低い優先度まで実行されます。同じ優先度がローテーションします。
カーネルRTパッチ:
次の2つのパラメーター
/ proc / sys / kernel / sched_rt_period_us
/ proc / sys / kernel / sched_rt_runtime_us
は、RTが実行時に期間内にのみ実行できることを示し
ます
。3.2通常のプロセススケジューリングSCHED_OTHER:

3.2.1動的優先度(初期2.6)
プロセスには、IO消費とCPU消費の2つの測定パラメーターがあります。
優先度が高いとは、1)タイムスライスを増やす、2)ウェイクアップ時に優先度の低いものをプリエンプトできることを意味します。タイムスライスが回転します。
カーネルは静的優先度を格納し、ユーザーはniceを介して静的優先度を変更できます。
プロセスの動的優先度は、静的優先度に従ってリアルタイムで計算されます。スケジューリングアルゴリズムは、IO消費に報酬を与え(優先度を上げてリアルタイムパフォーマンスを向上させ)、CPU消費タイプにペナルティを課します(優先度を下げてリアルタイムを削減します)。パフォーマンス)

3.2.2 CFS:完全に公平なスケジューリング(新しいカーネル)
の赤黒木、左側のノードは右側のノードの値よりも小さい。
これまでで最小のvruntimeでプロセス
実行する。同時に、CPU / IOと優れた機能
常に最小のvruntimeでスレッドスケジューリングを見つけます。
vruntime = pruntime / weight×1024;
vruntimeは仮想ランタイム、pruntimeは物理ランタイムであり、重みはnice値(niceが小さいほど、重みが大きい)、実行時間が短いスレッド、およびnice値が低いほど、vruntimeGet優先順位スケジューリングが小さくなります。これは、操作によって動的に変化するプロセスです。

ここに画像の説明を挿入

ツールchrtとrenice:

设置SCHED_FIFO和50 RT优先级

chrt -f -a -p 50 10576
 设置nice 
 # renice -n -5 -g 9394 
 # nice -n 5 ./a.out

おすすめ

転載: blog.csdn.net/lingshengxueyuan/article/details/112615517