TL

ネットワークスタックのカーネル内データパス

パケットがNICからアプリへ届くまでカーネル内で何が起きるか。sk_buffのゼロコピー、NAPIの割り込み緩和、GRO/TSOのオフロードまで、Linuxネットワークの速さの正体を原理から掴めます。

応用Linuxカーネルネットワークsk_buffNAPITCP/IPゼロコピー最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.パケットはsk_buffという制御構造で表現され、本体データは触らずヘッダのポインタ(head/data/tail/end)だけ動かすことで、各層をまたぐコピーを避ける。
  • 2.NAPIは「1パケット1割り込み」をやめ、最初の割り込みで以後を無効化しポーリングで一括処理する割り込み緩和。高負荷ほどバッチ効率が上がる。
  • 3.GRO(受信)とGSO/TSO(送信)は複数の小パケットを1つの大セグメントに束ねて層をまたがせ、per-packetの固定コストを劇的に削る。

カーネル内データパスとは何か

アプリが recv() で受け取る1バイトは、NIC(ネットワークインターフェースカード)に電気信号として届いてから、ドライバ・プロトコル処理・ソケットバッファという何段もの層を通り抜けてきています。このNICとソケットの間を流れる経路がカーネル内データパスです。ここでの設計判断は「1パケットあたりに固定でかかるコスト(割り込み・関数呼び出し・コピー・ロック)を、どうやってパケット数で割り算して薄めるか」に集約されます。本記事は、その薄め方の中核である sk_buff・NAPI・オフロード を内部から解剖します。

sk_buff:パケットを表す中心構造

カーネルが扱うパケットは1つ残らず struct sk_buff(通称 skb)で表現されます。skbは2つの領域に分かれます。

  • メタデータ(skb本体):プロトコル種別、入力デバイス、チェックサム状態、各層ヘッダへのポインタなどの制御情報。
  • データバッファ:実際のパケットの中身(イーサネットヘッダ+IPヘッダ+TCPヘッダ+ペイロード)。

データバッファには4つのポインタが付随します。head(確保領域の先頭)、data(現在の有効データ先頭)、tail(有効データ末尾)、end(確保領域の末尾)です。headdata の間の隙間をヘッドルームtailend の間をテールルームと呼びます。

[ headroom ][===== 有効データ =====][ tailroom ]
^head       ^data              ^tail          ^end

この構造が効くのは層をまたぐ瞬間です。送信時、TCP層がヘッダを足すには skb_push()data をヘッドルーム側へ戻すだけ、IP層・イーサネット層も同様に data を前へ動かすだけで自分のヘッダ領域を確保できます。受信時は逆に skb_pull()data を後ろへ進め、処理済みヘッダを論理的に剥がします。ペイロード本体は一度も移動しません。これがカーネル内ゼロコピーの第一の柱です。

ヘッドルームが先に確保される理由

ドライバが受信バッファを確保する時点で、後段が skb_push できるようあらかじめヘッドルームを空けておくのが定石です。スタックを下りながら各層がヘッダを足す送信経路でも、ヘッドルームが足りないと再確保とコピーが発生します。NET_SKB_PAD などの定数で確保される余白は、この「後で足すヘッダの置き場」を保証するための設計です。

さらに大きなペイロードは、線形バッファに収まらないぶんを skb_shared_info 配下のページフラグメント配列で持ちます。skbの**複製(clone)**では、メタデータだけ別に持ちデータバッファは参照カウントで共有します。tcpdumpがパケットを覗くときも、本体をコピーせずcloneで枝分かれさせるため安価です。

ソケットからNICまでの処理経路

送信はスタックを下る動きです。send() のデータはまずソケットの送信バッファに積まれ、TCP層がセグメント分割・シーケンス番号付与・チェックサム準備を行い、IP層がルーティングとフラグメント判断、近隣探索(ARP)でL2アドレスを解決し、最後にqdisc(キューイング規律)を経てドライバの送信リングへ渡ります。

受信はスタックを上る逆向きの動きですが、ここに割り込みが絡みます。素朴な実装では「パケット1個到着 → 割り込み1回 → 全処理」ですが、これでは高負荷で割り込みが嵐になります。そこでLinuxは処理を二段に割ります。

  1. 上半分(ハードウェア割り込み):NICが「届いた」と知らせる。ここでは最小限——後述のNAPIをスケジュールするだけ。
  2. 下半分(ソフト割り込み, softirq)NET_RX_SOFTIRQ の文脈で、実際のプロトコル処理(GRO・IP・TCP)をまとめて回す。

この上半分/下半分の二段構えは割り込み処理の二段構え(上半分/下半分)で詳説した設計そのもので、ネットワーク受信はその最大の応用例です。割り込みそのものの仕組みは割り込みと入出力(I/O)を土台にしています。

NAPI:割り込み緩和の原理

NAPI(New API)は「1パケット1割り込み」をやめる仕組みです。流れはこうです。

  • パケット到着でハード割り込みが入ると、ドライバはそのデバイスの受信割り込みを無効化し、NAPIをソフト割り込みのポーリングリストに登録する。
  • softirq文脈で poll() が呼ばれ、受信リングから一度に最大 budget 個(既定64など)のパケットを引き抜いてスタックへ流す。
  • リングが空になれば割り込みを再び有効化して通常モードへ戻る。空にならず budget を使い切れば、割り込みは無効のまま次のpollサイクルで続きを処理する。
低負荷:  割込→無効化→poll(数個)→空→割込有効化   …割込ありモード
高負荷:  割込→無効化→poll(budget)→まだ有る→poll…→割込はずっと無効

ここが核心です。負荷が高いほど1回の割り込みで処理できるパケットが増え、割り込み1回あたりのコストがパケット数で割り算されて薄まります。低負荷では割り込み駆動で低遅延、高負荷では自動的にポーリング駆動へ移って高スループット——この自己調整が、固定閾値の割り込みコアレッシングにはない強みです。budget で1デバイスの処理量に上限を設けるのは、1枚のNICがCPUを占有して他デバイスやプロセスを飢餓させないスケジューリング上の公平性のためでもあります。

試験・面接の頻出ポイント

「NAPIは何を緩和するのか」を問われたら、答えは割り込み回数です。コピーを消すのはゼロコピー、システムコールを消すのは別の話。NAPIは受信割り込みを一時的に止めてポーリングに切り替え、per-packetの割り込みオーバーヘッドをバッチ化で償却します。「高負荷ほど効く」理由まで言えると満点です。

GRO/GSO/TSO:オフロードでpacketを束ねる

per-packetの固定コストはプロトコル処理自体にもあります。IPルーティング探索、TCP状態更新、各層の関数呼び出し——これらは1パケットごとに発生します。そこで複数の小パケットを1つの大セグメントとして層に通すのがオフロードの発想です。

名称方向束ねる場所効果
GRO受信ドライバ/NAPI poll内(ソフトウェア)連続セグメントを1つの大skbに合体させ、上位層を1回だけ通す
GSO送信スタック内(ソフトウェア、ドライバ直前で分割)大セグメントのまま下り、最後にMTUへ分割。分割を遅延させる
TSO送信NICハードウェア大セグメントをNICが自動でMTUごとに分割。CPUは分割に一切関与しない

**GRO(Generic Receive Offload)**は受信側で働きます。NAPIのpoll中、同じフローに属し連続する複数のTCPセグメントを見つけたら、ペイロードをページフラグメントとして繋ぎ1つの巨大skbに合体させます。IP層・TCP層はこの大skbを1回処理するだけで済み、何十個分ものヘッダ処理・ルーティング探索が1回に圧縮されます。合体できる条件(同一フロー・連続シーケンス・互換オプション)を満たさなければ束ねず、正しさは保たれます。

**送信側のGSO(Generic Segmentation Offload)**は逆方向の遅延戦略です。TCP層は最初からMTUサイズに刻まず、大きな1セグメントのままスタックを下らせ、分割をドライバ直前まで先延ばしします。層を通る回数が減るぶんCPUが軽くなります。

TSO(TCP Segmentation Offload)はGSOの分割作業すらNICハードウェアへ丸投げしたものです。カーネルは64KB級の大セグメントとテンプレートヘッダを渡すだけで、NICがMTUごとのパケットに切り分け、各パケットのシーケンス番号とチェックサムを補完します。CPUはセグメント分割から完全に解放されます。チェックサム計算自体をNICに任せるチェックサムオフロードも併用され、skbの ip_summed フィールドが「計算済み/NIC任せ」の状態を運びます。

GROとTSOは“同じアイデアの裏表”

受信のGROは「小さく届いたものを大きくまとめてから上げる」、送信のTSO/GSOは「大きいまま下ろして最後に小さく割る」。どちらも層をまたぐ回数=per-packetコストの発生回数を減らす点で同一思想です。GROで束ねたものをそのまま転送するルータ/ブリッジでは、送信時にGSOで割り直されるため、受信GRO→送信GSOが綺麗に対になります。

オフロードは遅延と相性が悪い場面がある

GROや割り込みコアレッシングはスループット最適化と引き換えに、束ねる待ち時間ぶんの遅延を足します。レイテンシ最重視のワークロード(高頻度取引、一部のRPC)では、あえてGROを切る・割り込みコアレッシングを弱める調整が効くことがあります。さらにTSO/GROにはNICドライバ実装のバグでまれにコーナーケースが残ることがあり、原因不明のTCP不調の切り分けで ethtool -K によるオフロード無効化が定石の一手になります。

まとめ

まとめ

カーネル内データパスの全設計は「per-packetの固定コストをいかに薄めるか」という一点に貫かれています。sk_buffはhead/data/tailのポインタ操作だけで層をまたぎ、cloneは参照カウントで本体を共有してコピーを避けますNAPIは最初の割り込み以後を無効化してポーリングへ移り、割り込み回数をバッチで償却——高負荷ほど効く自己調整です。GRO/GSO/TSOは小パケットを大セグメントに束ね、層をまたぐ回数そのものを削ります。土台にあるのは割り込みと入出力(I/O)上半分/下半分の二段構え。この経路が腑に落ちると、高性能I/Oモデル(epoll・io_uring)の内部が「カーネルが終えた仕事をアプリへ渡す最後の窓口」だと一本の線で見えてきます。

OS Article

ネットワークスタックのカーネル内データパスを実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

Linuxカーネル

比較で見る軸

難易度: advanced / カテゴリ: OS / タグ数: 6

導入後に効く点

NAPIは「1パケット1割り込み」をやめ、最初の割り込みで以後を無効化しポーリングで一括処理する割り込み緩和。高負荷ほどバッチ効率が上がる。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
OS
タグ数
6

判断チェックリスト

  • 自社の用途が「Linuxカーネル / ネットワーク」に近いか確認する。
  • 強みである「パケットはsk_buffという制御構造で表現され、本体データは触らずヘッダのポインタ(head/data/tail/end)だけ動かすことで、各層をまたぐコピーを避ける。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

Linuxカーネルネットワークsk_buffNAPITCP/IPLinuxカーネルネットワークsk_buff
参考: 公式情報