「ping」によるネットワーク通信機能を実装してみよう:H8マイコンボードで動作する組み込みOSを自作してみよう!(6)(3/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); …… }
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); …… }
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); …… }
リスト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); …… }
ここでリスト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); …… }
最後に、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); …… }
リスト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(); …… }
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:~>%
おおっ! 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>
こちらも問題なく通信できています。「Wireshark」などでキャプチャーしてみると、MACアドレス解決されて、ICMPパケットが流れている様子が確認できます。
8.次回
今回はネットワーク機能の“序盤”として、pingによる疎通が行えるようにプログラムを実装しました。次回はいよいよTCP/IPを実装し、本連載の最終目標である「フルスクラッチの自作Webサーバ」を動作させます。ご期待ください! (次回に続く)
筆者紹介:
坂井弘亮(さかいひろあき)
某企業でネットワーク系の開発を行いながら、個人で組み込みOS「KOZOS」の開発や書籍/雑誌記事執筆、各種セミナーでの発表やイベント出展、勉強会の開催など多方面で活動中。IPAセキュリティ&プログラミングキャンプ講師(2010〜)。著書は「12ステップで作る組込みOS自作入門」など多数。
Copyright © ITmedia, Inc. All Rights Reserved.