8_Linuxプロセススケジューリングのタイミングとプロセス切り替え

7436 ワード

陈诚
オリジナル作品の転載は出典を明記してください
「Linuxカーネル分析」MOOCコースhttp://mooc.study.163.com/course/USTC-1000029000 
==========================================================================
    最後の週の実験はプロセスの切り替えとスケジューリングタイミングについてである.オペレーティングシステムの原理の中で大量のプロセススケジューリングアルゴリズムを紹介して、これらのアルゴリズムは実現の角度から見るとただ運行キューの中から1つの新しいプロセスを選択して、選択の過程の中で異なる策略を運用しました.オペレーティングシステムの動作メカニズムを理解するには、逆にプロセスのスケジューリングタイミングとプロセスの切り替えメカニズムが重要です.
実験棟に入って実験を開始し、gdbはschedule()関数の処理過程を追跡してデバッグする.
1.実験棟の仮想マシンを開く
2.shellで次のコマンドを順次実行
 cd LinuxKernel
 rm menu -rf  
 git clone https://github.com/mengning/menu.git 
 cd menu  
 mv test_exec.c test.c 
 make rootfs    
3.QEMUウィンドウを閉じ、shellウィンドウでcd LinuxKernelをLinuxKernelディレクトリに戻し、次のコマンドでカーネルを起動し、CPUがコードを実行する前に停止してデバッグする.
  qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S  
次に、新しいshellウィンドウを水平に分割し、次のコマンドを使用してgdbデバッグを開始します. 
 gdb 
 file linux-3.18.6/vmlinux 
 target remote:1234  
カーネル関数scheduleの入り口にブレークポイントを設定し、cを実行し続けるとその関数に停止し、コマンドnまたはsを使用して逐次追跡し、詳細に参照できます.
pick_next_task、switch_to等関数の実行手順
8_Linux进程调度的时机和进程切换_第1张图片
schedule全体の実行手順を下図に示します   |———————————-|   schedule    sched_submit_work(tsk)    _schedule()      pick_next_task      context_switch(rq,prev,next)        prepare_task_switch  カーネルスレッドかどうかを判断        switch_mm        switch_to          _switch_to        finish_task_switch
解析:プロセススケジューリングのタイミング1)割り込み処理プロセス(クロック割り込み、I/O割り込み、システム呼び出し、例外を含む)でschedule()を直接呼び出すか、ユーザー状態に戻るときneed_reschedタグ呼び出しschedule()2)カーネルスレッドはschedule()を直接呼び出してプロセス切替を行うこともできるし、割り込み処理中にスケジューリングを行うこともできる.すなわち、カーネルスレッドは特殊なプロセスとしてアクティブにスケジューリングすることもできるし、パッシブにスケジューリングすることもできる.3)ユーザ状態プロセスはアクティブスケジューリングを実現することができず,カーネル状態に陥った後のある時点でのみスケジューリングを行うことができ,すなわち割り込み処理中にスケジューリングを行うことができる.プロセスの切り替え    プロセスの実行を制御するために、カーネルはCPUで実行中のプロセスを保留し、以前に保留していたプロセスの実行を回復する能力が必要です.これをプロセス切替、タスク切替、コンテキスト切替と言います.CPU上で実行中のプロセスを保留し、中断時に現場を保存するのとは異なり、中断前後は同じプロセスコンテキストで、ユーザ状態からカーネル状態に移行するだけで実行されます.プロセスコンテキストにはプロセス実行に必要なすべての情報が含まれている:1)ユーザアドレス空間:プログラムコード,データ,ユーザスタックなどを含む2)制御情報:プロセス記述子,カーネルスタックなど3)ハードウェアコンテキスト(割り込みに注意してハードウェアコンテキストを保存するのは保存する方法だけが異なる)    schedule()関数は、新しいプロセスを選択して実行し、context_を呼び出します.switchはコンテキストの切り替えを行い、このマクロはswitch_を呼び出す.toは、キーコンテキストの切り替えを行います.next = pick_next_task(rq, prev);  //プロセススケジューリングアルゴリズムは、この関数の内部context_をカプセル化します.switch(rq, prev, next);  //プロセスコンテキスト切り替えswitch_to prevとnextの2つのパラメータを利用した:prevは現在のプロセスを指し、nextはスケジューリングされたプロセスを指す.
31#define switch_to(prev, next, last)                    \
32do {                                 \
33  /*                              \
34   * Context-switching clobbers all registers, so we clobber  \
35   * them explicitly, via unused output variables.     \
36   * (EAX and EBP is not listed because EBP is saved/restored  \
37   * explicitly for wchan access and EAX is the return value of   \
38   * __switch_to())                     \
39   */                                \
40  unsigned long ebx, ecx, edx, esi, edi;                \
41                                  \
42  asm volatile("pushfl
\t" /* save flags */ \ 43 "pushl %%ebp
\t" /* save EBP */ \ 44 "movl %%esp,%[prev_sp]
\t" /* save ESP */ \ 45 "movl %[next_sp],%%esp
\t" /* restore ESP */ \ 46 "movl $1f,%[prev_ip]
\t" /* save EIP */ \ 47 "pushl %[next_ip]
\t" /* restore EIP */ \ 48 __switch_canary \ 49 "jmp __switch_to
" /* regparm call */ \ 50 "1:\t" \ 51 "popl %%ebp
\t" /* restore EBP */ \ 52 "popfl
" /* restore flags */ \ 53 \ 54 /* output parameters */ \ 55 : [prev_sp] "=m" (prev->thread.sp), \ 56 [prev_ip] "=m" (prev->thread.ip), \ 57 "=a" (last), \ 58 \ 59 /* clobbered output registers: */ \ 60 "=b" (ebx), "=c" (ecx), "=d" (edx), \ 61 "=S" (esi), "=D" (edi) \ 62 \ 63 __switch_canary_oparam \ 64 \ 65 /* input parameters: */ \ 66 : [next_sp] "m" (next->thread.sp), \ 67 [next_ip] "m" (next->thread.ip), \ 68 \ 69 /* regparm parameters for __switch_to(): */ \ 70 [prev] "a" (prev), \ 71 [next] "d" (next) \ 72 \ 73 __switch_canary_iparam \ 74 \ 75 : /* reloaded segment registers */ \ 76 "memory"); \ 77} while (0)

ここで、switch_toはマクロ定義であり、主に以下の作業を完了します. 
1)現在のプロセスのflags状態と現在のプロセスのebpを保存する 
  "pushfl\t"                 //save flags
  "pushl %%ebp\t"           //save EBP
2)espでのカーネルスタックの切り替え完了 
   "movl %%esp,%[prev_sp]\t" //save ESP
   "movl %[next_sp],%%esp\t" //restore ESP
3)eipの値を保存する 
  "movl $1f,%[prev_ip]\t"   //save EIP
  "pushl %[next_ip]\t"       //restore EIP 
1:のアドレスをprev->thread.ipに保存し、次のプロセスが呼び出されると、1の場所から実行されます. 
注記:前にnextもswitch_toは外に出たことがありますが、next->thread.ipには次の1 fの記号が保存されていますが、nextプロセスが作成されたばかりで、switch_toは外に出たことがあります.next->thread.ipに保存されているのはret_です.ftom_fork、すなわちプロセスがforkによって実行する直後にexecが実行される. 
4)jmp __switch_to//パラメータをスタックに押し込まずにレジスタ伝値を用いて__を呼び出すswitch_to eaxはprevを格納し、edxはnextを格納する
まとめ:
Linuxでのプロセス切り替えの一般的な手順は、次のとおりです. 
1)現在のプロセスの状態を検出し、現在のプロセスのIO要求を保留してデッドロックを防止する
2)現在実行中のCPUとその実行可能プロセスキューを取得する
3)プロセスキューから現在のプロセスのtask_を取得するstructは、プロセススケジューリングアルゴリズムによってキューから適切なプロセスを呼び出し対象プロセスとして選択します.
4)呼び出し対象プロセスの状態を検出して正確性を確保する
5)switch_の使用toマクロは、現在のプロセスと呼び出されるプロセスの切り替えを行う(期間中に新しいプロセスのリソース準備が完了する)
6)新しいプロセスがschedule()を完了し、プロセス切替プロセス全体を終了する
最も一般的な場合:実行中のユーザ状態プロセスXがユーザ状態プロセスYを実行するプロセスに切り替わる
実行中のユーザ状態プロセスXが中断される--save cs:eip/esp/eflags(current)to kernel stack then load cs:eip(entry of a specific ISR)and ss:esp(point to kernel stack)
SAVE_ALL    //保存フィールド割込み処理中または割込みが戻る前にschedule()が呼び出され、switch_to重要なプロセスコンテキスト切替符号1が作成された後、ユーザ状態プロセスYの実行が開始される(ここで、Yは上記のステップで切替られたので、符号1から実行を継続することができる)restore_all  //リカバリフィールドiret-pop cs:eip/ss:esp/eflags from kernel stackユーザー状態プロセスYの実行を続行
いくつかの特殊な状況:
1)割り込み処理中のスケジューリングタイミングにより、ユーザ状態プロセスとカーネルスレッド間の相互切替とカーネルスレッド間の相互切替は、最も一般的な状況と非常に類似しているが、カーネルスレッドの実行中に割り込みが発生し、プロセスユーザ状態とカーネル状態の転換がない.
2)カーネルスレッドはschedule()をアクティブに呼び出し、プロセスコンテキストの切り替えだけで、コンテキストの切り替えを中断することはなく、最も一般的な状況とは少し簡略である.
3)サブプロセスを作成するシステム呼び出しのサブプロセスでの実行開始点とforkなどのユーザ状態を返す.
4)新しい実行可能プログラムをロードしてユーザ状態に戻る場合、例えばexecve;