1. 序章

オペレーティングシステムのプロセスとスレッドの違いについて混乱したことがありますか? この記事では、Linuxのコンテキストでのプロセスとスレッドの詳細について説明します。

2. プロセス

プロセスは実行中のコンピュータプログラムです。 Linuxは常に多くのプロセスを実行しています。 ps コマンドを使用して端末で、またはシステムモニターUIでそれらを監視できます。 たとえば、psコマンドを使用してマシンで実行されているすべてのプロセスを表示する例を見てみましょう。

[user@fedora ~]$ ps -ef 
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 Jun28 ?        00:00:16 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
root           2       0  0 Jun28 ?        00:00:00 [kthreadd]
root           3       2  0 Jun28 ?        00:00:00 [rcu_gp]
root           4       2  0 Jun28 ?        00:00:00 [rcu_par_gp]
root           6       2  0 Jun28 ?        00:00:04 [kworker/0:0H-kblockd]
root           8       2  0 Jun28 ?        00:00:00 [mm_percpu_wq]
root           9       2  0 Jun28 ?        00:00:00 [rcu_tasks_kthre]
root          10       2  0 Jun28 ?        00:00:00 [rcu_tasks_rude_]
root          11       2  0 Jun28 ?        00:00:00 [rcu_tasks_trace]
root          12       2  0 Jun28 ?        00:00:11 [ksoftirqd/0]
root          13       2  0 Jun28 ?        00:01:24 [rcu_sched]
root          14       2  0 Jun28 ?        00:00:00 [migration/0]
root          16       2  0 Jun28 ?        00:00:00 [cpuhp/0]
root          17       2  0 Jun28 ?        00:00:00 [cpuhp/1]

新しいコマンド/アプリケーションを実行するか、古いコマンドが完了すると、プロセスの数が動的に増減することがわかります。 Linuxプロセスは分離されており、互いの実行を中断しません

PIDを使用すると、Linuxの任意のプロセスを識別できます。 内部的には、カーネルはこの番号を一意に割り当て、プロセスの終了後に再利用できるように解放します。 上記のpsコマンドの出力では、PIDが2番目の列として表示されます。

Linuxでは常に多くのプロセスが実行されているため、CPUを共有する必要があります。 CPU上で実行中の2つのプロセスを切り替えるプロセスは、プロセスコンテキスト切り替えと呼ばれます。カーネルは古いレジスタを保存し、現在のレジスタ、メモリマップ、およびその他のリソースをロードする必要があるため、プロセスコンテキストの切り替えにはコストがかかります

3. スレッド

スレッドは軽量プロセスです。 プロセスは、1つ以上のスレッドを作成することにより、複数の作業単位を同時に実行できます。 これらのスレッドは軽量であるため、すばやく生成できます。

例を見て、 ps -eLf コマンドを使用して、Linuxのプロセスとそのスレッドを特定しましょう。 PID、LWP、およびNLWP属性に関心があります。

  • PID:一意のプロセス識別子
  • LWP:プロセス内の一意のスレッド識別子
  • NLWP:特定のプロセスのスレッド数
[user@fedora ~]$ ps -eLf 
UID          PID    PPID     LWP  C NLWP STIME TTY          TIME CMD
root           1       0       1  0    1 Jun28 ?        00:00:16 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
root           2       0       2  0    1 Jun28 ?        00:00:00 [kthreadd]
root           3       2       3  0    1 Jun28 ?        00:00:00 [rcu_gp]
root           4       2       4  0    1 Jun28 ?        00:00:00 [rcu_par_gp]
root           6       2       6  0    1 Jun28 ?        00:00:05 [kworker/0:0H-acpi_thermal_pm]
root           8       2       8  0    1 Jun28 ?        00:00:00 [mm_percpu_wq]
root          12       2      12  0    1 Jun28 ?        00:00:11 [ksoftirqd/0]
root          13       2      13  0    1 Jun28 ?        00:01:30 [rcu_sched]
root          14       2      14  0    1 Jun28 ?        00:00:00 [migration/0]
root         690       1     690  0    2 Jun28 ?        00:00:00 /sbin/auditd
root         690       1     691  0    2 Jun28 ?        00:00:00 /sbin/auditd
root         709       1     709  0    4 Jun28 ?        00:00:00 /usr/sbin/ModemManager
root         709       1     728  0    4 Jun28 ?        00:00:00 /usr/sbin/ModemManager
root         709       1     729  0    4 Jun28 ?        00:00:00 /usr/sbin/ModemManager
root         709       1     742  0    4 Jun28 ?        00:00:00 /usr/sbin/ModemManager

NLWP値によって、シングルスレッドおよびマルチスレッドのプロセスを簡単に識別できます。 PID690および709のNLWPはそれぞれ2および4です。 したがって、それらは2スレッドと4スレッドのマルチスレッドです。 他のすべてのプロセスのNLWPは1で、シングルスレッドです。

注意深く観察すると、シングルスレッドプロセスは、同じものであるかのように同じPIDおよびLWP値を持っていることがわかります。 ただし、マルチスレッドプロセスでは、1つのLWPのみがそのPIDに一致し、他のLWPのLWPの値は異なります。 また、LWPに割り当てられた値は、別のプロセスに渡されることはないことに注意してください。

3.1. シングルスレッドプロセス

プロセス内で作成されたスレッドはすべて、プロセスの同じメモリとリソースを共有します。 シングルスレッドプロセスでは、発生することが1つしかないため、プロセスとスレッドは同じです。 検証することもできます ps -eLf PIDとLWPはシングルスレッドプロセスで同じであるという以前の説明からの出力。

3.2. マルチスレッドプロセス

マルチスレッドプロセスでは、プロセスに複数のスレッドがあります。 このようなプロセスは、複数のタスクを同時にまたはほぼ同時に実行します。

ご存知のように、 スレッドは、プロセスの同じアドレス空間を共有します。 したがって、プロセス内で新しいスレッドを生成するのは安価になります (システムリソースの観点から)新しいプロセスを開始する場合と比較して。 スレッドは、CPU内のプロセスと比較して、(プロセスとアドレス空間を共有しているため)より高速に切り替えることもできます。 内部的には、スレッドのメモリにはスタックしかなく、ヒープ(プロセスメモリ)を親プロセスと共有します。

このスレッドの性質により、軽量プロセス(LWP)とも呼ばれます。

同じメモリを他のスレッドと共有することには、長所と短所の両方があります。

最も重要な利点は、メモリとリソースを割り当てる必要がないため、プロセスよりも高速にスレッドを作成できることです。 もう1つの利点は、スレッド間通信のコストが低いことです。

プロセスコンテキストスイッチと同様に、スレッドコンテキストスイッチの概念があります。 スレッドは、切り替えが発生する前にスタック値のみを記録するため、スレッドコンテキストの切り替えが高速になります。

1つの大きな欠点があります:スレッドは同じメモリを共有するため、プロセスが多くの同時タスクを実行すると、スレッドが遅くなる可能性があります。

4. プロセス内部

このセクションでは、Linuxプロセスの内部とその実装について説明します。

4.1. システムコール

Linuxには、ファイル管理、ネットワーク管理、プロセス管理などの基本的なOS機能に対して定義されたシステムコールがあります。 有効なLinuxプログラムはすべて、これらのシステムコールを使用します。 したがって、アプリケーション開発を容易にするために、 GNUCライブラリはそれらをAPIとして公開します。

Linux でプロセスを作成するために、fork(またはclone)とexecveシステムコールを使用します。 ここで、 fork システムコールは、親プロセスと同等の子プロセスを作成します。 execve システムコールは、子プロセスの実行可能ファイルを置き換えます。 最近の実装では、forkシステムコールは内部でcloneシステムコールを使用します。 したがって、cloneシステムコールに焦点を当てます。

4.2. 内部構造

Linuxシステムユーザーとして、プロセスの内部データ構造を作成する必要はありません。 ただし、Linuxプロセスの内部データ構造を理解することが不可欠です。 Linuxは、Cのデータ構造を使用してすべてのプロセスを作成します。 task_struct。 Linuxカーネルは、それらを動的リストに保持して、呼び出されたすべての実行中のプロセスを表します。 タスクリスト。 これでタスクリスト 、各要素は task_struct タイプ。Linuxプロセスを表します。

タスク構造のさまざまなフィールドを、スケジューリングパラメータ、メモリイメージ、信号、マシンレジスタ、システムコールの状態、ファイル記述子、カーネルスタックなどに分類します。 したがって、新しいプロセスを作成すると、Linuxカーネルはカーネルメモリに新しい task_struct を作成し、新しく作成されたprocessを指します。

4.3. 作成フロー

ls コマンドプロセスをトレースして、プロセス作成フローを視覚化してみましょう。 strace コマンドを使用して、呼び出しをトレースします。

[pathak_home@toolbox ~]$ strace -f -etrace=execve,clone bash -c '{ ls; }'
execve("/usr/bin/bash", ["bash", "-c", "{ ls; }"], 0x7fff153d0ed0 /* 36 vars */) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 115538 attached
, child_tidptr=0x7fe8b4f10a10) = 115538
[pid 115538] execve("/usr/bin/ls", ["ls"], 0x55eed8e42be0 /* 36 vars */) = 0
Desktop  Documents  Downloads  Dropbox	IdeaProjects  Music  Pictures  Public  Templates  Videos  revision  soft
[pid 115538] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=115538, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

上記の出力から、プロセスを作成するために2つのステップが必要であることがわかります。

  • clone システムコールは、bashプロセスのクローンとしてプロセスを作成します
  • 次に、 execve システムコールは、プロセス内の実行可能ファイルをlsコマンドバイナリに置き換えます。

もう1つの重要な点は、子プロセスは同じアドレス空間を持つ親のコピー/クローンであると言っても、実装に関しては、Linuxは子が書き込むまで子のメモリをコピーしないということです。 Linuxでのプロセスのこの巧妙な実装は、RAMスペースを節約し、不要なメモリ割り当てを回避します。 この実装は、コピーオンライト(COW)とも呼ばれます。

4.4. プロセスツリー

の中に strace 上記の結果では、プロセスが内部的に呼び出していることがわかりますクローン後に execve 実行します bash -c lsこのプロセスフローは、 bash プロセスはの親です ls 指図

同様に、親プロセスは、PID 1(INITプロセス)を除くすべてのプロセスをLinuxで作成します。 pstree コマンドは、プロセス階層を視覚化するのに役立ちます。

[user@fedora ~]$ pstree
systemd─┬─ModemManager───3*[{ModemManager}]
        ├─NetworkManager───2*[{NetworkManager}]
        ├─accounts-daemon───3*[{accounts-daemon}]
        ├─2*[agetty]
        ├─alsactl
        ├─auditd───{auditd}
        ├─avahi-daemon───avahi-daemon
        ├─bluetoothd
        ├─chronyd
        ├─colord───3*[{colord}]
        ├─cupsd
        ├─dbus-broker-lau───dbus-broker
        ├─firewalld───{firewalld}
        ├─gdm─┬─gdm-session-wor─┬─gdm-wayland-ses─┬─gnome-session-b───3*[{gnome-session-b}]
        │     │                 │                 └─2*[{gdm-wayland-ses}]
        │     │                 └─2*[{gdm-session-wor}]
        │     └─2*[{gdm}]
        ├─gnome-keyring-d───3*[{gnome-keyring-d}]
        ├─gssproxy───5*[{gssproxy}]
        ├─low-memory-moni───2*[{low-memory-moni}]

5. スレッド内部

プロセスと同様に、cloneシステムコールを使用してスレッドを作成します。 cloneシステムコールは非常に用途が広いです。 定義上、プロセスでもスレッドでもないものを作成することもできます。 cloneシステムコールのシグネチャを見てみましょう。

pid = clone(function, stack ptr, sharing flags, arg);

cloneシステムコールは、プロセスまたはスレッドの作成を決定するために提供されたCLONEフラグを使用します

上記のtableを参照として使用し、cloneシステムコールを使用してプロセスまたはスレッドを作成します。

たとえば、スレッドを作成する場合は、CLONE_VMフラグを設定します。 同様に、プロセスを作成するには、CLONE_VMフラグの設定を解除します。

CLONE_VM フラグを指定することにより、cloneコマンドに親プロセスメモリを子と共有するように指示します。 設定すると、共有ファイルシステム情報( CLONE_FS )、開いているファイル( CLONE_FILES )などを使用して新しいスレッドまたはそのバリアントを作成する他のフラグがあります。

clone はフラグが異なるため、さまざまな方法で使用できるため、すべてのUnixおよびLinuxバリアント間でポータブルスレッドを作成するための実装標準があります。

POSIX(Portable Operating System Interface)は、Unix、Linux、およびそれらのバリアントとの互換性のためにAPI、シェルコマンド、およびユーティリティインターフェイスを定義する標準の1つです。移植性の理由から、POSIXからのpthread_createAPI呼び出しを使用することをお勧めします[X267X ]

6. プロセスとスレッドの違い

Linuxコンテキストでのプロセスとスレッドの違いを確認してみましょう。

プロセス スレッド
プロセスは重いです。 スレッドは、LWPとも呼ばれる軽量プロセスです。
プロセスには独自のメモリがあります。 スレッドは、親プロセスおよびプロセス内の他のスレッドとメモリを共有します。
メモリが分離されているため、プロセス間通信は遅くなります。 共有メモリにより、スレッド間通信が高速になります。
プロセス間のコンテキスト切り替えは、古いプロセスメモリを保存し、新しいプロセスメモリとスタック情報をロードするため、コストがかかります。 スレッド間のコンテキスト切り替えは、共有メモリにより安価です。
コンポーネントに複数のプロセスがあるアプリケーションは、メモリが不足しているときにメモリ使用率を向上させることができます。 アプリケーション内の非アクティブなプロセスに低い優先度を割り当てることができます。 このアイドルプロセスは、ディスクにスワップする資格があります。 これにより、アプリケーションのアクティブなコンポーネントの応答性が維持されます。 メモリが不足している場合、マルチスレッドアプリケーションはメモリを管理するためのプロビジョニングを提供しません。

7. 結論

この記事では、Linuxのプロセスとスレッドの基本を理解することから始めました。

その後、実行中のすべてのプロセスを表示し、シングルスレッドプロセスとマルチスレッドプロセスを識別する方法を学びました。 次に、 strace コマンドを使用してサンプルプロセスフローをトレースすることにより、Linuxプロセスの内部構造とLinuxがプロセスを作成する方法を調査しました。 次に、プロセスとスレッドの作成に使用できるさまざまなcloneシステムコールについて説明しました。

最後に、プロセスとスレッドの違いを要約しました。