ネットワークインタフェースで受信したパケットは、デバイスドライバのH/W割り込み処理処理で刈り取られる。デバイスドライバは受信したパケットをカーネルの受信キューに積み、ソフトウェア割り込みを発生させる。
受信ソフトウェア割り込みのハンドラは、受信キューに積まれているパケットを取りだし該当プロトコルの受信ハンドラを呼び出す。
デバイスドライバの受信処理が受信キューにパケットを積むだけで、受信処理のメインはソフトウェア割り込み処理で実装しているのは、H/W割り込みの処理を極力短くしてシステムのレスポンスを向上させるため。
受信処理の流れを図1に示す。

図1 NAPI未対応の受信処理
従来の受信処理ではパケット受信の度にハードウェア割込みを処理しなければならないため、受信負荷が高い場合にシステムが重くなる問題があった。Kernel2.6ではデバイスドライバに対してNAPI(New API)と呼ばれる新しい受信APIが提供されるようになった。
NAPIに対応した受信処理ではパケットを受信してハードウェア割込みが発生すると、H/W割込みを禁止してポーリング処理によってデバイスの受信バッファからパケットを取り出していく。バッファが空になって受信処理が完了すると割込みを再度許可状態にして、次の受信が発生するのを待つようにしている。
NAPI対応の受信処理を図2に示す。

/* 自CPUのsoftnet_dataを取得 */ *queue = &__get_cpu_var(softnet_data); while (!list_empty(&queue->poll_list)) { /* poll_listからポーリング(受信処理)が必要な * インタフェースを取得 (*1) */ dev = list_entry(queue->poll_list.next, struct net_device, poll_list); /* ドライバのポーリングルーチン(dev->poll()) * を呼び出してパケット刈り取り (*2) */ if (dev->quota <= 0 || dev->poll(dev, &budget)) { /* キューの受信パケットを一部だけ処理した */ /* net_deviceをpoll_listの後ろにチェーンしなおして * 後で再開する */ } else { /* キューの受信パケットを全て処理した */ } } return;
for (;;) {
/* 自CPUの受信キューからパケット取り出し */
skb = __skb_dequeue(&queue->input_pkt_queue);
if (!skb)
goto job_done; /* キューが空。受信完了 */
netif_receive_skb(skb);
work++;
/* 処理したパケット数がQuotaを越えたか
* 受信処理に時間がかかったら一旦中断 */
if (work >= quota || jiffies - start_time > 1)
break;
}
return -1;
job_done:
/* 受信が完了したのでpoll_listからbacklog_devを削除 */
list_del(&backlog_dev->poll_list);
return 0;
: : /* ETH_P_ALL(全パケット種別)で登録されている * エントリがあれば受信ハンドラを呼び出す。(*1) */ list_for_each_entry_rcu(ptype, &ptype_all, list) { if (!ptype->dev || ptype->dev == skb->dev) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); /* (*2) */ pt_prev = ptype; } } handle_diverter(skb); /* Bridge処理 * Bridge動作をするようにカーネルが * コンパイルされていなければなにもしない */ if (handle_bridge(&skb, &pt_prev, &ret, orig_dev)) goto out; /* プロトコルタイプ(MACヘッダのTypeフィールドの値) */ type = skb->protocol; /* * 該当プロトコルの受信ルーチンを呼び出す */ list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) { if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev)) { if (pt_prev) ret = deliver_skb(skb, pt_prev, orig_dev); /* (*2) */ pt_prev = ptype; } } if (pt_prev) { ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev); } else { kfree_skb(skb); /* Jamal, now you will not able to escape explaining * me how you were going to use this. :-) */ ret = NET_RX_DROP; }
送信処理の流れを図3に示す。
プロトコル層はdev_queue_xmit()でパケットを送信する。dev_queue_xmit()はパケットをデバイスの送信キュー(dev->qdisc)に積んでqdisc_run()を呼び出す。qdisc_run()はキューの状態をチェックした後、qdisc_restart()を呼び出す。qdisc_restart()は送信キューからパケットを取り出し、デバイスドライバの送信ルーチンを呼び出す(dev->hard_start_xmit())。
デバイスがパケットを送信すると、送信完了割込みが発生し、割込みハンドラからnetif_wake_queue()を呼び出し送信キュー内のパケットを送信させようとする。
netif_wake_queueは__netif_schedule()を呼び出して送信ソフトウェア割込みを発生させる。送信ソフトウェア割込みはqdisc_run()を呼び出して送信キューにパケットがあれば送信する。

送信キューにはスケジューラが存在する。このため、キューに積む時(dev->qdisc->enqueue())にスケジューラの種類に応じてパケットが並び替えられる。
スケジューラはdev_activate()でデバイスを活性化する時に設定される。デフォルトのスケジューラは"pfifo_fast"。このスケジューラは単純なFIFOでパケットの並び替えは行わない。"pfifo_fast"の場合、dev->qdisc->enqueue()はpfifo_fast_enqueue()を呼び出すことになる。
各スケジューラで使用される関数はstruct Qdisc_opsで定義される。
DEFINE_PER_CPU(struct softnet_data, softnet_data) = { NULL };
|
フィールド |
説明 |
|---|---|
|
output_queue |
送信をスケジュールされたネットワークデバイス(struct net_device)がチェーンされる。netif_schedule()でチェーンされる。 |
|
input_pkt_queue |
受信キュー。デバイスドライバによって受信パケットが積まれ、受信ソフトウェア割り込みでプロトコルの受信処理に渡される。 |
|
poll_list |
受信処理中のネットワークデバイス(struct
net_device)がチェーンされる。受信ソフトウェア割込みで、本リストに連なっているnet_deviceのポーリングルーチン(dev->poll())を呼び出して受信パケットを刈り取っていく。デバイスドライバがnetif_rx_schedule()で受信割込みを発生させる際にチェーンされる。 ドライバがNAPI未対応(netif_rx()を使う)の場合は、ここにはbacklog_devがつながれる。 |
|
completion_queue |
デバイスドライバがパケット送信完了後などにskbを解放する際、IRQ(H/W割込み)コンテキストであった場合、skbを本キューに一旦積んで後で解放する。 dev_kfree_skb_any()参照。 |
|
backlog_dev |
NAPI未対応ドライバがnetif_rx()でパケットをネットワーク層に渡す際に使用される仮想的なnet_device。poll_listに積まれる。本デバイスのポーリングルーチンはprocess_backlog()。 |
各プロトコルは初期化時に受信ルーチンの登録を行う。受信ルーチンの登録はdev_add_pack()でstruct packet_typeを登録することにより行う。struct packet_typeにはプロトコルタイプ(MACヘッダのTypeフィールドの値)と受信ハンドラへのポインタが格納されており、受信時にパケットのTypeをチェックしてマッチするエントリの受信ルーチンを呼び出すことで、パケットを各プロトコルの受信ルーチンに振り分けている。
packet_typeは以下のようにして管理される。

packet_typeはptype_allとptype_baseに分けて管理される。ptype_allには.typeがETH_P_ALL(全プロトコルが対象)であるpacket_typeが登録される。ptype_baseには.typeに個別のプロトコルが指定されたエントリが登録される。ptype_baseはハッシュテーブルになっており、Type値を元にハッシュキーが計算される。
Documentation/networking/NAPI_HOWTO.txt