Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.cmain()で、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つも登録されていないため、これ以上の出力はありません。