3.1. メインモジュール
自作プロトコルスタックのメインモジュールを移植します。まず、プロトコルスタック本体が必要とするプラットフォーム依存コードを実装し、その上にmicropsのメインモジュールを載せます。
プラットフォーム依存コードの実装
プロトコルスタック本体が利用するプラットフォーム依存の機能のうち、メモリ操作・ロック・乱数をplatform/xv6-riscvに実装します。まずplatform.hを、機能ごとのAPI宣言を持つ形に拡張します。
📝 kernel/net/platform/xv6-riscv/platform.h
#ifndef PLATFORM_H
#define PLATFORM_H
-#include "types.h"
+#include <sys/types.h>
#include "riscv.h"
+#include "spinlock.h"
#include "defs.h"
+extern int
+platform_init(void);
+extern int
+platform_run(void);
+extern int
+platform_shutdown(void);
+
+/*
+ * Memory
+ */
+
+extern void *
+memory_alloc(size_t size);
+extern void
+memory_free(void *ptr);
+
+/*
+ * Lock
+ */
+
+typedef struct spinlock lock_t;
+
+#define LOCK_INITIALIZER {0}
+
+extern int
+lock_init(lock_t *lock);
+extern int
+lock_acquire(lock_t *lock);
+extern int
+lock_release(lock_t *lock);
+
+/*
+ * Random
+ */
+
+extern uint16_t
+random16(void);
+
#endif
実装をplatform.cとして追加します。
- メモリ操作:
kalloc()/kfree()で実装します - ロック:
lock_tはxv6のスピンロックの別名で、各関数も対応するスピンロック操作を呼ぶだけです - 乱数: RTCから読んだ時刻をシードにした線形合同法(LCG)の簡易実装です
📝 kernel/net/platform/xv6-riscv/platform.c
#include "platform.h"
static uint32 seed = 1;
int
platform_init(void)
{
seed = rtcread();
return 0;
}
int
platform_run(void)
{
return 0;
}
int
platform_shutdown(void)
{
return 0;
}
/*
* Memory
*/
void *
memory_alloc(size_t size)
{
void *p;
if (PGSIZE < size) {
return NULL;
}
p = kalloc();
if (p) {
memset(p, 0, size);
}
return p;
}
void
memory_free(void *ptr)
{
kfree(ptr);
}
/*
* Lock
*/
int
lock_init(lock_t *lock)
{
initlock(lock, "");
return 0;
}
int
lock_acquire(lock_t *lock)
{
acquire(lock);
return 0;
}
int
lock_release(lock_t *lock)
{
release(lock);
return 0;
}
/*
* Random
*/
uint16_t
random16(void)
{
/* Linear Congruential Generator (LCG) */
seed = seed * 1103515245 + 12345;
return (seed >> 16) & 0xffff;
}
Note
xv6のカーネル内ではメモリを
kalloc()で確保します。確保するサイズは指定できず、常にページサイズ(4KB)のメモリが返されます。自作プロトコルスタックが一度に確保するメモリは4KBを超えないため、memory_alloc()はkalloc()したページをゼロ初期化して返すだけの実装で済ませています。
MakefileのOBJSにplatform.oを追加します。
📝 Makefile
OBJS = \
...
$K/virtio_disk.o \
$N/util.o \
$N/net.o \
+ $P/platform.o \
$L/stdio.o
暫定コードの削除
2.5で暫定的に作成したkernel/net/net.cを削除します。
$ rm kernel/net/net.c
メインモジュールのコードのコピー
メインモジュールのコードをmicropsからコピーします。
$ cp $MICROPS/net.{h,c} kernel/net/
1モジュールずつ移植していく都合上、まだ移植していないモジュールの初期化処理をコメントアウトしておきます。この後、各モジュールの移植が完了するたびにコメントを解除していきます。
📝 kernel/net/net.c
...
-#include "arp.h"
-#include "ip.h"
-#include "icmp.h"
-#include "udp.h"
-#include "tcp.h"
+//#include "arp.h"
+//#include "ip.h"
+//#include "icmp.h"
+//#include "udp.h"
+//#include "tcp.h"
int
net_init(void)
{
infof("initialize...");
if (platform_init() == -1) {
errorf("platform_init() failure");
return -1;
}
- if (arp_init() == -1) {
- errorf("arp_init() failure");
- return -1;
- }
- if (ip_init() == -1) {
- errorf("ip_init() failure");
- return -1;
- }
- if (icmp_init() == -1) {
- errorf("icmp_init() failure");
- return -1;
- }
- if (udp_init() == -1) {
- errorf("udp_init() failure");
- return -1;
- }
- if (tcp_init() == -1) {
- errorf("tcp_init() failure");
- return -1;
- }
+// if (arp_init() == -1) {
+// errorf("arp_init() failure");
+// return -1;
+// }
+// if (ip_init() == -1) {
+// errorf("ip_init() failure");
+// return -1;
+// }
+// if (icmp_init() == -1) {
+// errorf("icmp_init() failure");
+// return -1;
+// }
+// if (udp_init() == -1) {
+// errorf("udp_init() failure");
+// return -1;
+// }
+// if (tcp_init() == -1) {
+// errorf("tcp_init() failure");
+// return -1;
+// }
infof("success");
return 0;
}
...
割り込み関連コードのスタブ
メインモジュールはソフトウェア割り込みのハンドラ登録などで割り込み関連のAPI(intr_xxx())を必要とします。実体は3.4(ハードウェア割り込み)と3.5(ソフトウェア割り込み)で実装するので、ここではビルドを通すためのスタブを用意しておきます。
ソフトウェア割り込みのIRQ番号INTR_IRQ_SOFTは、PLICの割り込みソース番号(1〜53)と衝突しないよう、あらかじめ範囲外の64と定義しておきます(この値を選ぶ理由は3.5で説明します)。
📝 kernel/net/platform/xv6-riscv/intr.h
#ifndef INTR_H
#define INTR_H
#define INTR_IRQ_SOFT 64 /* out of PLIC source range (1-53) */
#define INTR_IRQ_SHARED 0x0001
typedef void (*intr_isr_t)(unsigned int irq, void *arg);
extern int
intr_register(unsigned int irq, intr_isr_t isr, int flags, void *arg);
extern int
intr_raise(unsigned int irq);
extern int
intr_init(void);
extern int
intr_run(void);
extern int
intr_shutdown(void);
#endif
📝 kernel/net/platform/xv6-riscv/intr.c
/* stub: to be implemented in the software interrupt step */
#include "platform.h"
int
intr_register(unsigned int irq, intr_isr_t isr, int flags, void *arg)
{
return 0;
}
int
intr_raise(unsigned int irq)
{
return 0;
}
int
intr_init(void)
{
return 0;
}
int
intr_run(void)
{
return 0;
}
int
intr_shutdown(void)
{
return 0;
}
platform.hの末尾でintr.hを読み込むようにします。
📝 kernel/net/platform/xv6-riscv/platform.h
...
extern uint16_t
random16(void);
+#include "intr.h"
+
#endif
platform_init()とplatform_run()に割り込みの初期化・起動処理を組み込みます。ソフトウェア割り込みが発生した際にメインモジュールのnet_softirq_handler()が呼び出されるようにハンドラを登録します(いまはスタブなのでまだ何も起きません)。
📝 kernel/net/platform/xv6-riscv/platform.c
#include "platform.h"
+#include "util.h"
+#include "net.h"
+
static uint32 seed = 1;
int
platform_init(void)
{
seed = rtcread();
+ if (intr_init() == -1) {
+ return -1;
+ }
+ intr_register(INTR_IRQ_SOFT, net_softirq_handler, 0, NULL);
return 0;
}
int
platform_run(void)
{
+ if (intr_run() == -1) {
+ return -1;
+ }
return 0;
}
...
MakefileのOBJSにintr.oを追加します(net.oは暫定コードで追加したものがそのまま使われます)。
📝 Makefile
OBJS = \
...
$N/util.o \
$N/net.o \
$P/platform.o \
+ $P/intr.o \
$L/stdio.o
プロトタイプ宣言の追加
kernel/defs.hにあるnet_init()のプロトタイプ宣言の下にnet_run()の宣言を追加します。
📝 kernel/defs.h
...
// net/net.c
int net_init(void);
+int net_run(void);
...
プロトコルスタックの起動
kernel/main.cのmain()で、net_init()に続けてnet_run()を呼び出すようにします。
📝 kernel/main.c
...
virtio_disk_init(); // emulated hard disk
printdate();
net_init(); // network stack
+ net_run(); // start networking
userinit(); // first user process
...
動作確認
再ビルドした後、make qemuを実行してxv6を起動させます。
xv6 kernel is booting
2026/08/11 10:00:00
10:00:00.101 [I] net_init: initialize... (kernel/net/net.c:277)
10:00:00.102 [I] net_init: success (kernel/net/net.c:302)
10:00:00.103 [I] net_run: startup... (kernel/net/net.c:311)
10:00:00.104 [I] net_run: success (kernel/net/net.c:319)
hart 1 starting
hart 2 starting
init: starting sh
$
Note
プロトコルスタックの初期化(
net_init)に続けて起動処理(net_run)が実行されていることが確認できます。まだネットワークデバイスが1つも登録されていないため、これ以上の出力はありません。