net_device 構造体
Linux カーネルは net_device 構造を使用して特定のネットワーク デバイスを表し、net_device はネットワーク全体によって駆動されます。
魂。ネットワーク ドライバーの中核は、net_device 構造体の各メンバー変数を初期化し、初期化を完了することです。
net_device が Linux カーネルに登録された後。net_device 構造は include/linux/netdevice.h で定義されています。
net_device は、次の内容 (縮小) を含む巨大な構造体です。
struct net_device {
char name[IFNAMSIZ];
struct hlist_node name_hlist;
char *ifalias;
/*
* I/O specific fields
* FIXME: Merge these and struct ifmap into one
*/
unsigned long mem_end;
unsigned long mem_start;
unsigned long base_addr;
int irq;
atomic_t carrier_changes;
/*
* Some hardware also needs these fields (state,dev_list,
* napi_list,unreg_list,close_list) but they are not
* part of the usual set specified in Space.c.
*/
unsigned long state;
struct list_head dev_list;
struct list_head napi_list;
struct list_head unreg_list;
struct list_head close_list;
......
const struct net_device_ops *netdev_ops;
const struct ethtool_ops *ethtool_ops;
#ifdef CONFIG_NET_SWITCHDEV
const struct swdev_ops *swdev_ops;
#endif
const struct header_ops *header_ops;
unsigned int flags;
......
unsigned char if_port;
unsigned char dma;
unsigned int mtu;
unsigned short type;
unsigned short hard_header_len;
unsigned short needed_headroom;
unsigned short needed_tailroom;
/* Interface address info. */
unsigned char perm_addr[MAX_ADDR_LEN];
unsigned char addr_assign_type;
unsigned char addr_len;
......
/*
* Cache lines mostly used on receive path (including
eth_type_trans())
*/
unsigned long last_rx;
/* Interface address info used in eth_type_trans() */
unsigned char *dev_addr;
#ifdef CONFIG_SYSFS
struct netdev_rx_queue *_rx;
unsigned int num_rx_queues;
unsigned int real_num_rx_queues;
#endif
......
/*
* Cache lines mostly used on transmit path
*/
struct netdev_queue *_tx ____cacheline_aligned_in_smp;
unsigned int num_tx_queues;
unsigned int real_num_tx_queues;
struct Qdisc *qdisc;
unsigned long tx_queue_len;
spinlock_t tx_global_lock;
int watchdog_timeo;
......
/* These may be needed for future network-power-down code. */
/*
* trans_start here is expensive for high speed devices on SMP,
* please use netdev_queue->trans_start instead.
*/
unsigned long trans_start;
......
struct phy_device *phydev;
struct lock_class_key *qdisc_tx_busylock;
};
以下に、いくつかの主要なメンバー変数を紹介します。
name はネットワークデバイスの名前です。
mem_end は共有メモリの終了アドレスです。
mem_start は共有メモリの開始アドレスです。
Base_addr はネットワーク デバイスの I/O アドレスです。
irq はネットワークデバイスの割り込み番号です。
dev_list は、グローバル ネットワーク デバイスのリストです。
napi_list は、napi ネットワーク デバイスのリスト エントリです。
unreg_list は、未登録のネットワークデバイスリストのエントリです。
close_list は、閉域ネットワークデバイスリストのエントリです。
netdev_ops は、一連のネットワーク デバイス操作コールバック関数を含む、ネットワーク デバイスの操作セット関数です。
キャラクターデバイスの file_operations と同様に、netdev_ops 構造体については後で説明します。
ethtool_ops はネットワーク管理ツールに関連する一連の関数であり、ユーザー空間のネットワーク管理ツールはこの構造体を呼び出します。
関連関数は、ネットワーク カードのステータスを取得したり、ネットワーク カードを設定したりします。
header_ops は、作成、解析、バッファリングなど、ヘッダーに関連する操作関数のセットです。
flags はネットワーク インターフェイス フラグであり、フラグのタイプは include/uapi/linux/if.h ファイルで定義されており、これは列挙型です。
種類は次のとおりです。
enum net_device_flags {
IFF_UP = 1<<0, /* sysfs */
IFF_BROADCAST = 1<<1, /* volatile */
IFF_DEBUG = 1<<2, /* sysfs */
IFF_LOOPBACK = 1<<3, /* volatile */
IFF_POINTOPOINT = 1<<4, /* volatile */
IFF_NOTRAILERS = 1<<5, /* sysfs */
IFF_RUNNING = 1<<6, /* volatile */
IFF_NOARP = 1<<7, /* sysfs */
IFF_PROMISC = 1<<8, /* sysfs */
IFF_ALLMULTI = 1<<9, /* sysfs */
IFF_MASTER = 1<<10, /* volatile */
IFF_SLAVE = 1<<11, /* volatile */
IFF_MULTICAST = 1<<12, /* sysfs */
IFF_PORTSEL = 1<<13, /* sysfs */
IFF_AUTOMEDIA = 1<<14, /* sysfs */
IFF_DYNAMIC = 1<<15, /* sysfs */
IFF_LOWER_UP = 1<<16, /* volatile */
IFF_DORMANT = 1<<17, /* volatile */
IFF_ECHO = 1<<18, /* volatile */
};
サンプル コードに戻り、net_device 構造体を確認します。
if_port はインターフェイスのポート タイプを指定します。デバイスが複数のポートをサポートしている場合は、if_port を使用して使用するポート タイプを指定します。オプションのポート タイプは、次のように列挙型である include/uapi/linux/netdevice.h で定義されます。
enum {
IF_PORT_UNKNOWN = 0,
IF_PORT_10BASE2,
IF_PORT_10BASET,
IF_PORT_AUI,
IF_PORT_100BASET,
IF_PORT_100BASETX,
IF_PORT_100BASEFX
};
DMA はネットワーク デバイスによって使用される DMA チャネルですが、すべてのデバイスが DMA を使用するわけではありません。
mtu はネットワークの最大伝送単位であり、1500 です。
type は ARP モジュールのタイプを指定するために使用されます。イーサネットの ARP インターフェイスは ARPHRD_ETHER、Linux です。
カーネルがサポートする ARP プロトコルは include/uapi/linux/if_arp.h に定義されていますので、ご自身で参照してください。
perm_addr は永続的なハードウェア アドレスです。ネットワーク カード デバイスに永続的なハードウェア アドレスがある場合、perm_addr には値が入力されます。
addr_len はハードウェア アドレスの長さです。
last_rx は、最後に受信したパケットのタイムスタンプで、jiffies 単位で記録されます。
dev_addr はハードウェア アドレスでもあり、現在割り当てられている MAC アドレスであり、ソフトウェアによって変更できます。
_rx は受信キューです。
num_rx_queues は受信キューの数です。 register_netdev を呼び出してネットワーク デバイスを登録するときに、
指定された数の受信キューを割り当てます。
real_num_rx_queues は、現在アクティブなキューの数です。
_tx は送信キューです。
num_tx_queues は送信キューの数であり、指定された数の送信キューが alloc_netdev_mq 関数によって割り当てられます。
送信キュー。
real_num_tx_queues は、現在有効な送信キューの数です。
trans_start は、最後に送信されたパケットのタイムスタンプで、jiffies 単位で記録されます。
phydev は、対応する PHY デバイスです。
1. net_device を申請する
ネットワークドライバーを作成するときは、まず net_device を適用し、alloc_netdev 関数を使用して net_device を適用します。
は次のように定義されたマクロです。
define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \
alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)
alloc_netdev の本質は alloc_netdev_mqs 関数であることがわかり、この関数のプロトタイプは次のとおりです。
struct net_device * alloc_netdev_mqs ( int sizeof_priv,
const char *name,void (*setup) (struct net_device *)),unsigned int txqs,
unsigned int rxqs);
関数のパラメータと戻り値は次の意味を持ちます。
sizeof_priv : プライベートデータのブロックサイズ。
名前 : デバイス名。
setup : コールバック関数。この関数はデバイスのデバイスを初期化した後に呼び出されます。
txqs : 割り当てられた送信キューの数。
rxqs : 割り当てられた受信キューの数。
戻り値: アプリケーションが成功した場合は、アプリケーションに適用された net_device ポインタを返し、失敗した場合は NULL を返します。
実際、ネットワーク デバイスには多くの種類があるため、イーサネットは 1 種類だけであると考えないでください。Linux カーネルでサポートされるネットワーク インターフェイス
ファイバー分散データ インターフェイス (FDDI)、イーサネット デバイス (イーサネット)、赤外線データ インターフェイス (InDA)、高ポートなどのポートが多数あります。
パフォーマンス パラレル インターフェイス (HPPI)、CAN ネットワークなど カーネルは、alloc_netdev に基づいてさまざまなネットワーク デバイスを提供します
この章で説明したイーサネットなどのカプセル化の 1 つの層、イーサネット カプセル化の net_device アプリケーション関数は alloc_etherdev です。
これもマクロですが、次のようになります。
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
#define alloc_etherdev_mq(sizeof_priv, count)
alloc_etherdev_mqs(sizeof_priv, count, count)
alloc_etherdev は最終的に、alloc_netdev_mqs の単純なカプセル化である alloc_etherdev_mqs 関数に依存していることがわかります。関数の内容は次のとおりです。
struct net_device *alloc_etherdev_mqs(int sizeof_priv,
unsigned int txqs,
unsigned int rxqs)
{
return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_UNKNOWN,
ether_setup, txqs, rxqs);
}
行 5 は、alloc_netdev_mqs を呼び出して net_device を適用します。ネットワーク カードの名前が「eth%d」に設定されていることに注意してください。
これはフォーマットされた文字列であり、開発ボードの Linux システムに入った後に表示される「eth0」や「eth1」などのネットワーク カードの名前は次のとおりです。
ここからです。同様に、ここでは Ethernet のセットアップ関数を ether_setup として設定し、さまざまなネットワーク デバイスのセットアップ関数を設定します。
関数は異なります。たとえば、CAN ネットワークのセットアップ関数は can_setup です。
ether_setup 関数は最初に net_device を初期化します。関数の内容は次のとおりです。
void ether_setup(struct net_device *dev)
{
dev->header_ops = ð_header_ops;
dev->type = ARPHRD_ETHER;
dev->hard_header_len = ETH_HLEN;
dev->mtu = ETH_DATA_LEN;
dev->addr_len = ETH_ALEN;
dev->tx_queue_len = 1000; /* Ethernet wants good queues */
dev->flags = IFF_BROADCAST|IFF_MULTICAST;
dev->priv_flags |= IFF_TX_SKB_SHARING;
eth_broadcast_addr(dev->broadcast);
}
net_device の申請はこれで完了です。ネットワークデバイスの場合は、alloc_etherdev alloc_etherdev_mqs を使用して net_device を申請します。NXP によって作成された公式ネットワーク ドライバーは、alloc_etherdev_mqs を使用して net_device を適用します。
2. net_device を削除します
ネットワークドライバーからログアウトする際には、以前に申請した net_device を解放する必要がありますが、解放関数は free_netdev であり、関数のプロトタイプは次のとおりです。
void free_netdev(struct net_device *dev)
関数のパラメータと戻り値は次の意味を持ちます。
dev: 解放される net_device ポインタ。
戻り値: なし。
3. net_device を登録する
net_device を適用して初期化した後、カーネルに net_device を登録する必要があります。関数 register_netdev が使用されます。関数のプロトタイプは次のとおりです。
int register_netdev(struct net_device *dev)
関数のパラメータと戻り値は次の意味を持ちます。
dev: 登録する net_device ポインタ。
戻り値: なし
net_device_ops 構造体
net_device には非常に重要なメンバー変数 netdev_ops があり、これは net_device_ops 構造体のポインター型であり、ネットワーク デバイスの操作セットです。net_device_ops 構造体は、include/linux/netdevice.h ファイルで定義されています。net_device_ops 構造体には、「ndo_」で始まるいくつかの関数が含まれています。これらの関数は、すべてではなく、ネットワーク ドライバー作成者によって実装される必要があります。実際のドライバー パートによると、という状況が実現できます。構造の内容は次のとおりです (構造は比較的大きいため、ここでは縮小されています)。
示例代码 69.3.2.1 net_device_ops 结构体
struct net_device_ops {
int (*ndo_init)(struct net_device *dev);
void (*ndo_uninit)(struct net_device *dev);
int (*ndo_open)(struct net_device *dev);
int (*ndo_stop)(struct net_device *dev);
netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev);
u16 (*ndo_select_queue)(struct net_device *dev, struct sk_buff *skb,
void *accel_priv,select_queue_fallback_t fallback);
void (*ndo_change_rx_flags)(struct net_device *dev,int flags);
void (*ndo_set_rx_mode)(struct net_device *dev);
int (*ndo_set_mac_address)(struct net_device *dev,void *addr);
int (*ndo_validate_addr)(struct net_device *dev);
int (*ndo_do_ioctl)(struct net_device *dev,struct ifreq *ifr, int cmd);
int (*ndo_set_config)(struct net_device *dev,struct ifmap *map);
int (*ndo_change_mtu)(struct net_device *dev,int new_mtu);
int (*ndo_neigh_setup)(struct net_device *dev,struct neigh_parms *);
void (*ndo_tx_timeout) (struct net_device *dev);
#ifdef CONFIG_NET_POLL_CONTROLLER
void (*ndo_poll_controller)(struct net_device *dev);
int (*ndo_netpoll_setup)(struct net_device *dev,struct netpoll_info *info);
void (*ndo_netpoll_cleanup)(struct net_device *dev);
#endif
int (*ndo_set_features)(struct net_device *dev,netdev_features_t features);
}
行 3: ndo_init 関数、この関数はネットワーク デバイスが初めて登録されるときに実行されます。デバイスはこの関数で後で初期化する必要があるいくつかのコンテンツを実行できますが、この関数は一般的なドライバー、仮想ネットワークでは使用されません。デバイスが使用される場合があります。
4 行目: ndo_uninit 関数。この関数はネットワーク デバイスがアンインストールされるときに実行されます。
5 行目: ndo_open 関数。この関数はネットワーク デバイスが開いたときに実行されます。ネットワーク ドライバーはこの関数を実装する必要があります。これは非常に重要です。NXP の I.MX シリーズ SOC ネットワーク ドライバーを例にとると、この機能では次の作業が行われます。
· ネットワーク周辺機器クロックを有効にします。
・ネットワークで使用するリングバッファを申請します。
• MAC 周辺機器を初期化します。
· インターフェイスに対応する PHY をバインドします。
·NAPI を使用する場合は、napi_enable 関数を通じて NAPI モジュールを有効にする必要があります。
· PHY をオンにします。• netif_tx_start_all_queues を呼び出して送信キューを有効にします。また、netif_start_queue 関数を呼び出すこともできます。
·……
6 行目: ndo_stop 関数。この関数はネットワーク デバイスが閉じられたときに実行され、ネットワーク ドライバーもこの関数を実装する必要があります。NXP の I.MX シリーズ SOC ネットワーク ドライバーを例にとると、この機能では次の作業が行われます。
• PHY を停止します。• NAPI 機能を停止します。・送信停止機能。· MAC の電源をオフにします。• PHY 接続を切断します。· ネットワーク時計をオフにします。・データバッファを解放します。·...
行 7: ndo_start_xmit 関数、この関数はデータを送信する必要があるときに実行されます、この関数には sk_buff 構造体ポインターのパラメーターがあります、sk_buff 構造体は Linux ネットワーク ドライバーで非常に重要です、sk_buff は上位層を保存し、それをネットワーク ドライバーに渡しますレイヤーデータ。つまり、送信するデータはsk_buffに格納されることになるが、sk_buffについては後で詳しく説明する。この関数は、送信が成功した場合は NETDEV_TX_OK を返し、送信が失敗した場合は NETDEV_TX_BUSY を返します。送信が失敗した場合はキューを停止する必要があります。
8 行目: ndo_select_queue 関数。デバイスが複数の送信キューをサポートしている場合に使用するキューを選択します。
行 11: ndo_set_rx_mode 関数。この関数は、アドレス フィルタリング リストを変更し、net_device の flags メンバー変数に従って SOC ネットワーク ペリフェラル レジスタを設定するために使用されます。たとえば、フラグは IFF_PROMISC、IFF_ALLMULTI、または IFF_MULTICAST であり、それぞれプロミスキャス モード、ユニキャスト モード、またはマルチキャスト モードを表します。
行 12: ndo_set_mac_address 関数。この関数は、ネットワーク カードの MAC アドレスを変更し、net_device の dev_addr メンバー変数を設定し、MAC アドレスをネットワーク周辺機器のハードウェア レジスタに書き込むために使用されます。
13行目:ndo_validate_addr関数。MACアドレスが有効かどうか、つまりnet_deviceのdev_addr内のMACアドレスが有効かどうかを検証し、is_valid_ether_addr関数を直接呼び出しています。
行 14: ndo_do_ioctl 関数。この関数は、PHY チップ関連のコマンド操作など、ユーザー プログラムが ioctl を呼び出すときに実行されます。通常は、phy_mii_ioctl 関数を直接呼び出します。
16行目:ndo_change_mtu関数、MTUサイズを変更します。
行 18: ndo_tx_timeout 関数。この関数は、通常、送信タイムアウトを引き起こすネットワークの問題により、送信タイムアウトが発生したときに実行されます。一般に、MAC と PHY が再起動されたり、データ送信が再開されたりすることがあります。
行 20: ndo_poll_controller 関数。クエリ メソッドを使用してネットワーク カード データの送受信を処理します。
行 24: ndo_set_features 関数。net_device の features 属性を変更し、対応するハードウェア属性を設定します。
sk_buff構造体
ネットワークは階層化されており、アプリケーション層については、特定の最下層の動作を気にする必要はなく、送受信するデータをプロトコルに従ってパッケージ化するだけで済みます。パッキング後、データは dev_queue_xmit 関数を通じて送信され、netif_rx 関数はデータの受信に使用できます。これら 2 つの関数を順番に見てみましょう。
dev_queue_xmit 関数
この関数はネットワーク データを送信するために使用されます。この関数は include/linux/netdevice.h で定義されています。関数のプロトタイプは次のとおりです: static inline int dev_queue_xmit(struct sk_buff *skb)
関数のパラメータと戻り値は次の意味を持ちます。
skb: 送信されるデータ。これは sk_buff 構造体ポインターです。sk_buff は Linux ネットワーク ドライバーの非常に重要な構造体です。ネットワーク データは sk_buff に保存されます。各プロトコル層は独自のプロトコル ヘッダーを sk_buff に追加します。基礎となるドライバーは sk_buff でデータを送信します。ネットワーク データの受信プロセスはその逆です。基盤となるネットワーク ドライバーは、受信した生データを sk_buff にパックし、それを上位層のプロトコルに送信します。上位層は対応するヘッダーを削除し、最終データをプロトコルに送信します。ユーザー。
戻り値: 0 が送信成功、負の値が送信失敗。
dev_queue_xmit 関数は長すぎるため、ここでは詳しく分析しません。dev_queue_xmit 関数は、最終的に net_device_ops 操作セットの ndo_start_xmit 関数を通じて送信されます。ndo_start_xmit はネットワーク ドライバー ライターによって実装されます。プロセス全体は、形:
netif_rx関数
上位層は netif_rx 関数を使用してデータを受信しますが、最も基本的なネットワーク データは通常、ポーリング、割り込み、または NAPI によって受信されます。netif_rx 関数は net/core/dev.c で定義されており、関数のプロトタイプは次のとおりです。
int netif_rx(struct sk_buff *skb)
関数のパラメータと戻り値は次の意味を持ちます。
skb: 受信データのsk_buffを保存します。
戻り値: NET_RX_SUCCESS 成功、NET_RX_DROP パケットドロップ。
sk_buff の構造に注目しましょう。sk_buff は Linux ネットワークの重要なデータ構造であり、データ パケットの送受信を管理するために使用されます。sk_buff の構造は include/linux/skbuff.h で定義されています。構造の内容は次のとおりです。以下のようになります (構造が比較的大きいため、スペースを減らすために重要な内容のみをリストしています)。
struct sk_buff {
union {
struct {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
union {
ktime_t tstamp;
struct skb_mstamp skb_mstamp;
};
};
struct rb_node rbnode; /* used in netem & tcp stack */
};
struct sock *sk;
struct net_device *dev;
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[48] __aligned(8);
unsigned long _skb_refdst;
void (*destructor)(struct sk_buff *skb);
unsigned int len, data_len;
__u16 mac_len, hdr_len;
__be16 protocol;
__u16 transport_header;
__u16 network_header;
__u16 mac_header;
__u32 headers_end[0];
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head, *data;
unsigned int truesize;
atomic_t users;
};
行 5 ~ 6: next と prev はそれぞれ次と前の sk_buff を指し、二重リンクリストを形成します。
行 9: tstamp は、パケットが受信されたとき、または送信準備ができたときのタイムスタンプを示します。
14 行目: sk は、現在の sk_buff が属するソケットを示します。
行 15: dev は、現在の sk_buff がどのデバイスから送受信されるかを示します。
行 22: cb は制御バッファーです。どの層がこのバッファーを自由に使用してプライベート データを配置できるかは関係ありません。
行 24: デストラクター関数。バッファーが解放されると、この関数内で特定のアクションを完了できます。
25行目:lenは実際のデータ長で、メインバッファのデータ長とフラグメントのデータ長を含みます。data_len はデータ長であり、スライス内のデータの長さのみを計算します。
26 行目: mac_len は接続層ヘッダーの長さ、つまり MAC ヘッダーの長さです。
27行目:プロトコルプロトコル。
行 28: Transport_header はトランスポート層ヘッダーです。
29 行目: network_header はネットワーク層のヘッダーです。
行 30: mac_header はリンク層ヘッダーです。
行 32: tail は実際のデータの終わりを指します。
行 33: end はバッファの終わりを指します。
34行目:headはバッファの先頭を指し、dataは実際のデータの先頭を指します。data と tail は実際のデータの先頭と末尾を指し、head と end はバッファの先頭と末尾を指します。構造を図に示します。
sk_buff カーネルには一連の操作および管理関数が提供されています。いくつかの一般的な API 関数を簡単に見てみましょう。
3. sk_buff を割り当てる
sk_buff を使用するには、最初に割り当てられる必要があります。まず alloc_skb 関数を見てみましょう。この関数は include/linux/skbuff.h で定義されています。関数のプロトタイプは次のとおりです:
static inline struct sk_buff *alloc_skb(unsigned int サイズ,gfp_t 優先度)
関数のパラメータと戻り値は次の意味を持ちます。
size: 割り当てられるサイズ、つまり skb データ セグメントのサイズ。
優先順位: GFP_KERNEL、GFP_ATOMIC などの GFP MASK マクロ。
戻り値: 割り当てが成功した場合は適用された sk_buff の先頭アドレスを返し、失敗した場合は NULL を返します。
Netdev_alloc_skb は、特定のデバイスの受信用に skb_buff を適用するために、ネットワーク デバイス ドライバーでよく使用されます。
include/linux/skbuff.h にも定義されており、関数プロトタイプは次のとおりです。
静的インライン struct sk_buff *netdev_alloc_skb(struct net_device *dev,unsigned int length)
関数のパラメータと戻り値は次の意味を持ちます。
dev: sk_buff を割り当てるデバイス。
length: 割り当てるサイズ。
戻り値: 割り当てが成功した場合は適用された sk_buff の先頭アドレスを返し、失敗した場合は NULL を返します。
4. sk_buffを解放する
使用が完了したら sk_buff を解放する必要があります. 解放関数は kfree_skb を使用できます. 関数は include/linux/skbuff.c に定義されており、関数のプロトタイプは次のとおりです:
void kfree_skb(struct sk_buff *skb);
関数のパラメータと戻り値は次の意味を持ちます。
skb: 解放する sk_buff 。
戻り値: なし。
ネットワーク デバイスの場合は、次のようなリリース関数を使用するのが最適です。
void dev_kfree_skb (struct sk_buff *skb)
この関数には、解放される sk_buff である 1 つのパラメータ skb のみが必要です。
戻り値: なし。
ネットワーク デバイスの場合は、次のようなリリース関数を使用するのが最適です。
void dev_kfree_skb (struct sk_buff *skb)
この関数には、解放される sk_buff である 1 つのパラメータ skb のみが必要です。
5、skb_put、skb_push、sbk_pull および skb_reserve
これら4つの関数はsk_buffを変更するために使用されます. まずskb_put関数を見てみましょう. この関数は最後のskb_buffのデータ領域を拡張するために使用されます, つまり, skb_buffの末尾をnバイト戻すために使用されますしたがって、skb_buff の len が n ワードずつ増加します。セクションのプロトタイプは次のとおりです。
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
関数のパラメータと戻り値は次の意味を持ちます。
skb: 操作対象の sk_buff。
len: 追加するバイト数。
戻り値:拡張データ領域の先頭アドレス。
skb_put 操作の前後のデータ領域を次の図に示します。
skb_push関数はhead内のskb_buffのデータ領域を拡張するために使用され、関数プロトタイプは以下のとおりです。
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
関数のパラメータと戻り値は次の意味を持ちます。
skb: sk_buff を操作する
len: 追加するバイト数
戻り値:拡張完了後の新データ領域の先頭アドレス
skb_push オペレーションの前後のデータ領域を図に示します。
sbk_pull関数はsk_buffのデータ領域の先頭からデータを削除する関数であり、関数プロトタイプは以下のとおりです。
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
関数のパラメータと戻り値は次の意味を持ちます。
skb: 操作対象の sk_buff。
len: 追加するバイト数。
戻り値: 拡張完了後の新しいデータ領域の先頭アドレス。
skb_push 操作の前後のデータ領域を次の図に示します。
sbk_reserve 函数用于调整缓冲区的头部大小,方法很简单将 skb_buff 的 data 和 tail 同时后移 n 个字节即可,函数原型如下所示:
static inline void skb_reserve(struct sk_buff *skb, int len)
函数参数和返回值含义如下:
skb:要操作的 sk_buff。
len:要增加的缓冲区头部大小。
返回值:无。
网络 NAPI 处理机制
如果玩过单片机的话应该都知道,像 IIC、SPI、网络等这些通信接口,接收数据有两种方 法:轮询或中断。Linux 里面的网络数据接收也轮询和中断两种,中断的好处就是响应快,数据 量小的时候处理及时,速度快,但是一当数据量大,而且都是短帧的时候会导致中断频繁发 生,消耗大量的 CPU 处理时间在中断自身处理上。轮询恰好相反,响应没有中断及时,但是在 处理大量数据的时候不需要消耗过多的 CPU 处理时间。Linux 在这两个处理方式的基础上提出 了另外一种网络数据接收的处理方法:NAPI(New API),NAPI 是一种高效的网络处理技术。 NAPI 的核心思想就是不全部采用中断来读取网络数据,而是采用中断来唤醒数据接收服务程 序,在接收服务程序中采用 POLL 的方法来轮询处理数据。这种方法的好处就是可以提高短数 据包的接收效率,减少中断处理的时间。目前 NAPI 已经在 Linux 的网络驱动中得到了大量的 应用,NXP 官方编写的网络驱动都是采用的 NAPI 机制。 关于 NAPI 详细的处理过程本章节不讨论,本章节就简单讲解一下如何在驱动中使用 NAPI, Linux 内核使用结构体 napi_struct 表示 NAPI,在使用 NAPI 之前要先初始化一个 napi_struct 实 例。
1、初始化 NAPI
首先要初始化一个 napi_struct 实例,使用 netif_napi_add 函数,此函数定义在 net/core/dev.c
中,函数原型:
void netif_napi_add(struct net_device *dev,struct napi_struct *napi,int (*poll)(struct napi_struct *, int), int weight)
函数参数和返回值含义如下:
dev:每个 NAPI 必须关联一个网络设备,此参数指定 NAPI 要关联的网络设备
napi:要初始化的 NAPI 实例。
poll:NAPI 所使用的轮询函数,非常重要,一般在此轮询函数中完成网络数据接收的工作。
weight:NAPI 默认权重(weight),一般为 NAPI_POLL_WEIGHT。
返回值:无。
2、删除 NAPI
如果要删除 NAPI,使用 netif_napi_del 函数即可,函数原型如下:
void netif_napi_del(struct napi_struct *napi)
函数参数和返回值含义如下:
napi:要删除的 NAPI。
返回值:无。
3、使能 NAPI
初始化完 NAPI 以后,必须使能才能使用,使用函数 napi_enable,函数原型如下:
inline void napi_enable(struct napi_struct *n)
函数参数和返回值含义如下:
n:要使能的 NAPI。
返回值:无。
4、关闭 NAPI
关闭 NAPI 使用 napi_disable 函数即可,函数原型如下:
void napi_disable(struct napi_struct *n)
函数参数和返回值含义如下:
n:要关闭的 NAPI。
返回值:无。
5、检查 NAPI 是否可以进行调度
使用 napi_schedule_prep 函数检查 NAPI 是否可以进行调度,函数原型如下:
inline bool napi_schedule_prep(struct napi_struct *n)
函数参数和返回值含义如下:
n:要检查的 NAPI。
返回值:如果可以调度就返回真,如果不可调度就返回假。
6、NAPI 调度
如果可以调度的话就进行调度,使用__napi_schedule 函数完成 NAPI 调度,函数原型如下:
void __napi_schedule(struct napi_struct *n)
函数参数和返回值含义如下:
n:要调度的 NAPI。
返回值:无。
我们也可以使用 napi_schedule 函数来一次完成 napi_schedule_prep 和__napi_schedule 这两个函数的工作,napi_schedule 函数内容如下所示:
static inline void napi_schedule(struct napi_struct *n)
{
if (napi_schedule_prep(n))
__napi_schedule(n);
}
从上面的示例代码可以看 出 ,napi_schedule 函 数 就 是 对 napi_schedule_prep 和__napi_schedule 的简单封装,一次完成判断和调度。
7、NAPI 处理完成
NAPI 处理完成以后需要调用 napi_complete 函数来标记 NAPI 处理完成,函数原型如下:
inline void napi_complete(struct napi_struct *n)
函数参数和返回值含义如下:
n:处理完成的 NAPI。
返回值:无。