【Linuxシステム】その8:Linuxオペレーティングシステムにおけるプロセス概念(フォン・ノイマン+オペレーティングシステム+プロセスステータス+プロセス優先度)

記事ディレクトリ

1. フォン・ノイマン・アーキテクチャー (ハードウェア)

フォン ノイマン アーキテクチャは現代のコンピューターの基礎であり、ほとんどのコンピューターは今でもフォン ノイマン コンピューター構造で編成されています。(以下はフォン・ノイマン建築の写真です)
ここに画像の説明を挿入
説明:

  • メモリー:弊社パソコンのメモリーに対応
  • 中央処理装置CPU:演算装置とコントローラの2つの部分で構成されています
  • 入力デバイス: キーボード、ハードディスク、マウスなどを含む
  • 出力機器:プリンター、モニターなど(入力機器と出力機器を総称して周辺機器と呼びます)
  • 入力デバイスと出力デバイスはどちらも、ディスク、ネットワーク カードなどです。ディスクはデータを永続的に保存できます

上の図から、いくつかの結論と CPU 処理データのプロセスを引き出すことができます
。 CPU の計算速度は非常に高速です.2 つの速度のバランスをとるために、CPU は最初に 2 つの動作速度の間でメモリと対話します。

2. データを読み取る場合、入力デバイスがデータを中間メモリに書き込み、メモリがデータを CPU に書き込み、CPU がデータを処理できるようにします。処理後、CPU はデータをメモリに書き戻します。最後に、メモリは出力デバイスにデータを書き込みます

要約:

CPU は周辺機器を直接扱うのではなく、メモリを直接扱う

例: QQ を使用して友人とチャットするときのデータの流れ

QQ を使用するには、まずインターネットに接続する必要があります. あなたとあなたの友人のコンピューターがすべてフォン ノイマン アーキテクチャであると仮定すると、友人にメッセージを送信する過程で、コンピューターのキーボードが入力デバイスとして機能します.モニターとネットワーク カードは出力デバイスとして機能し、友人のコンピューターのネットワーク カードは入力デバイスとして機能し、モニターは出力デバイスとして機能します。
ここに画像の説明を挿入

最初に、キーボードでメッセージを入力すると、キーボードがメッセージをメモリにロードします. この時点で、モニターはメモリからメッセージを取得して、自分のモニターに表示できます. このとき、あなたは見ることができます.自分のコンピュータでメッセージを送信しました。メッセージは送信されました。
キーボードがメッセージをメモリにロードした後、CPU はメモリからメッセージを取得し、さまざまな方法でメッセージをカプセル化し、メモリに書き戻します. このとき、ネットワーク カードはカプセル化されたメッセージをメモリから取得できます。一連の処理(ここではネットワーク処理の詳細は無視します)の後、友人のネットワーク カードは、ネットワークから送信されたメッセージを取得し、そのメッセージをメモリにロードし、友人の CPU は、メッセージがアンパックされ、アンパックされたメッセージがメモリに書き戻され、最後に友人のモニターがメモリからメッセージを取得し、コンピューターに表示します。
ここに画像の説明を挿入
注:同じデバイスが入力デバイスまたは異なるシナリオの入力デバイスである場合があります。

上記の一連の理論を理解した後、次の質問を理解できます。なぜプログラムを実行する前にメモリにロードする必要があるのですか?
実行可能プログラム (ファイル) はディスク (ペリフェラル) 上にあり、CPU はメモリからしかデータを取得できないため、最初にディスク上のデータをメモリにロードする、つまりプログラムをメモリにロードする必要があります。初め。

第二に、オペレーティング システム (ソフトウェア)

1. コンセプト

簡単な理解:オペレーティング システムは、ハードウェアおよびソフトウェア リソースを管理するソフトウェアです。

ここに画像の説明を挿入

2. オペレーティングシステムを設計する目的

  1. (ユーザー、プログラマー) 向け: 安定した、効率的で安全な動作環境をユーザーに提供し、プログラマーにさまざまな基本機能を提供します (OS はユーザーを信頼せず、ユーザーまたはプログラマーがハードウェアと対話することを許可しません)。
  2. 次へ: さまざまなハードウェアおよびソフトウェア リソースを管理する

3.ポジショニング

コンピューターのハードウェアおよびソフトウェア アーキテクチャ全体におけるオペレーティング システムの位置付けは、純粋な「管理ソフトウェアです。

4.「経営」をどう理解するか

OS の完全なアーキテクチャ

まず、肉眼で見えるのは実際のコンピューター、つまりコンピューターの基盤となるハードウェアです。これらのハードウェアは 1 つずつリストされているように見えますが、実際にはすべて下部にあるフォン ノイマンの組織形式に従っています。
ここに画像の説明を挿入

これらのハードウェアがあるだけでは十分ではなく、これらのハードウェアを管理するためのソフトウェアも必要です。たとえば、メモリが入力デバイスからデータを読み取るのはいつですか? どのくらいのデータが読み取られますか? メモリがバッファを出力デバイスにフラッシュするのはいつですか? 行ごとにリフレッシュされますか、それともすべてリフレッシュされますか? これらはソフトウェアによって管理されており、このソフトウェアがオペレーティング システム (Operator System) です。
ここに画像の説明を挿入

ここで質問があります。オペレーティング システムは、基盤となるハードウェアを直接処理しますか?
たとえば、オペレーティング システムがキーボード読み取り操作を単独で完了する場合、キーボード読み取り方法が変更されている限り、オペレーティング システムのカーネル ソース コードを再コンパイルする必要があり、オペレーティング システムが維持するにはコストがかかりすぎます。 . そこで、オペレーティング システムと基盤となるハードウェアの間にドライバー レイヤー
を追加しました. ドライバー レイヤーの主な役割は、基盤となるハードウェアのみを制御することです。たとえば、キーボードにはキーボード ドライバがあり、ネットワーク カードにはネットワーク カード ドライバがあり、ハードディスクにはハードディスク ドライバがあり、磁気ディスクにはディスク ドライバがあります。簡単に言えば、ドライバーは特定のハードウェアにアクセスし、ハードウェアの読み取りと書き込み、およびハードウェアの現在の状態などにアクセスすることです。ドライバー層はハードウェアを直接扱います。ドライバーは通常、ハードウェアの製造元によって提供されるか、オペレーティング システム関連のモジュール (ネットワーク カードなど) によって開発されます。この時点で、オペレーティング システムは、データの読み取り方法ではなく、いつデータを読み取るかだけを気にする必要があります。つまり、オペレーティング システムとハードウェア間の分離が完了します。

ここに画像の説明を挿入

オペレーティングシステムは正確に何を管理しますか? オペレーティング システムは、主に次の 4 つの管理タスクを実行します。

  1. メモリ管理: メモリ割り当て、メモリ共有、メモリ保護、メモリ拡張など
  2. ドライバー管理: コンピューター デバイス ドライバーの分類、更新、削除などの操作。
  3. ファイル管理:ファイルストレージスペース管理、ディレクトリ管理、ファイル操作管理、ファイル保護など
  4. 工程管理:その仕事は主に工程のスケジューリングです。

ここに画像の説明を挿入

オペレーティング システムは、コマンド ラインまたはグラフィカル インターフェイスを使用してさまざまな操作を実行できる場所であり、この層はユーザー層と呼ばれます。
ここに画像の説明を挿入

しかし、オペレーティングシステムは自身を保護するために、一部のインターフェースのみを公開し、ユーザーがオペレーティングシステムに直接アクセスすることを許可していません.この一連のインターフェースはシステムコールインターフェースと呼ばれます.
ここに画像の説明を挿入

しかし、システム コールを使用するための前提条件は、システムをある程度理解している必要があるためです。そのため、libc や libc++ など、多くのライブラリがシステム コール インターフェイスの上に構築されています。実際、言語レベルで使われるさまざまなライブラリは、システム コール インターフェイスをカプセル化しており、これらのライブラリでさまざまな関数 (printf や scanf など) を呼び出して、さまざまなプログラムを記述します。

ここに画像の説明を挿入

「経営」を理解する

管理の本質:データを管理する 管理
方法:最初に記述し、次に整理します。

オペレーティング システムをよく理解したい場合は、管理とは何かを正しく理解する必要があります。

まず意思決定と実行の違いを区別する

  1. 意思決定: 上記のオペレーティング システムの階層を例にとると、その中のオペレーティング システムには「意思決定力」があり、管理者になることができます。
  2. 実行: 上記のオペレーティング システム階層では、ドライバーはオペレーティング システムによって発行されたタスクを実行します。

次に、管理について説明する例を示します。例えば、学校経営(ざっくりまとめ)。
生徒、カウンセラー、校長の 3 つの役割が与えられます。当然、校長はこの3人のうちの管理者、生徒は管理者ですが、カウンセラーはどのような役割を果たしているのでしょうか。
ここに画像の説明を挿入
考えてみてください、私たちは一つのことを成し遂げるのに2つのプロセスを経なければなりません.1つ目は、これを行うかどうか、またはどのように行うかを決定すること(意思決定)と、これを実行することです。

校長は管理者として、学校が学校を運営するためのルールを策定できるため、決定を下すのは校長ですが、校長が決定した後は、自分で実行する必要はなく、カウンセラーに実行させる必要があります。 、したがって、カウンセラーの主なタスクは、マネージャーの決定を実行することです。私たちは彼らをエグゼクティブと呼びます
ここに画像の説明を挿入

校長が生徒を管理していると言われていますが、私たちは一般的に校内で校長本人を見ることはできません。

例: 校長はカウンセラーにさまざまな科目の学生の成績と情報を数えるように依頼し、カウンセラーが学生の情報を持ってくると、校長は奨学金を授与する上位 3 人の学生を選ぶ方法を見つけます。

この過程で、校長はこの 3 人の生徒に会うことなく管理していました。はい、彼はデータに基づいていました。

実は学校では、生徒一人ひとりの基本情報、成績情報、健康情報など、あらゆる情報を管理しています。
ここに画像の説明を挿入

このような一連の情報のそれぞれが学生を記述し、校長はこれらの情報の管理を通じて学生を管理できます。このような情報の集合は、C 言語では抽象構造、C++ ではオブジェクト指向と呼ばれます。

生徒数が増えると、校長は全生徒の情報を整理することができます. もちろん、整理の方法はたくさんありますが (リンクリスト、シーケンシャルリスト、ツリー)、それぞれの整理方法には独自の利点があります.は、データの管理方法、つまりデータ構造を教えることに特化したコースです。ここでは、校長がリンクされたリストの形式で学生情報を整理していると仮定します。
ここに画像の説明を挿入

このとき、各生徒に対する校長の管理は、実際にはこの連結リストの追加、削除、確認、修正となります。新入生がいる場合はリンクリストに直接ノードを追加し、卒業する場合はリンクリストから学生情報を削除するだけです。

5. システムコールとライブラリ関数の概念

  1. 開発の観点から見ると、オペレーティング システムは全体として外の世界に見えますが、上位層の開発のためにそのインターフェイスの一部を公開します.オペレーティング システムが提供するインターフェイスのこの部分は、システム コールと呼ばれます。
  2. システム コールに関しては、関数は比較的基本的ですが、ユーザーの要件は比較的高いです. したがって、関心のある開発者は、いくつかのシステム コールを適切にカプセル化してライブラリを形成することができます. ライブラリを使用すると、上位レベルのユーザーにとって非常に有益です.または開発者は、私たちがよく使用する cin や cout などの二次開発を行います。

3. 工程管理

1. プロセスの概念

書籍におけるプロセスの概念について、次の点を確認できます。

実行中の(メモリにロードされた)プログラム - - - プロセス
メモリ内のプログラム - - - プロセス
プログラムと比較して、プロセスは動的な性質を持っています

上記の点から、私たちはまだプロセスの概念を理解しておらず、プログラムとプロセスの違いを区別できません。

プログラムの本質は、ディスクに置かれたファイルです。

プロセスはどうですか?
コードを書いたことがある人なら誰でも、コードをコンパイルしてリンクすると、実行可能プログラムが生成されることを知っています. この実行可能プログラムは、基本的にはディスクに配置されるファイルです. 実行可能プログラムをダブルクリックして実行すると、本質的にプログラムをメモリにロードします。これは、CPU がメモリにロードされた後にのみ実行できるためです。プログラムがメモリにロードされると、このプログラムもはやプログラムと呼ぶべきではなく、厳密な意味でプロセスと呼ぶべきです。
ここに画像の説明を挿入

2. プロセスの説明 - - -PCB

システムには同時に多数のプロセスが存在する可能性があるため、コマンド ps aux を使用して、システムに存在するプロセスを表示します。
ここに画像の説明を挿入

コンピューターの電源を入れると、最初に起動するプログラムがオペレーティング システムです (つまり、オペレーティング システムが最初にメモリにロードされます)。オペレーティング システムが管理作業を行い、プロセス管理が含まれていることは誰もが知っています。 . システムには多数のプロセスがありますが、オペレーティング システムはプロセスをどのように管理するのでしょうか?

現時点では、管理の 6 文字のマントラを考える必要があります。まず説明し、次に整理します。同じことがオペレーティング システムの管理プロセスにも当てはまります. マネージャーとして, オペレーティング システムは管理対象 (プロセス) と直接通信する必要はありません. プロセスが現れると, オペレーティング システムはすぐにそれを記述し, その後プロセスを管理します. 実際には、その記述情報の管理です。

  • プロセス情報は、プロセス制御ブロックと呼ばれるデータ構造に配置されます。これは、プロセス属性の集合として理解できます。
  • 教科書ではPCB (プロセス制御ブロック) と呼ばれ、Linux オペレーティング システムでの PCB は次のとおりですtask_struct

task_struct-PCB の一種

プロセス制御ブロック (PCB) はプロセスを記述します. C++ ではオブジェクト指向と呼び、C 言語では構造と呼びます. Linux オペレーティング システムは C 言語で記述されているため、Linux のプロセスは制御ブロックを実装する必要があります.構造を持つ。

  • PCB は実際にはプロセス制御ブロックの総称であり、Linux でプロセスを記述する構造体は task_struct と呼ばれます。
  • task_struct は、RAM (メモリー) にロードされ、プロセス情報を含む Linux カーネルのデータ構造です。

task_ struct コンテンツの分類

task_struct は Linux のプロセス制御ブロックです。task_struct には主に次の情報が含まれます。

  • 識別子: 他のプロセスを区別するために使用される、このプロセスの一意の識別子を記述します。
  • ステータス: タスクのステータス、終了コード、終了シグナルなど。
  • 優先度: 他のプロセスに対する優先度。
  • プログラム カウンター (pc): 実行されるプログラム内の次の命令のアドレス。
  • メモリー・ポインター: プログラム・コードおよびプロセス関連データへのポインター、および他のプロセスと共有されるメモリー・ブロックへのポインターを含みます。
  • コンテキスト データ: プロセスが実行されているときのプロセッサのレジスタ内のデータ。
  • I/O ステータス情報: 表示された I/O 要求、プロセスに割り当てられた I/O デバイス、およびプロセスによって使用されるファイルのリストを含みます。
  • 請求情報: プロセッサー時間の合計、使用されたクロックの合計、制限時間、請求先アカウント番号などが含まれる場合があります。
  • その他の情報。

プロセス情報はプロセス制御ブロック (PCB) に配置されるため、オペレーティング システムは、プロセス制御ブロック (PCB) を形成するために各プロセスを 1 つずつ記述し、これらの PCB をリンクされたリストの形式で編成します。
ここに画像の説明を挿入
このようにして、オペレーティング システムは、このリンク リストのヘッド ポインタを取得する限り、すべての PCB にアクセスできます。それ以来、オペレーティングシステムによる各プロセスの管理は、このリンクされたリスト上の一連の操作になっています。
ここに画像の説明を挿入
たとえば、プロセスを作成するには、実際にはプロセスのコードとデータを最初にメモリにロードし、次にオペレーティング システムがプロセスを記述して対応する PCB を形成し、PCB をリンク リストに挿入します。プロセスを終了すると、実際にはリンクされたリストからプロセスの PCB が削除され、次にオペレーティング システムがメモリ内のプロセスに属するコードとデータを解放または無効化します。

一般に、オペレーティングシステムによるプロセスの管理は、実際にはリンクリストの追加、削除、チェック、および変更などの操作になります。

上記の説明から、プロセスが何で構成されているかを知ることができます。

プロセス = カーネル データ構造体 (task_struct) + ディスク コードとプロセスに対応するデータ

3. プロセスを見る

システム ディレクトリを介して表示

プロセス情報は、システム ディレクトリを表示することで表示できます/proc。Linux
ここに画像の説明を挿入
システムでは、ファイルを表示してプロセスを表示する方法が提供されています。各プロセスの作成の開始時に、ディレクトリが /proc ディレクトリの下に作成され、プロセス番号に基づいて名前が付けられます。プロセスが終了すると自動的に消えます。

psコマンドで見る

プロセス コマンドを表示します。

ps axj | grep myproc

myproc に関するすべてのプロセス情報を表示できます。
ここに画像の説明を挿入

ps axj | head -1 && ps axj | grep myproc

このコマンドは、プロセスのサブタイトルを運ぶことができます。
ここに画像の説明を挿入

4. システムコールでプロセス識別子(PID/PPID)を取得する

まず、2 つのシステム コールの機能を理解します。
機能: プロセス番号を取得します。
ここに画像の説明を挿入

サブプロセス getpid()

getpid() 子プロセスのプロセス ID を取得します。
コードでテストできます。

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>                              
int main()    
{
    
           
    printf("我是一个进程,我的ID:%d\n",getpid());
    sleep(1);             
    return 0;
}  

ここに画像の説明を挿入

親プロセス getppid()

getpid() プロセスの親プロセス ID を取得します。

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>                              
int main()    
{
    
           
    printf("我是一个进程,我的ID:%d,PPID: %d\n",getpid(),getppid());
    sleep(1);             
    return 0;
}  

ここに画像の説明を挿入

5. システム コールによるプロセスの作成 - fork initial

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

fork は、子プロセスを作成する機能を持つシステム コール レベルの関数です。
ここに画像の説明を挿入
まず、次のコードを見てください。

#include <stdio.h>      
#include <unistd.h>      
   
int main()      
{
    
        
    fork();    
    printf("MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid());                                                                                                               
    return 0;                                 
}  

コード操作の結果は次のとおりです。
ここに画像の説明を挿入
操作の結果は、2 行のデータを出力することです。2 行目の PPID は、1 行目に出力された PID 番号と同じであることがわかります。つまり、 、2つのプロセスの間には親子関係があり、下のプロセスは子プロセスです。

プロセスが出現するたびに、オペレーティング システムはそのプロセス用の PCB を作成します。fork 関数によって作成されたプロセスも例外ではありません。

メモリにロードされたコードとデータは親プロセスに属していることがわかります。では、fork 関数によって作成された子プロセスのコードとデータはどこから来るのでしょうか。


ここに画像の説明を挿入
以下のコードの実行結果を見てみましょう
ここに画像の説明を挿入
。デフォルトでは、親プロセスと子プロセスの両方が実装できます。親プロセスと子プロセスはコードを共有しますが、親プロセスと子プロセスのデータはそれぞれ (コピー オン ライトを使用して) スペースを開くことに注意してください。

ヒント: fork 関数を使用して子プロセスを作成すると、2 つのプロセスが存在しますが、これら 2 つのプロセスがオペレーティング システムによってスケジュールされる順序は不明であり、オペレーティング システムのスケジューリング アルゴリズムの特定の実装に依存します。

迂回する場合に使用

前述のように fork 関数で作成された子プロセスは親プロセスとコードを共有していますが、本当に親プロセスと子プロセスに同じことをさせてしまうと、子プロセスを作成しても意味がありません。
実際、フォークの後、通常は if ステートメントを使用して分割します。つまり、親プロセスと子プロセスに別のことをさせます。

シャントに if を使用する前に、 fork() 関数の戻り値を理解する必要があります。

fork 関数の戻り値:
ここに画像の説明を挿入

1. 子プロセスが正常に作成された場合、親プロセスには子プロセスの PID を返し、子プロセスには 0 を返します。
2. 子プロセスの作成に失敗した場合、親プロセスに -1 が返されます。

親プロセスと子プロセスで得られる fork 関数の戻り値が異なるため、これを利用して、親プロセスと子プロセスで異なるコードを実行させ、異なることを実行させることができます。
たとえば、次のコード:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    
        
                                                                    
    pid_t id=fork();                                                
    if(id==0)                                                       
    {
    
                                                                   
        //子进程    
        while(1)    
        {
    
                                                                                                                           
            printf("我是一个子进程,MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid(),id);    
            sleep(1);
        }                                                                      
    }                                                                                                  
    else if(id>0)                                                                                      
    {
    
                                                                                                      
        //父进程                                                                                       
        while(1)                                                                                       
        {
    
                                                                                                  
            printf("我是一个父进程,MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid(),id);      
            sleep(1);
        }                                                                                              
    }                                                                                                  
    else                                                                                               
    {
    
     
    }
    return 0;
}

fork が子プロセスを作成した後、子プロセスは if ステートメントのループ印刷に入り、親プロセスは else if ステートメントのループ印刷に入ります。
ここに画像の説明を挿入

第四に、プロセスの状態

プロセスは、さまざまな状態を表すさまざまなキューにあります。

Linux オペレーティング システムのソース コードには、プロセス状態の次の定義があります。

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
    
    
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

プロセスの現在の状態は、独自のプロセス制御ブロック (PCB) に保存されます。これは、Linux オペレーティング システムの task_struct にも保存されます。

Linux オペレーティング システムでは、ps aux または ps axj コマンドを使用してプロセスのステータスを表示できます。

1.ランニングステータス-R

#include <stdio.h>    
int main()    
{
    
        
    while(1);                           
    return 0;    
}

ここに画像の説明を挿入
実行状態 (実行中) のプロセスは、そのプロセスが実行中である必要があるという意味ではありません. 実行中状態は、プロセスが実行中であるか実行キューにあることを示します. つまり、複数の R 状態プロセスが同時に存在する可能性があります。

ヒント: 実行状態にあり、スケジュール可能なすべてのプロセスは実行キューに置かれます.オペレーティングシステムが実行するプロセスを切り替える必要がある場合、実行キューで実行するプロセスを直接選択します.

ステータスの後ろにある + 記号はフォアグラウンド プロセスを示し、+ 記号がない場合はバックグラウンド プロセスを示します。フォアグラウンド プロセスの実行中は、ctrl+c でプログラムを終了しない限り、ユーザーは命令の入力を続行できません。バックグラウンド プロセスの実行中は、ユーザーは命令を入力できますが、ctrl+c でプロセスを強制終了することはできません。コマンド kill -9 PID を使用して、プロセスを強制終了できます。

2.浅い睡眠状態-S

プロセスは軽いスリープ状態 (sleep) にあり, これはプロセスが何かが完了するのを待っていることを意味します. 軽いスリープ状態にあるプロセスは, いつでも起こすことができます.中断可能なスリープにすることができます)。

たとえば、次のコードを実行します。

#include <stdio.h>    
int main()    
{
    
        
    int a=0;    
    while(1)    
    {
    
        
        printf("%d\n",a++);               
    }                                  
    return 0;                          
} 

ここに画像の説明を挿入
値は常に印刷されますが、printf 関数はディスプレイにアクセスする必要があり、ほとんどの時間はディスプレイ IO の準備が整うのを待っており、印刷コードを実行しているのはほんのわずかな時間です。したがって、コードはスリープをレンダリングします。
周辺機器にアクセスする必要があるものは、通常、スリープ状態に属します。

軽いスリープ状態のプロセスは強制終了でき、kill コマンドを使用してプロセスを強制終了できます。
ここに画像の説明を挿入

3.ディープスリープ状態-D

プロセスはディープ スリープ状態 (ディスク スリープ) にあります。つまり、プロセスは強制終了されず、オペレーティング システムも強制終了されず、自動的にウェイクアップしたときにプロセスのみが自動的に回復されます。この状態は、中断できないスリープ状態 (uninterruptible sleep) と呼ばれることがあり、通常、この状態のプロセスは IO の終了を待ちます。

たとえば、プロセスがディスクへの書き込みを要求した場合、プロセスはディスクへの書き込み中にディープ スリープ状態になり、強制終了されません。成功したかどうか) ) それに応じて応答します。(ディスク休止状態)

4.一時停止状態-T

kill -19 PID を入力して、プロセスを中断状態にすることができます。
ここに画像の説明を挿入
T 中断状態 (停止): プロセスに SIGSTOP シグナルを送信することで、プロセスを停止 (T) できます。中断されたプロセスは、SIGCONT シグナルを送信することで再開できます。(kill -18 PID)
ここに画像の説明を挿入
ヒント: kill コマンドを使用して、現在のシステムでサポートされているシグナル セットを一覧表示します。

kill -l

ここに画像の説明を挿入

5.一時停止ステータスを追跡 -t

gdb を使用して実行可能ファイルをデバッグすると、プログラムはブレークポイントまで実行され、プログラムは t トレース一時停止状態 (トレース停止) に入ります。これは、プロセスがトレースされていることを示します。
ここに画像の説明を挿入

6.デスステイト-X

Death 状態は単なる return 状態であり、プロセスの exit 情報が読み込まれると、プロセスが要求したリソースはすぐに解放され、プロセスは存在しなくなるため、タスク リストに Death 状態は表示されません。 (死)。

7. ゾンビ状態 - Z

プロセスが終了しようとしているとき、システム レベルでは、プロセスが適用したリソースはすぐには解放されませんが、オペレーティング システムまたはその親プロセスが読み取れるように、一定期間一時的に保存されます。 , 関連するデータは解放されません. プロセスがその終了情報が読み取られるのを待っている場合, プロセスをゾンビ状態 (ゾンビ) で呼び出します.

プロセスの目的は特定のタスクを完了することであるため、まずゾンビ状態の存在が必要です。タスクが完了すると、呼び出し元はタスクの完了を知る必要があるため、ゾンビ状態が存在する必要があります。 、呼び出し元がタスクの完了を知ることができ、対応するフォローアップ操作を実行できるようにします。

では、プロセスはどのようにしてゾンビ状態に入るのでしょうか?

子プロセスが正常に終了し、親プロセスが子プロセスをリサイクルしない (子プロセスの戻り情報を読み取らない) シナリオをシミュレートします (kill -9 PID を使用して子プロセスを強制終了することもできます): プロセスを出力します。次の
ここに画像の説明を挿入
コマンドによるループ内の情報

while :; do ps axj | head -1 && ps axj | grep mypro | grep -v grep; sleep 1; done

子プロセスがゾンビ状態になっていることがわかります。その隣にあるのはデッドと解釈されます. プロセスは死んでいますが、リサイクルされていません. これはゾンビ状態です.

ゾンビ プロセスの終了結果は PCB に書き込まれます. プロセスが終了すると、そのコードとデータは解放されますが、PCB は解放されません. 親プロセスがこのリソースを再利用しない場合、システム メモリが発生します漏れ。次に、このゾンビプロセスを手動で強制終了して、ゾンビリソースを手動で解放できますか? いいえ、ゾンビ プロセスは停止しており、手動で強制終了することはできません。

状態 Z のプロセスがリサイクルされた後、プロセスの状態は X のデッド状態 (dead) に変わります。親プロセスが子プロセスの戻り情報を読み取った後、死体の収集速度が速すぎて見えなくなり、デッド状態になります。プロセスの親プロセスは、すぐにその親プロセスのリサイクルに置き換えられます。

5.ゾンビプロセス

  • ゾンビは特別な状態です。プロセスが終了し、親プロセス (後述の wait() システム コールを使用) が子プロセス終了のリターン コードを読み取らない場合、ゾンビ (死体) プロセスが生成されます。
  • ゾンビ プロセスは終了状態でプロセス テーブルに残り、親プロセスが終了ステータス コードを読み取るのを待ち続けます。
  • したがって、子プロセスが終了する限り、親プロセスは引き続き実行されますが、親プロセスは子プロセスの状態を読み取らず、子プロセスは Z 状態に入ります。

前述のように、プロセスが終了情報が読み取られるのを待っている場合、そのプロセスをゾンビ状態で呼び出します。ゾンビ状態のプロセスをゾンビプロセスと呼びます。

たとえば、次のコードでは、 fork 関数によって作成された子プロセスは情報を 5 回出力した後に終了しますが、親プロセスは常に情報を出力します。つまり、子プロセスが終了し、親プロセスはまだ実行されていますが、親プロセスは子プロセスの終了情報を読み取らず、この時点で子プロセスはゾンビ状態に入ります。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
    
    
	printf("I am running...\n");
	pid_t id = fork();
	if(id == 0)
	{
    
     
		//child
		int count = 5;
		while(count){
    
    
			printf("I am child process。PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
			sleep(1);
			count--;
		}
		printf("child quit...\n");
		exit(1);
	}
	else if(id > 0){
    
     //father
		while(1)
		{
    
    
			printf("I am father process。PID:%d, PPID:%d\n", getpid(), getppid());
			sleep(1);
		}
	}
	else
	{
    
     //fork error}
	return 0;
} 

コードを実行した後、次の監視スクリプトを使用して、プロセスの情報を毎秒検出できます。

while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep; sleep 1; done

ここに画像の説明を挿入
ゾンビプロセスの危険性

  1. ゾンビ プロセスの終了ステータスは、親プロセスに対応する終了情報を伝える必要があるため、常に維持する必要があります。ただし、親プロセスはそれを読み取っていないため、子プロセスは常にゾンビ状態になります。
  2. ゾンビプロセスの終了情報は task_struct (PCB) に格納され、ゾンビ状態は終了しないため、PCB を常に維持する必要があります。
  3. 親プロセスが多くの子プロセスを作成し、それらをリサイクルしない場合、データ構造オブジェクト自体がメモリを占有するため、リソースの浪費が発生します。
  4. ゾンビ プロセスによって要求されたリソースは再利用できないため、ゾンビ プロセスが多いほど、実際に使用できるリソースは少なくなります。つまり、ゾンビ プロセスはメモリ リークを引き起こします。

6.オーファンプロセス

Linux におけるプロセス関係の多くは親子関係であり、子プロセスが先に終了し、親プロセスが子プロセスの終了情報を読まないプロセスをゾンビプロセスと呼びます。しかし、親プロセスが先に終了すると、将来子プロセスがゾンビ状態になったときに、それを処理する親プロセスがなくなります. このとき、子プロセスは孤立プロセスと呼ばれます.
孤立プロセスの終了情報が常に処理されていないと、孤立プロセスが常にリソースを占有し、メモリ リークが発生します。そのため、オーファンプロセスが発生すると、オーファンプロセスは1号のinitプロセスに採用され、オーファンプロセスがゾンビ状態になると、intプロセスによって処理およびリサイクルされます。

たとえば、次のコードの場合、fork 関数によって作成された子プロセスは常に情報を出力しますが、親プロセスは情報を 5 回出力すると終了し、この時点で子プロセスは孤立プロセスになります。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    
    

    pid_t id = fork();
    if(id == 0)
    {
    
    
        while(1)
        {
    
            
        	printf("我是一个子进程:pid: %d, ppid: %d\n",getpid(),getppid());
	        sleep(2);
        }   
    }
    else if(id > 0)
    {
    
    
    	int count = 5;
        while(count)
        {
    
     
            printf("我是一个夫进程:pid: %d, ppid: %d\n",getpid(),getppid());       
            sleep(1);
            count--}
        printf("父进程退出.......\n");
        exit(1);
    }
	else{
    
     //fork error
	}
    return 0;
}

コードの実行結果を観察します.親プロセスが終了しない場合,子プロセスのPPIDは親プロセスのPIDです.親プロセスが終了した後,子プロセスのPPIDは1になります.つまり,子プロセスはプロセス1で採用。
ここに画像の説明を挿入

7. プロセスの優先度

1. 基本コンセプト

プライオリティとは?

優先度は実際には特定のリソースを取得する順序であり、プロセスの優先度は実際にはプロセスが CPU リソースの割り当てを取得する順序であり、プロセスの優先度を指します。

優先順位はなぜ存在するのか?

優先度が存在する主な理由は、リソースが限られていることであり、プロセスの優先度が存在する主な理由は、CPU リソースが限られていることです.CPU は一度に 1 つのプロセスしか実行できず、複数のプロセスが存在する可能性があるため、プロセスの優先度レベルが必要で、プロセスが CPU リソースを取得する順序を決定します。

2. システム プロセスを確認する

Linux または Unix オペレーティング システムでは、ps -l コマンドを使用すると、次の内容が同様に出力されます。

ps -l

ここに画像の説明を挿入
次のように、いくつかの重要な情報に簡単に気付くことができます。

  • UID : エグゼキュータの ID を表します
  • PID : このプロセスのコード名を表します
  • PPID : このプロセスが派生したプロセス、つまり親プロセスのコード名を表します
  • PRI : このプロセスを実行できる優先度を表し、値が小さいほど早く実行されます
  • NI : このプロセスの優れた値を表します

3、PRI与NI

  • PRIはプロセスの優先度を表したもので、平たく言えばCPUが実行するプロセスの順番で、値が小さいほどプロセスの優先度が高くなります。
  • NI はナイス値の略で、プロセスを実行できる優先度の修正値を表します。
  • PRI 値が小さいほど実行速度が速くなり、nice 値を追加すると、PRI はPRI(new) = PRI(old) + NI になります。
  • NI 値が負の場合、プロセスの PRI は小さくなります。つまり、その優先度は高くなります。
  • Linux では、プロセスの優先順位を調整することは、プロセスの nice 値を調整することです。
  • NI の値の範囲は -20 ~ 19 で、合計 40 レベルです。
  • したがって、Linux でのプロセスの許可範囲は [80-20, 80+19] であり、数値が小さいほど優先度が高くなります。

注: Linux オペレーティング システムでは、PRI(old) のデフォルトは 80、つまり PRI = 80 + NI です。

4. プロセスの優先度情報を表示する

プロセスを作成するときに、ps -al コマンドを使用して、プロセスの優先度情報を表示できます。

ps -al

ここに画像の説明を挿入
: Linux オペレーティング システムでは、初期プロセスの一般優先度PRI のデフォルトは 80 でNI のデフォルトは 0 です

5. top コマンドでプロセスのナイス値を変更します

top コマンドは、Windows オペレーティング システムのタスク マネージャーに相当し、システム内のプロセスのリソース使用状況をリアルタイムで動的に表示できます。
ここに画像の説明を挿入

top コマンドを使用した後に「r」キーを押すと、 nice 値を調整するプロセスの PID を入力するよう求められます

ここに画像の説明を挿入

プロセス PID を入力して Enter キーを押すと、調整されたナイス値を入力するよう求められます。
ここに画像の説明を挿入

ナイス値を入力し、Enter キーを押して終了します.ここで入力したナイス値が 15 の場合、ps コマンドを使用してプロセスの優先度情報を表示でき、プロセスの NI が 15 に変更されていることがわかります. PRI は 95 (80+NI) になります。
ここに画像の説明を挿入
ここで変更するのは、子プロセスの nice 値です。

注: NI 値を負の値に調整する場合、つまりプロセスの優先順位を上げる場合は、sudo コマンドを使用して権限を昇格する必要があります。

[wyt@VM-20-4-centos test]$ sudo top

あとは上記と同様ですが、ナイス値を変更する場合はマイナスの数値を入力してください。
ここに画像の説明を挿入

6. renice コマンドでプロセスのナイス値を変更します

renice コマンドを使用し、その後に変更された nice 値とプロセスの PID を入力します。
ここに画像の説明を挿入
ここで少し混乱していることがわかります。最初はなぜ 90 なのですか?nice 値を -10 に変更すると、新しい PRI 値は 80 ではなく 70 になります。実際、nice 値を変更した後、デフォルトでは PRI のデフォルト値である 80 から計算が開始されます。だから70です。

8. 4 つの重要な概念

  • 競争力:多くのシステム プロセスがありますが、CPU リソースは少量または 1 つしかないため、プロセスは競争力があります。タスクを効率的に完了し、関連するリソースをより合理的に競うために、優先順位があります。

  • 独立性:マルチプロセス操作では、さまざまなリソースを排他的に使用する必要があり、マルチプロセス操作中に互いに干渉しません。

  • 並列処理:複数のプロセスが複数の CPU で同時に実行されます。これは並列処理と呼ばれます。

  • 並行性:複数のプロセスが1 つの CPU の下でプロセス切り替えを使用して、複数のプロセスが一定時間内に進行できるようにします。これを並行性と呼びます。

プロセス切り替えとは?
cpu には、次の命令のアドレスを指す eip レジスタ (PC ポインタ) があります。

プロセスが実行中の場合、プロセスは CPU を占有し、現在のプロセスに属する大量の一時データを生成します。CPU 内には 1 セットのレジスタ ハードウェアしかありませんが、レジスタに格納されたデータは現在のプロセスに属します。(レジスタは共有されますが、データは各プロセスにプライベートです)

When a process is running, it has its own time slice. この時間が経過すると、プロセスが実行されていなくても、CPU はオペレーティング システムによってストリップされ、次のプロセスが実行されるように CPU が解放されます。

では、このプロセスが CPU に戻って次回も実行を継続するとき、オペレーティング システムはこのプロセスのコードが実行された場所をどのように知るのでしょうか?

まず第一に、プロセスが切り替わるとき、コンテキスト保護が必要になり、いくつかの一時データが PCB に保存されます; プロセスが再開されるとき、コンテキストは復元される必要があり、プロセスが実行するために CPU に戻るとき、データがロードされます。PC ポインターを介して次のコード行を実行し続けます。

おすすめ

転載: blog.csdn.net/m0_58124165/article/details/129134213