自作OS×自作ブラウザで学ぶ
Webページが表示されるまで
サポートページ
About
このページは、Web+DB Press Vol.120に掲載されている記事「自作OS×自作ブラウザで学ぶ Webページが表示されるまで」に関する補足情報をまとめたページです。
本ページの内容に関するお問い合わせは、著者である@hikaliumおよび@d0iasmまで、Issues経由でお願いします。
プロンプトの表記について
誌面および本ページ内では、各コマンドの前に、どの環境でそのコマンドを実行するべきなのかわかるよう、プロンプトをつけている場合があります。
$ something # 通常のLinuxホスト上で実行するコマンド
(host)$ something # ホスト上で実行することを明確にしたい場合
(liumos)$ something # liumOS上で実行するコマンド
(liumos-builder)$ something # Dockerインスタンス上のLinuxで実行するコマンド
このシェル($
以前)の部分は実際には入力する必要はありません。
各章ごとの補足
事前準備
liumOSのレポジトリはgithub:hikalium/liumosから取得することができます。
(host) git clone https://github.com/hikalium/liumos.git
以下の各コマンドについては、wdb_120ブランチで動作することを確認しています。
環境構築の手間を省くため、Dockerを利用する方法を誌面では説明しています。Dockerのインストール手順については公式ページの情報をご参照ください。
Docker環境の外でアプリケーションやOSをビルドする場合には、README.mdに記載の環境構築を事前に行ってください。
第1章
Dockerのインストールについては事前準備を参照してください。
Dockerなしで実験したい場合には、README.mdのSetup tap interface (for linux)の項に記載の手順で設定を行ってください。ただし、この方法はコンピューターのネットワーク設定を変更するため、特にリモートマシンで試される際には細心の注意を払って行ってください。
第2章
この章で実装するping.bin
のソースコードはapp/ping/ping.cから確認できます。
liumOS上で動作確認をしたい場合は
(host) make run_docker
を実行してliumOSを起動します。初回起動時は、Dockerコンテナのイメージをダウンロードするため、多少時間がかかりますが、次回以降は数秒で完了します。
次に、起動したliumOSのシリアルコンソールに、以下のコマンドでアタッチします。
(host) telnet localhost 1235
接続した直後はプロンプトが見えませんが、Enterキーを押すと以下のようにプロンプトが出てくるはずです。(出ない場合は自作OSの起動に失敗している可能性がありますので、再度make run_docker
を実行してください。)
$ telnet localhost 1235
Trying ::1...
Connected to localhost.
Escape character is '^]'.
# ここでEnterキーを押す
Not a command or file:
(liumos)$ # プロンプトが出てきた
その後、pingコマンドを実行してみます。
(liumos)$ ping.bin 10.0.2.2
ping.bin 10.0.2.2
Ping to 10.0.2.2...
kernel: sys_socket: socket (fd=3) created (IPv4, DGRAM, ICMP)
kernel: dst is in the same subnet.
kernel: ARP request sent to 10.0.2.2...
kernel: ARP entry found!
recvfrom returned: 8
00 00 FF FF 00 00 00 00
ICMP packet recieved from 10.0.2.2 ICMP Type = 0
うまくいかない場合は、一度すべてのシェルを閉じて、最初のmake run_docker
のステップからやり直してみてください。
第3章
この章で言及するソースコードの全体像は、下記から確認できます。
udpserver.bin
udpclient.bin
- システムコールハンドラの実装
- virtio-netドライバの実装
- ネットワーク関連の実装(本文では触れていないが参考までに)
(liumos) udpclient.bin -> (Linux) udpserver.bin
(host)$ make run_docker
(host)$ docker exec -it liumos-builder0 /bin/bash
(liumos-builder)$ app/udpserver/udpserver.bin 8888
(host)$ telnet localhost 1235
(liumos)$ udpclient.bin 10.0.2.2 8888 Hello!
(liumos)$ udpserver.bin <- (Linux) udpclient.bin
(host)$ make run_docker
(host)$ docker exec -it liumos-builder0 /bin/bash
(host)$ telnet localhost 1235
(serial)$ udpserver.bin 8889
(liumos-builder)$ app/udpclient/udpclient.bin 127.0.0.1 8889 hello
第4章
第4章で使用するソースコードは以下の通りです。
httpserver.bin
httpclient.bin
liumOS上のhttpclient.bin
と、Linux上のhttpserver.bin
の通信
(host)$ make run_docker
// server
(host)$ docker exec -it liumos-builder0 /bin/bash
(liumos-builder)$ /liumos/app/httpserver/httpserver.bin --port 8888
// client
(host)$ telnet localhost 1235
(liumos)$ httpclient.bin --ip 10.0.2.2 --port 8888 --path /index.html
Linux上のhttpclient.bin
と、liumOS上のhttpserver.bin
の通信
// server
(host)$ telnet localhost 1235
(liumos)$ httpserver.bin --port 8888
// client
(host)$ docker exec -it liumos-builder0 /bin/bash
(liumos-builder)$ /liumos/app/httpclient/httpclient.bin --ip 127.0.0.1 --port 8888 --path /index.html
ローカルのLinux環境で試す場合
// server
$ ./httpserver.bin --port 8888
// client
$ ./httpclient.bin --ip 127.0.0.1 --port 8888 --path /index.html
もし、ポート番号が他のアプリケーションですでに使用されていると、Error: Failed to bind a socket
というメッセージが出ます。その場合は--port
で指定するポート番号を変えてください。
訂正
第4章でHTTPサーバを実装するさいのリクエストを待ち受けるコードに間違いがありました。address
変数をbind()
する必要があります。
間違い(誌面)
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
recvfrom(socket_fd, request, SIZE_REQUEST, 0,
(struct sockaddr*) &address, &addrlen);
訂正
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
// 訂正箇所: bind関数によって、address変数の情報とsocketを結びつける必要があります。
if (bind(socket_fd, (struct sockaddr *) &address, addrlen) < 0) {
Println("Error: Failed to bind a socket");
exit(EXIT_FAILURE);
}
// 補足: recvfrom関数によってaddress変数はクライアントの情報へと上書きされます。後にsendto関数によってクライアントへレスポンスを送る際にaddress変数を再度使用します。
recvfrom(socket_fd, request, SIZE_REQUEST, 0,
(struct sockaddr*) &address, &addrlen);
第5章
第5章で使用するソースコードは以下の通りです。
browser.bin
- app/browser/browser.c: コマンドライン引数やURLをパースしたり、HTTPクライアントの役割をする。
main
関数を含む。 - app/browser/tokenize.c: HTML文字列のトークナイズを行う。
- app/browser/parse.c: トークン列からツリーを構築する。
- app/browser/rendering.c: ツリーを走査してMarkdownを出力する。
- app/browser/browser.c: コマンドライン引数やURLをパースしたり、HTTPクライアントの役割をする。
-
httpserver.bin
- app/httpserver/httpserver.c: 第4章で使用したものと同じ。
liumOS上のブラウザとLinux上のサーバで試す場合
// run docker
(host)$ make run_docker
// server
(host)$ docker exec -it liumos-builder0 /bin/bash
(liumos-builder)$ /liumos/app/httpserver/httpserver.bin --port 8888
// client
(host)$ telnet localhost 1235
(liumos)$ browser.bin --url http://10.0.2.2:8888/index.html
その他のURLを使用したbrowser.bin
のコマンド
browser.bin --url http://10.0.2.2:8888/page1.html
browser.bin --url http://10.0.2.2:8888/page2.html
browser.bin --url http://10.0.2.2:8888/not_exist.html
ローカルのLinux環境で試す場合
// server
$ ./httpserver.bin --port 8888
// client
$ ./browser.bin --url http://127.0.0.1:8888/index.html
もし、ポート番号が他のアプリケーションですでに使用されていると、Error: Failed to bind a socket
というメッセージが出ます。その場合は--port
で指定するポート番号を変えてください。
既知の問題
Docker上でping.binが動作しない
$ docker exec -it liumos-builder0 /bin/bash
(liumos-builder)$ app/ping/ping.bin 8.8.8.8
Ping to 8.8.8.8...
socket() failed
おそらく筆者が用意したDocker環境の問題です。解決策が見つかり次第修正します。
それまでは、Linux環境でDockerを使わずにliumOSの環境を整備して直接ビルドして実験するか、Linux上で動くかどうかのテストはスキップして、自作OS上でping.binを実行してみてください。(自作OS上で動かす方が簡単だなんて不思議ですね…。)
Virtio::Net not initialized yetというエラーでOSが起動しないんだけど…
自作OS側のバグで、現在修正中です。
再起動すれば多くの場合でうまくいくはずです。お手数をおかけしますが、もう一度お試しください。(もしくは、自作OSのデバッグの大変さを味わうというのもまた一興でしょう。)
Author
License
- このページの内容: CC BY 4.0
- このページのソース: MIT License
- Based on jekyll/minima