Open7
ネットワークパケットはどのようにNICやCPUを経由してアプリまで届いているのか?を調べる
ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識 | 戸根 勤, 日経NETWORK |本 | 通販 | Amazon をまず読み解く
アプリが通信を確立するとき
p79
- アプリが
socket
を呼び出すと、ソケットが作成される。アプリがソケットを識別するためのfd(ファイルディスクリプタ)が返ってくる。 - 続いて
bind(fd, port, ...)
をよんでソケットにポート番号を記録する - 続いて
listen(fd, ...)
を呼んでソケットに接続待ち状態であるという制御情報を記録する - 続いて
accept
を実行し、ソケットをコピーしクライアントからの通信を待ち受ける - 以後、新しい通信がくるたびにソケットをコピーして対応する
この時ソケットをコピーするが、クライアントからすると常に同じポート番号でアクセスできないと困るため、コピー先のソケットも同じポート番号を持つことになる。
そうなると区分けができなくなるので、クライアントのIP・ポート、サーバのIP・ポートの4つを key としてソケットを value とするハッシュテーブルで対応する。
サーバIP | サーバポート | クライアントIP | クライアントポート | ソケット |
---|---|---|---|---|
a | b | c | d | ソケットA |
a | b | c | e | ソケットB |
アプリがデータを送受信するとき
送信側
- アプリは送信データを自身のメモリに保存しておく
-
write(fd, data, data_length, ...)
を呼び出し、OSのプロトコルスタックに依頼する - プロトコルスタックが、任意のプロトコル(ex: TCP -> IP) でラップし、LANドライバに渡す
- LAN ドライバは LAN アダプタの buffer memory にコピーする(p 165)
- コピーしたら、LAN アダプタ内の MAC 回路にパケットを送信するようにコマンドを投げる
- MAC 回路は buffer memory から送信パケットを取り出して、L2(ex: Ethernet) でラップする
- 続いて、MAC 回路は電気信号に変換し、PHY(Physical Layer Unit) or MAU(Medium Attachment Unit) と呼ぶ信号送受信部分に送る
- デジタルデータを信号に変換する速度 = 伝送速度 (ex: 10Mbps)
- PHY(MAU) 回路がケーブルに送り出す形式に変換して、ケーブルに送信する
受信側
p 174 / p 402
- ケーブルからPHY(MAU) 回路が受け取った信号を共通形式に変換して、MAC回路に送る
- MAC回路は信号を頭からデジタルデータに変換し Buffer Memory に貯める
- この時FCSを使って破損チェックを行う
- Ethernet ヘッダの宛先MACアドレスを見て、自分のMACアドレスと一致していれば Buffer Memory に保存する
- パケットを受信したことを 「割り込み」 として通知する。
- LANアダプタが拡張バススロット部分にある割り込み用の信号線に信号を送る
- 信号線はコンピュータ本体の割り込みコントローラを通じて CPU につながっている
- CPU は信号が流れてくると実行していた、OS内部の割り込み処理用のプログラムに切り替える
- LAN ドライバが呼び出される
- 割り込みによって LAN ドライバが動き、LANアダプタの Buffer Memory から保存されたパケットを取得する
- LAN ドライバはMAC ヘッダの
type field
からプロトコルを判別し、対応するプロトコルスタックにパケットを渡す(ここでは IP -> TCP とする) - プロトコルスタックの IP 担当は、IP ヘッダから宛先 IP を取得し LAN アダプタに割り当てた IP と一致することを確認する。問題なければ TCP 担当に渡す
- TCP 担当は、IPヘッダの宛先IP、送信元IP、TCPヘッダの宛先ポート、送信元ポートを調べ該当するソケットを探す。
- ソケットの状態をチェックして、飛んできたパケットが制御系であれば応答用のパケットを TCP 担当が作って返したり、切断する。そうでなければ、データを受信バッファに貯める。
- 接続の場合、TCP担当は該当する接続待ちのソケットをコピーして新しいソケットを作成する
- アプリが、Socket ライブラリの
read(fd, receive_buffer, ...)
を介してOS のプロトコルスタックに受信動作を依頼する(ここは既に実施されているのが普通) - アプリにデータを渡し、受信バッファを空にする
今抜けているところ
- (Linux における) kernel のプロトコルスタックの処理がまだ解像度低い
- 今は NAIP という技術で interrupt が少なくなってるとかなんとか
-
read()
はわかったけど、これread
用にスレッド作ってるわけじゃないよね??どうしてるんだ? - I/O の多重化
epoll
/io_uring
とか絡むけどよくわからんので調べる
これ読む
read
の話
サーバー入門、非同期処理入門、epoll 入門 | blog.ojisan.io
これを見ると
- シングルスレッドで、
for - loop
内でread()
を毎回叩きパケット飛んできた時に処理 - マルチスレッドで、
read()
を専門でやるスレッドを作る - シングルスレッドで、
epoll
などのシステムコールを使ったイベント駆動
が紹介されてた。
epoll(7) は Linux 系の OS に備わっているイベント通知インターフェース
epoll では監視したいイベントを登録しておけば、そのイベントが完了した時に通知を受け取れます。一般的には epoll instance を作り、そのファイルディスクリプタに監視対象を紐付け、そのファイルディスクリプタを監視することでイベントの発生を検知します。
うーーん。わからないのは、epoll
システムコールを使うとカーネルからアプリに push してくれるということなんかな?
epoll
in Orelly
ちょっと風呂敷が広がってきた。
当初の目的の 「ネットワークパケットはどのようにNICやCPUを経由してアプリまで届いているのか?」は概ね理解できたので、デバイスドライバの本パラパラめくって終わりにする。
同僚がいい記事を書いてたのでしっかり読む