Linux ネットワーク・スタックの徹底調査

ソケットからデバイス・ドライバーまで

Linux ® オペレーティング・システムでとりわけ優れた特徴の 1 つとなっているのは、そのネットワーク・スタックです。当初、BSD スタックの派生的なものだったネットワーク・スタックは、今では明確な一連のインターフェースで整然と編成されています。インターフェースには、共通ソケット層インターフェースやデバイス層といったプロトコルに依存しないインターフェースから、それぞれのネットワーク・プロトコルに固有のインターフェースまであります。この記事では、層という観点から Linux ネットワーク・スタックの構造を説明するとともに、そこに含まれる主要な構造をいくつか取り上げます。

M. Tim Jones, Consultant Engineer, Emulex

M. Tim JonesM. Tim Jones は組み込みソフトウェアのエンジニアであり、『GNU/Linux Application Programming』や『AI Application Programming』(現在、第 2 版)、それに『BSD Sockets Programming from a Multilanguage Perspective』などの著者でもあります。技術的な経歴は静止軌道衛星用のカーネル開発から、組み込みシステム・アーキテクチャーやネットワーク・プロトコル開発まで、広範にわたっています。また、コロラド州ロングモン所在のEmulex Corp. の顧問エンジニアでもあります。



2007年 6月 27日

プロトコルの概要

ネットワークの正式な紹介ではオープン・システム間相互接続 (OSI) モデルに目を向けるのが一般的ですが、この記事ではインターネット・モデルとして知られる 4 層モデルを取り上げて Linux の基本ネットワーク・スタックを紹介します (図 1 を参照)。

図 1. ネットワーク・スタックのインターネット・モデル
The Internet model of a network stack

スタックの一番下にあるのはリンク層 (link layer) です。リンク層とは物理層へのアクセスを提供するデバイス・ドライバーのことで、この層にはシリアル・リンクやイーサネット・デバイスなど、数々の媒体が考えられます。リンク層の上には、パケットをそれぞれの宛先に転送する役割を持つネットワーク層 (network layer) があり、さらにその上のトランスポート層 (transport layer) がホスト内などでピアツーピアの通信を行います。つまり、ネットワーク層がホスト間の通信を管理する一方、トランスポート層はホスト内でのエンドポイント間通信を管理するということです。スタックの一番上にはアプリケーション層 (application layer) があります。アプリケーション層は通常、送信対象のデータを認識するセマンティック層です。例えば HTTP (Hypertext Transfer Protocol) は、サーバーとクライアント間で Web コンテンツに関する要求と応答の受け渡しを行います。

事実上、ネットワーク・スタックの各層はもっとわかりやすい名前で通っています。リンク層にあるのは、最もよく使われている高速メディア、イーサネットです。イーサネット以前の古いリンク層プロトコルとしては、SLIP (Serial Line Internet Protocol)、CSLIP (Compressed SLIP)、PPP (Point-to-Point Protocol) などもあります。ネットワーク層で最も一般的なネットワーク・プロトコルは IP (Internet Protocol) ですが、その他のニーズを満たす ICMP (Internet Control Message Protocol) や ARP (Address Resolution Protocol) などのプロトコルもこの層にあります。トランスポート層にあるのは、TCP (Transmission Control Protocol) と UDP (User Datagram Protocol) です。そしてアプリケーション層には、標準 Web プロトコルの HTTP、そして E メール・プロトコルの SMTP (Simple Mail Transfer Protocol) など、お馴染みの数多くのプロトコルがあります。


コア・ネットワーク・アーキテクチャー

今度は Linux ネットワーク・スタックのアーキテクチャーに目を向けて、このインターネット・モデルがどのように実装されているかを見てみましょう。図 2 は、Linux ネットワーク・スタックの概要です。一番上にあるユーザー空間 (User space) 層、つまりアプリケーション層がネットワーク・スタックのユーザーを定義し、一番下にある物理デバイス (physical devices hardware) がネットワーク (シリアル・ネットワーク、またはイーサネットなどの高速ネットワーク) との接続を行います。記事で焦点とするネットワーク・サブシステムは、この 2 つの層の中間にあるカーネル空間 (kernel space) です。このネットワーク・スタックの内側を流れるソケット・バッファー (sk_buff) が、パケット・データをソースとシンクとの間で移動させます。sk_buff 構造については、後で説明します。

図 2. Linux ネットワーク・スタックのアーキテクチャー概要
Linux high-level network stack architecture

ここでひとまず Linux ネットワーク・サブシステムのコア要素を簡単に紹介してから、以降のセクションで詳細を説明していきます。まず、最上部 (図 2 を参照) にはシステム・コール・インターフェース (System call interface) があります。これは、ユーザー空間アプリケーションがカーネルのネットワーク・サブシステムにアクセスするための手段を提供するだけのインターフェースです。その下のプロトコル非依存 (Protocol agnostic) 層は、基礎となるトランスポート・レベルのプロトコルと連動するための共通手段を提供します。この層に続くのが実際のプロトコル (Network protocols) で、Linux では TCP と UDP、そしてもちろん IP などの組み込みプロトコルがあります。その下には使用可能なデバイス・ドライバーとの共通インターフェースを許可するもう 1 つの非依存 (Device agnostic) 層があり、これに続くネットワーク・サブシステムの最下部に個々のデバイス・ドライバー (Device drivers) 自体があります。


システム・コール・インターフェース

システム・コール・インターフェースは、2 つの見方から説明できます。まず、ユーザーがネットワークを呼び出したときには、システム・コール・インターフェースは、この呼び出しをカーネルに多重化します。多重化された呼び出しは最終的に ./net/socket.c にある sys_socketcall を呼び出すことになり、さらに目的のターゲットへの呼び出しに逆多重化されます。もう一方で、システム・コール・インターフェースは、通常のファイル操作を使ってネットワーク入出力を行います。例えば、ネットワーク・ソケット (標準ファイルと同じようにファイル記述子で表されます) では一般的な読み取り書き込み操作が実行されることがあります。そのため、ネットワーク固有の操作 (socket 呼び出しによるソケットの作成、connect呼び出しによる宛先へのソケット接続など) だけでなく多数の標準ファイル操作もあり、これらの操作が通常のファイルと同じようにネットワーク・オブジェクトに適用されます。つまり、システム・コール・インターフェースはユーザー空間アプリケーションとカーネルとの間で制御を移す手段となるということです。


プロトコル非依存インターフェース

ソケット層はプロトコルに依存しないインターフェースとして、一連の共通関数で各種プロトコルをサポートします。ソケット層は一般的な TCP および UDP プロトコルだけでなく、IP、ありのままのイーサネット、そして SCTP (Stream Control Transmission Protocol) といったその他のトランスポート・プロトコルもサポートします。

ネットワーク・スタックでの通信は、ソケットで行われます。Linux でのソケット構造は、linux/include/net/sock.h に定義されている struct sock です。この大規模な構造には、ソケットが使用する特定のプロトコル、そしてそのソケットで実行できる操作を含め、特定のソケットに必要なすべての状態がすべて含まれます。

ネットワーク・サブシステムは、その機能を定義する特殊な構造によって使用可能なプロトコルを認識します。それぞれのプロトコルが維持する proto という構造 (linux/include/net/sock.h 内に配置) が、ソケット層からトランスポート層に対して実行できる特定のソケット操作を定義します (ソケットの作成方法、ソケットによる接続の確立方法、ソケットのクローズ方法など)。


ネットワーク・プロトコル

ネットワーク・プロトコルのセクションでは、使用可能な特定のネットワーク・プロトコル (TCP、UDP など) を定義します。これらのプロトコルはまず、linux/net/ipv4/af_inet.c 内の inet_init という関数で初期化されます (TCP と UDP は inet プロトコル群に含まれるため)。inet_init 関数がそれぞれの組み込みプロトコルを登録するために使うのは proto_register 関数です。linux/net/core/sock.c に定義されたこの関数は、プロトコルをアクティブ・プロトコル・リストに追加する他、必要な場合にはオプションで 1 つ以上のスラブ・キャッシュを割り当てます。

個々のプロトコル自体の特定方法は、linux/net/ipv4/ にある tcp_ipv4.c、udp.c、raw.c の各ファイルの proto 構造体を見るとわかります。各プロトコル構造体はタイプおよびプロトコル別に inetsw_array にマッピングされ、この配列が組み込みプロトコルをそれぞれの操作にマッピングします。inetsw_array の構造体とその関係を図 3 に示します。この配列に含まれる各プロトコルは、inet_init から inet_register_protosw への呼び出しによって最初に inetsw に初期化されます。inet_init 関数はその他、ARP、ICMP、IP モジュールなどの各種 inet モジュールと TCP および UDP モジュールも初期化します。

図 3. インターネット・プロトコルの配列構造体
Structure of the Internet protocol array

ソケット・プロトコルの相関関係

ソケットを作成するときには、例えば my_sock = socket( AF_INET, SOCK_STREAM, 0 ) といったように、タイプとプロトコルを定義することを思い出してください。AF_INET は、SOCK_STREAM (上の図では inetsw_array 内に記載) として定義されたストリーム・ソケットでインターネット・アドレス・ファミリーを指定します。

図 3 では、proto 構造体がトランスポート固有のメソッドを定義し、proto_ops 構造体が汎用のソケット・メソッドを定義していることに注目してください。その他のプロトコルも、inet_register_protosw 呼び出しによって inetsw プロトコル切り替えに追加できます。例えば、SCTP を追加する場合には linux/net/sctp/protocol.c の sctp_init を呼び出します。SCTP についての詳細は、「参考文献」セクションを参照してください。

ソケットのデータ移動は、ソケット・バッファー (sk_buff) と呼ばれるコア構造を使用して行なわれます。sk_buff にはパケット・データの他、プロトコル・スタックの複数の層を対象とした状態データも含まれます。各パケットの送受信を表現するのは、sk_buff です。図 4 に、linux/include/linux/skbuff.h に定義されている sk_buff 構造体を示します。

図 4. ソケット・バッファーおよび他の構造体との関係
Socket buffer and its relationship to other structures

ご覧のように、特定の接続に対して複数の sk_buff を連結することができます。それぞれのsk_buff が、パケットの送信先とするデバイス構造体 (net_device)、あるいはパケットを送信してきたデバイス構造体を識別します。各パケットは sk_buffによって表現されることから、パケット・ヘッダーの位置指定には一連のポインター (メディア・アクセス制御 (MAC) ヘッダーの場合は thiph、および mac) を使えます。sk_buff はソケット・データ管理の中核であるため、sk_buffの作成と消滅、クローン作成、そしてキュー管理を目的とした多数のサポート関数が作成されています。

ソケット・バッファーは特定のソケットに対して互いにリンクするように設計されています。ソケット・バッファーには、プロトコル・ヘッダーへのリンク、タイムスタンプ (パケットが送信または受信された時刻)、そしてパケットに関連付けられたデバイスなど、さまざまな情報が組み込まれます。


デバイス非依存インターフェース

プロトコル層の下にあるのは、もう 1 つの非依存インターフェース層です。このインターフェース層が、さまざまな機能を持つ各種ハードウェア・デバイス・ドライバーにプロトコルを接続します。下位レベルのネットワーク・デバイス・ドライバーは、この層が提供する共通の関数セットを使用して、高位レベルのプロトコル・スタックと連動します。

まず、デバイス・ドライバーは、register_netdevice または unregister_netdevice を呼び出すことで、ドライバー自体をカーネルに対して登録または登録解除します。呼び出し側は net_device 構造体に入力してから、この構造体を登録処理のために渡します。するとカーネルはその init 関数 (定義されている場合) を呼び出して一連の正常性チェックを行い、sysfs エントリーを作成して新規デバイスをデバイス・リスト (カーネル内でアクティブなデバイスのリンク・リスト) に追加します。net_device 構造体は linux/include/linux/netdevice.h にあります。これらの各種関数が実装されているのは、linux/net/core/dev.c です。

sk_buff をプロトコル層からデバイスに送信するには、dev_queue_xmit 関数を使用します。この関数は sk_buff をキューに入れて、基礎となるデバイス・ドライバーが (sk_buffnet_device または sk_buff->dev 参照で定義されたネットワーク・デバイスによって) 最終的に送信できるようにします。sk_buff の送信を初期化するドライバー関数を保持するのは、dev 構造体に含まれる hard_start_xmit というメソッドです。

パケットの受信は通常、netif_rx によって実行されます。下位レベルのデバイス・ドライバーが (割り当てられた sk_buff に含まれる) パケットを受信すると、netif_rx を呼び出すことによって sk_buff がネットワーク層に渡されます。続いて netif_rx 関数は sk_buff を上位層のプロトコルのキューに入れ、netif_rx_schedule で処理できるようにします。dev_queue_xmit 関数と netif_rx 関数は linux/net/core/dev.c にあります。

最近ではカーネルに新しいアプリケーション・プログラム・インターフェース (NAPI) が導入され、ドライバーがデバイス非依存層 (dev) とのインターフェースを取れるようになっています。NAPI を使用するドライバーは一部にはあるものの、大多数のドライバーは今でも古いフレーム受信インターフェースを使っています (およそ 6 対 1 の割合)。NAPI は受信フレームごとの割り込みを避けるため、高負荷状態でのパフォーマンス改善につながります。


デバイス・ドライバー

ネットワーク・スタックの一番下にあるデバイス・ドライバーは、物理ネットワーク・デバイスを管理します。この層に位置するデバイスの例としては、シリアル・インターフェースの SLIP ドライバー、あるいはイーサネット・デバイスのイーサネット・ドライバーがあります。

デバイス・ドライバーは初期化時に net_device 構造体を割り当ててから、必要なルーチンでその構造体を初期化します。ルーチンの 1 つに、送信を行うために上位層が sk_buff をキューに入れる方法を定義する dev->hard_start_xmitがあります。このルーチンが使用するのは sk_buff です。この dev->hard_start_xmit が保持するドライバー関数の操作は基礎となるハードウェアによって異なりますが、sk_buff が記述するパケットは共通してハードウェア・リングまたはキューに移されます。デバイス非依存層で説明したように、フレームの受信に使用されるのは netif_rx インターフェース、または NAPI 準拠のネットワーク・ドライバーの場合は netif_receive_skb インターフェースです。NAPI ドライバーは基礎となるハードウェアの機能に制約を設けます。詳細については「参考文献」セクションを参照してください。

デバイス・ドライバーが dev 構造体でそのインターフェースを構成すると、register_netdevice の呼び出しによってそのドライバーが使用できるようになります。ネットワーク・デバイス固有のドライバーは、linux/drivers/net にあります。


この先の展開

ネットワーク・デバイス・ドライバーをはじめ、各種デバイスに対応したデバイス・ドライバーの設計を学ぶのに最適な方法は、Linux ソース・コードを見ることです。ここに記載されている使用可能なカーネル API は設計も使用方法もさまざまですが、そのそれぞれが有益な情報となったり、あるいは新しいデバイス・ドライバーを作成する際の出発点として役に立ったりします。ネットワーク・スタックの残りのコードは共通しているので、新規プロトコルが必要でない限りそのまま使用できます。さらに TCP (ストリーム・プロトコルの場合) または UDP (メッセージ・ベースのプロトコルの場合) の実装は、新しい開発を始める際にモデルとして役立つはずです。

参考文献

学ぶために

  • TCP/IP、UDP、ICMP の概要については、www.linuxjunkies.org のIntroduction to the Internet Protocols を参照してください。
  • 「Linux システム・コールを使用したカーネル・コマンド」 (developerWorks、2007年3月) では、Linux カーネルの重要な層であるシステム・コール・インターフェースについて説明しています。このシステム・コール・インターフェースには、ユーザー空間とカーネル間の関数呼び出しを可能にする GNU C ライブラリー (glibc) によるユーザー空間のサポートが備わっています。
  • 「/proc ファイルシステムを利用した Linux カーネルへのアクセス」 (developerWorks、2006年3月) では、ユーザー空間アプリケーションがカーネルと通信するための革新的方法を提供する仮想ファイルシステム、/proc ファイルシステムを取り上げています。この記事では /proc とともに、ロード可能カーネル・モジュールも実例を用いて紹介しています。
  • ネットワーク・プロトコルに興味がある方には、BSD と同じく、Linux も理想的なオペレーティング・システムとなります。 「SCTP によるネットワーキングの向上」 (developerWorks、2006年2月) では、非常に興味深いネットワーク・プロトコルの 1 つ、SCTP を取り上げています。SCTP は TCP のように動作しますが、メッセージング、マルチホーミング、マルチストリーミングなど、可用性を高める多数の追加機能があります。
  • 「Linux スラブ・アロケーターの徹底調査」 (developerWorks、2007年5月) では、Linux のメモリー管理でとりわけ特徴的なスラブ・アロケーターについて説明しています。このメカニズムは SunOS で開始されたものですが、Linux カーネルにぴったりの場所を見つけました。
  • ユーザー空間での Linux プログラミングについての詳細は、著者の本 『GNU/Linux Application Programming』 で調べてください。
  • 著者の本 『BSD Sockets Programming from a Multi-Language Perspective』 で、BSD Sockets API を使用したソケット・プログラミングについて学んでください。
  • developerWorks Linux ゾーンでは、 Linux 関連の記事をはじめ、Linux 開発者向けの豊富な資料を紹介しています。
  • developerWorks technical events and Webcastsで最新情報を入手してください。

製品や技術を入手するために

  • developerWorks から直接ダウンロードできる IBM ソフトウェア評価版を使用して、Linux で次の開発プロジェクトを構築してください。

議論するために

  • developerWorks コミュニティーを通して、開発者向けブログ、フォーラム、ポッドキャスト、そしてコミュニティーの話題に参加してください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=242323
ArticleTitle=Linux ネットワーク・スタックの徹底調査
publish-date=06272007