goスケジューラのソースコード9番目のシナリオ分析:オペレーティングシステムのスレッドとスレッドのスケジューリング

次のコンテンツはhttps://mp.weixin.qq.com/s/OvGlI5VvvRdMRuJegNrOMgから複製されてい ます

阿波はオリジナルプログラムを書くのが大好き張 ソーストラベル 2019-04-25

この記事は、「GoSchedulerソースコードシナリオ分析」シリーズの最初の章の予備知識の9番目のセクションです。

goroutineのスケジューラーを深く理解するには、オペレーティングシステムスレッドの一般的な理解が必要です。goのスケジューリングシステムはオペレーティングシステムスレッド上に構築されているため、簡単に紹介します。

特にマルチスレッドプログラミングに触れたことがない読者にとっては、スレッドの正確でわかりやすい定義を与えることは困難です。スレッドが何であるかを理解するのは簡単ではないかもしれないので、定義を脇に置いて、 Cから直接開始言語プログラムは、スレッドが何であるかを視覚的に確認し始めます。C言語を使用する理由は、通常、C言語でpthreadスレッドライブラリを使用し、スレッドライブラリを使用して作成されたユーザーモードスレッドは、実際にはLinuxオペレーティングシステムカーネルでサポートされているスレッドであり、ワーカーと同じであるためです。 go言語のスレッド。、これらのスレッドはLinuxカーネルによって管理およびスケジュールされ、go言語はオペレーティングシステムスレッドの上にゴルーチンを作成して、2レベルのスレッドモデルを実装します。

#include <stdio.h> 
#include <unistd.h> 
#include <pthread.h> 

#define N(1000 * 1000 * 1000)

volatile int g = 0; 

void * start(void * arg)
{ 
        int i; 

        for(i = 0; i <N; i ++){ 
                g ++; 
        } 

        NULLを返します。
} 

int main(int argc、char * argv [])
{ 
        pthread_t tid; 

        //使用pthread_create関数数创建一OT新線程执行start関数数
        pthread_create(&tid、NULL、start、NULL); 

        for(;;){ 
                usleep(1000 * 100 * 5); 
                printf( "loop g:%d \ n"、g); 
                if(g == N){ 
                        ブレーク;
                } 
        } 

        pthread_join(tid、NULL); //子スレッドの実行が終了するのを待つreturn0 

        ; 
}

プログラムの実行後、2つのスレッドがあります。1つはオペレーティングシステムがプログラムをロードするときに作成されるメインスレッドで、もう1つはpthread_createを呼び出すメインスレッドによって作成される開始サブスレッドです。メインスレッドはその後1つおきに作成されます。サブスレッドが作成されます。gが10億に等しくなるまで、グローバル変数gの値を500ミリ秒で出力し、開始スレッドが開始した後、gを1ずつインクリメントする10億サイクルの実行を開始します。これらの2つのスレッドシステム内で同時に実行され、オペレーティングシステムが責任を負います。それらをスケジュールすることにより、スレッドがいつ実行されるかを正確に予測することはできません。

オペレーティングシステムのスレッドのスケジューリングに関して、明確にする必要がある2つの問題があります。

  • スケジューリングはいつ行われますか?

  • スケジューリング中に何が行われますか?

最初の質問を最初に見てみましょう。オペレーティングシステムはいつスケジューリングを開始しますか?一般的に、オペレーティングシステムはスケジューリングを開始する前にCPUの制御を取得する必要があります。では、カーネルが制御を取得できるように、ユーザープログラムがCPUで実行されているときに、CPUはどのようにオペレーティングシステムコードを実行できますか?一般的に、2つの場合、オペレーティングシステムコードを実行するためにユーザープログラムコードが実行されます。

  1. ユーザープログラムは、システムコールを使用してオペレーティングシステムカーネルに入ります。

  2. ハードウェア割り込み。ハードウェア割り込みハンドラはオペレーティングシステムによって提供されるため、ハードウェアに割り込みが発生すると、オペレーティングシステムのコードが実行されます。ハードウェア割り込みには、特に重要なクロック割り込みがあります。これは、オペレーティングシステムがプリエンプティブスケジューリングを開始するための基礎となります。

オペレーティングシステムは、オペレーティングシステムコードを実行するパスの特定のポイントでスケジューリングが必要かどうかを確認するため、オペレーティングシステムのスレッドのスケジューリングは、上記の2つの状況でもそれに応じて行われます。

著者のシングルコアコンピューターでプログラムを実行した場合の出力を見てみましょう。

bobo @ ubuntu:〜/ study / c $ gcc thread.c -o thread -lpthread 
bobo @ ubuntu:〜/ study / c $ ./thread 
loop g:98938361 
loop g:198264794 
loop g:297862478 
loop g:396750048 
loop g :489684941
ループg:584723988
ループg:679293257
ループg:777715939
ループg:876083765
ループg:974378774
ループg:1000000000

出力から、メインスレッドと開始スレッドが順番に実行されていることがわかります。これは、オペレーティングシステムがそれらをスケジュールした結果です。オペレーティングシステムは、開始スレッドをしばらく実行するようにスケジュールしてから、メインスレッドをスケジュールします。再度実行します。

プログラムの出力から、メインスレッドが開始スレッド中に実行されており、開始スレッドによって実行される開始関数にシステムコールがまったくなく、このプログラムがで実行されているため、プリエンプティブスケジューリングの図を見ることができます。シングルコアシステム。他のCPUはメインスレッドを実行するため、中断中にプリエンプティブスケジューリングが発生しない場合、オペレーティングシステムはCPUの制御を取得できず、スレッドスケジューリングは発生しません。

次に、スレッドをスケジュールするときにオペレーティングシステムが何をするかを見てみましょう。

上記のように、オペレーティングシステムは同じCPUで実行するように異なるスレッドをスケジュールし、各スレッドは実行時にCPUレジスタを使用しますが、各CPUにはレジスタのセットが1つしかないため、オペレーティングシステムはスレッドBをスケジュールします。 CPUの場合、最初にメモリで実行されていたスレッドAが使用するすべてのレジスタ値を保存してから、メモリに保存されたスレッドBのすべてのレジスタ値をCPUレジスタに戻す必要があります。このようにして、スレッドBを以前に実行していた状態に復元してから実行することができます。

一般レジスタに加えて、スレッドスケジューリング中にオペレーティングシステムが保存および復元する必要のあるレジスタには、命令ポインタレジスタrip、スタックトップレジスタrsp、およびスタックに関連するスタックベースアドレスレジスタrbpも含まれます。ripレジスタは決定します。スレッドが実行する必要のある次の命令。2つのスタックレジスタは、スレッドの実行時に使用する必要のあるスタックメモリを決定します。したがって、CPUレジスタの値を復元することは、CPUによって実行される次の命令を変更すると同時に、関数呼び出しスタックを切り替えることと同じです。したがって、スケジューラの観点から、スレッドには少なくとも次のものが含まれます。 3つの重要な内容:

  • 一連の汎用レジスターの値

  • 次に実行される命令のアドレス

  • スタック

したがって、オペレーティングシステムによるスレッドのスケジューリングは、カーネルスケジューラによって異なるスレッドによって使用されるレジスタとスタックの切り替えとして簡単に理解できます。

最後に、オペレーティングシステムスレッドの単純で不正確な定義があります:オペレーティングシステムスレッドはカーネルによってスケジュールされ、独自のレジスタ値のプライベートセットとスタック実行フローを持っています。


最後に、この記事がお役に立てば幸いです。記事の右下にある「Looking」をクリックするか、友達のサークルに転送してください。ありがとうございます。

画像

おすすめ

転載: blog.csdn.net/pyf09/article/details/115238594