4.4. UDPモジュール
UDPモジュールを移植します。UDPは受信データをキューに溜めてアプリケーションに渡すため、タスクの休止・起床(sched)の仕組みを必要とします。あわせてプラットフォーム依存のタスク制御コードも用意します。
UDPモジュールのコードのコピー
UDPモジュールのコードをmicropsからコピーします。修正は不要で、そのまま利用できます。
$ cp $MICROPS/udp.{h,c} kernel/net/
環境依存のタスク制御
UDPモジュールは、受信キューが空のときにタスクを休止し、パケットが届いたら起床する、という制御を行います。xv6のsleep()/wakeup()を使ってこれを実装したコードをsched.c/sched.hとして追加します。
Note
xv6では
sleep()とwakeup()でタスクを休止・起床できます。両者は休止と起床を対応付けるために任意のポインタ(xv6では「チャネル」と呼びます)を受け取りますが、これはアドレスの一致確認にのみ使われるため、どのような資源を指定しても構いません。また、sleep()を呼び出す際にはあらかじめスピンロックを獲得しておく必要があります。pthread_condを使う場合とほぼ同じように実装できます。
📝 kernel/net/platform/xv6-riscv/sched.h
#ifndef SCHED_H
#define SCHED_H
#include <time.h>
struct sched_task {
struct sched_task *next;
int interrupted;
int wc; /* wait count */
};
#define SCHED_TASK_INITIALIZER {NULL, 0, 0}
extern int
sched_task_init(struct sched_task *task);
extern int
sched_task_destroy(struct sched_task *task);
extern int
sched_task_sleep(struct sched_task *task, lock_t *lock, const struct timespec *abstime);
extern int
sched_task_wakeup(struct sched_task *task);
extern int
sched_init(void);
extern int
sched_run(void);
extern int
sched_shutdown(void);
#endif
📝 kernel/net/platform/xv6-riscv/sched.c
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include "platform.h"
#include "util.h"
int
sched_task_init(struct sched_task *task)
{
task->next = NULL;
task->interrupted = 0;
task->wc = 0;
return 0;
}
int
sched_task_destroy(struct sched_task *task)
{
if (task->wc) {
return -1;
}
return 0;
}
/*
* NOTE: sleep() releases the lock while sleeping and reacquires it on wakeup,
* so the caller can use it just like pthread_cond_wait().
*/
int
sched_task_sleep(struct sched_task *task, lock_t *lock, const struct timespec *abstime)
{
(void)abstime; /* timeout is not supported */
if (task->interrupted) {
errno = EINTR;
return -1;
}
task->wc++;
sleep(task, lock);
task->wc--;
if (task->interrupted) {
if (!task->wc) {
task->interrupted = 0;
}
errno = EINTR;
return -1;
}
return 0;
}
int
sched_task_wakeup(struct sched_task *task)
{
wakeup(task);
return 0;
}
int
sched_init(void)
{
return 0;
}
int
sched_run(void)
{
return 0;
}
int
sched_shutdown(void)
{
return 0;
}
sched.hはstruct timespec(タイムアウト指定の型)を参照するので、kernel/time.hに定義を追加します。
📝 kernel/time.h
...
struct timeval {
long tv_sec;
long tv_usec;
};
+struct timespec {
+ long tv_sec;
+ long tv_nsec;
+};
+
struct tm {
...
platform.hとplatform.cにスケジューラの初期化・起動を組み込みます。あわせて、プロトコルスタック本体が参照するerrnoの実体をplatform.cに置きます。
📝 kernel/net/platform/xv6-riscv/platform.h
...
#include "intr.h"
#include "timer.h"
+#include "sched.h"
#endif
📝 kernel/net/platform/xv6-riscv/platform.c
#include "platform.h"
#include "util.h"
#include "net.h"
+int errno;
+
static uint32 seed = 1;
int
platform_init(void)
{
...
if (timer_init() == -1) {
return -1;
}
+ if (sched_init() == -1) {
+ return -1;
+ }
return 0;
}
int
platform_run(void)
{
...
if (timer_run() == -1) {
return -1;
}
+ if (sched_run() == -1) {
+ return -1;
+ }
return 0;
}
...
初期化関数の呼び出し
net.cでUDPモジュールのヘッダをインクルードし、net_init()からudp_init()を呼び出すようにします。
📝 kernel/net/net.c
...
-//#include "udp.h"
+#include "udp.h"
...
int
net_init(void)
{
...
-// if (udp_init() == -1) {
-// errorf("udp_init() failure");
-// return -1;
-// }
+ if (udp_init() == -1) {
+ errorf("udp_init() failure");
+ return -1;
+ }
...
}
...
Makefileの修正
udp.oとsched.oをビルド対象に追加します。
📝 Makefile
...
$N/ip.o \
$N/icmp.o \
+ $N/udp.o \
$P/platform.o \
$P/intr.o \
$P/timer.o \
+ $P/sched.o \
$P/driver/virtio_net.o \
...
動作確認
再ビルドした後、make qemuを実行してxv6を起動します。起動ログから、プロトコル番号17(UDP)がIPモジュールに登録されていることが確認できます。
10:00:00.101 [I] ip_protocol_register: success, protocol=17 (kernel/net/ip.c:290)
UDPの受信を試すアプリケーションはまだ無いので、ここではプロトコルの登録とビルドが通ることの確認までとします。ホストから192.0.2.2のUDPポートにパケットを送ると、そのポートを使うアプリケーションが無いためxv6側はICMPのPort Unreachableを返します。この挙動からもUDPモジュールが受信処理まで動いていることが分かります。
$ echo hello | nc -u -w1 192.0.2.2 7
10:00:05.201 [D] udp_input: 192.0.2.1:xxxxx => 192.0.2.2:7, len=14, dev=net0 (kernel/net/udp.c:201)
10:00:05.202 [D] icmp_output: 192.0.2.2 => 192.0.2.1, len=96 (kernel/net/icmp.c:163)