フォーク三部作のクローンの誕生

この記事はフォーク三部作の追記です。最初に読むことをお勧めします。

この記事では、従来のUNIXフォークの後に、Linuxカーネルの従来のUNIXフォークの変形である素晴らしいクローンシステム呼び出しを行います。


フォークの本来の意味を理解するために、メルビン・コンウェイがフォークのアイデアを提案した元の論文A Multiprocessor System Designを見る必要があります 

https://archive.org/details/AMultiprocessorSystemDesignConway1963/page/n7 「プロセス」 と 「processpr」 の概念:

  • プロセスは、プロセッサで処理するために特定である必要はありません。

  • プロセッサは必ずしも特定のプロセスを処理するわけではありません。

  • システム内のプロセスの数とプロセッサの数は同じである必要はありません。

フォークは、上記のコアアイデアを実現する手段を提供します。その後、フォークはUNIXシステムに導入され、新しいプロセスを作成するために何十年も変更されていない一般的な操作になりました。

さらに興味深いのは、UNIXフォークが有名なfork-execシーケンスでよく知られていることです。これは、UNIXフォークが提供する並列マルチプロセッシングのためではありません。これは、スレッドコンセプトの出現後、並列処理がスレッドが責任を負っているので、誰もフォークを覚えていません。

一連のプロセスが完全に並列である場合、  相互に依存するリソースはありません。これが、最新のオペレーティングシステムプロセス(プロセス)抽象化の基礎です。プロセス抽象化に基づく最新のオペレーティングシステムは、それ自体が並列システムであることがわかります。並列化可能なシステムでは、リソースがプロセス間で分離され、結合操作が必要な場合は、IPCメカニズムが導入されます。

スレッドが登場する前に、forkによって提供された並列マルチプロセッシングがどれほど効率的であったかを見てみましょう。最も典型的な例は、TCPサービスプログラミングモデルです。

void handle_request(int csd)
{
	...
	// 读取请求
	ret = recv(csd, buf_req, len);
	ret = read(fd, buf_tosend, ret);
	ret = send(csd, buf_tosend, ret);
	close(csd);
}
void server(int sd)
{
	...
	while (1) {
		csd = accept(sd, 10);
		if (fork() == 0) {
			close(sd);
			handle_request(csd); // 可并行处理
		}
	}
}

これは、サーバープログラミングパラダイム、select / poll / epollプログラムを理解および設計するための前提条件、そして後のApache WebServerおよびNginxを理解するための基盤になりつつあります。

上記の単純なコード、WindowsのCreateProcess APIを使用して実装するにはどうすればよいですか?

スレッドAPIを使用せず、APIのみを処理します。複数のリクエストを並行して処理するには、CreateProcessはhandle_requestを実行するためにディスクプログラムイメージをロードする必要があります。イメージプログラムは次のようになります(これは最も効率的な書き込み方法ではありません。これはただの方法です。直接的な書き方):

void handle_request(int csd)
{
	...
	// 读取请求
	ret = recv(csd, buf_req, len);
	ret = read(fd, buf_tosend, ret);
	ret = send(csd, buf_tosend, ret);
	close(csd);
}
int main(int argc, char **argv)
{
	char *client_info = argv[1];
	int sd;

	sd = GetOrCreateSocket(client_info);
	handle_request(sd);
}

プログラムのロードによるイメージのオーバーヘッドが非常に大きいことはわかっていますが、並列処理の場合はそうする必要があります。そうでない場合、Windowsはhandle_requesと次のacceptをシリアルに処理する必要があります。Windowsにはフォークがなく、プロセスのフォークをいつでも実現できるメカニズムもありません。

もちろん、実際には、WindowsはマルチスレッドAPICreateThreadを使用してこれを行うことができます。マルチスレッドはマルチプロセスソリューションよりも効率的であるとも言えます。しかし、マルチスレッドがない場合、おそらくWindowsは、フォークの挑発に直面して飲み込んでため息をつくことしかできません。

したがって、UNIXフォークには2つのレベルの意味があります。

  1. 新しいプロセスを作成するために、fork-execシーケンス(フォーク自体ではない)は、WindowsCreateProcessまたはPOSIXスポーンと競合します。

  2. 並列マルチプロセッシング、フォークはマルチプロセスとしてマルチスレッドを競合します。

明らかに、どのレベルでも、フォークは対戦相手に遅れをとっています。

  1. 新しいプロセスを作成します。CreateProcess/ spawnは、不要なリソースコピー操作を排除します。

  2. 並列マルチプロセッシング、マルチスレッド共有のリソースは、高価なIPCに取って代わります。

マルチプロセスの最適化または置換として、マルチスレッドの本質とフォークの本来の意味はそれほど異ならないようです。唯一の違いは、リソース共有の深さです。

フォークの本来の意味は、Linuxカーネルタスクの設計で継続され、昇華されます!

Linuxカーネルの設計者は、これをずっと前に認識していたようです。初期の頃、Linuxカーネルはプロセスを表す構造を設計せず、task_struct(以下、タスクと呼びます)、構造のみを設計していました。一連の命令を実行するために必要な最小限のものが含まれています  したがって、プロセスまたはスレッドの概念に固有のフィールドは含まれていません。

タスクオブジェクトまたはタスクのグループとは正確には、それをどのように展開するかによって異なります。 それは、同じ言葉、異なる組み合わせ、または呪いや祝福を使うようなものです。

タスクオブジェクトは単なる原材料であり、他のタスクオブジェクトとのリソース共有関係によってそれが何であるかが決まります。

この写真をリリースする時が来ました:640?wx_fmt = png

enum pid_type
{
    PIDTYPE_PID,   
    PIDTYPE_TGID, 
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX
};

上の図の詳細については、次の記事「ナイーブUNIX-プロセス/スレッドモデル」を参照してください 

https://blog.csdn.net/dog250/article/details/40208219

基礎となるタスクの柔軟な設計に対応して、アプリケーションには、この柔軟性に適応するためのインターフェイスを与える必要があります。この適応は、非常に初期のLinuxカーネル(少なくともバージョン2.2)にすでに存在するLinuxクローンシステム呼び出しによって行われます。

#define _GNU_SOURCE
#include <sched.h>

int clone(int (*fn)(void *), void *child_stack,
          int flags, void *arg, ...
          /* pid_t *ptid, void *newtls, pid_t *ctid */ );

/* For the prototype of the raw system call, see NOTES */

多くのパラメータがあることがわかります。ここでのflagsパラメータを使用すると、呼び出し元は子プロセスとリソースを共有する方法を制御できます。この制御権を持つことは、cloneとforkの最大の違いです。640?wx_fmt = png

#define _GNU_SOURCE

これは、クローンが非標準であることを意味します。確かに、それはLinuxの単なるシステム呼び出しです。この柔軟なクローン呼び出しの存在は、完全にLinuxカーネルの下部にあるタスクの柔軟な設計によるものです。

従来のUNIXシステムまたはUNIXに似たシステムでは、クローンは実装されていません。これは、UNIXが最初からプロセスを明確に定義していたためかもしれませんが、その後、UNIXがスレッドをサポートする必要が生じたときに、 特定のリソースを共有できるプロセスを意味する、いわゆる軽量プロセスの新しい概念を導入する必要がありました 。 。有名なブランドUNIXSolarisでのlwpの実現をご覧ください。

これらの古いUnixシステムでは、最初は重すぎたプロセスコンセプトが、マルチスレッドメカニズムを導入する際に障害を引き起こしていました。ただし、Linuxの場合、スレッドをサポートするために新しいデータ構造を導入する必要はまったくありません。

クローン呼び出しは軽量プロセスを作成するとよく言われますが、これは単なる名前です。Linuxカーネルには、軽量プロセスを表す構造はありません。

Linuxカーネルの低レベルのタスク設計とシステム呼び出しインターフェースの設計は、Posixスレッド仕様を実装するのが非常に簡単になるように運命づけられています。クローンパラメータを実行できます:640?wx_fmt = png「(Linux2.4.0以降)」 アノテーション。これは、Linuxカーネルが2.4カーネルより前のPosixスレッドをサポートしていないことを意味します。ただし、ここではサポートされていません。Posix仕様で必要なスレッドのセマンティクスをカーネルレベルで実装してはならないというだけです。並列マルチプロセッシングメカニズムでサポートされていないという意味ではありません。POSIXスレッドのセマンティクスについては、ユーザーモードでのサポートも方法です。それはすべて2.4カーネルの前です。

2.4カーネル以降、Linuxのスレッドのサポートは完全にカーネルレベルになります。pthreadライブラリは完全にCLONE_THREADに基づいています。CLONE_THREADのコメントは、上の図に示されているクローンのマニュアルを参照しています。

具体的にスレッドを作成するにはどうすればよいですか?一番下で何が起こったのですか?以下の最も簡単なデモを参照してください。

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

void *func(void *unused)
{
	printf("sub thread\n");
	return (void *)123;
}

int main(int argc, char **argv)
{
	pthread_t t1;
	void *p;

	pthread_create(&t1, NULL, *func, NULL);
	pthread_join(t1, &p);
	printf("main thread:%d\n", (int)p);
	return 0;
}

スレッドに関しては、作成と破棄という2つの重要なポイントがあります。それを追跡しましょう:640?wx_fmt = png

  • 黄色:共有されているリソース、MM、FILES、FSなどを示します。

  • 赤:プロセスPIDの共有や信号送信など、POSIXスレッドのセマンティクスを実現します。

クローン作成後、スレッドが作成されます。スレッドはfuncを実行した後、終了します。問題は、スレッドはどのように終了するのかということです。

通常のCプログラムの場合、メイン関数がCライブラリに戻り、Cライブラリがexitを呼び出して、メインが戻った後にプログラムを終了することがわかっています。マルチスレッドプログラムの場合、コードをコンパイルするときに、Cと同様のlibpthreadを明示的にリンクします。 libpthreadライブラリは、マルチスレッドプログラムの処理を行います。

おおよそのpthread_createは次のようになります。

void clone_func(Thread *thread)
{
	ret = thread->fn(...);
	exit(ret);
}
int pthread_create(..., fn, ...)
{
	thread = malloc(sizeof(&thread));
	thread->fn = fn;
	ret = clone(clone_func, &thread);
	return ERR_NO(ret);
}

上記のstraceから、スレッドはexitシステム呼び出しを使用して終了し、メインプロセスはexit_groupシステム呼び出しを使用して終了することがわかります。2つの違いは、Posixプロセス/スレッドのセマンティクスにあります。厳密に言えば、exitシステム呼び出しexit_groupは、現在のtask_structが配置されているプロセスのすべてのtask_structを終了しますが、現在のtask_structを終了するだけです。マルチスレッドプログラムの場合、確実にすべてのスレッドを終了します。

これは、Linuxカーネルレベルのスレッドの実現原理です。

ただし、cloneシステム呼び出しは、マルチスレッドの単一の実装をはるかに超えており、別のレベルのUNIXフォークを最適化することもできます。2つのレベルでの従来のUNIXフォークのユーティリティによると、Linuxクローンの対応する説明は次のとおりです。

  1. 新しいプロセスを実行するレベルでは、cloneはCLONE_VMを使用してのみ軽量プロセス高速実行を実装し、不要なリソースのコピーを回避できます。

  2. 並列マルチプロセッシングのレベルでは、前述のように、CLONE_THREADと組み合わせたクローンCLONE_XXは、カーネルレベルのPOSIXスレッドを実装できます。


この記事はフォークに関する投稿ですが、フォークがそうではないことは言うまでもなく、フォークのアイデアは最終的にLinuxに継承されて引き継がれ、1963年にすべてがコンウェイの元の論文に戻り、並列マルチプロセッシングが行われ、最終的にLinuxクローンシステム呼び出しで取得されました実装する:

  • クローンは、マルチスレッドの並列実行シーケンスを作成できます。

  • クローンは新しいプロセスを作成し、不要なリソースの重複を減らします。

さて、これは私があなたに伝えたい「フォーク」の 話です 


浙江文州の革靴は濡れているので、雨でも太りません。

(終了)

よりエキサイティングな、すべて「Linuxリーディングフィールド」で、以下のQRコードをスキャンしてフォローしてください

迅速な再投稿またはクリックして表示することが、私たちの最大のサポートです。

おすすめ

転載: blog.csdn.net/juS3Ve/article/details/101086067