[読書ノート] Linuxカーネルの設計と実装プロセス管理

1.プロセス

プロセスは実行中のプログラムです(ターゲットコードは記憶媒体に格納されます)。

ps:プロセスは、実行可能コードのセクション(コードセクション、テキストセクションとも呼ばれます)に限定されません。通常、プロセスには、オープンファイル、一時停止されたセマフォなどの他のリソースも含まれます。

実行スレッド、または略してスレッドは、プロセス内でアクティブなオブジェクトです。各スレッドには、独立したプログラムカウンター、プロセススタック、および一連のプロセスレジスタがあります。

カーネルによってスケジュールされたオブジェクトは、プロセスではなくスレッドです。

ps:Linuxシステムのスレッド実装は非常に特別です。スレッドとプロセスを特別に区別しません。つまり、スレッドは特別なプロセスにすぎません。

2.プロセス記述子とタスク構造

カーネルは、プロセスのリストを、タスクリストと呼ばれる二重循環リンクリストに格納します。
リンクされたリストの各項目は、<linux / sched.h>で定義された、タイプ記述子task_structであり、プロセス記述子(プロセス記述子)構造と呼ばれます。
プロセス記述子には、特定のプロセスに関するすべての情報が含まれています。

プロセス記述子に含まれるデータは、実行中のプログラムを完全に記述できます。プログラムが開くファイル、プロセスのアドレス空間、保留中のシグナル、プロセスの状態などです。
ここに画像の説明を挿入

2.1プロセス記述子の割り当て

ここに画像の説明を挿入

2.2プロセス記述子の保存-PID

カーネルは、一意のプロセス識別値またはPIDによって各プロセスを識別します。
PIDは、実際にはint型であるpid_tの暗黙の型として表される数値です。
PIDは、実際にはシステムで許可されている同時プロセスの最大数です。
PIDの最大デフォルト値は32768(<linux / thread.h>で定義されたPIDの最大値によって制限される)であり、/ proc / sys / kernel / pid_maxを介して表示できます。

2.3プロセスのステータス

プロセス記述子の状態フィールドは、プロセスの現在の状態を示します。次の5つのタイプ

  1. TASK_RUNNING(実行R)-プロセスは実行可能です。実行キューで実行中か、実行待ちです。
  2. TASK_INTERRUPTIBLE(割り込み可能S)-プロセスはスリープ(ブロック)しており、特定の条件が満たされるのを待っています。
  3. TASK_UNINTERRUPTIBLE(Uninterruptible D)-この状態は、信号が受信されても​​起動されない、または動作の準備ができていないことを除いて、割り込み可能な状態と同じです。
  4. __TASK_TRACED(Z)-ptraceを介したトレーサープロセスプロセスなど、他のプロセスによって追跡されるプロセス。
  5. __TASK_STOPPED(Tの停止)-プロセスは実行を停止します;プロセスは動作しませんし、動作することもできません。通常、この状態は、SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOUなどの信号を受信したときに発生します。また、デバッグ中に受信した信号によって、プロセスがこの状態になります。

ここに画像の説明を挿入

2.4現在のプロセス状態を設定する– set_task_state

set_task_state(task,state);  /*等价于*/
task->state = state;

PS:

set_current_state(state); /* 等价于,参考<linux/sched.h> */
set_task_state(current,state);

2.5プロセスのコンテキスト

実行可能プログラムコードは、プロセスの重要な部分です。
これらのコードは、実行可能ファイルからプロセスのアドレス空間に読み込まれて実行されます。
一般的なプログラムはユーザー空間で実行されます。プログラムコールがシステムコールを実行するか、例外をトリガーすると、カーネルスペースに分類されます
この時点では、カーネルを「プロセスの代わりに実行される」と呼び、プロセスのコンテキスト呼びます

2.6プロセスファミリーツリー–すべてのプロセスは、PID 1のinitプロセスの子孫です。

プロセス間の関係は、プロセス記述子に格納されます。
各task_structには、親プロセスと呼ばれる親プロセスtast_structへのポインターが含まれています。また、jという名前の子プロセスのリストも含まれています。

Q:親プロセスのプロセス記述子を取得するにはどうすればよいですか?
A:struct task_struct *my_parent = current->parent;

Q:子プロセスにアクセスするにはどうすればよいですか?
A:

struct task_struct *task;
struct list_head *list;

list_for_each(list,&current->children)
{
	task = list_entry(list,struct task_struct,sibling);
}

initプロセスのプロセス記述子は、init_taskとして静的に割り当てられます。
など:

struct task_struct *task;
for(task = current;task != &init_task; task = task->parent);
/*task 现在指向init*/

ps:
for_each_process(タスク)マクロは、タスクキュー全体(双方向循環リンクリスト)に順次アクセスする機能を提供しますが、多数のプロセスがあるシステムで繰り返しを介してすべてのプロセスをトラバースするコストは非常に高くなります。
したがって、正当な理由がない(または他の方法がない)場合は、行わないでください。
例:

struct task_struct *task;
for_each_process(task){
	/*打印出每一个任务(进程)的名称和PID*/
	printk("%s[%d]\n",task->comm,task->pid);
}

3.プロセス作成フォークおよび実行ファミリーの機能

fork()は、現在のプロセスをコピーして子プロセスを作成します。
execファミリー関数は、実行可能ファイルをコピーして読み取り、それをアドレス空間にロードして実行を開始します。

3.1コピーオンライト-Linuxがプロセスをすばやく実行できる理由の1つ

COWテクノロジーは、リソースコピーを書き込む必要がある場合にのみ参照します。それ以前は、読み取り専用でのみ共有されていました(つまり、親プロセスと子プロセスが同じコピーを共有しています)。

3.2 fork()

Linuxはclone()システムコールを介してfork()を実装します。
次に、一般的なコールフローを示します。

fork()->clone()->do_fork()->copy_process()

ps:do_fork()はkernel / fork.cファイルで定義されています。(異なるカーネルバージョンの異なるパスに存在する場合があります)
do_fork関数はcopy_process関数を呼び出し、都市の実行を開始できるようにします。
Copy_process関数のワークフローは次のとおりです。

  1. dup_task_struct()を呼び出して、新しいプロセスのカーネルスタック、thread_info構造体、およびtask_structを作成します。これらの値は、都市に入力された現在の値と同じです。現時点では、子プロセスと親プロセスの記述子はまったく同じです。
  2. このサブプロセスを新しく作成した後、現在のユーザーが所有するプロセスの数が、割り当てられたリソースの制限を超えていないことを確認してください。
  3. 子プロセスは、それ自体を親プロセスと区別することを目指しています。プロセス記述子の多くのメンバーは、クリアするか、初期値に設定する必要があります。エントリ記述子のメンバーを継承していない人は主に統計情報です。task_structのほとんどのデータは変更されません。
  4. 子プロセスの状態はTASK_UNINTERRUPTIBLEに設定され、動作しないようにします。
  5. copy_process()はcopy_flags()を呼び出してtask_structのフラグメンバーを更新します。プロセスにスーパーユーザー権限があるかどうかを示すPF_SUPERPRIVフラグは0にクリアされます。プロセスがまだexec()関数を呼び出していないことを示すPF_FORKNOEXECフラグが設定されています。
  6. alloc_pid()を呼び出して、新しいプロセスに有効なPIDを割り当てます。
  7. clone()に渡されたパラメーターフラグに従って、copy_process()は、開いているファイル、ファイルシステム情報、信号処理関数、プロセスアドレス空間、および名前空間をコピーまたは共有します。通常の状況では、これらのリソースは特定のプロセスのすべてのスレッドで共有されます。それ以外の場合、これらのリソースはプロセスごとに異なるため、ここ(COW)にコピーされます。
  8. 最後に、copy_process()はテールクリーニング作業を行い、子プロセスへのポインタを返します。

次に、do_fork()関数に戻り、copy_process()関数が正常に戻ると、新しく作成された子プロセスが起こされて動作します。
カーネルは意図的に子プロセスを最初実行することを選択します(常にそうであるとは限りません)。通常、子プロセスはすぐにexecファミリー関数を呼び出します。これにより、書き込み時のコピーによる余分なオーバーヘッドを回避できます。親プロセスが最初に実行されると、アドレススペースへの書き込みが開始される場合があります。

3.3 vfork()

vfork()システムコールは、親プロセスのページテーブルエントリをコピーしないことを除いて、fork()と同じ機能を持っています。
子プロセスは、そのアドレス空間で親プロセスの個別のスレッドとして実行されます。親プロセスは、子プロセスが終了するかexec()を実行するまでブロックされます。
子プロセスはアドレス空間に書き込むことができません。

ps:理想的には、システムはvfork()を呼び出さず、カーネルがそれを実装する必要もありません。

4. Linuxでのスレッドの実装

スレッド化メカニズムは、同じプログラム内の共有メモリアドレス空間で実行される一連のスレッドを提供します。
Linuxはすべてのスレッドをプロセスとして扱います。
スレッドは単に、特定のリソースを他のプロセスと共有するプロセスと見なされます(各スレッドには独自のtask_structがあるため、カーネルでは通常のプロセスのように見えますが、スレッドと他のいくつかのプロセスはアドレス空間などの特定のリソースを共有しています)

4.1スレッドを作成する

スレッドの作成は通常のプロセスの作成と似ていますが、clone()を呼び出すときに、共有する必要があるリソースを示すためにいくつかのパラメーターフラグを渡す必要があります。

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,0);  /*结果和调用fork()差不多,只是父子俩共享地址空间,文件系统资源,文件描述符和信号处理程序*/

clone関数に渡されるパラメーターフラグは、新しく作成されたプロセスの動作と、親プロセスと子プロセス間の共有リソースのタイプを決定します。
次の表に示すように:(<linux / sched.h>)

パラメータフラグ 意味
CLONE_FILES 親プロセスと子プロセスが開いているファイルを共有する
CLONE_FS 親子プロセスの共有ファイルシステム情報
CLONE_IDLETASK PIDを0に設定(アイドルプロセスでのみ使用)
CLONE_NEWNS 子プロセスの新しい名前空間を作成します
CLONE_PARENT 子プロセスと親プロセスが同じ親プロセスを持つことを指定します
CLONE_PTRACE 子プロセスのデバッグを続行します
CLONE_SETTID TIDをユーザー空間に書き戻す
CLONE_SETTLS 子プロセスの新しいTLSを作成する
CLONE_SIGHAND 父と息子のプロセスは信号処理機能とブロックされた信号を共有します
CLONE_SYSVSEM 父と息子のプロセスはSystem V SEM_UNDOセマンティクスを共有します
CLONE_THREAD 親プロセスと子プロセスは同じスレッドグループに入れられます
CLONE_VFORK Vfork()が呼び出されると、親プロセスはスリープ状態になり、子プロセスがスリープ状態を解除するのを待ちます
CLONE_UNTRACED トラッキングプロセスが子プロセスでCLONE_PTRACEを強制しないようにする
CLONE_STOP TASK_STOPPED状態でプロセスを開始します
CLONE_SETTLS 子プロセス用の新しいTLS(スレッドローカルストレージ)を作成する
CLONE_CHILD_CLEARTID 子プロセスのTIDをクリアします
CLONE_CHILD_SETTID 子プロセスのTIDを設定する
CLONE_PARENT_SETTID 親プロセスのTIDを設定する
CLONE_VM 親子プロセスの共有アドレス空間

ps:
アイドルプロセスの概念:
単にアイドルはプロセスであり、そのpid番号は0です。その前身は、システムによって作成された最初のプロセスであり、fork()によって生成されなかった唯一のプロセスです。smpシステムでは、各プロセッサユニットには独立した実行キューがあり、各実行キューにはアイドルプロセスがあります。つまり、アイドルプロセスと同じ数のプロセッサユニットがあります。システムのアイドル時間は、実際にはアイドルプロセスの「実行時間」を指します。アイドルプロセスpid == o、これはinit_taskです。

4.2カーネルスレッド–カーネル空間で独立して実行される標準プロセス

カーネルスレッドと通常のプロセスの違いは、カーネルスレッドには独立したアドレス空間がないことです(実際には、アドレス空間へのmmポインターはNULLに設定されます)。
これらはカーネル空間でのみ実行され、ユーザー空間に切り替えられることはありません。
カーネルプロセスと通常のプロセスは遠くに移動し、呼び出してプリエンプトできます。

5.プロセスの終了– do_exit()

do_exit()は、カーネル/ exit.cで定義されているシステムコールexit()によって呼び出され、次のことを行います。

  1. task_struct(本ではtast_structとして記述)のフラグメンバーをPF_EXITINGに設定します。
  2. コアタイマーを削除するには、del_timer_sync()を呼び出します。返された結果に基づいて、タイマーがキューに入れられておらず、タイマーハンドラーが実行されていないことを確認します。
  3. BSDプロセスアカウンティング機能が有効になっている場合、do_exit()はacct_update_integrals()を呼び出してアカウンティング情報を出力します。
  4. 次に、exit_mm()関数を呼び出して、プロセスが占有しているmm_structを解放します。他のプロセスがそれらを使用していない(つまり、このアドレス空間が共有されていない)場合は、それらを完全に解放します。
  5. 次に、sem__exit()関数を呼び出します。プロセスがIPCシグナルのキューに入れられた場合、キューから出ます。
  6. exit_files()およびexit_fs()を呼び出して、それぞれファイル記述子およびファイルシステムデータの参照カウントをデクリメントします。いずれかの参照カウントの値がゼロに低下した場合、対応するリソースを使用しているプロセスがなく、この時点で解放できることを意味します。
  7. 次に、task_structのexit_codeメンバーに格納されているタスク終了コードを、exit()によって提供される終了コードとして設定するか、カーネルメカニズムによって指定された他の終了アクションを完了します。親プロセスがいつでも取得できるように、終了コードがここに格納されます。
  8. exit_notify()を呼び出して、親プロセスにシグナルを送信して、子プロセスの養子縁組を見つけます。養子縁組は、スレッドグループまたはinitプロセスの別のスレッドであり、プロセスの状態(task_struct構造体のexit_stateに格納されています)はEXIT_ZOMBIEに設定されています
  9. do_exit()は、schedule()を呼び出して新しいプロセスに切り替えます。EXIT_ZOMBIE状態のプロセスはスケジュールされなくなるため、これはプロセスによって実行される最後のコードであり、do_exit()は戻りません。

ps:プロセスがこれらのリソースの唯一のユーザーである場合、プロセスは実行できず(実際には、実行するためのアドレス空間がありません)、EXIT_ZOMBIE終了状態です。それが占有するすべてのメモリは、カーネルスタック、thread_info構造体、およびtask_struct構造体です。
現時点でのプロセスの唯一の目的は、親プロセスに情報を提供することです。親プロセスが情報を取得するか、情報が無関係であることをカーネルに通知した後、プロセスによって保持されている残りのメモリが解放され、システムに返されます。

5.1プロセス記述子待機ファミリー機能の削除

プロセスの最後に必要なクリーンアップ作業とプロセス記述子の削除は、別々に実行されます。
wait()ファミリーの機能は、システムコールwait4()のみによって実装されます。その標準アクションは、子プロセスの1つが終了するまで呼び出しプロセスを一時停止することです。この時点で、関数は子プロセスのPIDを返します。

最後にプロセス記述子を解放する必要がある場合、release_task()が呼び出されます。プロセスは次のとおりです。

  1. これは、__ exit_signal()を呼び出し、__ unhash_process()を呼び出し、次にdetach_pid()を呼び出して、pidhashからプロセスを削除し、タスクリストからプロセスを削除します。
  2. __exit_signal()は、現在のゾンビプロセスで使用されている残りのすべてのリソースを解放し、プロセスが最終的にカウントおよび記録されます。
  3. このプロセスがスレッドグループの最後のプロセスであり、リードプロセスが停止している場合、release_task()は親プロセスにゾンビリードプロセスを通知します。
  4. release_task()はput_task_struct()を呼び出して、プロセスのカーネルスタックとthread_info構造によって占有されているページを解放し、task_structによって占有されているスラブキャッシュを解放します。

5.2孤立プロセスによって引き起こされるジレンマ

親プロセスが子プロセスの前に終了する場合、子プロセスが新しいを見つけられるようにするメカニズムが必要です。そうでない場合、これらの孤立したプロセスは、終了時に常にデッド状態になり、リソースを浪費します。
このメカニズムは、子プロセスの現在のスレッドグループで親としてスレッドを見つけることです。そうでない場合は、initプロセスを親プロセスにします
ps:initプロセスは定期的にwait()を呼び出して、その子プロセスをチェックし、関連するすべてのゾンビプロセスを削除します。

公開された91件の元の記事 賞賛された17件 50,000回以上の閲覧

おすすめ

転載: blog.csdn.net/qq_23327993/article/details/105065705