NOTE:

内核版本: 2.6.19, maybe

简介

struct sk_buff

  • 存储一个封包 (packet)
  • 所有网络分层都会使用这个结构存储
    • 报头
    • 有关用户数据的信息(有效载荷)
    • 用来协调其工作的其它内部信息

struct net_device

  • 在 Linux 内核中每种网络设备都有这个数据结构表示,包括软硬件的配置信息

struct sock

  • 存储套接字的网络信息

套接字缓冲区:sk_buff 结构

相关阅读:

网络选项以及内核结构

  • 只有在编译期间定义了 CONFIG_NET_SCHED 符号,字段 tc_index 才会是此数据结构的一部分
1
2
3
4
5
6
7
8
9
struct sk_buff {
...

#ifdef CONFIG_NET_SCHED
__u16 tc_index; /* traffic control index */
#endif

...
};

布局字段

  • 内核在一个双向链表中维护所有的 sk_buff 结构,但该表的组织比传统的双向链表更复杂
    • 每个 sk_buff 结构必须能够迅速找出整个表的头。为了实现这项需求,在表的开端额外增加一个 sk_buff_head 结构作为 dummy element
1
2
3
4
5
6
7
8
struct sk_buff_head {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;

__u32 qlen; // 表中元素的数目
spinlock_t lock; // 防止对表的并发访问
};

struct sk_buff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;

// 指向拥有此缓冲区的套接字的 sock 数据结构
// 1. 当数据在本地产生或正由本地进程接收时,
// 该数据以及套接字相关的信息会由 L4(TCP或UDP) 以及用户应用程序使用
// 2. 当缓冲区只是被转发时,该指针为 NULL
struct sock *sk;
// 时间戳,表示封包何时接收
// 由 netif_rx 函数用 net_timestap 设置
struct skb_timeval tstamp;
// 网络设备
struct net_device *dev;
// 已被接收的封包所源自的设备
struct net_device *input_dev;

// 指向 TCP/IP 协议栈的协议报头的指针
// L4
union {
struct tcphdr *th;
struct udphdr *uh;
struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct ipv6hdr *ipv6h;
unsigned char *raw; // 用于初始化
} h;

// L3
union {
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
unsigned char *raw;
} nh;

// L2
union {
unsigned char *raw;
} mac;

// 由路由子系统使用
struct dst_entry *dst;
struct sec_path *sp;

/*
* 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];

// 缓冲区中数据区块的大小 (包括协议报头)
// 包括:主要缓冲区(head 所指)的数据和一些片段的数据(fragmented buffer)
// 当缓冲区从一个网络分层移往下一个网络分层时,其值会变化
unsigned int len,
// 只计算片段中的数据大小
data_len,
// MAC 报头的大小
mac_len,
// 校验和
csum;
// 正被传输或转发的封包 QoS(Quality of Service, 服务质量) 等级
__u32 priority;
__u8 local_df:1,
// 该结构是另一个 sk_buff 缓冲区的克隆
cloned:1,
ip_summed:2,
nohdr:1,
nfctinfo:3;
// 根据帧的 L2 目的地址进行类型划分
// PACKET_HOST: To us
// PACKET_BROADCAST: To all, 接收接口的广播地址
// PACKET_MULTICAST:To group, 该接口已注册的多播地址之一
// PACKET_OTHERHOST:To someone else
// PACKET_OUTGOING:Outgoing of any type, 封包正被发送
// PACKET_LOOPBACK:封包正传送至回环设备(loopback device)
// PACKET_FASTROUTE: 用 Fastroute 功能路由封包。2.6版本内核不再支持 Fastroute
__u8 pkt_type:3,
fclone:2,
ipvs_property:1;
// IP, IPv6, ARP 等,驱动程序使用这个字段通知其上层使用哪个处理例程
__be16 protocol;

// 当此缓冲区属于一个套接字时,通常设为 sock_rfree 或 sock_wfree
void (*destructor)(struct sk_buff *skb);
#ifdef CONFIG_NETFILTER
struct nf_conntrack *nfct;
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
struct sk_buff *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge;
#endif
__u32 nfmark;
#endif /* CONFIG_NETFILTER */
#ifdef CONFIG_NET_SCHED
__u16 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u16 tc_verd; /* traffic control verdict */
#endif
#endif
#ifdef CONFIG_NET_DMA
dma_cookie_t dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
__u32 secmark;
#endif


/* These elements must be at the end, see alloc_skb() for details. */
// 缓冲区总的大小,包括 sk_buff 结构本身。
// len + sizeof(sk_buff)
unsigned int truesize;
// 引用计数,使用这个 sk_buff 缓冲区的示例的数目
// 有时直接用 atomic_inc 和 atomic_dec 函数递增和递减
// 大多数情况,使用 skb_get 和 kfree_skb
atomic_t users;
unsigned char *head, // 已分配缓冲区空间的开端
*data, // 实际数据的开端
*tail, // 实际数据的尾端
*end; // 已分配缓冲区空间的尾端
};

char cb[48]

  • 私有信息的存储空间,为每一层内部使用起维护的作用

  • 在 sk_buff 结构内静态分配

TCP 使用这个空间存储 tcp_skb_cb 数据结构

1
2
3
4
5
6
7
8
struct tcp_skb_cb {
...
__u32 seq; /* Starting sequence number */
__u32 end_seq; /* SEQ + FIN + SYN + datalen */
__u32 when; /* used to compute rtt's */
__u8 flags; /* TCP header flags. */
...
};
  • 在每一层的代码中通过宏进行访问
1
#define TCP_SKB_CB(__skb)	((struct tcp_skb_cb *)&((__skb)->cb[0]))

TCP 使用 tcp_transmit_skb 函数把一个数据段压入 IP 层以便于传输

管理函数

include/linux/skbuff.hnet/core/skbuff.c 文件中几乎所有函数都有两个版本

  • do_something:wrapper 函数,增加了额外的合理性检查,或在调用第二个函数前后加入上锁机制

  • __do_something

分配内存:alloc_skb

建立一个缓冲区会涉及到两次内存分配

  1. 分配缓冲区
  2. 分配 sk_buff 结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static inline struct sk_buff *alloc_skb(unsigned int size,
gfp_t priority)
{
return __alloc_skb(size, priority, 0);
}

struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
int fclone)
{
struct sk_buff *skb;

/* Get the HEAD */
skb = kmem_cache_alloc(cache, gfp_mask & ~__GFP_DMA);
if (!skb)
goto out;

/* Get the DATA. Size must match skb_add_mtu(). */
size = SKB_DATA_ALIGN(size);
data = kmalloc_track_caller(size + sizeof(struct skb_shared_info),
gfp_mask);
if (!data)
goto nodata;

...
}

分配内存:dev_alloc_skb

  • 由设备驱动程序使用的缓冲区分配函数,应该在中断模式中执行
  • 只是一个包裹 alloc_skb 的函数
  • 要求原子操作
1
2
3
4
5
6
7
8
9
10
11
12
13
static inline struct sk_buff *dev_alloc_skb(unsigned int length)
{
return __dev_alloc_skb(length, GFP_ATOMIC);
}

static inline struct sk_buff *__dev_alloc_skb(unsigned int length,
gfp_t gfp_mask)
{
struct sk_buff *skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);
if (likely(skb))
skb_reserve(skb, NET_SKB_PAD);
return skb;
}

释放内存:kfree_skb

  • 释放一个缓冲区,使其返回缓冲池(缓存)
  • skb->users 计数器为 1 时,才会释放一个缓冲区;否则,递减计数器

释放内存:dev_kfree_skb

1
#define dev_kfree_skb(a)	kfree_skb(a)

数据预留及对齐:skb_reserve, skb_put, skb_push, skb_pull

几种 Ethernet 驱动程序的接收函数,把任何数据存储到刚分配的缓冲区之前,都会使用下列命令:

1
skb_reserve(skb, 2);

当缓冲区往下传播经过网络协议栈时,每个协议都会把 skb->data 往下传,并将其报头拷贝进来,然后更新 skb->len

skb_reserve 函数没有真正把任何东西移入数据缓冲区,只是更新两个指针。

1
2
3
4
5
static inline void skb_reserve(struct sk_buff *skb, int len)
{
skb->data += len;
skb->tail += len;
}

skb_push 把一个数据块添加到缓冲区的开端

skb_put 把一个数据块添加到缓冲区的尾端

skb_pull 通过 head 指针向前移动,把一个数据块从缓冲区的头部删除。

skb_shared_info 结构和 skb_shinfo 函数

如图 2-5 所示,数据缓冲区尾端有个名为 skb_shared_info 的数据结构,用以保持此数据区块的附加信息。

1
2
3
4
5
6
7
8
9
10
11
12
struct skb_shared_info {
// 数据块的用户数目
atomic_t dataref;
unsigned short nr_frags;
unsigned short gso_size;
/* Warning: this field is not always filled in (UFO)! */
unsigned short gso_segs;
unsigned short gso_type;
unsigned int ip6_frag_id;
struct sk_buff *frag_list;
skb_frag_t frags[MAX_SKB_FRAGS];
};
  • skb_is_nonlinear 函数:检查缓冲区是否为片段

  • skb_linearize 函数:把几个片段合压(collapse)成一个均一的(single flat)缓冲区

sk_buff 结构中没有指向 skb_shared_info 数据结构的字段,必须使用返回 end 指针的 skb_shinfo 宏:

1
#define skb_shinfo(SKB)		((struct skb_shared_info *)((SKB)->end))

skb_clone

只拷贝 sk_buff 结构,然后使用引用计数,以免过早释放共享的数据块。

也可用于检查一个 skb 缓冲区的克隆状态。

  • skb_buff 的克隆没有链接到任何表(list),也没有引用套接字的拥有者
  • skb->cloned 在克隆和原有的缓冲区都置为 1
  • 克隆的 skb->users 置为 1
  • 对包含数据的缓冲区的引用数目 (dataref) 则会递增

skb_share_check

检查引用计数 skb_users,当该缓冲区是共享时,可以克隆该缓冲区。

skb_copy, pskb_copy

当一个缓冲区被克隆时,数据区块的内容不能修改,访问该数据的代码不需要加锁。

  • 只需修改介于 skb->startskb->end 的区域的数据内容时,可以使用 pskb_copy 只克隆该区域

  • 必须连片段数据区块的内容也修改时,必须使用 skb_copy

列表管理函数

操作 sk_buff 元素列表,列表也称为队列(queue)

skb_queue_head_init:

skb_queue_head, skb_queue_tail

skb_dequeue, skb_dequeue_tail

skb_queue_purge: 把队列变为空队列

skb_queue_walk: 依次循环运行队列中的每个元素

这类函数都必须以原子方式执行,每个函数按照以下方式实现:

1
2
3
4
5
6
7
8
void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk)
{
unsigned long flags;

spin_lock_irqsave(&list->lock, flags);
__skb_queue_head(list, newsk);
spin_unlock_irqrestore(&list->lock, flags);
}

net_device 结构

  • 存储特定网络设备的所有信息
  • 每种设备都有一个这种结构,无论真实设备(Ethernet NIC)或虚拟设备(Bonding, VLAN)
  • 所有设备的 net_device 结构放在一个全局变量 dev_base 所指的全局列表中。

net_device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
struct net_device
{

/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* the interface.
*/
// 设备的名称(如,eth0)
char name[IFNAMSIZ];
/* device name hash chain */
struct hlist_node name_hlist;

/*
* I/O specific fields
* FIXME: Merge these and struct ifmap into one
*/
// 描述设备所用的共享内存,用于设备与内核沟通
// 其初始化和访问只会在设备驱动程序内进行
unsigned long mem_end; /* shared mem end */
unsigned long mem_start; /* shared mem start */
// 设备自由内存映射到 I/O 内存的起始地址
unsigned long base_addr; /* device I/O address */
// 设备用于与内核对话的中断编号,此值可由多个设备共享
// 驱动程序使用 request_irq 函数分配此变量,使用 free_irq 释放
unsigned int irq; /* device IRQ number */

/*
* Some hardware also needs these fields, but they are not
* part of the usual set specified in Space.c.
*/
// 此接口所使用的端口类型
unsigned char if_port; /* Selectable AUI, TP,..*/
// 设备所使用的 DMA 通道
unsigned char dma; /* DMA channel */

// 由网络队列子系统所使用的一组标识
unsigned long state;

struct net_device *next;

/* The device initialization function. Called only once. */
int (*init)(struct net_device *dev);

/* ------- Fields preinitialized in Space.c finish here ------- */

/* Net device features */
// 存储其他一些设备功能
// 可报告适配卡的功能,以便于CPU通信,
// 如适配卡能够对高端内存做 DMA 或者硬件能否对所有封包做校验和工作
unsigned long features;
#define NETIF_F_SG 1 /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM 2 /* Can checksum only TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM 4 /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM 8 /* Can checksum all the packets. */
#define NETIF_F_HIGHDMA 32 /* Can DMA to high memory. */
#define NETIF_F_FRAGLIST 64 /* Scatter/gather IO. */
#define NETIF_F_HW_VLAN_TX 128 /* Transmit VLAN hw acceleration */
#define NETIF_F_HW_VLAN_RX 256 /* Receive VLAN hw acceleration */
#define NETIF_F_HW_VLAN_FILTER 512 /* Receive filtering on VLAN */
#define NETIF_F_VLAN_CHALLENGED 1024 /* Device cannot handle VLAN packets */
#define NETIF_F_GSO 2048 /* Enable software GSO. */
#define NETIF_F_LLTX 4096 /* LockLess TX */

/* Segmentation offload features */
#define NETIF_F_GSO_SHIFT 16
#define NETIF_F_GSO_MASK 0xffff0000
#define NETIF_F_TSO (SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
#define NETIF_F_UFO (SKB_GSO_UDP << NETIF_F_GSO_SHIFT)
#define NETIF_F_GSO_ROBUST (SKB_GSO_DODGY << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO_ECN (SKB_GSO_TCP_ECN << NETIF_F_GSO_SHIFT)
#define NETIF_F_TSO6 (SKB_GSO_TCPV6 << NETIF_F_GSO_SHIFT)

/* List of features with software fallbacks. */
#define NETIF_F_GSO_SOFTWARE (NETIF_F_TSO | NETIF_F_TSO_ECN | NETIF_F_TSO6)

#define NETIF_F_GEN_CSUM (NETIF_F_NO_CSUM | NETIF_F_HW_CSUM)
#define NETIF_F_ALL_CSUM (NETIF_F_IP_CSUM | NETIF_F_GEN_CSUM)

// 由软中断之一使用
struct net_device *next_sched;

/* Interface index. Unique device identifier */
// UID,当设备以 dev_new_index 注册时分派给每个设备
int ifindex;
// 由(虚拟)隧道设备使用,可用于标识抵达隧道另一端的真实设备
int iflink;


// 设备驱动程序所收集的一些统计数据,可以使用用户控件程序显示,如 ifconfig, ip
struct net_device_stats* (*get_stats)(struct net_device *dev);

/* List of functions to handle Wireless Extensions (instead of ioctl).
* See <net/iw_handler.h> for details. Jean II */
// 其它无线设备使用的参数和函数指针
const struct iw_handler_def * wireless_handlers;
/* Instance data managed by the core of Wireless Extensions. */
struct iw_public_data * wireless_data;

// 指向一组函数指针,用于设置或取出不同设备参数的配置
const struct ethtool_ops *ethtool_ops;

/*
* This marks the end of the "visible" part of the structure. All
* fields hereafter are internal to the system, and may change at
* will (read: may be cleaned up at will).
*/

// 某些位代表网络设备的功能(如 IFF_MULTICAST)
// 其他位代表状态的改变(如 IFF_UP, IFF_RUNNING)
unsigned int flags; /* interface flags (a la BSD) */
// 几乎不再使用
unsigned short gflags;
// 存储用户空间不可见的标识
// 由 VLAN 和 Bridge 虚拟设备使用
unsigned short priv_flags; /* Like 'flags' but invisible to userspace. */
unsigned short padded; /* How much padding added by alloc_netdev() */

unsigned char operstate; /* RFC2863 operstate */
unsigned char link_mode; /* mapping policy to operstate */

// 最大传输单元(Maximum Transmission Unit),表示设备能处理的帧的最大尺寸
unsigned mtu; /* interface MTU value */
// 设备所属的类型(Ethernet, Frame Relay 等)
unsigned short type; /* interface hardware type */
// 以字节为单位的设备头的大小
unsigned short hard_header_len; /* hardware hdr length */

// EQL, Bonding, TEQL 协议允许一组设备作为单一设备
struct net_device *master; /* Pointer to master device of a group,
* which this device is member of.
*/

/* Interface address info. */
unsigned char perm_addr[MAX_ADDR_LEN]; /* permanent hw address */
unsigned char addr_len; /* hardware address length */
// IPv6 使用
unsigned short dev_id; /* for shared network cards */

// 指向此设备的 dev_mc_list 结构列表表头的指针
struct dev_mc_list *mc_list; /* Multicast mac addresses */
// 此设备的多播地址数目
int mc_count; /* Number of installed mcasts */
// 混杂模式,计数器,对此字段的操作使用 `dev_set_promiscuity` 函数实现
// 非零时,flags 的 IFF_PROMISC 标识位也会设置
int promiscuity;
// 设置或清除 flags 中表示该设备是否监听所有地址的标识
int allmulti;


/* Protocol specific pointers */
// 指向特定协议专用的数据结构,每个数据结构都包含一些该协议私有的参数
void *atalk_ptr; /* AppleTalk link */
// 指向一个类型为 in_device 的数据结构
void *ip_ptr; /* IPv4 specific data */
void *dn_ptr; /* DECnet specific data */
void *ip6_ptr; /* IPv6 specific data */
void *ec_ptr; /* Econet specific data */
void *ax25_ptr; /* AX.25 specific data */

/*
* Cache line mostly used on receive path (including eth_type_trans())
*/
struct list_head poll_list ____cacheline_aligned_in_smp;
/* Link to poll list */

int (*poll) (struct net_device *dev, int *quota);
int quota;
int weight;
// 最后一个封包到达的时间
unsigned long last_rx; /* Time of last Rx */
/* Interface address info used in eth_type_trans() */
// 设备链路层地址
unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address, (before bcast
because most packets are unicast) */

// 链路层广播地址
unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */

/*
* Cache line mostly used on queue transmit path (qdisc)
*/
/* device queue lock */
spinlock_t queue_lock ____cacheline_aligned_in_smp;
// 管理入口和出口的封包队列,以及不同CPU对此设备的访问
struct Qdisc *qdisc;
struct Qdisc *qdisc_sleeping;
struct list_head qdisc_list;
// 设备的传送队列的长度
unsigned long tx_queue_len; /* Max frames per queue allowed */

/* Partially transmitted GSO packet. */
struct sk_buff *gso_skb;

/* ingress path synchronizer */
spinlock_t ingress_lock;
struct Qdisc *qdisc_ingress;

/*
* One part is mostly used on xmit path (device)
*/
/* hard_start_xmit synchronizer */
spinlock_t _xmit_lock ____cacheline_aligned_in_smp;
/* cpu id of processor entered to hard_start_xmit or -1,
if nobody entered there.
*/
int xmit_lock_owner;
void *priv; /* pointer to private data */
// 传输一个帧
int (*hard_start_xmit) (struct sk_buff *skb,
struct net_device *dev);
/* These may be needed for future network-power-down code. */
// 最近的一个帧传输启动的时间
unsigned long trans_start; /* Time (in jiffies) of last Tx */

// 定时器
int watchdog_timeo; /* used by dev_watchdog() */
struct timer_list watchdog_timer;

/*
* refcnt is a very hot point, so align it on SMP
*/
/* Number of references to this device */
// 引用计数
atomic_t refcnt ____cacheline_aligned_in_smp;

/* delayed register/unregister */
// 网络设备的注册和除名是以两步骤进行。todo_list 用于处理第二步骤
struct list_head todo_list;
/* device index hash chain */
struct hlist_node index_hlist;

/* register/unregister state machine */
enum { NETREG_UNINITIALIZED=0,
NETREG_REGISTERED, /* completed register_netdevice */
NETREG_UNREGISTERING, /* called unregister_netdevice */
NETREG_UNREGISTERED, /* completed unregister todo */
NETREG_RELEASED, /* called free_netdev */
} reg_state; // 设备的注册状态

/* Called after device is detached from network. */
void (*uninit)(struct net_device *dev);
/* Called after last user reference disappears. */
void (*destructor)(struct net_device *dev);

/* Pointers to interface service routines. */
int (*open)(struct net_device *dev);
int (*stop)(struct net_device *dev);
#define HAVE_NETDEV_POLL
int (*hard_header) (struct sk_buff *skb,
struct net_device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);
int (*rebuild_header)(struct sk_buff *skb);
#define HAVE_MULTICAST
void (*set_multicast_list)(struct net_device *dev);
#define HAVE_SET_MAC_ADDR
// 改变设备的 MAC 地址
int (*set_mac_address)(struct net_device *dev,
void *addr);
#define HAVE_PRIVATE_IOCTL
// ioctl 是系统调用,用于向设备发出命令
int (*do_ioctl)(struct net_device *dev,
struct ifreq *ifr, int cmd);
#define HAVE_SET_CONFIG
// 配置驱动程序参数,如硬件参数 irq, id_addr, if_port
int (*set_config)(struct net_device *dev,
struct ifmap *map);
#define HAVE_HEADER_CACHE
int (*hard_header_cache)(struct neighbour *neigh,
struct hh_cache *hh);
void (*header_cache_update)(struct hh_cache *hh,
struct net_device *dev,
unsigned char * haddr);
#define HAVE_CHANGE_MTU
// 改变设备的 MTU 值
int (*change_mtu)(struct net_device *dev, int new_mtu);

#define HAVE_TX_TIMEOUT
// 在开门狗定时器到期时调用
void (*tx_timeout) (struct net_device *dev);

void (*vlan_rx_register)(struct net_device *dev,
struct vlan_group *grp);
void (*vlan_rx_add_vid)(struct net_device *dev,
unsigned short vid);
void (*vlan_rx_kill_vid)(struct net_device *dev,
unsigned short vid);

int (*hard_header_parse)(struct sk_buff *skb,
unsigned char *haddr);
int (*neigh_setup)(struct net_device *dev, struct neigh_parms *);
#ifdef CONFIG_NETPOLL
struct netpoll_info *npinfo;
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
void (*poll_controller)(struct net_device *dev);
#endif

/* bridge stuff */
// 当此设备配置成桥接端口时
struct net_bridge_port *br_port;

#ifdef CONFIG_NET_DIVERT
/* this will get initialized at each interface type init routine */
// 分流器,允许改变输入的封包的源和目的地址
struct divert_blk *divert;
#endif /* CONFIG_NET_DIVERT */

/* class/net/name entry */
// 由新的通用内核驱动程序基础架构使用
struct class_device class_dev;
/* space for optional statistics and wireless sysfs groups */
struct attribute_group *sysfs_groups[3];
};

标识符

int ifindex

独一无二的 ID,当设备以 dev_new_index 注册时分派给每个设备

由(虚拟)隧道设备使用,可用于标识抵达隧道另一端的真实设备

unsigned short dev_id

IPv6 使用

配置

dma

  • 为了从内核获取和释放 DMA 通道,kernel/dma.c 定义了函数 request_dmafree_dma

  • 取得 DMA 通道后,使用各种 include/asm-architecture 文件中所提供的 enable_dmadisable_dma 开启或关闭

  • 这些函数由 ISA 设备使用;PCI 设备采用了其它方法

flags, gflags, priv_flags

include/linux/if.h 中可以看到完整列表

  • 可通过 dev_change_flags 函数进行修改

  • 设备驱动程序会在初始化期间设置这些功能,而这些状态标识由内核管理,以响应外部事件

  • 通过 ifconfig 查看标识

1
2
3
4
5
6
7
8
9
$ ifconfig lo
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 18 bytes 9432 (9.4 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 18 bytes 9432 (9.4 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
  • UP,LOOPBACK,RUNNING 相当于标识 IFF_UP, IFF_LOOGBACK, IFF_RUNNUNG

MTU

有时会发现 Ethernet MTU 定义为 1518 或 1514

  • 第一个 Ethernet 帧的最大尺寸包括报头
  • 第二个包括报头但不含帧检查序列 (4个字节的校验和)

type

include/linux/if_arp.h 包含可能类型的完整列表

hard_header_len

Ethernet 报头是 14 字节。由 include/linux/if_ether.h 中的 ETH_HLEN 定义

接口类型和端口

有些设备有一个以上的连接器,允许用户根据需求选择其中之一。

以下代码是一个设备驱动程序如何根据已有配置而设定其接口模式:

1
2
3
4
5
6
7
8
switch (dev->if_port) {
case IF_PORT_10BASE2:
writeb((readb(addr) & 0xf8) | 1, addr);
break;
case IF_PORT_10BASET:
writeb((readb(addr) & 0xf8), addr);
break;
}

混杂模式 (promiscuous mode):设备可以接收所有封包

某些网络管理任务会要求一个系统接收在一条共享缆线传播的所有帧,而不仅限于地址直接指定给该系统的帧

drivers/net/3c59x.c 驱动代码的片段,说明了如何根据 flags 字段中的标识位设置不同的接收模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void set_rx_mode(struct net_device *dev)
{
struct vortex_private *vp = netdev_priv(dev);
void __iomem *ioaddr = vp->ioaddr;
int new_mode;

if (dev->flags & IFF_PROMISC) {
if (vortex_debug > 3)
printk(KERN_NOTICE "%s: Setting promiscuous mode.\n", dev->name);
new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast|RxProm;
} else if ((dev->mc_list) || (dev->flags & IFF_ALLMULTI)) {
new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast;
} else
new_mode = SetRxFilter | RxStation | RxBroadcast;

iowrite16(new_mode, ioaddr + EL3_CMD);
}

统计数据

net_device 结构没有提供一个用来记录统计数据的收集字段

  • 引入由驱动程序设置的 priv 指针,指向一个存储有关接口信息的私有数据结构

  • 私有数据由统计数据组成,如已收发的封包数目,已发生的错误数目

  • 不同的 Ethernet 卡使用不同的私有结构,几乎所有结构都包括一个类型为 net_device_stats 的字段,可通过 get_stats 方法获取

  • 无线设备使用 iw_statistics 字段,通过 get_wireless_stats 获取

设备状态

state

1
2
3
4
5
6
7
8
9
10
11
12
enum netdev_state_t
{
__LINK_STATE_XOFF=0,
__LINK_STATE_START,
__LINK_STATE_PRESENT,
__LINK_STATE_SCHED,
__LINK_STATE_NOCARRIER,
__LINK_STATE_RX_SCHED,
__LINK_STATE_LINKWATCH_PENDING,
__LINK_STATE_DORMANT,
__LINK_STATE_QDISC_RUNNING,
};
  • 个别位的设置和清除使用 set_bitclear_bit

要停止一个设备队列时,子系统会调用 netif_stop_queue

1
2
3
4
5
6
7
8
static inline void netif_stop_queue(struct net_device *dev)
{
#ifdef CONFIG_NETPOLL_TRAP
if (netpoll_trap())
return;
#endif
set_bit(__LINK_STATE_XOFF, &dev->state);
}

列表管理

net_device 数据结构均被插入到一个全局列表和两个 hash 表中

next

name_hlist

index_hlist

链路层多播

多播:把数据传递给多位接收者

  • 可以在 L3 网络层以及 L2 链路层中使用

  • 链路层多播的传送,可通过在链路层报头中使用特殊地址或控制信息

每个设备都会为其监听的每个链路层多播地址保存一个 dev_mc_list 结构的实例。链路层多播地址可以分别使用 dev_mc_adddev_mc_delete 函数添加和删除

流量管理

  • 当队列类型为 FIFO 或者相当简单的队列时,通常会使用它

  • 所有队列为 0 的设备都是虚拟设备:虚拟设备依赖相关联的真实设备去做所有的队列化工作

  • 回环设备除外,回环设备由内核内部使用,会立即传递所有流量

功能专用

通用

函数指针

本章涉及的文件