「ping」によるネットワーク通信機能を実装してみようH8マイコンボードで動作する組み込みOSを自作してみよう!(6)(3/3 ページ)

» 2011年11月02日 15時27分 公開
[坂井弘亮@IT MONOist]
前のページへ 1|2|3       
※本記事はアフィリエイトプログラムによる収益を得ています

 次に送信処理を追ってみましょう。icmpタスクから転送されたメッセージは、ipタスクがkz_recv()で受け取り、ip_proc()からリスト6の「ip_send()」が呼ばれます。ここではIPヘッダを付加して、ethernetタスクに転送します。


static int ip_send(struct netbuf *pkt)
{
  ……
  pkt->cmd = ETHERNET_CMD_SEND;
  ……
  kz_send(MSGBOX_ID_ETHPROC, 0, (char *)pkt);
  ……
}
リスト6 IPの送信処理(ip.c)

 ipタスクから転送されたメッセージは、ethernetタスクがkz_recv()で受け取り、ethernet_proc()からリスト7の「ethernet_send()」が呼ばれます。ethernet_send()では、まだ送信先のMACアドレスが不明なのでリスト7のif文の中に入り、そのままarpタスクに転送します。

static int ethernet_send(struct netbuf *pkt)
{
  ……
  /* 送信先MACアドレスが不明なので,ARPタスクに転送して解決してもらう */
  if (!memcmp(pkt->option.ethernet.send.dst_macaddr,
              "\x00\x00\x00\x00\x00\x00", MACADDR_SIZE)) {
    pkt->cmd = ARP_CMD_SEND;
    kz_send(MSGBOX_ID_ARPPROC, 0, (char *)pkt);
    return 1;
  }
 
  pkt->cmd = NETDRV_CMD_SEND;
  ……
  kz_send(MSGBOX_ID_NETPROC, 0, (char *)pkt);
  ……
}
リスト7 Ethernetの送信処理(ethernet.c)

 arpタスクは、リスト8のようになっています。メイン関数の「arp_main()」で、kz_recv()によってメッセージを受信し、「arp_proc()」が呼ばれ、送信パケットならば「arp_send()」が呼ばれます。arp_send()では、まずパケットを“MSGBOX_ID_ARPPKTLIST”というキューに保存し、解決済みのMACアドレスならば「arp_flush()」を呼ぶことで、そのMACアドレスを補填してからパケットをethernetタスクに渡して送信依頼します。MACアドレスが未解決ならば、パケットはキューイングしたままでARP Requestを送信し、解決を試みます。

static int arp_sendpkt(uint16 operation, uint8 macaddr[], uint32 ipaddr)
{
  ……
  pkt->cmd = ETHERNET_CMD_SEND;
  ……
  pkt->option.ethernet.send.type = ETHERNET_TYPE_ARP;
 
  kz_send(MSGBOX_ID_ETHPROC, 0, (char *)pkt);
  ……
}
 
……
 
static int arp_send(struct netbuf *pkt)
{
  ……
  kz_send(MSGBOX_ID_ARPPKTLIST, 0, (char *)pkt);
  ……
  addr = arp_getaddr(pkt->option.ethernet.send.dst_ipaddr);
 
  if (addr) {
    arp_flush(-1);
  } else {
    /* ARP request を送信 */
    arp_sendpkt(ARP_OPERATION_REQUEST, NULL,
                pkt->option.ethernet.send.dst_ipaddr);
  }
  ……
}
 
static int arp_proc(struct netbuf *buf)
{
  ……
  switch (buf->cmd) {
  ……
  case ARP_CMD_RECV:
    ret = arp_recv(buf);
    break;
  case ARP_CMD_SEND:
    ret = arp_send(buf);
    break;
  ……
}
 
int arp_main(int argc, char *argv[])
{
  ……
  while (1) {
    kz_recv(MSGBOX_ID_ARPPROC, NULL, (char **)&buf);
    ret = arp_proc(buf);
    ……
}
リスト8 ARPの送信処理(arp.c)

 リスト9は、ARPパケットを受信した場合のarpタスクの処理です。「arp_recv()」が呼ばれてMACアドレス解決され、さらにarp_flush()が呼ばれることで“MSGBOX_ID_ARPPKTLIST”に送信保留されているパケットがethernetタスクに転送され、送信処理が再開されます。

static void arp_flush(int count)
{
  ……
  while (1) {
    kz_recv(MSGBOX_ID_ARPPKTLIST, NULL, (char **)&pkt);
    ……
    addr = arp_getaddr(pkt->option.ethernet.send.dst_ipaddr);
    ……
    memcpy(pkt->option.ethernet.send.dst_macaddr, addr->macaddr, MACADDR_SIZE);
 
    pkt->cmd = ETHERNET_CMD_SEND;
    kz_send(MSGBOX_ID_ETHPROC, 0, (char *)pkt);
    ……
}
 
……
 
static int arp_recv(struct netbuf *pkt)
{
  ……
  switch (arphdr->operation) {
  case ARP_OPERATION_REPLY:
  case ARP_OPERATION_REQUEST:
    ……
    addr = arp_setaddr(ipaddr, arphdr->sender_macaddr);
    arp_flush(-1);
    if (arphdr->operation == ARP_OPERATION_REPLY)
      break;
    ……
    /* ARP reply を返送 */
    arp_sendpkt(ARP_OPERATION_REPLY, addr->macaddr, addr->ipaddr);
    ……
}
リスト9 ARPの受信処理(arp.c)

 ここでリスト7をもう一度見てください。送信再開によって、arpタスクからethernetタスクに渡されたパケットは、今度はarpタスクによってMACアドレスが補填されているため、ethernet_send()のif文には入らずに、netdrvタスクに対して転送されています。

 リスト10は、netdrvタスクの送信処理部分です。メッセージは「netdrv_main()」のkz_recv()によって受信し、「netdrv_proc()」が呼ばれて実際の送信処理が行われます。

/* スレッドからの要求を処理する */
static int netdrv_proc(struct netbuf *buf)
{
  switch (buf->cmd) {
  ……
  case NETDRV_CMD_SEND: /* イーサネットへのフレーム出力 */
    rtl8019_send(0, buf->size, buf->top);
    ……
}
 
int netdrv_main(int argc, char *argv[])
{
  ……
  kz_setintr(SOFTVEC_TYPE_ETHINTR, netdrv_intr); /* 割り込みハンドラ設定 */
 
  while (1) {
    kz_recv(MSGBOX_ID_NETPROC, NULL, (char **)&buf);
    netdrv_proc(buf);
    ……
}
リスト10 フレームの送信処理(netdrv.c)

 最後に、KOZOS側からのICMP Echoの送信について説明します。icmpタスクは、ICMP Echoの“自分からの送信機能”も持っています。icmpタスクに“ICMP_CMD_SEND”というメッセージを送ることで、リスト11の「icmp_send()」が呼ばれ、ICMP Echoが送信されます。

static int icmp_send(struct netbuf *pkt)
{
  ……
    icmp_sendpkt(pkt->option.icmp.send.ipaddr,
                 ICMP_TYPE_REQUEST, 0, id, i,
                 64 - sizeof(struct icmp_header), NULL);
  ……
}
リスト11: ICMPの送信処理(icmp.c)

 リスト12は、コマンド処理タスク(command.c)に対する改造です。マイコンボードのコンソール経由でpingコマンドを実行することで、icmpタスクに対して“ICMP_CMD_SEND”をメッセージ送信し、ICMP Echoの送信を開始します。

/* pingの開始をicmpタスクに依頼する */
static void send_icmp()
{
  ……
  buf->cmd = ICMP_CMD_SEND;
  ……
  kz_send(MSGBOX_ID_ICMPPROC, 0, (char *)buf);
}
 
int command_main(int argc, char *argv[])
{
  ……
  while (1) {
    ……
    /* コンソールからの受信文字列を受け取る */
    kz_recv(MSGBOX_ID_CONSINPUT, &size, &p);
    ……
    } else if (!strncmp(p, "ping", 4)) { /* pingコマンド */
      send_write("ping start.\n");
      send_icmp();
    ……
}
リスト12 pingコマンドの実装(command.c)

7.pingの応答を試してみよう

 マイコンボードで実際に動作させてみましょう。まずは、前述のWebサイトの「ソースコードはこちら」からソースコード(h8_05)をダウンロードし、ビルドします。ビルドと起動の手順は前回までと同様です(ただし部分的に修正が必要です。詳しくはWebサイトの方を参照してください)。

 PCとマイコンボードをLANケーブルで接続します。接続にはクロスケーブルを用いるか、間にハブを入れる必要があります。

 KOZOS側のIPアドレスは「192.168.10.16」、通信相手のIPアドレスは「192.168.10.1」に固定してあるので、PCのIPアドレスを「192.168.10.1」に設定します。この状態で、PCから「192.168.10.16」に対してpingを発行してみます(リスト13)。

hiroaki@letsnote:~>% ping 192.168.10.16
PING 192.168.10.16 (192.168.10.16): 56 data bytes
64 bytes from 192.168.10.16: icmp_seq=0 ttl=64 time=79.725 ms
64 bytes from 192.168.10.16: icmp_seq=1 ttl=64 time=59.871 ms
64 bytes from 192.168.10.16: icmp_seq=2 ttl=64 time=59.878 ms
64 bytes from 192.168.10.16: icmp_seq=3 ttl=64 time=59.790 ms
64 bytes from 192.168.10.16: icmp_seq=4 ttl=64 time=59.795 ms
^C
--- 192.168.10.16 ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 59.790/63.812/79.725/7.957 ms
hiroaki@letsnote:~>% 
リスト13 pingの受信と応答

 おおっ! pingに応答できています。感動です! さらにマイコンボードでpingを実行して、KOZOS側から疎通確認をしてみましょう(リスト14)。

command> ping
ping start.
command> ICMP received: c0a80a01 00 00 08ec
ICMP received: c0a80a01 00 00 08eb
ICMP received: c0a80a01 00 00 08ea
 
unknown.
command> 
リスト14 pingの送信の実行

 こちらも問題なく通信できています。「Wireshark」などでキャプチャーしてみると、MACアドレス解決されて、ICMPパケットが流れている様子が確認できます。

8.次回

 今回はネットワーク機能の“序盤”として、pingによる疎通が行えるようにプログラムを実装しました。次回はいよいよTCP/IPを実装し、本連載の最終目標である「フルスクラッチの自作Webサーバ」を動作させます。ご期待ください! (次回に続く)


筆者紹介:

坂井弘亮(さかいひろあき)


某企業でネットワーク系の開発を行いながら、個人で組み込みOS「KOZOS」の開発や書籍/雑誌記事執筆、各種セミナーでの発表やイベント出展、勉強会の開催など多方面で活動中。IPAセキュリティ&プログラミングキャンプ講師(2010〜)。著書は「12ステップで作る組込みOS自作入門」など多数。


モノづくりが大好き!(筆者Webサイト)

KOZOSのブログ(筆者ブログ)



前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.