エンジョイC051回目newproc関数が1を返す仕組み(2021-04-18)

1
ほえほえ@スプシマン @hoehoe1234

親プロセスのnewprocは自身の中でsavuを行っているのでこの状態で、SPとBPは子プロセスのu構造体にも設定されています。もちろん、カーネルスタックは親プロセスのものがそのままコピーされています。

2021-04-22 18:11:09
ほえほえ@スプシマン @hoehoe1234

ここまでの状況をまとめます。 ①子プロセスがretuで選択された ②子プロセスのSP,BP、カーネルスタックは親プロセスがnewprocでsavuした時点でのコピー ③BPの下に戻りアドレスがあるがこの戻りアドレスはforkがnewprocを呼ぶ命令(JSR PC, newproc)の次の命令位置(すなわちnewprocの戻りアドレス)

2021-04-22 18:14:14
ほえほえ@スプシマン @hoehoe1234

ここまでである程度状況が整理できました。話は続きます。いま動いているのはswtch関数でした。この関数はどのプロセスからも呼び出されます(この仕組は後述)。40行目で子プロセスが選択されたあとは何がおきるでしょうか?追っていきましょう。 pic.twitter.com/SZ9eP2mKFV

2021-04-22 18:21:10
拡大
ほえほえ@スプシマン @hoehoe1234

今興味があるのは「なぜnewprocで1が返ってきたようにみえるのか?」ですので間の処理は飛ばします。retuで子プロセスが選択されます。この状況は整理しました。 pic.twitter.com/ZTUOUCdFjq

2021-04-22 18:23:02
拡大
ほえほえ@スプシマン @hoehoe1234

swtch関数でreturn 1が行われると、アセンブラレベルでは(アセンブラが見れないので推測ですが) ①mov 1, r0   戻り値 ②mov bp, sp  SPの復旧 ③pop bp 旧BPの復旧(これでBPはforkのBPに戻る) ④rsr pc となり、現在のSPの指す場所(青のPC)の値をPCに設定します。

2021-04-22 18:29:27
ほえほえ@スプシマン @hoehoe1234

この図上青のPCは、forkからnewproc関数を呼び出した命令の次の命令位置です。ですからfork関数がその位置から実行されます。戻り値(R0)の値は、swtch関数が実行した1です。

2021-04-22 18:30:55
ほえほえ@スプシマン @hoehoe1234

まとめると、swtch関数から戻り値1でリータンを実行しますが、このときの子プロセスのスタックの状態はfork->newprocを実行したときの状態(savuのタイミング)であり、RSRは「スタックの内容に従って」リターンするので、swtch関数が実行したリターンでがnewporcからリターンしたように見えます。

2021-04-22 18:32:50
ほえほえ@スプシマン @hoehoe1234

これがnewprocが1を返す理由です。C言語だけで考えていてはなかなか理解できないですね。スタックの状況を図に書いて、C言語の関数呼び出しがJSRとRSRを使った実質ジャンプであることを理解する必要があります。しかし、考慮するべき点はまだまだあります。

2021-04-22 18:34:04
ほえほえ@スプシマン @hoehoe1234

なぜ他のプロセスが実行したswtch関数なのに、関係のない子プロセスのnewproc関数からリターンしたように見えるのでしょうか?次にこの仕組について解説しました。ポイントは「切り替えているのはカーネルスタックのみ」というところです。

2021-04-22 18:35:51
ほえほえ@スプシマン @hoehoe1234

演習問題の2です。swtch関数がどのように動いているのかの理解のためにswtch関数内でsavuが呼ばれた直後のスタックの内容を解析します。 pic.twitter.com/nR9RqRymhw

2021-04-22 18:55:59
拡大
ほえほえ@スプシマン @hoehoe1234

swtch関数の1つ目のプロセスの切り替えです。これはswtchを(間接的に)呼び出したプロセスからスケジューラプロセスへの切り替えになります(シンキングプロセスと言います)。まずは切り替えるプロセスの状況がどうなっているのか整理します。 pic.twitter.com/4weBV4Uc6h

2021-04-22 18:58:29
拡大
ほえほえ@スプシマン @hoehoe1234

ある関数がswtchを呼んだときのスタックの状態です(x86系規約による想定です)。赤で囲まれているSPとBPがカーネルスタックにセーブされます。BPはswtchのBPです。これは旧BPを指し、その下にはswtchを呼んだ関数の呼び出した命令の次のアドレスが設定されています(いわゆる戻りアドレス)。 pic.twitter.com/cORsehIS1n

2021-04-23 02:01:00
拡大
ほえほえ@スプシマン @hoehoe1234

今後v6エミュレータを使ってアセンブラで確認する予定ですが現時点ではこのような理解です。コンパイラのコード生成部を確認するのも良いかなと思います。図右は左の図が全体の中でどのような位置を占めるかを示しています。 pic.twitter.com/nXfjdzoGY7

2021-04-23 02:09:33
拡大
ほえほえ@スプシマン @hoehoe1234

図左から、プロセスAのカーネル側のスタックは物理メモリ上ではプロセスAのPPDAのu構造体の下にあり(これはプロセスAからは見えません)、カーネルの仮想アドレスC000に割り当てられています。これはPAR6にプロセスAのp_addrを設定することにより行われます。これが全体像です。

2021-04-23 02:12:49
ほえほえ@スプシマン @hoehoe1234

このような状態でswtchの10行目でsavu後にカレントプロセスを外れたシンキングプロセスは、どこから再開するのでしょうか? pic.twitter.com/uppukjQBQ5

2021-04-23 02:14:53
拡大
ほえほえ@スプシマン @hoehoe1234

そうですね。シンキングプロセスが再度カレントプロセスに選ばれるのはswtchの40行目です。その後、sureg関数にてユーザ空間のPARが正しく設定されます(スワップなどでプロセスAの物理アドレスは変わる可能性があります)。 pic.twitter.com/rfr7T90TYz

2021-04-23 02:17:24
拡大
ほえほえ@スプシマン @hoehoe1234

swtch関数の10行目から40行目の間はproc[0]、もともとカーネルとして起動されたプロセスが担当します。このプロセスは「スケージューラ」と呼ばれています。シンキングプロセス->スケジューラー>ライジングプロセスという2度のretuの実行によってプロセスは切り替わります。

2021-04-23 02:19:07
ほえほえ@スプシマン @hoehoe1234

シンキングプロセスー>ライジングプロセスと直接切り替わらない理由は、切り替えるべきプロセスが見つからない場合のidleループが必要だからでしょうか?まだ検証できていませんが割り込みなどを考えるとアイドルループはスケジューラが実施する必要がありそうに見えます。 pic.twitter.com/JJkHEieF9F

2021-04-23 02:23:31
拡大
ほえほえ@スプシマン @hoehoe1234

すこし脱線してしまいました。結局、swtchを呼んだ関数はその状態でSP、BP、スタックが保存され(10行)、次のにカレントプロセスとして選択されると40行目からスタートしてretrun 1します。これはswtchを呼び出した関数から見ると、時系列的には間に他のプロセスが実行されているのも関わらず、

2021-04-23 02:26:07
ほえほえ@スプシマン @hoehoe1234

swtchを呼び出してswtchから返ってきたように見えます。この当たりのスタックの動きは、改めてv6用のアセンブラコードをみて再確認したいと考えています。

2021-04-23 03:06:21
ほえほえ@スプシマン @hoehoe1234

swtchがretrun 1をすると(想定ですが) mov 1, R0 戻り値 ①mov bp, sp SPの復旧 ②pop bp 旧BPの復旧 この時点でSPはスタック上の「PC」指している ③rsr pc が行われます。このときのPCの値がまさに、forkがnewprocを呼んだ(jsr pc, newproc)の次の命令となります。R0には1が設定されています。 pic.twitter.com/ZFfKhKtqjy

2021-04-23 16:54:42
拡大
ほえほえ@スプシマン @hoehoe1234

forkのjsr pc, newprocの次の行は戻り値をテストする命令になっているはずです。なので親プロセスはif文を実行せず、子プロセスでは1がかえるのでif文中のコードを実行します。これがnewproc関数が1を返す理由です。 pic.twitter.com/6e4a76I8EL

2021-04-23 16:58:43
拡大
ほえほえ@スプシマン @hoehoe1234

この挙動は、あくまでもx86系のアセンブラ(gcc)から想定したものです。しかし、辻褄はこれで合っていますね。疑問点としてはspを保存する必要があるのか?なのですが、これはv6のアセンブラコードをみて確認したいと思います。

2021-04-23 17:00:14
ほえほえ@スプシマン @hoehoe1234

仮想記憶、関数の動く仕組み、アセンブラコードの詳細な挙動確認、ユーザプロセスとカーネルとの関係、、、、多くのものを順番に理解してやっとnewprocが1を返す本当のところの理由が分かりました。

2021-04-23 17:01:57
ほえほえ@スプシマン @hoehoe1234

この不思議な挙動をもうすこし解説してみました。カーネルは「システムコールライブラリ」とも呼ばれるようにユーザプロセスからみると単なるシステムコール関数を実行しているだけに見えます。しかし、この呼び出しでプロセスが切り替わります。どうしてこのような不思議な挙動ができるのでしょうか? pic.twitter.com/m8dbwEX4X2

2021-04-23 17:05:39
拡大