Open25

HinaOSにプロセス間通信として共有メモリを実装したい

けいすけけいすけ

メッセージ送信の流れ

送信元プロセスがメッセージ構造体に依頼内容を詰めて ipc_call を呼び出す

下記は数値を2倍にして返してくれるプロセスにメッセージを送った例
https://github.com/keisuke713/microkernel-book/blob/main/servers/shell/command.c#L153-L157

システムコールが呼び出され処理がカーネルに移行
https://github.com/keisuke713/microkernel-book/blob/main/kernel/syscall.c#L208-L227

送信先プロセスの一時領域にメッセージを格納
https://github.com/keisuke713/microkernel-book/blob/main/kernel/ipc.c#L89-L90
送信元は current から判断している

続いて受信側の処理
メッセージが届くのを待っている
https://github.com/keisuke713/microkernel-book/blob/main/servers/twice/main.c#L13

受信用のシステムコールを投げる
https://github.com/keisuke713/microkernel-book/blob/main/libs/user/ipc.c#L135

受信用の処理に移る
むーどの段階でsrc=5と判別しているんだ?
https://github.com/keisuke713/microkernel-book/blob/main/kernel/ipc.c#L160

けいすけけいすけ

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にセットすると子でも呼び出すことになるから受け取るところを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段目コピーされている?
https://github.com/nuta/microkernel-book/blob/main/kernel/riscv32/vm.c#L134-L136

けいすけけいすけ

改めてページフォルトが起きているアドレス確かめてみたけど、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_trampolineriscv32_user_entry を呼んで実行開始アドレスを設定してsretする