【Linux】プロセス学習(2) --- プロセスの動作を理解する


プロセスを見る

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

ルート ディレクトリに proc という名前のシステム フォルダがあります (以下に示す結果を確認してください)。この proc フォルダには多くのプロセス情報が含まれており、その中には番号が付いているものもあります。これらの番号は、実際には特定のプロセスの PID であり、フォルダには、対応するプロセスに関するさまざまな情報が記録されています。PID 1 のプロセスのプロセス情報を表示する場合は、 1 という名前のフォルダーを表示できます/proc/1
ここに画像の説明を挿入

psコマンドで見る

1. ps コマンドを単独で使用すると、すべてのプロセス情報が表示されます。

[nan@VM-8-10-centos test_23_4_23]$ ps axj

2. ps コマンドと grep コマンドを併用して、特定のプロセスの情報のみを表示します。

ps axj | head -1 && ps axj | grep myproc | grep -v grep
//head -1  这个指令可以带上进程的小标题。
//grep -v grep 由于grep本身也是一个进程,加上这句话可以过滤掉grep这个进程的显示

ここに画像の説明を挿入
3.プロセスを中止する

法一:Ctrl+c
法二:kill -9 [进程PID]

システムコールでプロセス識別子を取得する

  • プロセス ID (PID)
  • 親プロセス ID (PPID)

を使用してシステムコール機能、getpid および getppid は、それぞれプロセスの PID および PPID を取得できます。
インクルードする必要があるヘッダー ファイル コードを#include<unistd.h> #include<sys/types.h>
使用して、次の状況を観察してみましょう。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
    
    
     while(1)
     {
    
    
        printf("你好,我已经是一个进程了,我的PID是:%d,我的父进程是:%d\n",getpid(),getppid());
        sleep(1);
     }
     return 0;                                                                                                                                                                    
 }

コードによって生成された実行可能プログラムを実行した後、プロセスの PID と PPID を周期的に出力できます。ps コマンドでプロセスの情報を参照できますが、ps コマンドで取得したプロセスの PID と PPID は、システム コール関数 getpid と getppid を使用して取得したプロセスの PID と PPID と同じであることがわかります。
ここに画像の説明を挿入
別の現象として、コマンド ラインでプログラムを繰り返し実行すると、プロセスの PID は毎回異なりますが、プロセスの PPID は同じです.ps を介して、親プロセスの次の属性情報を表示できます。このプロセスの親プロセスは bash であることがわかりました。

結論: コマンド ラインで開始されたすべてのプログラムは最終的にプロセスになり、このプロセスに対応する親プロセスは bash であるため、bash コマンド ライン インタープリターは本質的にプロセスです。bash はサブプロセスを fork してプログラムを実行しますが、プログラムにバグがあって終了した場合、それはサブプロセスの問題であり、bash には何の影響もありません。kill コマンドを使用して bash プロセスを終了すると、コマンド ラインが無効になります (興味のある学生はそれを試すことができ、再起動後に復元されます)。
ここに画像の説明を挿入

システムコールによるプロセスの作成

fork 関数を理解する

  • fork はシステムコールレベルの関数で、その関数は子プロセスを作成することです
  • 実行してman forkfork システムコール関数のユーザーマニュアルを表示
  • fork には 2 つの戻り値があります
  • 親プロセスと子プロセス間のコード共有、データ用の個別スペース、プライベート コピー (コピー オン ライト)

まず、テスト コードの一部を見てみましょう。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
    
    
    printf("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:PID:%d,PPID:%d\n",getpid(),getppid());                                                                                               
    fork();
    printf("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB:PID:%d,PPID:%d\n",getpid(),getppid());
    sleep(1);
    
    return 0;
}

実行結果:
ここに画像の説明を挿入

この操作の結果を見ると、B の情報が 2 行印刷されているのはなぜだろうかと疑問に思う人もいるでしょう。

説明: そして、実行結果から、1 回目に印刷された B の PID は 22063 (プロセス PID) であり、2 回目に印刷された B の PPID (親プロセス ID) も 22063 であることがわかります。この結果は、コードが実行されるとき fork 関数の後、子プロセスを自分で作成しました。そして、この子プロセスの親プロセスが実行する myproc プロセスであり、myproc プロセスの親プロセス 20166 が base です。fork によって子プロセスが作成された後に現在のプロセスが分割されるため (図に示すように)、出力結果には B の行が 2 行あります。1 つは現在の myproc プロセスで、もう 1 つは子プロセスです。
ここに画像の説明を挿入

親プロセスと子プロセス間のコード共有、データ用の個別スペース、プライベート コピー (コピー オン ライト)

fork の前は、親プロセスは PCB とコードとデータを持っています. fork が子プロセスを作成した後、親プロセスのコードとデータはコピーされませんが、カーネル内のプロセスに対応する別の PCB が作成されます.子プロセスのプロセス属性の一部は親プロセスによってテンプレート化され、子プロセスの PID や PPID などの属性のごく一部は子プロセス専用です。つまり、fork の後、親プロセスと子プロセスは (親プロセスの) コードとデータのコピーを共有します

fork 関数の戻り値

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

上記にAとBのテストコードを出力します.fork関数で作成した子プロセスは親プロセスとコードを共有していますが,親プロセスと子プロセスに同じことをさせても意味がありません. -else ステートメントは通常、fork の後に使用されます。シャントの場合、親プロセスと子プロセスは互いに独立しており、異なるタスクを実行できます。

テストコードは次のとおりです

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
    
    
     printf("I am running...\n");
     //接收fork函数的返回值
     pid_t id=fork();
 
     if(id==0)
     {
    
    
         //子进程
         while(1)
         {
    
    
            printf("我是子进程...\n");
             sleep(1);
         }
     }
     else if(id>0)
     {
    
    
         //父进程
         while(1)
         {
    
    
             printf("我是父进程...\n");
             sleep(1);
         }
     }
     else
     {
    
    
         //fork error
     }
     return 0;                                                                                                                                                                    
}

実行結果:親子工程サイクル印刷
ここに画像の説明を挿入
ここに画像の説明を挿入

上記のコードから、fork が子プロセスを作成した後、if-else ステートメントが実際には個別に実行されることが明確に理解できます。2 つの myproc プロセスが実行中です ~

結論:
a . fork 後、1 つの実行フローが 2 つの実行フローに変更されます。
b . fork の後、OS によって 2 つのプロセスがスケジュールされる順序は、オペレーティング システムのスケジューリング アルゴリズムの特定の実装によって異なります。
c . fork 後、fork 後のコードを共有し、if と else を使用して実行ストリーム分割を実行します。

注: 親プロセスと子プロセスは互いに独立しています。

プロセスが実行されているとき、それは独立しています! 親プロセスと子プロセスが実行されている場合、それらも独立しています。kill -9 子プロセスの PID を見ると、親プロセスが正常に動作していることがわかります。それらのコードは共有され、データはコピー オン ライト方式で個人所有されます。

フォークはコードとデータをどのように認識しますか?

コード: コードは読み取り専用、
データ: 実行フローがデータを変更しようとすると、オペレーティング システムは現在のプロセスのコピー オン ライトを自動的にトリガーします。親プロセスと子プロセスの間のデータは互いに影響しません。

プロセスの状態

ブロックされた状態と実行中の状態

プロセスのさまざまな状態を理解するには、まず何がブロックされ、何が実行されているかを理解する必要があります。

聞く?ソフトウェアを開くと、常に実行されていますか?

答えはノーです。CPU は次のプロセスを処理する前に 1 つのプロセスを処理しません。代わりに順番に処理を行っていますが、処理速度が非常に速いため、時間差を感じません。

ブロッキング状態: 特定のリソースが使用可能になるまでプロセスが待機するプロセス。

プロセスは、特定のリソースが他のリソースによって使用されるのを待ってから、それ自体で使用される必要があります。task_struct 構造体は、OS によって管理される何らかのリソースの下でキューに入れる必要があります。そのため、ある条件が整うのを待って処理が進まない、つまり処理が滞っている状態をブロッキングと呼びます。たとえば、銀行の窓口に行って事務処理をしているのに、店員が横に行ってフォームに記入するように頼んだ場合、ブロックされた状態になります。

プロセスは、CPU リソースだけでなく、ハードウェア リソースも占有します。CPU の場合、プロセスの要求をすばやく処理できますが、ネットワーク カードなどのハードウェアの場合、速度が非常に遅く、Thunder、Baidu Netdisk、QQ などのリソースを取得する必要があるプロセスが存在する可能性があります。また、キュー内の PCB オブジェクトのヘッド ノードを指すキュー ポインタを実行する task_struct* キューもあります。

それでは、CPU とハードウェアの速度差は非常に大きく、システムはこの速度のバランスをどのようにとるべきでしょうか? 実行状態のプロセスがハードウェア リソースにアクセスする必要があることを CPU が検出すると、アクセスする必要があるハードウェアの実行キューにそのプロセスをキューに入れ、CPU は次のプロセスの実行を続行します。

次に、CPU によってハードウェア実行キューに取り除かれたプロセス状態は、ブロック状態と呼ばれます。プロセスがハードウェアへのアクセスを終了すると、プロセスの状態は実行中の状態に変わります。つまり、プロセスは CPU の実行中のキューに戻ります。

概要: PCB はさまざまなキューで維持できます。

ブロッキングおよび保留状態: ハードウェアの速度は遅いですが、多数のプロセスがハードウェアにアクセスする必要があるため、必然的により多くのブロッキング プロセスが生成されます.これらのブロッキング プロセスのコードとデータは、短期間で実行されません.それらがすべてメモリに存在する場合、それらはメモリの使用を引き起こします。

この問題について, メモリ内にブロックされたプロセスが多すぎてメモリが不足している場合, オペレーティングシステムはそのコードとデータを最初にディスクに移動し, メモリスペースを節約するためにPCB構造だけを残します. このプロセス状態は保留中。プロセス関連のデータをディスクにロードまたは保存するプロセスは、メモリ データのスワップ インおよびスワップ アウトと呼ばれます。

プロセスのブロック状態は必ずしも中断状態ではなく、オペレーティング システムによっては、新しい状態の中断または実行中の中断状態になる場合があります。

Linux カーネル ソース コードのプロセス状態

実行中のプロセスが何を意味するのかを理解するには、プロセスのさまざまな状態を知る必要があります。プロセスにはいくつかの状態があります (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 *task_state_array[] = {
    
    
	"R (running)",       /*  0*/
    "S (sleeping)",      /*  1*/
    "D (disk sleep)",    /*  2*/
    "T (stopped)",       /*  4*/
    "T (tracing stop)",  /*  8*/
    "Z (zombie)",        /* 16*/
    "X (dead)"           /* 32*/
};

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

task_struct プロセス制御ブロックの内容分類

次に、各状態の定義を詳細に分析し始めます。
ここに画像の説明を挿入
プロセス状況表示コマンド

ps axj | head -1 && ps axj | grep 进程PID | grep -v grep

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

テストコード:

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

Query process status:
ここに画像の説明を挿入
R running status (running) : A process is running, but is not mean that the process is running. このプロセスは実行中か、実行中のキュー (キューイング) にある可能性があります。実行状態のすべてのプロセスは実行キューに入れられ、オペレーティング システムが実行するプロセスを切り替えると、実行するプロセスが実行キューから選択されます。

浅い眠り状態-S

スリープ状態の本質はブロッキング状態です。

テストコード:

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

プロセスのステータスを表示:
ここに画像の説明を挿入

ライト スリープ状態 S : プロセスはライト スリープ状態 (スリープ状態) にあります. 表面上は, プロセスは何かが完了するのを待っています. ライト スリープ状態のプロセスはいつでも起こすことができます. (浅い睡眠は中断可能な睡眠とも呼ばれます)。

一部の人々は、コードが明らかに実行されているのに、なぜブロックされた状態なのか疑問に思うかもしれません。
というのは, このテストコードは, 前回のテスト実行状態のコードよりも printf という印刷関数が一つ増えたからです. 印刷関数なので当然周辺機器(表示画面)にアクセスする必要があります. mytest プロセスの PCB ペリフェラルの実行中のキューに移動して、ペリフェラルを待機します (ブロックされた状態) CPU の処理速度はペリフェラルの速度よりもはるかに高速であるため、わずかな確率しかありません。プロセス ステータスが R であり、ほとんどのクエリがプロセス ステータスがまだ S であることがわかります。

ps: フォアグラウンド プロセスを示すためにステータスの後ろに + 記号があり、バックグラウンド プロセスを示す + 記号はありません。
フォアグラウンド プロセスは Ctrl+c でプロセスを終了できますが、バックグラウンド プロセスは端末によって制御されず、Ctrl+c でプロセスを終了することはできません。

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

ディープ スリープ状態-D: 割り込み不可能なスリープ状態とも呼ばれ、オペレーティング システムを含め、プロセスが強制終了されないことを意味します。 、プロセスは通常、IO が終了するのを待ちます。

一時停止状態-T

サスペンド状態の本質はブロッキング状態でもあります。

中断状態 -T (停止) : Linux では、SIGSTOP (kill -19 プロセス PID) を送信してプロセスを中断状態にし、SIGCONT シグナル (kill -18 PID) を送信して中断状態でプロセスを実行し続けることができます。州。
ここに画像の説明を挿入

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

ゾンビ状態 - Z

ゾンビ状態-Z (ゾンビ) : プロセスが終了しようとしているとき、オペレーティング システム OS はプロセスのリソースをすぐに解放しませんが、親プロセスまたはオペレーティング システムが戻り値を読み取れるようになるまでしばらく待機します。子プロセスの終了コード (つまり、終了コード) の結果、子プロセスの終了コードが読み取られない場合、ゾンビ プロセスが生成されますゾンビ プロセスは終了状態でプロセス テーブルに残り、親プロセスが終了ステータス コードを読み取るまで永久に待機します。したがって、子プロセスが終了する限り、親プロセスは引き続き実行されますが、親プロセスは子プロセスの状態を読み取らず、子プロセスはゾンビ状態 -Z に入ります。

たとえば、C/C++ コードを記述すると、常に最後に 0 が返されます.この 0 は、実際には終了コードであり、親プロセスが子プロセスを待機しているときに取得する必要がある結果でもあります.終了コードは、プロセス制御ブロックに一時的に保存されます。
親プロセスは、あるタスクを完了する子プロセスを派生させるものなので、最後に子プロセスのタスク完了ステータスも親プロセスに報告する必要があります。
Linux オペレーティング システムでは、echo $? コマンドを使用して、最後のプロセス exit の終了コードを取得できます

[nan@VM-8-10-centos test_23_4_27]$ echo $?

ゾンビ状態をシミュレートし、コードをテストします。次のコードの子プロセスは一度出力され、exit(1) を実行すると終了しますが、親プロセスは情報を出力し続けます。つまり、子プロセスは終了します。親プロセスはまだ実行中ですが、親プロセスが子プロセスの終了結果を読み取らない場合、子プロセスはゾンビ状態に陥ります。

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <stdlib.h>    
int main()    
{
    
        
    
    pid_t id=fork();    
    if(id==0)    
    {
    
        
        //子进程    
        while(1)    
        {
    
        
            printf("子进程,PID=%d,PPID=%d\n",getpid(),getppid());    
            sleep(1);    
            exit(1);    
        }    
    }                                                                                                                        
    else if(id>0)      
    {
    
                              
        //父进程    
        while(1)      
        {
    
      
            printf("父进程,PID=%d,PPID=%d\n",getpid(),getppid());
            sleep(1);   
        }
    }
    else 
    {
    
    
        perror("fork error\n");
        exit(-1);                                                                                                            
    }
    return 0;
}  

テスト結果を図に示します。
ここに画像の説明を挿入

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

  1. ゾンビ プロセスの終了ステータスは、プロセス (親プロセス) に通知する必要があるため、維持する必要があります。しかし、親プロセスが常にそれを読み取らない場合、子プロセスは常にゾンビ Z 状態になります。
  2. 終了ステータスを維持するには、プロセスの基本情報でもあるデータの維持が必要なため、ゾンビプロセスの終了情報は task_stuct (PCB) に格納されます. 親プロセスが子プロセスの終了結果を読み取っていない場合、その場合、Z 状態は終了せず、PCB は常に維持されます。
  3. 親プロセスが多くの子プロセスを作成しても、どれも再利用されない場合、データ構造オブジェクト (task_stuct) 自体がメモリを占有するため、メモリ リソースの浪費が発生します。メモリ リークなど、深刻な問題です。

デスステート-X

X デッド状態 (dead ): この状態は単なるリターン状態です。この状態はタスク リストには表示されません。プロセスの終了状態は、その親プロセスによってすぐに再利用されますが、これは速すぎてわかりません。

おすすめ

転載: blog.csdn.net/weixin_63449996/article/details/130322571