HinaOSにプロセス間通信として共有メモリを実装したい
そもそもプロセス間通信とは??
その名の通りだが他のプロセスとやり取りをすること
hinaOSでは送信元が下記message構造体にメッセージ種別送信元プロセスと内容を入れカーネルに依頼する
メッセージ送信の流れ
送信元プロセスがメッセージ構造体に依頼内容を詰めて ipc_call
を呼び出す
下記は数値を2倍にして返してくれるプロセスにメッセージを送った例
システムコールが呼び出され処理がカーネルに移行
送信先プロセスの一時領域にメッセージを格納current
から判断している
続いて受信側の処理
メッセージが届くのを待っている
受信用のシステムコールを投げる
受信用の処理に移る
むーどの段階でsrc=5と判別しているんだ?
IPCのコード読むの飽きたから実装に入る
目標は下記のように子プロセスを作ってそこから親の変数の値を弄れるようになること
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
void fork_example() {
pid_t p = fork();
int n;
if (p < 0) {
perror("fork fail");
exit(1);
} else if (p == 0) {
n = 0; // ここで親プロセスの変数の値を弄れるようにしたい
printf("hello %d from child \n", n);
} else {
n = 1;
sleep(5);
printf("hello %d from parent \n", n); // 子プロセスでの変更が反映されるようにしたい
}
}
int main() {
fork_example();
return 0;
}
そもそもfork自体なかったから作らなきゃ
システムコール呼び出すライブラリ作りー
カーネルで既存のプロセスをコピーする処理を作りー(PCはどうセットしようか)
元のプロセスには1、子には0を返す
リンクできない事件が起きたけどとりあえずユーザーライブラリ側はできた
明日はカーネル側の処理を書いていく
子プロセスのPCは親でforkが呼ばれたアドレスになると思うんだけどこれどうやってPCセットしたらいいんだろうか?
んん、forkを呼び出すアドレスをPCにセットすると子でも呼び出すことになるから受け取るところをPCにセットするのか?これは宿題
それにシステムコールからは親と子それぞれに戻り値を返すか、これどうやるんだ??
とりあえずプロセスをコピーする
PCをセットする
親を元にプロセス作れたけどページフォルト起きるおい
8103ec28
ってカーネルのメモリアドレスだよなー
親プロセス実行中に仮想アドレスのマップみると含まれているからコピー中のどこかで処理漏れたか?
子プロセス切り替え直後のページテーブルを確認する
実行可能なアドレスにマップされていない??フラグがrwだけになっている
やっぱりPCの値じゃなくてPCが保存されているアドレスを指していた
0x80000000より後のアドレスにアプリケーションがアクセスしようとするとページフォルトか。。。
でもsyscall呼ばれた後のアドレスをPCにセットしないと無限ループになるからしょうがない気がするんだけど、どうしたものか。。
error msg
child_process: page fault at unmappable vaddr: vaddr=8000ac7a, ip=8000ac7a
マッピング情報
vaddr paddr size attr
-------- ---------------- -------- -------
80000000 0000000080000000 00009000 rwx--a-
80009000 0000000080009000 00001000 rwx----
8000a000 000000008000a000 00007000 rwx--a-
80011000 0000000080011000 00003000 rw---a-
確かに実行可能フラグ付きでマッピングされているよね、、?
だからそもそもページフォルト起きなくないか?
ページフォルトが起きているのは0x8000~だから1弾目のページテーブルエントリのインデックスは512のはず
uint32_t *table = (uint32_t *)task->vm.table; // ページテーブルの先頭のアドレス
uint32_t *entry = table + 512; // 512 * 4を足して該当のエントリを確認する
INFO("child process table: %x, entry address: %x, entry: %x", table, entry, *entry);
// child process table: 81078000, entry address: 81078800, entry: 20174801
一段目のエントリは存在していそう
ちなみに以下はカーネルと親プロセスのページテーブル1段目の512番目の値
// kernel_table: 805d1800, entry: 20174801
// ork process table: 8103b000, entry address: 8103b800, entry: 20174801
3エントリの値全て 20174801
明日は2段目を見ていく
てか思ったけどこれ2段目コピーされている?
改めてページフォルトが起きているアドレス確かめてみたけど、arch_task_switch
の中途半端なところで起きている?
一旦方針切り替えてとりあえずプロセスをエントリポイントからループ的にコピーできるようにしようと思ったけど今度はwarningが大量に出る
下記の処理でスタック
[vm] Oops: Unexpected error (Operation Would Block)
[vm] WARN: #0: 38007d6e ipc_reply()+0x52
[vm] WARN: #1: 38000394 main()+0x38c
[vm] WARN: #2: 380094c2 start()+0x12
メッセージの返信もとを送信元と一致させたらとりあえずメッセージのやり取りはできるようになった
そんでコピー元と全く同じプロセスを作成できるようになった
次はPCを弄る
親のリターンアドレスをそのまま子にもセットしたらいけると思ったけどエラーです
// 子プロセスのエントリーポイントを親のリターンアドレスと揃える
//
// riscv32_set_entry(uint32_t *child_sp);
// a0レジスタ
//
.align 4
.global riscv32_set_entry
riscv32_set_entry:
sw ra, 0 * 4(a0)
[kernel] WARN: fork: invalid exception: scause=2, stval=00000000
user-modeで書き込もうとするとエラーが起きるらしい?
だけど権限を確認するにもuser-modeだとどっちみちできないからこれはシステムコールを作らないとか?
in user mode, can I store ra to any memory address using riscv assembly?
ChatGPT
In user mode, you typically don't have direct access to manipulate certain registers directly related to control flow and privilege levels, such as the return address register (ra). Attempting to directly access or manipulate these registers might result in illegal instruction exceptions or privilege violations.
However, if you need to store the return address (ra) to memory in user mode, you can still achieve it indirectly. You can first move the return address to a general-purpose register (t registers, for example) and then store it to memory. Here's an example:
Copy code
# Assuming s0 contains the memory address where you want to store the return address
# Move the return address to a temporary register
mv t0, ra
# Store the return address from the temporary register to memory
sw t0, 0(s0)
In this example, we first move the return address from ra to a temporary register (t0) using the mv instruction. Then, we store the value of the temporary register (t0) to the memory address specified by s0 using the sw instruction.
This way, you're indirectly storing the return address to memory without directly manipulating the ra register, which is not directly accessible in user mode.
システムコールの流れについて
ユーザーアプリ側からecallで呼ばれるとまず riscv32_trap_handler
がハンドラとして設定されているので呼ばれる
各レジスタの状態をプロセスが確保しているメモリ(カーネルスタック)に待避する
そのあと riscv32_handle_trap
を呼び出して割り込みの内容によって処理を変える
終わったら復元してsretする
ということはシステムコールは全てsmodeで行われる(当たり前だが)
親の riscv32_trap_frame
をコピーすることによってシステムコール終了後も親と子が同じように動くってことかな、最終的にa0レジスタの値を書き換えることになると思うけどそれは一旦後回しにしよう
pagesのコピーがしっくりこないこれは複数のプロセスが同じ物理ページ(4KiBのメモリ)を参照するということ?
riscv32_user_entry_trampoline
が riscv32_user_entry
を呼んで実行開始アドレスを設定してsretする
risc-v 32bits modeの場合関数の戻り値は a0
にはいる
xv6の実装を見ていたがtrapframeとpagetableのコピーはやっぱり必要みたい