スレッドはLinuxのプロセスとして実装されていますか?


65

私は通過つもりだこの本マーク・ミッチェル、ジェフリー・オールダム、そしてアレックス・サミュエルことにより、高度なLinuxのプログラミング。2001年からですので、少し古いです。しかし、とにかくかなり良いと思います。

しかし、シェル出力でLinuxが生成するものとは異なる点に到達しました。92ページ(ビューアでは116ページ)の4.5 GNU / Linuxスレッド実装の章は、次の文を含む段落から始まります。

GNU / LinuxでのPOSIXスレッドの実装は、他の多くのUNIX系システムでのスレッド実装とは重要な点で異なります。GNU/ Linuxでは、スレッドはプロセスとして実装されます。

これは重要なポイントのようで、後でCコードで説明します。本の出力は次のとおりです。

main thread pid is 14608
child thread pid is 14610

そして、私のUbuntu 16.04では:

main thread pid is 3615
child thread pid is 3615

ps 出力はこれをサポートします。

2001年から現在までに何かが変わったに違いないと思います。

次のページの次のサブセクション4.5.1シグナル処理は、前のステートメントに基づいています。

シグナルとスレッド間の相互作用の動作は、UNIXに似たシステムごとに異なります。GNU / Linuxでは、スレッドはプロセスとして実装されるという事実によって動作が決定されます。

そして、これはこの本の後半でさらに重要になるようです。誰かがここで何が起こっているのか説明できますか?

私はこれを見ましたLinuxカーネルスレッドは本当にカーネルプロセスですか?、しかしそれはあまり役に立ちません。よくわかりません。

これはCコードです:

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

void* thread_function (void* arg)
{
    fprintf (stderr, "child thread pid is %d\n", (int) getpid ());
    /* Spin forever. */
    while (1);
    return NULL;
}

int main ()
{
    pthread_t thread;
    fprintf (stderr, "main thread pid is %d\n", (int) getpid ());
    pthread_create (&thread, NULL, &thread_function, NULL);
    /* Spin forever. */
    while (1);
    return 0;
}

1
混乱の原因はわかりません。スレッドは、親とアドレス空間を共有するプロセスとして実装されます。
ヨハンMyréen17年

2
@JohanMyréenでは、なぜスレッドPIDが等しいのですか?
トマス

ああ、今は見える。はい、何かが本当に変わりました。@ilkkachuの回答を参照してください。
ヨハンMyréen17年

5
スレッドはまだプロセスとして実装されていますが、現在getpidスレッドグループIDと呼ばれるものを返し、使用する必要のあるプロセスの一意のIDを取得しますgettid。ただし、カーネル以外のほとんどの人とツールは、他のシステムとの一貫性のために、スレッドグループをプロセスと呼び、プロセスをスレッドと呼びます。
user253751

あんまり。プロセスには独自のメモリとファイル記述子があり、スレッドと呼ばれることはありません。スレッドと呼ばれることは、他のシステム一貫性があります。
reinierpost

回答:


50

clone(2)マニュアルページのこの部分で、違いが明らかになると思います。PID:

CLONE_THREAD(Linux 2.4.0-test8以降)
CLONE_THREADが設定されている場合、子プロセスは呼び出しプロセスと同じスレッドグループに配置されます。
スレッドグループは、単一のPIDを共有する一連のスレッドのPOSIXスレッド概念をサポートするために、Linux 2.4で追加された機能でした。内部的には、この共有PIDは、スレッドグループのいわゆるスレッドグループ識別子(TGID)です。Linux 2.4以降、getpid(2)を呼び出すと、呼び出し元のTGIDが返されます。

「スレッドはプロセスとして実装される」という表現は、スレッドが過去に別々のPIDを持っていた問題を指します。基本的に、Linuxはもともとプロセス内にスレッドを持たず、仮想メモリやファイル記述子などの共有リソースを持っている可能性のある個別のプロセス(個別のPID)を備えていました。CLONE_THREADプロセスID (*)とスレッドID の分離により、Linuxの動作は他のシステムのように見え、この意味でPOSIX要件のようになります。技術的には、OSにはまだスレッドとプロセスの個別の実装がありません。

シグナル処理は、古い実装ではもう1つの問題のある領域でした。これについては、@ FooF が回答で言及している論文で詳しく説明されています

コメントで述べたように、Linux 2.4は本と同じ年である2001年にもリリースされたので、ニュースがその印刷物に届かなかったことは驚くことではありません。


2
仮想メモリやファイル記述子などの共有リソースを持っている可能性のある個別のプロセス。Linuxスレッドが動作する方法はまだほとんどありますが、あなたが言及した問題はクリーンアップされています。カーネルで使用されるスケジューリングユニットを「スレッド」または「プロセス」と呼ぶことは、実際には無関係です。Linux上で「プロセス」のみと呼ばれるようになったからといって、それだけではないというわけではありません。
アンドリューヘンレ

@AndrewHenle、ええ、少し編集しました。私は言葉遣いに苦労しているように見えますが、それがあなたの考えを捉えることを願っています。(必要に応じて先に進んでその部分を編集してください。)他のUnixライクなOSにはスレッドとプロセスがより明確に分離されているものがあることを理解しています。両方の機能。しかし、私は他のシステムについて十分に知らないし、ソースが手元にないので、具体的なことを言うのは難しい。
-ilkkachu

@tomasこの回答は、Linuxの現在の動作を説明していることに注意してください。ilkkachuが示唆しているように、本が書かれたときは異なった働きをしました。FooFの答えは、当時のLinuxの仕組みを説明しています。
ジル 'SO-悪であるのをやめる'

38

確かに、「2001年から現在までに何かが変わったに違いありません」。あなたが読んでいる本は、LinuxThreadsと呼ばれるLinuxでのPOSIXスレッドの最初の歴史的な実装に従って世界を説明しています(Wikipediaの記事も参照してください)。

LinuxThreadsには、POSIX標準との互換性の問題(PIDを共有していないスレッドなど)やその他の重大な問題がありました。これらの欠陥を修正するために、NPTL(Native POSIX Thread Library)と呼ばれる別の実装がRed Hatの先頭に立ち、必要なカーネルとユーザー空間ライブラリのサポートを追加してPOSIXコンプライアンスを改善しました(NGPTと呼ばれるIBMによるさらに別の競合する再実装プロジェクトから良い部分を取得します)次世代Posixスレッド」)、NPTLに関するウィキペディアの記事を参照してください。clone(2)システムコールに追加された追加フラグ(特に彼の回答CLONE_THREAD@ikkkachu指摘されている)は、おそらくカーネル変更の最も明白な部分です。作業のユーザー空間部分は、最終的にGNU Cライブラリに組み込まれました。

それでも、今日、彼らはのlibc小さいメモリフットプリント版と呼ばれる使用しているため、いくつかの組込みLinux SDKが古いLinuxThreadsの実装を使用して(もμClibc呼ばれる)uClibcをし、それが移植されたGNUでLibCからNPTLユーザ空間の実装前の年のかなりの量を取ったし、としデフォルトのPOSIXスレッドの実装。一般的に、これらの特別なプラットフォームは、超高速で最新の方法に従うようには努力していません。これは、これらのプラットフォーム上の異なるスレッドのPIDも、POSIX標準の仕様とは異なり、実際に読んでいる本が説明しているように異なっていることに気づくことで観察できます。実際に電話したらpthread_create()、混乱をまとめるために追加のプロセスが必要になったため、突然プロセス数を1から3に増やしました。

Linux pthreads(7)のマニュアルページには、2つの違いの包括的で興味深い概要が記載されています。時代遅れではあるが、違いについてのもう一つの啓発的な説明は、NPTLの設計に関するUlrich DepperとIngo Molnarによるこの論文です。

本のその部分をあまり真剣に受け取らないことをお勧めします。代わりに、ブテンホフのプログラミングPOSIXスレッドと、このテーマに関するPOSIXおよびLinuxのマニュアルページをお勧めします。このテーマに関する多くのチュートリアルは不正確です。


22

(ユーザー空間)スレッドは、独自のプライベートアドレス空間を持たず、親プロセスのアドレス空間を共有するという点で、Linuxではプロセスとして実装されていません。

ただし、これらのスレッドはカーネルプロセスアカウンティングシステムを使用するように実装されているため、独自のスレッドID(TID)が割り当てられますが、親プロセスと同じPIDおよび 'スレッドグループID'(TGID)が与えられます-これは対照的ですフォーク。新しいTGIDとPIDが作成され、TIDはPIDと同じです。

そのため、最近のカーネルにはクエリ可能な個別のTIDがあったようです。これはスレッドごとに異なるため、上記のmain()thread_function()のそれぞれでこれを示す適切なコードスニペットは次のとおりです。

    long tid = syscall(SYS_gettid);
    printf("%ld\n", tid);

したがって、これを含むコード全体は次のようになります。

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

void* thread_function (void* arg)                                                                                                                             
{                                                                                                                                                             
    long tid = syscall(SYS_gettid);                                                                                                                           
    printf("child thread TID is %ld\n", tid);                                                                                                                 
    fprintf (stderr, "child thread pid is %d\n", (int) getpid ());                                                                                            
    /* Spin forever. */                                                                                                                                       
    while (1);                                                                                                                                                
    return NULL;                                                                                                                                              
}                                                                                                                                                             

int main ()                                                                                                                                                   
{                                                                                                                                               
    pthread_t thread;                                                                               
    long tid = syscall(SYS_gettid);     
    printf("main TID is %ld\n", tid);                                                                                             
    fprintf (stderr, "main thread pid is %d\n", (int) getpid ());                                                    
    pthread_create (&thread, NULL, &thread_function, NULL);                                           
    /* Spin forever. */                                                                                                                                       
    while (1);                                                                                                                                                
    return 0;                                                                                                                                                 
} 

次の出力例を示します。

main TID is 17963
main thread pid is 17963
thread TID is 17964
child thread pid is 17963

3
@tomas einonmが正しい。本が言っていることを無視してください、それはひどく混乱しています。Dunnoは、アイデアの著者が伝えたいことを知っていましたが、ひどく失敗しました。したがって、Linuxにはカーネルスレッドとユーザースペーススレッドがあります。カーネルスレッドは、基本的にユーザースペースのないプロセスです。ユーザー空間のスレッドは、通常のPOSIXスレッドです。ユーザー空間プロセスはファイル記述子を共有し、コードセグメントを共有できますが、完全に独立した仮想アドレス空間に存在します。プロセス内のユーザースペーススレッドは、コードセグメント、静的メモリ、およびヒープ(動的メモリ)を共有しますが、プロセッサレジスタセットとスタックは別々です。
ボリスブルコフ

8

基本的に、Linuxのスレッドの実装履歴がひどく悪いため、本の情報は歴史的に正確です。SOに関する関連する質問に対する私によるこの回答は、あなたの質問に対する回答としても役立ちます。

https://stackoverflow.com/questions/9154671/distinction-between-processes-and-threads-in-linux/9154725#9154725

これらの混乱はすべて、カーネルがメモリとファイル記述子を共有する方法を提供している限り、カーネルプロセスがプリミティブとしてカーネルをほぼ完全にユーザー空間に実装できるという不合理で間違った見方をカーネル開発者が持っていたという事実に起因しています。これは、POSIXスレッドの悪名高い悪いLinuxThreads実装につながります。これは、POSIXスレッドのセマンティクスに似たものをリモートで提供しないため、むしろ誤った呼び名でした。最終的にはLinuxThreadsが(NPTLに)置き換えられましたが、多くの紛らわしい用語と誤解が残っています。

最初に認識すべき最も重要なことは、「PID」がカーネル空間とユーザー空間で異なることを意味するということです。カーネルがPIDと呼ぶものは、実際にはカーネルレベルのスレッドID(多くの場合TIDと呼ばれます)pthread_tであり、別の識別子であると混同しないでください。システム上の各スレッドは、同じプロセスであろうと異なるスレッドであろうと、一意のTID(またはカーネルの用語では「PID」)を持っています。

一方、「プロセス」というPOSIXの意味でPIDと見なされるものは、カーネルでは「スレッドグループID」または「TGID」と呼ばれます。各プロセスは、それぞれが独自のTID(カーネルPID)を持つ1つ以上のスレッド(カーネルプロセス)で構成されますが、すべて同じTGIDを共有しmainます。これは、実行される初期スレッドのTID(カーネルPID)と同じです。

ときにtopあなたのスレッドを示し、それはのTID(カーネルのPID)、ではないのPID(カーネルTGIDs)を見せて、各スレッドが別々のものを持っている理由です。

NPTLの出現により、PID引数を取るか、呼び出しプロセスで動作するほとんどのシステムコールは、PIDをTGIDとして扱い、「スレッドグループ」全体(POSIXプロセス)で動作するように変更されました。


8

内部的には、Linuxカーネルにはプロセスやスレッドなどはありません。プロセスとスレッドは主にユーザーランドの概念であり、カーネル自体は「タスク」のみを参照します。「タスク」は、リソースを他のタスクと共有しない、一部、またはすべてのスケジュール可能なオブジェクトです。スレッドは、ほとんどのリソース(アドレススペース、mmap、パイプ、オープンファイルハンドラー、ソケットなど)を親タスクと共有するように構成されたタスクであり、プロセスは、最小限のリソースを親タスクと共有するように構成されたタスクです。 。

Linux APIを直接使用する場合fork()およびpthread_create()の代わりにclone())、共有するリソースまたは共有しないリソースの量をより柔軟に定義でき、完全ではないタスクを作成できます処理も完全なスレッドも。これらの低レベルの呼び出しを直接使用する場合、すべてのリソースを親タスクと実際に共有する新しいTGID(ほとんどのユーザーランドツールによってプロセスとして扱われる)でタスクを作成することもできます。親タスクとリソースを共有しない共有TGIDを持つタスク(したがって、ほとんどのユーザーランドツールによってスレッドとして扱われます)。

Linux 2.4はTGIDを実装していますが、これは主にリソースアカウンティングのためだけです。多くのユーザーとユーザースペースツールは、関連するタスクをグループ化し、リソースの使用状況を一緒にレポートできると便利です。

Linuxでのタスクの実装は、ユーザースペースツールによって提示されるプロセスとスレッドの世界観よりもはるかに流動的です。


リンクされている@FooF の論文は、カーネルがプロセスとスレッドを別個のエンティティ(シグナル処理とexec()など)として考慮する必要があるいくつかのポイントを説明しているので、読んだ後は、「そんなものはありません」とは言いませんLinuxカーネルのプロセスまたはスレッドとして。」
イルッカチュ

5

Linus Torvaldsは、1996年のカーネルメーリングリストの投稿で、「スレッドとプロセスの両方が「実行のコンテキスト」として扱われる」と述べました。これは「そのCoEのすべての状態の複合体です...状態、MMUの状態、権限、さまざまな通信状態(開いているファイル、シグナルハンドラなど)」。

// simple program to create threads that simply sleep
// compile in debian jessie with apt-get install build-essential
// and then g++ -O4 -Wall -std=c++0x -pthread threads2.cpp -o threads2
#include <string>
#include <iostream>
#include <thread>
#include <chrono>

// how many seconds will the threads sleep for?
#define SLEEPTIME 100
// how many threads should I start?
#define NUM_THREADS 25

using namespace std;

// The function we want to execute on the new thread.
void threadSleeper(int threadid){
    // output what number thread we've created
    cout << "task: " << threadid << "\n";
    // take a nap and sleep for a while
    std::this_thread::sleep_for(std::chrono::seconds(SLEEPTIME));
}

void main(){
    // create an array of thread handles
    thread threadArr[NUM_THREADS];
    for(int i=0;i<NUM_THREADS;i++){
        // spawn the threads
        threadArr[i]=thread(threadSleeper, i);
    }
    for(int i=0;i<NUM_THREADS;i++){
        // wait for the threads to finish
        threadArr[i].join();
    }
    // program done
    cout << "Done\n";
    return;
}

ご覧のとおり、このプログラムは一度に25個のスレッドを生成し、各スレッドは100秒間スリープし、その後メインプログラムに再び参加します。25個すべてのスレッドがプログラムに再参加すると、プログラムは終了し、終了します。

これを使用topすると、「threads2」プログラムの25個のインスタンスを見ることができます。しかし、キドナは退屈。の出力ps auwxはさらに面白くありません... しかし、ps -eLfちょっと刺激的になります。

UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
debian     689   687   689  0    1 14:52 ?        00:00:00 sshd: debian@pts/0  
debian     690   689   690  0    1 14:52 pts/0    00:00:00 -bash
debian    6217   690  6217  0    1 15:04 pts/0    00:00:00 screen
debian    6218  6217  6218  0    1 15:04 ?        00:00:00 SCREEN
debian    6219  6218  6219  0    1 15:04 pts/1    00:00:00 /bin/bash
debian    6226  6218  6226  0    1 15:04 pts/2    00:00:00 /bin/bash
debian    6232  6219  6232  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6233  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6234  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6235  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6236  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6237  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6238  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6239  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6240  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6241  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6242  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6243  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6244  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6245  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6246  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6247  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6248  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6249  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6250  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6251  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6252  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6253  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6254  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6255  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6256  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6232  6219  6257  0   26 15:04 pts/1    00:00:00 ./threads2
debian    6260  6226  6260  0    1 15:04 pts/2    00:00:00 ps -eLf

ここでは、thread2プログラムが作成した26個のCoEすべてを見ることができます。それらはすべて同じプロセスID(PID)と親プロセスID(PPID)を共有しますが、それぞれに異なるLWP ID(軽量プロセス)があり、LWPの数(NLWP)は26のCoE(メインプログラムと25個のスレッドが生成されます。


正しい、スレッドは単なる軽量プロセス(LWP)
fpmurphy

2

それは、Linuxのプロセスやスレッドになるとしている一種の同じこと。つまり、同じシステムコールで作成されますclone

考えてみると、スレッドとプロセスの違いは、カーネルオブジェクトが子と親によって共有されることです。プロセスについては、それほど多くはありません。オープンファイル記述子、書き込まれていないメモリセグメント、おそらく頭の外では考えられない他のいくつかのセグメントです。スレッドの場合、多くのオブジェクトが共有されますが、すべてではありません。

Linuxでスレッドとオブジェクトを近づけるのは、unshareシステムコールです。共有として開始されるカーネルオブジェクトは、スレッドの作成後に共有解除できます。したがって、たとえば、同じプロセスの2つのスレッドに異なるファイル記述子スペースを持たせることができます(スレッドの作成後にファイル記述子の共有を取り消すことにより)。スレッドを作成し、unshare両方のスレッドで呼び出してからすべてのファイルを閉じて、両方のスレッドで新しいファイル、パイプ、またはオブジェクトを開くことで、自分でテストできます。次に見てみる/proc/your_proc_fd/task/*/fdと、各taskスレッド(スレッドとして作成したもの)が異なるfdを持っていることがわかります。

実際、新しいスレッドと新しいプロセスの両方の作成は、clone下で呼び出し、新しく作成されたprocess-thread-thingamajig(つまりtask)が呼び出しプロセス/スレッドと共有するカーネルオブジェクトを指定するライブラリルーチンです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.