1、Socket Buffer 结构
struct sk_buff {
struct sk_buff *next; // 用于将系统中的 sk_buff 组织成一个链表
struct sk_buff *prev;
ktime_t tstamp; //-------------- 接收数据包到达内核的时间,由 netif_rx来修改
struct sock *sk; // 当数据包由本机产生\从网络收到的包的目的地址是本机的
// 的某个AP时,此处要设置为 对应的套接字(IP-port的组合)
struct net_device *dev; //-------------- 该数据包是由哪个网络设备 接收或发送的
############### 控制缓冲区,各层协议可以用来存放 其私有信息或变量
# 各层协议定义了 相关宏来访问此 缓存 比如 UDP的 UDP_SKB_CB
char cb[48] __aligned(8);
unsigned long _skb_dst;
#ifdef CONFIG_XFRM
struct sec_path *sp; // IPSEC用来 跟踪网络数据包的传送路径。
#endif
unsigned int len, //---------- 整个 sk_buff 中数据包的长度,包括如下部分:
// (sk_buff->head 指向的内存块中 协议头除外的--->tail之间 ) S1
// 各分片数据长度 (skb_shared_info.frags[0].szie + [1].size +[n].size)
// 协议头长度
// 在协议栈各层中,值会变化
unsigned int data_len; //---------- 分片数据长度
__u16 mac_len, //---------- 链路层协议头长度
hdr_len; //---------- 克隆的数据包的 包头长度
// 克隆时,不会克隆数据包的 协议头长度,克隆 sk_buff 只需从此域获取长度
############### 检验和相关
# 发送数据包时,在将数据从用户控件复制到内核空间时,以相应算法计算数据包检验和,存放于 csum
# 接受数据包时,csum 存放网络设备计算的检验和,其中须包含 csum_start/csum_offset 对
union {
__wsum csum;
struct {
__u16 csum_start; // 以skb->head为起始地址,指出检验和 从什么位置开始计算
__u16 csum_offset; // 指明检验和 存放在哪里
};
};
############### 用于质量服务(GOS),描述数据包 在传送队列中的 优先级别
# 填充方式
# 如果数据包由本地产生,则由socket层填充;
# 如果是前送包,则由路由子系统根据IP协议头中的 TOS域来填充。
# 位域划分 :priority 被划分成如下几个域
#
__u32 priority;
kmemcheck_bitfield_begin(flags1);
__u8 local_df:1, // IPSEC会用到,IP协议用它检测 是否允许对已分割的数据片段在本地 进一步分割成更小片段
cloned:1, // 如果多个协议处理函数来处理某个skb,则有必要克隆此skb,
// 此时,原skb 及 目的 skb 的此域都 置1; 数据包则共享。
ip_summed:2, // 表明 网络首页被是否 可用硬件来 对数据包进行 检验编码或解码
nohdr:1, // 克隆数据包时,是否克隆协议头。
nfctinfo:3; // 如果当前本机有网络连接,该标志说明 此skb是否与 这些网络连接相关
__u8 pkt_type:3, // 数据包类型,按照链路层中 设置的目的地址类型来划分的
fclone:2, // 克隆类型: 未克隆 被克隆的skb 克隆出来的skb
ipvs_property:1, // 表明 此skb 是否为IP虚拟server拥有的数据包
peeked:1, // 用于UDP RAW,表明此skb已经在前边的处理过程中设置了状态,此处不再设置
nf_trace:1; // 用于netfilter,==1,表明需要对此skb过滤检查。
kmemcheck_bitfield_end(flags1);
############### 表明该skb应该交由 网络层中哪个协议来处理(TCP/IP)
# 该值 由 网卡设备驱动程序填充。
__be16 protocol;
void (*destructor)(struct sk_buff *skb);
############### 如果启用连接跟踪: 记录有什么数据包经过 本主机 或者 数据包如何进入网络连接的
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
struct nf_conntrack *nfct; // 用于连接跟踪计数
struct sk_buff *nfct_reasm; // 用于连接跟踪时的 数据包的重组装。
#endif
############### 如果启用了桥防火墙
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge; // 存放桥数据包
#endif
int skb_iif; // 接受/发送数据包的 网络设备索引号:eth0 eth1 等
############### 如果启用了流量控制: 多个数据包通过网络设备发送出去时。涉及到先后问题。
# 如果没有指定,则使用 FIFO 策略。
#ifdef CONFIG_NET_SCHED
__u16 tc_index; // 存放 数据包发送选择算法的 索引
#ifdef CONFIG_NET_CLS_ACT
__u16 tc_verd; // 存放选择流量控制后对数据包 所做处理动作的索引
// 动作包括:排序、优先级设置
#endif
#endif
############### 发送网络数据包所在队列 与 设备硬件发送队列的 映射关系
kmemcheck_bitfield_begin(flags2);
__u16 queue_mapping:16;
#ifdef CONFIG_IPV6_NDISC_NODETYPE
__u8 ndisc_nodetype:2;
#endif
kmemcheck_bitfield_end(flags2);
/* 0/14 bit hole */
############### 在多个DMA操作中,由skb的DMA函数完成 DMA请求
#ifdef CONFIG_NET_DMA
dma_cookie_t dma_cookie;
#endif
############### 允许有安全标记的数据包通过主机
#ifdef CONFIG_NETWORK_SECMARK
__u32 secmark;
#endif
union {
__u32 mark;
__u32 dropcount;
};
__u16 vlan_tci; // 虚拟局域网的标记控制信息
############### 如下域描述 协议栈中 各层协议头 在 数据包中的 存放位置
# 对于64Bit CPU,表示 以skb->head为起始的 偏移量
# 对于32Bit CPU,表示 在数据包中的 起始地址
sk_buff_data_t transport_header; // 传输层协议头地址
sk_buff_data_t network_header; // 网络层
sk_buff_data_t mac_header; // 链路层
############### 数据包缓冲区控制指针
# 各层,可以再 head data 之间 填充协议头信息
# 各层,可以在 tail end 之间 填充存放新的数据。
sk_buff_data_t tail; //---------- 指向负载数据块的 末尾
sk_buff_data_t end; //---------- 指向整个数据包缓冲区的 末尾
unsigned char *head, //---------- 指向整个数据包缓冲区的 头部
*data; //---------- 指向负载数据块的 头部
unsigned int truesize; //---------- 总长度:sk_buff的长度 + 数据包长度
atomic_t users; //---------- 所有正在使用此 sk_buff 的进程的统计计数
};
2、skb操作函数
{
2.1、两个sk_buff内存池
由于高速网络中,某一时刻,需处理的数据包数量巨大,因而要采取内存池来预分配。
skbuff_head_cache
skbuff_fclone_cache
2.2、创建sk_buff
{
1、__alloc_skb skbuff.c
struct sk_buff *__alloc_skb(unsigned int size, // 数据包缓冲区大小
gfp_t gfp_mask, // GFP_ATOMIC,用于在中断处理程序中(中断不能休眠)
// GFP_KERNEL 用于常规内核函数申请内存。
int fclone, // 是否为clone型的
int node) // numa node to allocate memory on
{
struct kmem_cache *cache;
struct skb_shared_info *shinfo;
struct sk_buff *skb;
u8 *data;
##################### 是否克隆
cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;
##################### 从对应的cache中分配一个 sk_buff
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
prefetchw(skb);
##################### 强制边界对齐
size = SKB_DATA_ALIGN(size);
##################### 调用 kmalloc 分配数据包缓冲区,紧接着data之后的是 skb_shared_info 区
data = kmalloc_node_track_caller(size + sizeof(struct skb_shared_info), gfp_mask, node);
prefetchw(data + size);
/*
* Only clear those fields we need to clear, not those that we will
* actually initialise below. Hence, don't put any more fields after
* the tail pointer in struct sk_buff!
*/
##################### skb 相关域初始化
memset(skb, 0, offsetof(struct sk_buff, tail));
skb->truesize = size + sizeof(struct sk_buff);
atomic_set(&skb->users, 1);
skb->head = data;
skb->data = data;
skb_reset_tail_pointer(skb);
skb->end = skb->tail + size;
kmemcheck_annotate_bitfield(skb, flags1);
kmemcheck_annotate_bitfield(skb, flags2);
#ifdef NET_SKBUFF_DATA_USES_OFFSET
skb->mac_header = ~0U;
#endif
##################### skb_shared_info 区初始化
shinfo = skb_shinfo(skb); // ((struct skb_shared_info *)(skb->end))
memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
atomic_set(&shinfo->dataref, 1);
##################### 如果为 clone型的,
if (fclone) {
struct sk_buff *child = skb + 1;
atomic_t *fclone_ref = (atomic_t *) (child + 1);
kmemcheck_annotate_bitfield(child, flags1);
kmemcheck_annotate_bitfield(child, flags2);
skb->fclone = SKB_FCLONE_ORIG;
atomic_set(fclone_ref, 1);
child->fclone = SKB_FCLONE_UNAVAILABLE;
}
out:
return skb;
nodata:
kmem_cache_free(cache, skb);
skb = NULL;
goto out;
}
2、alloc_skb(unsigned int size, gfp_t priority)
return __alloc_skb(size, priority, 0, -1);
3、alloc_skb_fclone(unsigned int size, gfp_t priority)
return __alloc_skb(size, priority, 1, -1);
4、dev_alloc_skb(unsigned int length); // 中断处理函数用此来分配 skb,
// 采用 GFP_ATOMIC 使得分配过程不会被中断
__dev_alloc_skb(length, GFP_ATOMIC);
{
// skbuff.h
struct sk_buff *skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);
// 在数据包缓冲区前,预留16字节空间,以避免 头信息增长时而原空间不够用
skb_reserve(skb, NET_SKB_PAD);
return skb;
}
5、netdev_alloc_skb(struct net_device *dev, unsigned int length)
// 指定此skb 将由指定的网络设备来发送
__netdev_alloc_skb(dev, length, GFP_ATOMIC);
{
// (struct net_device *dev, unsigned int length, gfp_t gfp_mask)
int node = dev->dev.parent ? dev_to_node(dev->dev.parent) : -1;
struct sk_buff *skb = __alloc_skb(length + NET_SKB_PAD, gfp_mask, 0, node);
skb_reserve(skb, NET_SKB_PAD);
skb->dev = dev;
return skb;
}
}
2.3、skb释放 kfree_skb
void kfree_skb(struct sk_buff *skb)
{
if (unlikely(!skb))
return;
if (likely(atomic_read(&skb->users) == 1))
smp_rmb();
##################### skb->users -- ,引用计数减1。
# 递减后,如果引用计数!=0,则立即返回
# 如果引用计数==0,则继续处理释放处理
else if (likely(!atomic_dec_and_test(&skb->users)))
return;
#####################
trace_kfree_skb(skb, __builtin_return_address(0));
__kfree_skb(skb);
{
skb_release_all(skb);
{
skb_release_head_state(skb);
{
######### 释放 _skb_refdst
skb_dst_drop(skb);
{
if (skb->_skb_refdst) {
refdst_drop(skb->_skb_refdst);
skb->_skb_refdst = 0UL;
}
}
#ifdef CONFIG_XFRM
secpath_put(skb->sp);
#endif
//------- 如果设定了destructor,则调用之
if (skb->destructor) {
WARN_ON(in_irq());
skb->destructor(skb);
}
//------- 释放对其他数据结构的引用。连接更总、网络桥、QOS等
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
nf_conntrack_put(skb->nfct);
nf_conntrack_put_reasm(skb->nfct_reasm);
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
nf_bridge_put(skb->nf_bridge);
#endif
/* XXX: IS this still necessary? - JHS */
#ifdef CONFIG_NET_SCHED
skb->tc_index = 0;
#ifdef CONFIG_NET_CLS_ACT
skb->tc_verd = 0;
#endif
#endif
}
skb_release_data(skb);
{
//------- 如果满足如下条件,则 释放数据缓冲区、所有分片数据缓冲区及队列
if (!skb->cloned || // 无克隆
// 或者 对数据包的引用计数为1
!atomic_sub_return( skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1, &skb_shinfo(skb)->dataref )
)
{
if (skb_shinfo(skb)->nr_frags)
{
int i;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
put_page(skb_shinfo(skb)->frags[i].page);
}
if (skb_has_frags(skb))
skb_drop_fraglist(skb);
kfree(skb->head);
}
}
}
kfree_skbmem(skb);
{
struct sk_buff *other;
atomic_t *fclone_ref;
//----------- skb 是否为克隆
switch (skb->fclone)
{
######### 不是克隆,则直接将 skb 返回给 cache
case SKB_FCLONE_UNAVAILABLE:
kmem_cache_free(skbuff_head_cache, skb);
break;
######### 是克隆,则skb克隆计数--,如果 克隆计数 == 0 ,则将 skb返回给cache
//------- 被克隆的skb
case SKB_FCLONE_ORIG:
fclone_ref = (atomic_t *) (skb + 2);
if (atomic_dec_and_test(fclone_ref))
kmem_cache_free(skbuff_fclone_cache, skb);
break;
//------- 克隆的skb
case SKB_FCLONE_CLONE:
fclone_ref = (atomic_t *) (skb + 1);
other = skb - 1;
/* The clone portion is available for fast-cloning again. */
skb->fclone = SKB_FCLONE_UNAVAILABLE;
if (atomic_dec_and_test(fclone_ref))
kmem_cache_free(skbuff_fclone_cache, other);
break;
}
}
}
}
2.4、数据空间的操作
{
1、skb_reserve 在数据存储区加入协议头 或 强制数据边界对齐
{
// (struct sk_buff *skb, int len)
skb->data += len;
skb->tail += len;
}
2、skb_put skb->tail 后移len字节,用于在 数据包之后预留len空间
{ // (struct sk_buff *skb, unsigned int len)
unsigned char *tmp = skb_tail_pointer(skb);
SKB_LINEAR_ASSERT(skb);
skb->tail += len;
skb->len += len;
if (unlikely(skb->tail > skb->end))
skb_over_panic(skb, len, __builtin_return_address(0));
return tmp;
}
3、skb_push skb->data前移len字节,在数据包缓冲区的头部 预留len空间
{ // (struct sk_buff *skb, unsigned int len)
skb->data -= len;
skb->len += len;
if (unlikely(skb->datahead))
skb_under_panic(skb, len, __builtin_return_address(0));
return skb->data;
}
4、skb_pull skb->data后移len字节,从数据包缓冲区的头部 移除len字节
{ // (struct sk_buff *skb, unsigned int len)
return skb_pull_inline(skb, len);
return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
{
// __skb_pull(skb, len);
skb->len -= len;
BUG_ON(skb->len < skb->data_len);
return skb->data += len;
}
}
5、skb_trim skb->tail 前移 (skb->len-len) 个字节,从 缓冲区尾部移除 (skb->len-len)空间数据
{ // (struct sk_buff *skb, unsigned int len)
if (skb->len > len)
__skb_trim(skb, len);
{
if (unlikely(skb->data_len)) {
WARN_ON(1);
return;
}
skb->len = len;
skb_set_tail_pointer(skb, len);
skb->tail = skb->data + offset;
}
}
}
2.5、skb克隆
{
1、使用情况:
某些时候,同一个SKB会由不同的进程独立处理,但它们不会修改数据缓冲区,此时为提高性能,内核不需
对整个SKB(包含了数据缓冲区)进行完全复制。而只需要对sk_buff结构本身做复制。
克隆之后,它们公用的skb缓冲区就不能再被修改,保持只读状态
2、复制操作接口 skb_clone
struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t priority);
{
struct sk_buff *n;
n = skb + 1;
// 如果原skb是 被克隆型的、且 cache中后续skb未被克隆,则直接用后续的 skb
// 后续skb->fclone 设定为 克隆出来的的
if (skb->fclone == SKB_FCLONE_ORIG && n->fclone == SKB_FCLONE_UNAVAILABLE)
{
atomic_t *fclone_ref = (atomic_t *) (n + 1);
n->fclone = SKB_FCLONE_CLONE;
atomic_inc(fclone_ref);
}
// 否则,如果 原skb不是被克隆型的、或者 后续skb已经 克隆过,此时要重新再cache中分配skb
// 新分配skb->fclone 设定为 SKB_FCLONE_UNAVAILABLE;
else
{
n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);
if (!n)
return NULL;
kmemcheck_annotate_bitfield(n, flags1);
kmemcheck_annotate_bitfield(n, flags2);
n->fclone = SKB_FCLONE_UNAVAILABLE;
}
return __skb_clone(n, skb);
{
// (struct sk_buff *n, struct sk_buff *skb)
#define C(x) n->x = skb->x
n->next = n->prev = NULL;
n->sk = NULL;
__copy_skb_header(n, skb);
C(len);
C(data_len);
C(mac_len);
n->hdr_len = skb->nohdr ? skb_headroom(skb) : skb->hdr_len;
n->cloned = 1;
n->nohdr = 0;
n->destructor = NULL;
C(tail);
C(end);
C(head);
C(data);
C(truesize);
atomic_set(&n->users, 1);
atomic_inc(&(skb_shinfo(skb)->dataref));
skb->cloned = 1;
return n;
#undef C
}
}
}
2.6、skb复制,多个进程需要对某个 skb 执行: 既要修改skb结构体、也要修改缓冲区时,须进行复制
{
1、skb_copy 既要修改主数据包中的内容,又要修改分片数据包的内容,此时主数据和分片都要复制
2、pskb_copy 只修改主数据包内容
}
2.7、skb队列的操作
{
// 对skb队列的操作,须保证是原子操作,即首先要获取 sk_buff_head 中的 spinlock
skb_queue_head_init // 初始化sk_buff等待队列,空
(struct sk_buff_head *list)
######################### 插入到头尾
skb_queue_head // 将一个skb插入到 sk_buff队列的头部
(struct sk_buff_head *list, struct sk_buff *newsk);
skb_queue_tail // 将一个skb插入到 sk_buff队列的尾部
(struct sk_buff_head *list, struct sk_buff *newsk);
######################### 指定位置插入
skb_insert // 将 newsk 放到 list队列中指定的 old 节点前
(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list);
skb_append // 将 newsk 放到 list队列中指定的 old 节点后
(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list);
######################### 从头尾获取skb
skb_dequeue // 从list的头部读取一个 skb
(struct sk_buff_head *list);
skb_dequeue_tail // 从list的尾部读取一个 skb
(struct sk_buff_head *list);
######################### 删除一个skb
skb_unlink // 从list中删除 一个skb
(struct sk_buff *skb, struct sk_buff_head *list);
skb_drop_list // 释放队列中所有的skb,并释放 队列
(struct sk_buff **listp)
######################### 遍历一个skb链表
// 从skb后续正向遍历
#define skb_queue_walk(queue, skb) \
for (skb = (queue)->next; \
prefetch(skb->next), (skb != (struct sk_buff *)(queue)); \
skb = skb->next)
// 从skb后续正向遍历,安全的
#define skb_queue_walk_safe(queue, skb, tmp) \
for (skb = (queue)->next, tmp = skb->next; \
skb != (struct sk_buff *)(queue); \
skb = tmp, tmp = skb->next)
// 从skb正向遍历
#define skb_queue_walk_from(queue, skb) \
for (; prefetch(skb->next), (skb != (struct sk_buff *)(queue)); \
skb = skb->next)
// 从skb正向遍历,安全的
#define skb_queue_walk_from_safe(queue, skb, tmp) \
for (tmp = skb->next; \
skb != (struct sk_buff *)(queue); \
skb = tmp, tmp = skb->next)
// 从skb之前,反遍历
#define skb_queue_reverse_walk(queue, skb) \
for (skb = (queue)->prev; \
prefetch(skb->prev), (skb != (struct sk_buff *)(queue)); \
skb = skb->prev)
}
2.8、协议头指针操作
{
}
2.9、数据包分片
{
1、skb_shared_info
skb缓冲区之后,紧跟着的是 skb_shared_info ,用于 支持IP数据分片和 TCP数据分段的内容。
skb_shared_info 管理者skb 单向链表。
2、skb_shared_info struct
struct skb_shared_info {
unsigned short nr_frags; // 数据片计数,用于IP分片使用
##################### 适用于GSO协议TCP分段,(Generic segmentation Offload)
unsigned short gso_size; // TCP数据包分段数量
unsigned short gso_segs; // 是否分片
unsigned short gso_type; // 网络设备是否具备 硬件上对 TCP包进行分段
__be32 ip6_frag_id;
union skb_shared_tx tx_flags;
##################### 用于IP分片
struct sk_buff *frag_list; // 如果有分片,此域指向 分片链表的起始地址
struct skb_shared_hwtstamps hwtstamps;
##################### 此前的域都被 __alloc_skb 清零
atomic_t dataref; // 主数据包缓冲区的 引用计数
##################### 用于TCP分段
skb_frag_t frags[MAX_SKB_FRAGS]; // IP分片数组,总大小为64KB
{
struct skb_frag_struct {
struct page *page; // 指向 存放TCP分段的页面
__u32 page_offset; // TCP数据段在 页面中 相对于 页面的偏移量
__u32 size; // 段的大小
};
}
/* Intermediate layers must ensure that destructor_arg remains valid until skb destructor */
void * destructor_arg;
};
3、skb_shared_info 操作接口
skb_is_nonlinear // skb的数据包是否有分片/段
(const struct sk_buff *skb)
skb_linearize // 将分了片的小数据包组装成一个大的数据包
{
// (struct sk_buff *skb)
return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;
return __pskb_pull_tail(skb, skb->data_len) ? 0 : -ENOMEM;
{
// skbuff.c
}
}
skb_shinfo(SKB) // 返回 skb->end,即 skb_shared_info 的起始地址
((struct skb_shared_info *)(skb_end_pointer(SKB)))
}
}
3、套接字层、网络子系统、IP协议栈子系统初始化
{
3.1、套接字层初始化
{
###################################################################################
####
#### 主要步骤
####
/* 初始化套接字缓存槽
* 初始化 sk_buff 缓存槽
* 创建文件系统所需的inode 高速缓冲区 sock_inode_cache
* 注册 套接字文件系统类型 到 file_systems 链表上
* 安装套接字文件系统 sock_fs_type
* 初始化网络过滤子系统
*/
###################################################################################
####
#### 重要全局静态数据结构定义
####
//-------------- socket.c
static struct vfsmount *sock_mnt __read_mostly;
static struct file_system_type sock_fs_type = {
.name = "sockfs",
.mount = sockfs_mount,
.kill_sb = kill_anon_super,
};
static const struct super_operations sockfs_ops = {
.alloc_inode = sock_alloc_inode,
.destroy_inode = sock_destroy_inode,
.statfs = simple_statfs,
};
###################################################################################
####
#### 实际初始化过程
####
int __init sock_init(void)
{
##################### 初始化套接字缓存槽
// 为sock 分配内存时,从该slab cache中分配,释放时返回该内存槽中
sk_init();
{
if (totalram_pages <= 4096)
{
sysctl_wmem_max = 32767;
sysctl_rmem_max = 32767;
sysctl_wmem_default = 32767;
sysctl_rmem_default = 32767;
}
else if (totalram_pages >= 131072)
{
sysctl_wmem_max = 131071;
sysctl_rmem_max = 131071;
}
}
##################### 初始化 sk_buff 缓存槽 skbuff_head_cache skbuff_fclone_cache
// 创建sk_buff时,从该内存槽中分配,反之,释放。
skb_init();
{
skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
sizeof(struct sk_buff),
0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC,
NULL);
skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",
(2*sizeof(struct sk_buff)) +
sizeof(atomic_t),
0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC,
NULL);
}
//-------------------- 创建虚拟文件系统 -----------------------//
##################### 创建文件系统所需的inode 高速缓冲区 sock_inode_cache
# 创建套接字文件的inode时,从中分配对象,
#
init_inodecache();
{
sock_inode_cachep = kmem_cache_create("sock_inode_cache",
sizeof(struct socket_alloc),
0,
(SLAB_HWCACHE_ALIGN |
SLAB_RECLAIM_ACCOUNT |
SLAB_MEM_SPREAD),
init_once);
if (sock_inode_cachep == NULL)
return -ENOMEM;
return 0;
}
##################### 注册 套接字文件系统类型 到 file_systems 链表上
// 为套接字创建名为 sock_fs_type 的文件系统,这样AP对I/O的调用可以使用统一的API传送给它们要
// 访问的设备、文件、套接字,与此同时,设备、文件、套接字必须注册到文件系统中。
register_filesystem(&sock_fs_type);
##################### 安装套接字文件系统 sock_fs_type
// 把该文件系统注册到 super_blocks上
/*
创建vfsmount结构,并申请super_blocks
之后创建一个套接字就是在 sockfs 文件系统中创建一个特殊文件,在sock_mnt->mnt_sb 父节点之上
创建一个inode 表示某个文件。
*/
// 定义在socket.c中 : static struct vfsmount *sock_mnt __read_mostly;
sock_mnt = kern_mount(&sock_fs_type);
##################### 初始化网络过滤子系统
#ifdef CONFIG_NETFILTER
netfilter_init();
#endif
return 0;
}
core_initcall(sock_init); /* early initcall */
}
3.2、网络子系统初始化 net_dev_init
{
1、__init net_dev_init(void) // subsys_initcall(net_dev_init);
{
int i, rc = -ENOMEM;
BUG_ON(!dev_boot_phase);
##################### 初始化 /proc fs 中网络设备对应入口
if (dev_proc_init())
goto out;
##################### 初始化 /sys/class/net/xxx 入口
if (netdev_kobject_init())
goto out;
##################### 注册 需处理来自网络设备数据的 上层协议实例的 处理函数。
INIT_LIST_HEAD(&ptype_all);
for (i = 0; i < PTYPE_HASH_SIZE; i++)
INIT_LIST_HEAD(&ptype_base[i]);
if (register_pernet_subsys(&netdev_net_ops))
goto out;
##################### 初始化 每个CPU的 数据包输入\输出队列
for_each_possible_cpu(i) {
struct softnet_data *queue;
queue = &per_cpu(softnet_data, i);
skb_queue_head_init(&queue->input_pkt_queue); // 初始化输入数据包队列
queue->completion_queue = NULL; // 完成队列为空
INIT_LIST_HEAD(&queue->poll_list); // 建立轮询设备队列
queue->backlog.poll = process_backlog;
queue->backlog.weight = weight_p;
queue->backlog.gro_list = NULL;
queue->backlog.gro_count = 0;
}
dev_boot_phase = 0;
##################### 初始化 lookback网络设备
if (register_pernet_device(&loopback_net_ops))
goto out;
##################### 遍历网络设备链表,保留有效设备。
# 将初始化失败的设备从 设备链表中 移走
if (register_pernet_device(&default_device_ops))
goto out;
##################### 注册网络子系统 接收\发送 软件中断处理函数
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
##################### 注册接收 CPU事件通知的处理函数 到 CPU热插拔事件通知链表中
# 当前唯一的事件是 CPU暂停,如有发生,则相应的 CPU的网络数据包接收队列 停止接收数据包
#并通知数据链路层接收函数 netif_rx
hotcpu_notifier(dev_cpu_callback, 0);
##################### 初始化路由表
# 初始化 独立于协议栈的 目的路由缓冲存储器 dst cache
dst_init();
##################### 初始化组传送设备
dev_mcast_init();
rc = 0;
out:
return rc;
}
}
3.3、IP协议子系统的初始化(PF_INET 协议族初始化)
{
3.3.1、套接字层、TCP/UDP/RAW协议、IP 层通信接口定义
{
1、AF_INET协议族中 各协议实例的 套接字层对应系统调用 到 协议自身实现的套接字接口的 交换表
{
/*
* 管理和描述
* 套接字层对应系统调用套接字操作接口集struct proto_ops 与
* 内核协议相关套接字操作函数集 struct proto
* 之间的对应关系。
* 内核使用该协议交换表,将AP通过socketcall系统调用指定的套接字操作
* 转换成某个协议实例实现的套接字操作函数的调用。
*/
struct inet_protosw {
struct list_head list; // 协议族中同一套接字类型的该结构 链表
unsigned short type; // AF_INET协议族套接字类型,如TCP协议实例为,SOCK_STREAM
unsigned short protocol; // 协议族中某个协议实例的编号,如TCP协议编号为 IPPROTO_TCP
struct proto *prot; // 内核协议实现的 套接字操作函数集
const struct proto_ops *ops; // 套接字层对应的 系统调用套接字操作接口集
char no_check; // 是否对 发送接收数据包做 检验和
unsigned char flags; // 该套接字属性的相关标识
// TCP、UDP是永久协议,不能从内核中移除,采用 INET_PROTOSW_PERMANENT 标记
};
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,// socket系统调用的第二个参数.
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.no_check = 0,// 发送或接收报文时是否需要校验和
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_PERMANENT,
},
{
.type = SOCK_RAW,
.protocol = IPPROTO_IP, /* wild card */
.prot = &raw_prot,
.ops = &inet_sockraw_ops,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_REUSE,// 表明该套接字端口可重用
}
};
##
######################### 套接字层对应的系统调用
##
// af_inet.c
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_stream_connect,
.socketpair = sock_no_socketpair,
.accept = inet_accept,
.getname = inet_getname,
.poll = tcp_poll,
.ioctl = inet_ioctl,
.listen = inet_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = tcp_sendmsg,
.recvmsg = inet_recvmsg,
.mmap = sock_no_mmap,
.sendpage = tcp_sendpage,
.splice_read = tcp_splice_read,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
#endif
};
//----------------------- UDP
const struct proto_ops inet_dgram_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_dgram_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = inet_getname,
.poll = udp_poll,
.ioctl = inet_ioctl,
.listen = sock_no_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
.mmap = sock_no_mmap,
.sendpage = inet_sendpage,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
#endif
};
//----------------------- RAW
static const struct proto_ops inet_sockraw_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_dgram_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = inet_getname,
.poll = datagram_poll,
.ioctl = inet_ioctl,
.listen = sock_no_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
.mmap = sock_no_mmap,
.sendpage = inet_sendpage,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
#endif
};
##
######################### 协议自身实现的套接字接口
##
//----------------------- TCP 协议自身实现的套接字接口
// tcp_ipv4.c
struct proto tcp_prot = {
.name = "TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.backlog_rcv = tcp_v4_do_rcv,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.sockets_allocated = &tcp_sockets_allocated,
.orphan_count = &tcp_orphan_count,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp_sock),
.slab_flags = SLAB_DESTROY_BY_RCU,
.twsk_prot = &tcp_timewait_sock_ops,
.rsk_prot = &tcp_request_sock_ops,
.h.hashinfo = &tcp_hashinfo,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_tcp_setsockopt,
.compat_getsockopt = compat_tcp_getsockopt,
#endif
};
//----------------------- UDP 协议自身实现的套接字接口
// udp.c
struct proto udp_prot = {
.name = "UDP",
.owner = THIS_MODULE,
.close = udp_lib_close,
.connect = ip4_datagram_connect,
.disconnect = udp_disconnect,
.ioctl = udp_ioctl,
.destroy = udp_destroy_sock,
.setsockopt = udp_setsockopt,
.getsockopt = udp_getsockopt,
.sendmsg = udp_sendmsg,
.recvmsg = udp_recvmsg,
.sendpage = udp_sendpage,
.backlog_rcv = __udp_queue_rcv_skb,
.hash = udp_lib_hash,
.unhash = udp_lib_unhash,
.get_port = udp_v4_get_port,
.memory_allocated = &udp_memory_allocated,
.sysctl_mem = sysctl_udp_mem,
.sysctl_wmem = &sysctl_udp_wmem_min,
.sysctl_rmem = &sysctl_udp_rmem_min,
.obj_size = sizeof(struct udp_sock),
.slab_flags = SLAB_DESTROY_BY_RCU,
.h.udp_table = &udp_table,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_udp_setsockopt,
.compat_getsockopt = compat_udp_getsockopt,
#endif
};
//----------------------- RAW 协议自身实现的套接字接口
// net/ipv4/raw.c
struct proto raw_prot = {
.name = "RAW",
.owner = THIS_MODULE,
.close = raw_close,
.destroy = raw_destroy,
.connect = ip4_datagram_connect,
.disconnect = udp_disconnect,
.ioctl = raw_ioctl,
.init = raw_init,
.setsockopt = raw_setsockopt,
.getsockopt = raw_getsockopt,
.sendmsg = raw_sendmsg,
.recvmsg = raw_recvmsg,
.bind = raw_bind,
.backlog_rcv = raw_rcv_skb,
.hash = raw_hash_sk,
.unhash = raw_unhash_sk,
.obj_size = sizeof(struct raw_sock),
.h.raw_hash = &raw_v4_hashinfo,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_raw_setsockopt,
.compat_getsockopt = compat_raw_getsockopt,
#endif
};
}
2、TCP、UDP、RAW 到IP层之间的接口,由IP调用:上传IP数据包到传输层
{
// protocol.h
const struct net_protocol *inet_protos[MAX_INET_PROTOS]; // MAX_INET_PROTOS 256
// af_inet.c
static const struct net_protocol igmp_protocol = {
.handler = igmp_rcv,
.netns_ok = 1,
};
static const struct net_protocol tcp_protocol = {
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
.gso_send_check = tcp_v4_gso_send_check,
.gso_segment = tcp_tso_segment,
.gro_receive = tcp4_gro_receive,
.gro_complete = tcp4_gro_complete,
.no_policy = 1,
.netns_ok = 1,
};
static const struct net_protocol udp_protocol = {
.handler = udp_rcv,
.err_handler = udp_err,
.gso_send_check = udp4_ufo_send_check,
.gso_segment = udp4_ufo_fragment,
.no_policy = 1,
.netns_ok = 1,
};
static const struct net_protocol icmp_protocol = {
.handler = icmp_rcv,
.no_policy = 1,
.netns_ok = 1,
};
}
3、IP层中对传输层数据包的 发送处理的 相关结构 struct sock
{
//-----------------------------------------------------
//
套接字在内核中用struct sock结构去表示。通常该结构嵌套在特定协议族对应的套接字结构体中
比如:PF_INET族对应的套接字对应的是 inet_sock
1、struct sock , 网络层套接字管理控制块
{
//-----------------------------------------------------
//
共分为如下两部分:
所有协议族都套接字会使用的通用属性 sock_common
适用于某个特定协议的,从协议特有的缓冲区中分配的内存空间。
//-----------------------------------------------------
//
struct sock
{
struct sock_common __sk_common;
#define sk_node __sk_common.skc_node //连接hash链表中成员的哈希节点,
#define sk_nulls_node __sk_common.skc_nulls_node
#define sk_refcnt __sk_common.skc_refcnt
#define sk_tx_queue_mapping __sk_common.skc_tx_queue_mapping
#define sk_copy_start __sk_common.skc_hash// 引用的hash值
#define sk_hash __sk_common.skc_hash
#define sk_family __sk_common.skc_family
#define sk_state __sk_common.skc_state
#define sk_reuse __sk_common.skc_reuse
#define sk_bound_dev_if __sk_common.skc_bound_dev_if
#define sk_bind_node __sk_common.skc_bind_node
#define sk_prot __sk_common.skc_prot
#define sk_net __sk_common.skc_net
// 套接字选项设置
kmemcheck_bitfield_begin(flags);
unsigned int sk_shutdown : 2, // 存放RCV_SHUTDOWN,SEND_SHUTDOWN,后者TCP表示关闭套接字时发送RST数据包
sk_no_check : 2, // SO_NO_CHECK,表示禁止检验和
sk_userlocks : 4, //存放,SO_RCVBUF,SO_SNDBUF,表明可以设置套接字发送、接收缓冲区大小
sk_protocol : 8, //在网络协议族中,该套接字属于哪个协议使用
sk_type : 16; //套接字类型,SOCK_XXXX
kmemcheck_bitfield_end(flags);
int sk_rcvbuf; // 套接字接收缓冲区大小,字节为单位,AP可以通过SO_RCVBUF来设置该值
socket_lock_t sk_lock; //套接字并发访问锁。
/*
* The backlog queue is special, it is always used with
* the per-socket spinlock held and requires low latency
* access. Therefore we special case it's implementation.
*/
struct {
struct sk_buff *head;
struct sk_buff *tail;
int len;
} sk_backlog; //TCP接收过程中中,FAST PATH 、SLOW PATH,中快速路数据包存放在套机子的prequeue队列中
// 慢速数据包存放在该队列中。
struct socket_wq *sk_wq;//
struct dst_entry *sk_dst_cache; //套接字目标地址在路由表高速缓存中的入口
#ifdef CONFIG_XFRM
struct xfrm_policy *sk_policy[2]; //安全策略库中的字段。
#endif
spinlock_t sk_dst_lock; //路由表并发访问锁
atomic_t sk_rmem_alloc; //套接字接收队列中收到的数据包的字节数
atomic_t sk_wmem_alloc; //套接字发送队列中发送的数据包的字节数
atomic_t sk_omem_alloc; //套接字选项活其他设置的长度
int sk_sndbuf; //套接字发送缓冲区的大小,由SO_SNDBUF选项设置
struct sk_buff_head sk_receive_queue;//套接字的接收队列,将从此协议栈上传给AP的数据包缓冲区放入该队列
struct sk_buff_head sk_write_queue; //套接字的发送队列,将从AP写入套接字向外发送的数据包缓冲区放入该队列
#ifdef CONFIG_NET_DMA
struct sk_buff_head sk_async_wait_queue;//直接存储器访问方式传送数据包的队列
#endif
int sk_wmem_queued; //永久写队列
int sk_forward_alloc; //预分配页面的字节数
gfp_t sk_allocation; //内存分配模式
int sk_route_caps; //路由权限,NETIF_F_TSO
int sk_route_nocaps; //
int sk_gso_type; //GSO类型,如SKB_GOS_TCPV4
unsigned int sk_gso_max_size; //可构造的GSO段大小
int sk_rcvlowat; //SO_RCVLOWAT选项,用于设置接收数据前的缓冲区内的最小字节数,默认为1
#ifdef CONFIG_RPS
__u32 sk_rxhash;
#endif
unsigned long sk_flags; //包含如下选项:
/* SO_BROADCAST,允许或禁止发送广播数据包
SO_KEEPALIVE,保持套接字活动,如果协议为TCP,
且当前套接字不在侦听或者关闭状态,则启用TCP活动定时器
SO_OOBINLINE,指明将紧急数据放到普通数据流
*/
unsigned long sk_lingertime;// 如果设置了SO_LINGER选项,则该字段为TRUE,此时套接字的关闭、
//shutdown操作将等到所有套接字里等待发送的消息成功发送活等待延迟时
//超时后才会返回,否则调用立即结束。
struct sk_buff_head sk_error_queue;//套接字错误信息队列
struct proto *sk_prot_creator; // 协议处理函数的指针,这些指针是定义在AF_INET协议族内的处理函数快
// 该函数集用于 套接字层与传输层之间 在内核空间的通信
rwlock_t sk_callback_lock; // 用于sock中定义的6个回调函数并访问的锁
int sk_err, //在该套接字上的最后一个错误
sk_err_soft; //在该套接字上出现的错误,此错误不会引起套接字操作完全失败,只是超时
atomic_t sk_drops; //裸套接字,UDP套接字上丢失的数据包计数
unsigned short sk_ack_backlog; //当前套接字正在侦听的backlog队列
unsigned short sk_max_ack_backlog;//在sk_ack_backlog上可以侦听的最大进入连接数
__u32 sk_priority; //存放SO_PRIORITY选项,用于设置套接字上发送数据包的协议制定的优先级,linux
//通过该值来排列网络队列
struct ucred sk_peercred; // 存放选项SO_PEERCRED,只用于PF_UNIX套接字,
//用于设置套接字的进程ID等ID
long sk_rcvtimeo; //存放选项:SO_RCVTIMEO
long sk_sndtimeo; //存放选项:SO_SNDTIMEO
struct sk_filter *sk_filter; //套接字过滤指令
void *sk_protinfo; // 各种协议使用的私有数据域
struct timer_list sk_timer; //清除sock结构值的时钟
ktime_t sk_stamp; //最近在该套接字上收到的数据包的时间戳
struct socket *sk_socket; //AP层套接字指针
void *sk_user_data; //存放远程过程调用的私有数据
struct page *sk_sndmsg_page;//发送messgae的页面链表
struct sk_buff *sk_send_head;//传送数据前的信息
__u32 sk_sndmsg_off; //缓存的发送数据在缓存中的偏移量
int sk_write_pending; //表明一个向STREAM类型套接字的写操作挂起了,等待启动
#ifdef CONFIG_SECURITY
void *sk_security;
#endif
__u32 sk_mark; //常规数据包的标志
u32 sk_classid;
//套接字状态改变时的回调函数,内核使用这些函数来获取套接字上某些事件发生的消息
void (*sk_state_change)(struct sock *sk);//
void (*sk_data_ready)(struct sock *sk, int bytes); //用户进程等待数据到达时,会调用
void (*sk_write_space)(struct sock *sk); //发送队列中存在有效空间,可以发送新的数据包
void (*sk_error_report)(struct sock *sk); //有错误信息到达
int (*sk_backlog_rcv)(struct sock *sk,
struct sk_buff *skb); //处理套接字的backlog队列的函数
void (*sk_destruct)(struct sock *sk); //套接字资源释放
};
}
2、struct ipcm_cookie 包含发送数据包需要的各种信息
struct ipcm_cookie {
__be32 addr; // 输出数据包的目的IP地址
int oif; // ...的网络设备索引号
struct ip_options *opt; // 指向 IP选项内存块
# 通过同一套接字发送的数据包,其IP选项都一样,所以将其放置在此
#从而避免了 为每个数据包都重建这些信息。
union skb_shared_tx shtx;
};
3、struct cork , 包含在 inet_sock 中
4、PF_INET族对应的套接字:inet_sock
struct inet_sock
{
struct sock sk; // sk 和 pinet6 必须是 PF_INET族套接字的最前成员
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
struct ipv6_pinfo *pinet6;
#endif
/* Socket demultiplex comparisons on incoming packets. */
__be32 inet_daddr; // 数据包的目标IP地址
__be32 inet_rcv_saddr; // 与 创建数据包的套接字 绑定的本地IP地址
__be16 inet_dport; // 数据包目的地址的套接字端口号,对应着目的地址上接收此包的AP
__u16 inet_num; // 创建该套接字的 本地AP对应的端口号
__be32 inet_saddr; // 发送此包的 源IP地址
__s16 uc_ttl; // 当 发送数据包的目的地址 是单一主机地址时,此数据包的存活时间的ttl值
__u16 cmsg_flags; // 控制消息 标识
__be16 inet_sport; // 发送数据包的 源端口号
__u16 inet_id; // 未分片数据包的 数据包标识符
struct ip_options *opt;
__u8 tos; // 数据包的服务类型TOS,代表了发送的优先级
__u8 min_ttl; //
__u8 mc_ttl; // 当目的地址为 组播时,存活时间
__u8 pmtudisc; // MTU 发送路由上 网络设备的最大发送单元
__u8 recverr:1, // 是否有接收错误
is_icsk:1, // 此套接字是否为 一个连接套接字
freebind:1, // 此sk是否可以与任意的IP绑定
hdrincl:1,
mc_loop:1,
transparent:1,
mc_all:1;
int mc_index;
__be32 mc_addr;
struct ip_mc_socklist *mc_list; // 组发送网络设备的 索引号
struct
{
unsigned int flags; // 在IPV4中,此处只能设置成 IPCORK_OPT,表示opt中有选项
unsigned int fragsize; // 分片大小(包括 IP协议头、负载数据),通常与PMTU相同
struct ip_options *opt;
struct dst_entry *dst; // 发送数据包使用到的 路由表缓冲区入口
int length; // IP数据包的大小,(包括: 所有分片的总和,不包括 IP协议头)
__be32 addr; // 发送数据包的 目的IP地址
struct flowi fl; // 存放 TCP协议中 通信两端的连接信息
} cork;
};
5、sock inet_sock 结构的操作集
{
1、保存 到达目的地址使用到的 路由入口
__sk_dst_set(struct sock *sk, struct dst_entry *dst)
sk_dst_set(struct sock *sk, struct dst_entry *dst)
2、检查 到达目的地址的路由 是否有效
__sk_dst_check(struct sock *sk, u32 cookie);
sk_dst_check(struct sock *sk, u32 cookie);
3、设定 sk_buff.sk 套接字域
skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
skb_set_owner_r(struct sk_buff *skb, struct sock *sk)
4、分配 sk_buff 所需的内存空间
//--------------- 分配单个缓冲区或、一系列分片数据包的 第一个缓冲区
//
// skb中数据缓冲区长度为 0
struct sk_buff *sock_alloc_send_skb(struct sock *sk,
unsigned long size, // header_len
int noblock, //
int *errcode);
// skb中数据缓冲区长度为 size
struct sk_buff *sock_alloc_send_pskb(struct sock *sk,
unsigned long header_len,
unsigned long data_len,
int noblock,
int *errcode);
// 管理其余的子分片
struct sk_buff *sock_wmalloc(struct sock *sk,
unsigned long size,
int force,
gfp_t priority);
}
6、IP选项
包含了各种标志位数据域、偏移量数据域,用于指明 函数应在协议头的什么位置存放IP选项要求的
时间戳、IP地址等信息。
告诉我们 应在IP选项中写入什么内容。
}
}
3.3.2、协议栈初始化(inet_init)
{
int __init inet_init(void)
{
struct sk_buff *dummy_skb;
struct inet_protosw *q;
struct list_head *r;
int rc = -EINVAL;
BUILD_BUG_ON(sizeof(struct inet_skb_parm) > sizeof(dummy_skb->cb));
sysctl_local_reserved_ports = kzalloc(65536 / 8, GFP_KERNEL);
if (!sysctl_local_reserved_ports)
goto out;
##################### 注册 TCP 与套接字之间的 接口 到网络层,此链表在linux 2.6以后未用到
# 先后为tcp、udp、raw的proto接口申请空间,并挂载到全局 proto_list 上。
# 这三个变量,是联系传输层和IP层的纽带。
# 分别为协议申请其成员对应的slab内存块
# prot->slab、prot->rsk_prot->slab、prot->twsk_prot->twsk_slab
# 再将prot->node挂载到 proto_list 上。
rc = proto_register(&tcp_prot, 1);
if (rc) goto out_free_reserved_ports;
rc = proto_register(&udp_prot, 1);
if (rc) goto out_unregister_tcp_proto;
rc = proto_register(&raw_prot, 1);
if (rc) goto out_unregister_udp_proto;
##################### 注册协议族 inet_family_ops 到 net_families[inet_family_ops->family]元素处
# 用户创建 socket 时,先指定 INET 地址族,再指定套接字类型。
(void)sock_register(&inet_family_ops);
#ifdef CONFIG_SYSCTL // yes
ip_static_sysctl_init();
#endif
##################### 注册TCP 到IP 层之间的 接口 到INET层
# 分别将协议proto注册到 inet_protos[hash], hash = protocol&(MAX_INET_PROTOS-1)
# socket 层必须区分哪一个用户接收哪一个数据包。于是需要socket解复用,
# 以下即注册接收函数 icmp_rcv tcp_v4_rcv udp_rcv
inet_add_protocol(&icmp_protocol, IPPROTO_ICMP);
inet_add_protocol(&udp_protocol, IPPROTO_UDP);
inet_add_protocol(&tcp_protocol, IPPROTO_TCP);
inet_add_protocol(&igmp_protocol, IPPROTO_IGMP);
##################### 挂载 AF_INET协议族交换表数组成员 到 inetsw[pinet_protosw->type] 链表上
/* inetsw
表示某一地址族AF_FAMILY所包含的协议类型链表头数组
将来在创建 socket 的时候 会用到
inetsw_array
协议切换表,每一个被注册的协议都会存储到该表中。
成员type 表示socket系统调用的第二个参数,表示套接字类型
成员.ops 表示该协议对应的一系列套接字操作接口指针。
*/
for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
INIT_LIST_HEAD(r);
/* 将pinet_protosw成员添加到 inetsw[pinet_protosw->type]指向的链表中 */
for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
inet_register_protosw(q);
/* Set the ARP module up */
##################### 初始化 ARP 协议
arp_init();
##################### 初始化 IP路由表 rt_hash_table
ip_init();
{
//--------------- 初始化路由子系统
ip_rt_init();
//--------------- 初始化裸IP
inet_initpeers();
#if defined(CONFIG_IP_MULTICAST) && defined(CONFIG_PROC_FS) // yes
igmp_mc_proc_init();
#endif
}
tcp_v4_init();
/* Setup TCP slab cache for open requests. */
##################### 初始化 TCP 协议需要的各项 hash 表和sysctl_xxx 全局配置项
tcp_init();
/* Setup UDP memory threshold */
udp_init();
/* Add UDP-Lite (RFC 3828) */
udplite4_register();
/* Set the ICMP layer up */
if (icmp_init() < 0)
panic("Failed to create the ICMP control socket.\n");
/* Initialise the multicast router */
#if defined(CONFIG_IP_MROUTE) // NO
if (ip_mr_init())
printk(KERN_CRIT "inet_init: Cannot init ipv4 mroute\n");
#endif
/* Initialise per-cpu ipv4 mibs */
if (init_ipv4_mibs())
printk(KERN_CRIT "inet_init: Cannot init ipv4 mibs\n");
ipv4_proc_init();
##################### 初始化IP分段管理器
ipfrag_init();
{
....
}
##################### 注册IP层与链路层的数据接收接口
dev_add_pack(&ip_packet_type);
rc = 0;
out:
return rc;
out_unregister_udp_proto:
proto_unregister(&udp_prot);
out_unregister_tcp_proto:
proto_unregister(&tcp_prot);
out_free_reserved_ports:
kfree(sysctl_local_reserved_ports);
goto out;
}
}
}
}
4、网络设备在内核中的抽象 net_device
{
3.1、协议栈软件与网络设备之间的接口
1、接口应满足如下条件
抽象出网络适配的硬特性
为协议栈提供统一的调用接口
2、实现需求
两个软件: dev.c 网络设备驱动程序
一个结构 net_device
3.2、dev.c 实现对上层协议的 统一调用接口
第一类接口 根据系统中注册的设备实例,调用设备驱动程序接口,实现数据收发。
第二类接口 对于 net_device 结构的数据域进行统一的初始化,驱动程序调用这些接口来设置 其设备对应
的默认值。
3.3、设备驱动程序的第一项功能
初始化一个 net_device 结构,作为网络设备在内核中的实体,
将该结构的数据域初始化为可工作状态
将该设备实例注册到内核中,为协议栈提供服务。
3.4、net_device 结构
{
1、主要功能,
描述设备属性
提供 上层协议栈统一访问接口的指针操作集,其具体实现由设备驱动程序提供。
2、具体定义 netdevice.h
struct net_device {
char name[IFNAMSIZ]; // 网络设备名,例如 eth0 eth1
struct pm_qos_request_list *pm_qos_req;
struct hlist_node name_hlist; // 以网络设备名为key的hash表
char *ifalias; // 网络设备别名,用于SMTP访问
##################### 如下缓冲区指明 网卡上有效缓冲存储区,内核与设备之间共享
# 只在设备驱动程序中初始化和访问
unsigned long mem_end; /* shared mem end */
unsigned long mem_start; /* shared mem start */
##################### 设备的存储区映射到内存地址方位的 I/O起始地址
unsigned long base_addr; /* device I/O address */
unsigned int irq; // 设备所使用的中断号
/*
* Some hardware also needs these fields, but they are not
* part of the usual set specified in Space.c.
*/
unsigned char if_port; // 设备的接口类型。常用的是RJ45头
##################### 如果设备支持DMA传输,此域为 设备使用的DMA通道号
# 主要用于ISA设备,PCI设备不需要
unsigned char dma;
##################### 设备状态,一般由 数据包收发队列操作接口来设置 和清除
unsigned long state;
struct list_head dev_list; // 系统中网络设备实例链表,表头为 dev_base_head
struct list_head napi_list; // 支持NAPI(new access protocol interface)的设备的链表
struct list_head unreg_list;
#################### 网络设备硬件功能特性
# 由设备驱动程序设置、主要描述 设备和CPU之间的通信能力
# 设备是否可以在 高端内存中 执行DMA操作
# 设备是否可以对 所有的数据包 做检验和 和检查
# 主要有如下定义,形如 NETIF_F_XXX
unsigned long features;
// 如果设备可以 传输 被分割成几个存储段的包,则应该设置此值
#define NETIF_F_SG 1 /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM 2 /* Can checksum 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_IPV6_CSUM 16 /* Can checksum TCP/UDP over IPV6 */
#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 - deprecated. Please */
/* do not use LLTX in new drivers */
#define NETIF_F_NETNS_LOCAL 8192 /* Does not change network namespaces */
#define NETIF_F_GRO 16384 /* Generic receive offload */
#define NETIF_F_LRO 32768 /* large receive offload */
/* the GSO_MASK reserves bits 16 through 23 */
#define NETIF_F_FCOE_CRC (1 << 24) /* FCoE CRC32 */
#define NETIF_F_SCTP_CSUM (1 << 25) /* SCTP checksum offload */
#define NETIF_F_FCOE_MTU (1 << 26) /* Supports max FCoE MTU, 2158 bytes*/
#define NETIF_F_NTUPLE (1 << 27) /* N-tuple filters supported */
#define NETIF_F_RXHASH (1 << 28) /* Receive hashing offload */
#define NETIF_F_GSO_SHIFT 16 /* Segmentation offload features */
#define NETIF_F_GSO_MASK 0x00ff0000
#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)
#define NETIF_F_FSO (SKB_GSO_FCOE << 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_V4_CSUM (NETIF_F_GEN_CSUM | NETIF_F_IP_CSUM)
#define NETIF_F_V6_CSUM (NETIF_F_GEN_CSUM | NETIF_F_IPV6_CSUM)
#define NETIF_F_ALL_CSUM (NETIF_F_V4_CSUM | NETIF_F_V6_CSUM)
/*
* If one device supports one of these features, then enable them
* for all in netdev_increment_features.
*/
#define NETIF_F_ONE_FOR_ALL (NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_ROBUST | \
NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_FRAGLIST)
##################### 向内核注册网络设备时,会调用 dev_new_index 为每个设备分配 索引号
int ifindex; // 用于标识虚拟设备中实际 完成数据传送的真实设备。
int iflink;
##################### 网络设备工作的状态统计信息,具体定义在 netdevice.h中
struct net_device_stats stats;
#ifdef CONFIG_WIRELESS_EXT
// 一个函数集,用于无线网络设备 实现扩展功能处理的接口。
const struct iw_handler_def * wireless_handlers;
// 无线网络设备处理函数 会用到的数据域
struct iw_public_data * wireless_data;
#endif
##################### 网络设备驱动程序实现的 功能函数集。
const struct net_device_ops *netdev_ops;
##################### 用于 控制网络接口的工具函数的 集合
# ethtool 主要控制 速度、介质类型、双工操作、DMA设置、硬件校验、LAN唤醒
const struct ethtool_ops *ethtool_ops;
##################### 链路层协议头操作 函数集
const struct header_ops *header_ops;
#####################
unsigned int flags; // 某些位表示设备的工作模式:多播等。驱动程序初始化时设置。
// 其他位,表示接口状态的改变,入UP RUNNING IFF_OFF等,
// 由内核来动态管理
// 可以通过ifconfig来查看
unsigned short gflags; // 用于兼容,很少用到。
unsigned short priv_flags; // 对用户控件不可见的标识。主要用于WLAN和桥虚拟设备。
#####################
unsigned short padded; // alloc_netdev() 为设备分配内存空间时,应该在最后补齐多少个0
##################### 设备操作状态,反映的是接口传输数据的能力。有如下两方面
# 只能由driver设定的两个标识
# driver设定的,用于标识 驱动RFC2863兼容(operstate)的状态、策略。
# 对比:设备管理状态,反映的是 系统管理员是否将使用该设备来交换数据
unsigned char operstate; // 反应了 访问管理信息的能力
unsigned char link_mode; // 映射到 RFC2863兼容 的策略
##################### 网络接口的 最大传输单元---一次可以处理的最大字节数
unsigned int mtu;
unsigned short type; // 网络设备的硬件类型: 以太网、帧中继网等
unsigned short hard_header_len; // 网络设备可以传送的 数据帧头信息的 最大长度。
##################### 设备填写信息时,可能需要在skb中预留的 额外的空间
unsigned short needed_headroom;
unsigned short needed_tailroom;
struct net_device *master; // 某些协议允许 系列接口组成一个一个组,此域指向 主设备
##################### 设备MAC地址相关
unsigned char perm_addr[MAX_ADDR_LEN]; // MAC地址
unsigned char addr_len; // 地址长度
##################### 多个linux内核可以共享一个网卡
unsigned short dev_id; // 设备驱动程序会给每个 内核 分配一个唯一的 共享设备号、
spinlock_t addr_list_lock; // 用于实现对 主机网络地址(unicat)列表 和
// 组地址(multicat)列表的并发访问
#################### 一个设备上可以配置多个主机网络地址,此列表来管理他们
struct netdev_hw_addr_list uc; /* Unicast mac addresses */
#################### 网络设备 配置的组传送 MAC 地址列表
struct netdev_hw_addr_list mc; /* Multicast mac addresses */
#################### 混杂模式:设备允许接收 所有经过该设备的数据包,而不管目的地址是否于此相同
int uc_promisc; // promiscuity 混杂模式下 主机网络地址的个数
unsigned int promiscuity;// 表明当前网络设备是否 在混杂模式下
// 由于可能有多个进程要使用混杂模式,此处用计数器,
// 如果非0,则flags域中IFF_promisc位都应置位。
################### 对 所有组传送地址的监听
unsigned int allmulti; // 0变为非0时,dev_set_allmulti 会向设备发出命令,
// 让网卡监听所有的组传送地址
/* Protocol specific pointers */
##################### 指向 具体的网络层协议实例的 数据结构指针
# 如 ip_ptr 指向in_device结构,包含了IPV4协议使用的各种参数、为网络接口配置的IP地址
#ifdef CONFIG_NET_DSA
void *dsa_ptr; /* dsa specific data */ // DSA 规范数据。
#endif
void *atalk_ptr; /* AppleTalk link */
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 */
struct wireless_dev *ieee80211_ptr; /* IEEE 802.11 specific data, assign before registering */
/*
* Cache line 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; // 存放以太网MAC地址(eth_type_trans使用此地址)
struct netdev_hw_addr_list dev_addrs; /* list of device hw addresses */
unsigned char broadcast[MAX_ADDR_LEN]; // 加入到 广播地址中的硬件地址
#ifdef CONFIG_RPS
struct kset *queues_kset;
struct netdev_rx_queue *_rx;
/* Number of RX queues allocated at alloc_netdev_mq() time */
unsigned int num_rx_queues; // 接收队列个数
#endif
##################### 设备中的 数据包发送/接收队列
struct netdev_queue rx_queue; // 数据包发送/接收队列
struct netdev_queue *_tx ____cacheline_aligned_in_smp;
/* Number of TX queues allocated at alloc_netdev_mq() time */
unsigned int num_tx_queues; // 总的发送队列个数
unsigned int real_num_tx_queues; // 当前可用发送队列的个数
/* root qdisc from userspace point of view */
struct Qdisc *qdisc;
unsigned long tx_queue_len; // 每个发送队列中 允许存放的最大数据帧 数目,
// eth网通常为1000,其他多为 100
// 可以通过 sys/class/net/device_name 查看到
spinlock_t tx_global_lock; // 发送队列的访问控制锁
/*
* One part is mostly used on xmit path (device)
*/
/* 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; // 网络层设置的 表示传送数据包超时的时钟
// 如果超时,则调用driver的 tx_timeout 做处理
// 网络设备引用计数
atomic_t refcnt ____cacheline_aligned_in_smp;
/* delayed register/unregister */
struct list_head todo_list; // 设备注册之后,会存放到 todo链表中
struct hlist_node index_hlist; // 以设备号为key的hash表中,此设备对应的节点。
##################### 存放链路查看机制 所需的内存。此机制用于查看 传输介质的所有事件
struct list_head link_watch_list;
##################### 设备注册、注销的状态机
/* 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 */
NETREG_DUMMY, /* dummy device for NAPI poll */
} reg_state:16;
enum {
RTNL_LINK_INITIALIZED,
RTNL_LINK_INITIALIZING,
} rtnl_link_state:16;
/* Called from unregister, can be used to call free_netdev */
void (*destructor)(struct net_device *dev);
##################### netpoll 用于,在网络和I/O子系统都不可用时,是内核可以发送和接收报文
# 用于远程网络控制台 和 远程内核调试。
#ifdef CONFIG_NETPOLL
struct netpoll_info *npinfo;
#endif
##################### 该设备所属的 网络名字空间,名字空间使得 不同进程的系统视图 不同
#ifdef CONFIG_NET_NS
/* Network namespace this network device is inside */
struct net *nd_net;
#endif
void *ml_priv; /* mid-layer private */
##################### 用于在链路层实现如下功能。
struct net_bridge_port *br_port; // 用于在链路层实现 桥功能
struct macvlan_port *macvlan_port; // 用于在链路层实现 macvlan
struct garp_port *garp_port; // 用于在链路层实现 GARP
##################### sys/class/net/xxx 下次设备对应的入口
struct device dev;
/* space for optional device, statistics, and wireless sysfs groups */
const struct attribute_groupattribute_group *sysfs_groups[4];
const struct rtnl_link_ops *rtnl_link_ops; /* rtnetlink link ops */
unsigned long vlan_features; /* VLAN feature mask */
/* for setting kernel sock attribute on TCP connection setup */
#define GSO_MAX_SIZE 65536
unsigned int gso_max_size;
#ifdef CONFIG_DCB
const struct dcbnl_rtnl_ops *dcbnl_ops; /* Data Center Bridging netlink ops */
#endif
#if defined(CONFIG_FCOE) || defined(CONFIG_FCOE_MODULE)
unsigned int fcoe_ddp_xid; /* max exchange id for FCoE LRO by ddp */
#endif
/* n-tuple filter list attached to this device */
struct ethtool_rx_ntuple_list ethtool_ntuple_list;
};
}
3.5、net_device 域分类分析
{
3.5.1、设备管理域
{
1、设备名 name ifalias
2、设备标识符
ifindex 系统分配给设备的标识符
iflink 用于虚拟网络设备
dev_id 用于共享网络设备
}
3.5.2、设备配置管理域
{
1、硬件配置信息
I/O操作相关配置 :
网络上的数据缓冲区
状态/控制寄存器在 内存中的映射区 mem_start mem_end
设备按I/O端口操作时 I/O端口的基地址 base_addr
irq、dma等
硬件连接接口信息 if_port
硬件功能特性 features
硬件是否可以对数据包做校验和
硬件是否可以完成数据包的分割与重组
2、设备物理特性 type、 hard_head_len tx_queue_len
3、链路层配置
协议头长度 needed_headroom needed_tailroom
硬件地址 perm_addr addr_len *uc_list uc_count add_list_lock
组传送地址 *mc_list mc_count
混杂模式地址数 uc_promisc
4、网络层配置
*dsa_ptr *atalk_ptr *ip_ptr *ip6_ptr *ec_ptr *ax25_ptr *ieee80211_ptr
}
3.5.3、设备状态
{
设备收发数据包的时间信息 trans_start last_tx
设备物理工作状态 state
网卡是否连接
设备传送队列是否可以开始传送新的数据
队列是否已满
传输介质是否就绪
设备通信模式状态 flags gflags priv_flags
设备注册状态 reg_state
设备分组状态 *master
}
3.5.4、设备相关链表
3.5.5、链路层组传送
{
1、组传送、可以将数据包传送给多个接收主机的机制
2、相关域
struct dev_addr_list *mc_list 组传送地址列表
mac_count 设备监听的组传送地址的个数
allmulti 需要监听组传送的 应用计数
}
3.5.6、流量管理
{
1、新的队列组织机制
早期的内核中只有一个发送队列
对于无线多媒体扩展网络设备中,存在四种不同的服务
1、图像、声音
这些数据包,要优先发送,他们的传送会占用更长时间的无线通道
传送队列较短,处理方式:如果不能快速传送数据包,则接收方就不再接受它们。即如果延时过长,则扔掉数据包
2、最好效果、后台服务
优先级低,比如:bt下载、电子邮件等
后台处理队列较长
2、每种服务有独立的发送队列
有多个发送对垒的设备,应该建议一个 struct netdev_queue 的数组
数组起始地址存放到 net_device.tx 中
struct netdev_queue {
/*
* read mostly part
*/
struct net_device *dev; // 队列所属的网络设备
struct Qdisc *qdisc; // 队列操作函数与 skb 链表
unsigned long state; // 队列状态
struct Qdisc *qdisc_sleeping; // 休眠队列
// 队列并发访问锁
spinlock_t _xmit_lock ____cacheline_aligned_in_smp;
int xmit_lock_owner;
/*
* please use this field instead of dev->trans_start
*/
unsigned long trans_start;
unsigned long tx_bytes;
unsigned long tx_packets;
unsigned long tx_dropped;
} ____cacheline_aligned_in_smp;
3、多队列网络设备驱动程序,须调用如下接口来 初始化设备的内存分配
struct net_device *alloc_netdev_mq( int sizeof_priv,
const char *name,
void (*setup)(struct net_device *),
// 最大发送队列数目 ,该数值存放于 net_device.real_num_tx_queue中。
unsigned int queue_count);
4、发送队列的管理
// 从任意指定队列中 得到发送的数据包
netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev);
// 获取当前发送队列的索引
u16 skb_get_queue_mapping(const struct sk_buff *skb)
return skb->queue_mapping;
// 选择某个发送队列
select_queue
4、通知内核 某个发送队列的状态
// 获取当前发送队列
netdev_get_tx_queue
{
// (const struct net_device *dev,unsigned int index)
return &dev->_tx[index];
}
// 启动发送队列
netif_tx_start_queue
{
// (struct netdev_queue *dev_queue)
clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
// 停止发送队列
void netif_tx_stop_queue
{
// (struct netdev_queue *dev_queue)
set_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
// 唤醒发送队列
netif_wake_queue
{
// (struct net_device *dev)
netif_tx_wake_queue(netdev_get_tx_queue(dev, 0));
{
if (test_and_clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state))
__netif_schedule(dev_queue->qdisc);
}
}
5、操作所有发送队列
netif_tx_start_all_queues
{
// (struct net_device *dev)
unsigned int i;
for (i = 0; i < dev->num_tx_queues; i++) {
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
netif_tx_start_queue(txq);
}
}
netif_tx_wake_all_queues
{
// (struct net_device *dev)
unsigned int i;
for (i = 0; i < dev->num_tx_queues; i++) {
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
netif_tx_wake_queue(txq);
}
}
netif_tx_stop_all_queues
{
// (struct net_device *dev)
unsigned int i;
for (i = 0; i < dev->num_tx_queues; i++) {
struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
netif_tx_stop_queue(txq);
}
}
}
3.5.7、操作接口集
{
net_dev_ops 网络设备驱动程序需要实现的 函数集合
ethtool_ops driver实现,用于支持 修改和报告设备设置的 功能
header_ops 操作链路层协议头
}
}
3.6、driver 要实现的方法 net_dev_ops
{
struct net_device_ops {
##################### 设备初始化相关
//--------- 设备初始化
# 创建 net_device
# 初始化 net_device 的 name irq base_addr等成员
# 向内核注册 net_device
int (*ndo_init)(struct net_device *dev);
void (*ndo_uninit)(struct net_device *dev);
//--------- 打开设备
# 注册设备需要的所有资源: I/O 端口、中断号 DMA通道
int (*ndo_open)(struct net_device *dev);
//--------- 停止设备
int (*ndo_stop)(struct net_device *dev);
##################### 数据包传输
//--------- 初始化 数据包发送过程
# 若执行成功,则skb已存放到了 dev的发送队列中
# 此数据包含有: 协议头、负载数据
netdev_tx_t (*ndo_start_xmit) ( struct sk_buff *skb,
struct net_device *dev);
//--------- 选择发送队列
# 并将skb存放到选中的某个队列中
u16 (*ndo_select_queue)(struct net_device *dev, struct sk_buff *skb);
//--------- 设定 接收模式 对应的标识
# 调用该函数,可以使drver立即同步由 ndo_set_multicast_list ndo_set_rx_mode
#设置的地址列表。
void (*ndo_change_rx_flags)(struct net_device *dev,int flags);
//--------- 设置接收模式
# 根据 net_device.flags域 IFF_PROMISC IFF_MULTICAST IFF_ALLMULTI
#标志位来设定。
void (*ndo_set_rx_mode)(struct net_device *dev);
//--------- 存放网络设备地址到 组传送地址列表中
void (*ndo_set_multicast_list)(struct net_device *dev);
##################### 配置修改
//--------- 配置设备参数: irq、base_addr
# 帮组 administrator 在没探测到网络接口时,手动设置。
int (*ndo_set_config)(struct net_device *dev, struct ifmap *map);
//--------- 修改MAC地址
# 如果使用 ech_mac_addr 修改新地址到 dev->dev_addr 中,则在执行ndo_open时,应该用
#此域来初始化网络设备的mac
int (*ndo_set_mac_address)(struct net_device *dev, void *addr);
int (*ndo_change_mtu)(struct net_device *dev, int new_mtu);
//--------- 检查 dev中的MAC地址 是否与 dev_addr 中的值相同
int (*ndo_validate_addr)(struct net_device *dev);
//--------- 建立于响铃协议(ARP)的连接
int (*ndo_neigh_setup)(struct net_device *dev, struct neigh_parms *);
int (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
void (*ndo_tx_timeout) (struct net_device *dev);
//--------- 获取网络设备的统计信息
# netstat -i 就调用此接口来实现
struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);
void (*ndo_vlan_rx_register)(struct net_device *dev, struct vlan_group *grp);
void (*ndo_vlan_rx_add_vid)(struct net_device *dev, unsigned short vid);
void (*ndo_vlan_rx_kill_vid)(struct net_device *dev, unsigned short vid);
#ifdef CONFIG_NET_POLL_CONTROLLER
//--------- 在禁用中断情况下,用来查看网络接口上发生的事件
# 主要用于 远程控制中断、网络跟踪调试内核等
void (*ndo_poll_controller)(struct net_device *dev);// 在禁用中断时,轮询读取数据包
void (*ndo_netpoll_cleanup)(struct net_device *dev);
#endif
int (*ndo_set_vf_mac)(struct net_device *dev,int queue, u8 *mac);
int (*ndo_set_vf_vlan)(struct net_device *dev,int queue, u16 vlan, u8 qos);
int (*ndo_set_vf_tx_rate)(struct net_device *dev,int vf, int rate);
int (*ndo_get_vf_config)(struct net_device *dev,int vf,struct ifla_vf_info *ivf);
int (*ndo_set_vf_port)(struct net_device *dev,int vf,struct nlattr *port[]);
int (*ndo_get_vf_port)(struct net_device *dev,int vf, struct sk_buff *skb);
#if defined(CONFIG_FCOE) || defined(CONFIG_FCOE_MODULE)
int (*ndo_fcoe_enable)(struct net_device *dev);
int (*ndo_fcoe_disable)(struct net_device *dev);
int (*ndo_fcoe_ddp_setup)(struct net_device *dev,u16 xid,struct scatterlist *sgl,unsigned int sgc);
int (*ndo_fcoe_ddp_done)(struct net_device *dev,u16 xid);
#define NETDEV_FCOE_WWNN 0
#define NETDEV_FCOE_WWPN 1
int (*ndo_fcoe_get_wwn)(struct net_device *dev,u64 *wwn, int type);
#endif
};
}
3.7、协议头操作集 header_ops
{
struct header_ops {
//------------- 创建链路层 协议头数据
# 在 hard_start_xmit 之前被调用
int (*create) ( struct sk_buff *skb,
struct net_device *dev,
unsigned short type,
const void *daddr, // 目标MAC地址
const void *saddr, // 源MAC地址
unsigned len);
//------------- 解析skb中网络数据包的 源地址,
# 存放到 haddr 中
int (*parse) ( const struct sk_buff *skb,
unsigned char *haddr);
int (*rebuild)( struct sk_buff *skb);
#define HAVE_HEADER_CACHE
//------------- 填充hh_cache结构,用于存放 由ARP子系统转换好的 MAC地址、L2层协议头
int (*cache)( const struct neighbour *neigh,
struct hh_cache *hh);
//------------- 当数据包的目的地址发生变化,用此接口来更新 hh_cache 结构。
void(*cache_update)(struct hh_cache *hh,
const struct net_device *dev,
const unsigned char *haddr);
};
}
}
5、网络设备在linux内核中识别
{
5.1、内核初始化、内核命令行参数的使用
{
//-----------------------------------------------------------------------------------
//----------------------- 内核初始化的特点
//
{
1、内核子系统初始化,要在系统启动时执行,执行完毕后可以不再使用。可以释放其对应代码级用到的数据结构
占用的内存
2、内核初始化程序的组成
必需的子系统初始化,须按照一定的顺序完成
内核其他组件初始化,无特定顺序
主要任务:为内核的各个子系统简历运行所需的共享资源:内存管理、中断、时钟子系统。
3、允许用户通过boot给内核提供各种配置参数
}
//-----------------------------------------------------------------------------------
//----------------------- 内核第二部分初始化(其他子系统初始化) 由 do_initcalls 完成
//
{
1、[__initcall_start,initcall_end]段
子系统按照优先级的不同,通过宏 xxx_initcall 将其初始化函数注册到 .initcallN.init段中。
这些段都位于此空间内。
// vmlinux.lds.h (include\asm-generic)
#define INITCALLS \
*(.initcallearly.init) \
VMLINUX_SYMBOL(__early_initcall_end) =.;\
*(.initcall0.init) \ // pure_initcall
*(.initcall0s.init) \ //
*(.initcall1.init) \ // core_initcall
*(.initcall1s.init) \ //
*(.initcall2.init) \ // postcore_initcall
*(.initcall2s.init) \
*(.initcall3.init) \ // arch_initcall
*(.initcall3s.init) \
*(.initcall4.init) \ // subsys_initcall
*(.initcall4s.init) \
*(.initcall5.init) \ // fs_initcall
*(.initcall5s.init) \
*(.initcallrootfs.init) \ // rootfs_initcall
*(.initcall6.init) \ // device_initcall __initcall module_init
*(.initcall6s.init) \ //
*(.initcall7.init) \ // late_initcall
*(.initcall7s.init)
#define INIT_CALLS \
VMLINUX_SYMBOL(__initcall_start) =.;\
INITCALLS \
VMLINUX_SYMBOL(__initcall_end) = .;
2、do_initcalls 依次执行[__early_initcall_end,__initcall_end]之间的子系统初始化函数
static void __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
3、各子系统宏
// init.h
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
#define __initcall(fn) device_initcall(fn)
#define module_init(x) __initcall(x);
}
//-----------------------------------------------------------------------------------
//----------------------- 内核命令行参数
//
{
1、定义格式:
// boot
netdev= xxx
// kernel
static int __init function(char * str)
{
....
}
__setup("netdev=",function)
2、参数的存放和管理
1、命令行参数及处理函数,均保存在 obs_kernel_param 中,
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
2、__setup 将 obs_kernel_param 实例存放在内存的 .init.setup 标识的内存段中,
__setup 及 early_param 注册的所有命令参数对象,都存放于 .init.setup的[__setup_start,___setup_end]区域内
这部分区域在内核初始化结束后释放。
3、参数解析,两次执行
start_kernel
// 第一次解析,解析优先级更高的选项(early_param宏定义),这部分区域在内核初始化结束后释放。
parse_early_param
parse_args
// 第二次解析,解析默认选项(___setup定义),此次,会将 obs_kernel_param 复制到 [___start_param,__stop_param]中
// 该区域在内核初始化结束后,不会被释放
parse_args
2、parse_args 解析具体过程
以命令行参数名(netdev)为关键字,在上述内存段中,查找该命令行对应的处理函数。
xxx,作为 处理函数的参数,传递进去。
}
//-----------------------------------------------------------------------------------
//----------------------- 网络子系统命令行参数
//
{
1、格式
netdev=
设备端口地址:port adderss
中断号 irq
网络设备名 ethx
设备缓冲区起始、结束地址 mem_start mem_end
DMA通道号
网络设备连接端口类型
// 关键字在命令行中可以多次出现
// 实例
netdev=5,0x260,eth0 netdev=15,0x300,eth1
2、解析函数
// net/core/dev.c
int __init netdev_boot_setup(char *str)
{
int ints[5];
struct ifmap map;
str = get_options(str, ARRAY_SIZE(ints), ints);
if (!str || !*str)
return 0;
/* Save settings */
memset(&map, 0, sizeof(map));
if (ints[0] > 0)
map.irq = ints[1];
if (ints[0] > 1)
map.base_addr = ints[2];
if (ints[0] > 2)
map.mem_start = ints[3];
if (ints[0] > 3)
map.mem_end = ints[4];
/* Add new entry to the list */
return netdev_boot_setup_add(str, &map);
{
// (char *name, struct ifmap *map)
struct netdev_boot_setup *s;
int i;
s = dev_boot_setup;
for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) {
if (s[i].name[0] == '\0' || s[i].name[0] == ' ') {
memset(s[i].name, 0, sizeof(s[i].name));
strlcpy(s[i].name, name, IFNAMSIZ);
memcpy(&s[i].map, map, sizeof(s[i].map));
break;
}
}
return i >= NETDEV_BOOT_SETUP_MAX ? 0 : 1;
}
}
__setup("netdev=", netdev_boot_setup);
3、解析完毕的参数对象,存储在 dev_boot_setup[] 中
struct netdev_boot_setup {
char name[IFNAMSIZ];
struct ifmap map;
{
unsigned long mem_start;
unsigned long mem_end;
unsigned short base_addr;
unsigned char irq;
unsigned char dma;
unsigned char port;
/* 3 bytes spare */
}
};
4、命令行参数的作用
内核子系统后期,调用 netdev_boot_setup_check 查看 dev_boot_setup[] 是否非空,并将 dev_boot_setup[]
的各项参数对象,保存到 多个 net_device 对象实例中。
int netdev_boot_setup_check(struct net_device *dev)
{
struct netdev_boot_setup *s = dev_boot_setup;
int i;
for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) {
if (s[i].name[0] != '\0' && s[i].name[0] != ' ' && !strcmp(dev->name, s[i].name)) {
dev->irq = s[i].map.irq;
dev->base_addr = s[i].map.base_addr;
dev->mem_start = s[i].map.mem_start;
dev->mem_end = s[i].map.mem_end;
return 1;
}
}
return 0;
}
}
}
5.3、网络设备的注册 net_device 结构的实例化及挂载
{
4.3.1、网络设备实例的声明、注册、初始化、注销
{
// 网络设备驱动程序初始化函数主要完成如下任务
创建 net_device 网络设备实例
初始化对网络设备实例的域(一部分由内核赋值,一部分由driver赋值)
注册网络设备实例到内核
// 网络设备什么时候注册到内核中
在 执行网络设备驱动程序提供的 模块初始化函数(宏 module_init 指定的函数或 init_module 函数本身)
的过程中。即在内核启动过程中 或 系统运行时手动装在模块时
4.6.3.1、 alloc_netdev_mq 创建 net_device ,由 module_init(xxx_init) 调用
{
struct net_device *alloc_netdev_mq( int sizeof_priv, // deriver通过此私有数据空间来 扩展 net_device
const char *name, // 设备名
// driver 初始化例程,用于初始化 net_device的部分域
void (*setup)(struct net_device *),
unsigned int queue_count // 发送队列的个数
)
{
struct netdev_queue *tx;
struct net_device *dev;
size_t alloc_size;
struct net_device *p;
BUG_ON(strlen(name) >= sizeof(dev->name));
##################### 分配网络设备实例所需的内存空间
# net_device结构本身所需空间 sizeof(struct net_device); // dev
# 私有数据空间 sizeof_priv // dev+ sizeof(dev)
alloc_size = sizeof(struct net_device);
if (sizeof_priv) {
/* ensure 32-byte alignment of private area */
alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
alloc_size += sizeof_priv;
}
/* ensure 32-byte alignment of whole construct */
alloc_size += NETDEV_ALIGN - 1;
p = kzalloc(alloc_size, GFP_KERNEL);
##################### 分配设备发送队列所需空间 // dev->_tx
tx = kcalloc(queue_count, sizeof(struct netdev_queue), GFP_KERNEL);
dev = PTR_ALIGN(p, NETDEV_ALIGN);
dev->padded = (char *)dev - (char *)p;
if (dev_addr_init(dev))
goto free_tx;
dev_mc_init(dev);
dev_uc_init(dev);
dev_net_set(dev, &init_net);
##################### 安装发送队列
dev->_tx = tx;
dev->num_tx_queues = queue_count;
dev->real_num_tx_queues = queue_count;
dev->gso_max_size = GSO_MAX_SIZE;
##################### 关联net_device 到 发送/接收队列的相关域
netdev_init_queues(dev);
{
netdev_init_one_queue(dev, &dev->rx_queue, NULL);
dev->rx_queue.dev = dev;
netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL);
dev->_tx[i]->dev = dev;
spin_lock_init(&dev->tx_global_lock);
}
INIT_LIST_HEAD(&dev->ethtool_ntuple_list.list);
dev->ethtool_ntuple_list.count = 0;
INIT_LIST_HEAD(&dev->napi_list);
INIT_LIST_HEAD(&dev->unreg_list);
INIT_LIST_HEAD(&dev->link_watch_list);
dev->priv_flags = IFF_XMIT_DST_RELEASE;
##################### 调用设备初始化例程
setup(dev);
{
// 对于以太网,调用 ether_setup(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;
memset(dev->broadcast, 0xFF, ETH_ALEN);
}
strcpy(dev->name, name);
return dev;
free_tx:
kfree(tx);
free_p:
kfree(p);
return NULL;
}
}
4.6.3.2、网络设备初始化例程1 xxx_setup 由 alloc_netdev_mq 调用
初始化 同一类网络设备之 net_device 中通用的数据域
{
1、xxx_setup 例程的安装
以太网 alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count);
alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);
光纤分布式网络FDDI alloc_fddidev(int sizeof_priv)
alloc_netdev(sizeof_priv, "fddi%d", fddi_setup);
alloc_netdev_mq(sizeof_priv, "fddi%d", fddi_setup, 1)
高性能并行接口 HIPPI alloc_hippi_dev(int sizeof_priv)
alloc_netdev(sizeof_priv, "hip%d", hippi_setup);
令牌环TokenRing alloc_trdev(int sizeof_priv)
alloc_netdev(sizeof_priv, "tr%d", tr_setup);
光纤通道 alloc_fcdev(int sizeof_priv)
alloc_netdev(sizeof_priv, "fc%d", fc_setup);
IrDa alloc_irdadev(int sizeof_priv)
alloc_netdev(sizeof_priv, "irda%d", irda_device_setup);
2、主要初始化如下域及指针
type
hard_header_len
mtu
addr_len
tx_queue_len
flags
broadcast
// 如下指针
header_opschange_mtu
set_mac_adders
validate_addr
}
4.6.3.3、网络设备初始化例程2 xxx_probe , 由 module_init(xxx_init) 调用
初始化 net_device 中 因网络设备而异的 数据域
{
// 特定域
base_addr
irq
if_port
features
priv
// 函数指针复制
open
stop
hard_start_xmit
tx_timeout
watchdog_timeo
get_stats
get_wireless_stats
set_multicast_list
do_ioctl
init
uninit
poll
ethtool_ops
}
4.6.3.4、网络设备实例的注册 register_netdev register_netdevice
{
1、静态区
{
// sch_generic.c
struct Qdisc noop_qdisc = {
.enqueue = noop_enqueue,
.dequeue = noop_dequeue,
.flags = TCQ_F_BUILTIN,
.ops = &noop_qdisc_ops,
.list = LIST_HEAD_INIT(noop_qdisc.list),
.q.lock = __SPIN_LOCK_UNLOCKED(noop_qdisc.q.lock),
.dev_queue = &noop_netdev_queue,
};
}
2、实际注册接口 register_netdevice
int register_netdev(struct net_device *dev)
{
int err;
##################### 获取 routing netlink 锁
rtnl_lock();
if (strchr(dev->name, '%'))
err = dev_alloc_name(dev, dev->name);
##################### 将 net_device 放到全局链表 dev_base_head 和 两个 hash 表中
err = register_netdevice(dev);
{
// sch_generic.c
int ret;
struct net *net = dev_net(dev);
BUG_ON(dev_boot_phase);
ASSERT_RTNL();
might_sleep();
/* When net_device's are persistent, this will be fatal. */
BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
BUG_ON(!net);
spin_lock_init(&dev->addr_list_lock);
netdev_set_addr_lockdep_class(dev);
netdev_init_queue_locks(dev);
dev->iflink = -1;
#ifdef CONFIG_RPS // no
if (!dev->num_rx_queues) {
/*
* Allocate a single RX queue if driver never called
* alloc_netdev_mq
*/
dev->_rx = kzalloc(sizeof(struct netdev_rx_queue), GFP_KERNEL);
if (!dev->_rx) {
ret = -ENOMEM;
goto out;
}
dev->_rx->first = dev->_rx;
atomic_set(&dev->_rx->count, 1);
dev->num_rx_queues = 1;
}
#endif
################# 如果设备驱动程序提供了 init 接口,则调用它来初始化 私有数据
/* Init, if this function is available */
if (dev->netdev_ops->ndo_init) {
ret = dev->netdev_ops->ndo_init(dev);
if (ret) {
if (ret > 0)
ret = -EIO;
goto out;
}
}
ret = dev_get_valid_name(dev, dev->name, 0);
// 为设备分配唯一的标识符
dev->ifindex = dev_new_index(net);
if (dev->iflink == -1)
dev->iflink = dev->ifindex;
################# 查看 dev->features 是否为无效组合
/* Fix illegal checksum combinations */
if ((dev->features & NETIF_F_HW_CSUM) &&
(dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
printk(KERN_NOTICE "%s: mixed HW and IP checksum settings.\n",dev->name);
dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM);
}
if ((dev->features & NETIF_F_NO_CSUM) &&
(dev->features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
printk(KERN_NOTICE "%s: mixed no checksumming and other settings.\n",dev->name);
dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM);
}
dev->features = netdev_fix_features(dev->features, dev->name);
/* Enable software GSO if SG is supported. */
if (dev->features & NETIF_F_SG)
dev->features |= NETIF_F_GSO;
ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev);
ret = notifier_to_errno(ret);
if (ret)
goto err_uninit;
ret = netdev_register_kobject(dev);
if (ret)
goto err_uninit;
dev->reg_state = NETREG_REGISTERED;
/*
* Default initial state at registry is that the
* device is present.
*/
################# 设置dev->state的 __LINK_STATE_PRESENT 位,表明设备有效
# 当从系统拔掉一个设备/或者系统PMU支持suspend,且设备suspend了,则该标志位会被清除
set_bit(__LINK_STATE_PRESENT, &dev->state);
################# 初始化设备的 队列策略(策略定义了:数据包如何从队列中 取出及放入、多少数据包可放入)
# 发送/接收队列的 如下域
# qdisc 队列操作函数与 skb 链表
# qdisc_sleeping 休眠队列
#
dev_init_scheduler(dev);
{
dev->qdisc = &noop_qdisc;
// 设置每一个 发送队列 tx->qdisc tx->qdisc_sleeping
netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);
{
// ....
// dev_init_scheduler_queue(struct net_device *dev,struct netdev_queue *dev_queue, void *_qdisc)
(&dev->_tx[x])->qdisc = _qdisc; // noop_qdisc
(&dev->_tx[x])->qdisc_sleeping = _qdisc; // noop_qdisc
}
dev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
dev->rx_queue.qdisc = noop_qdisc;
dev->rx_queue.qdisc_sleeping = noop_qdisc;
setup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);
}
dev_hold(dev);
################# 将 net_device 放到全局链表 dev_base_head 和 两个 hash 表中
list_netdevice(dev);
{
struct net *net = dev_net(dev); // return &init_net;
ASSERT_RTNL();
write_lock_bh(&dev_base_lock);
############# 将 此 net_device 挂载到如下链表上
# net->dev_base_head 链表
# net->dev_name_head[name_hash_index] 链表
# net->dev_name_head[index_hash_index] 链表
list_add_tail_rcu(&dev->dev_list, &net->dev_base_head);
hlist_add_head_rcu(&dev->name_hlist, dev_name_hash(net, dev->name));
hlist_add_head_rcu(&dev->index_hlist,dev_index_hash(net, dev->ifindex));
write_unlock_bh(&dev_base_lock);
return 0;
}
################# 将 NETDEV_REGISTER 事件通知给 所有需要获知网络设备已经注册消息的 子系统
ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
ret = notifier_to_errno(ret);
if (ret) {
rollback_registered(dev);
dev->reg_state = NETREG_UNREGISTERED;
}
/*
* Prevent userspace races by waiting until the network
* device is fully setup before sending notifications.
*/
if (!dev->rtnl_link_ops ||
dev->rtnl_link_state == RTNL_LINK_INITIALIZED)
rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U);
out:
return ret;
err_uninit:
if (dev->netdev_ops->ndo_uninit)
dev->netdev_ops->ndo_uninit(dev);
goto out;
}
##################### 释放 routing netlink 锁
# 通过 netdev_run_todo 遍历 net_todo_list 列表,完成所有网络设备实例的注册(或注销)
# 任何时候,只有一个CPU可以运行 netdev_run_todo
rtnl_unlock();
{
netdev_run_todo
{
struct list_head list;
/* Snapshot list, allow later requests */
list_replace_init(&net_todo_list, &list);
__rtnl_unlock();
while (!list_empty(&list))
{
################### 对于注册,此时需要将 节点从 net_todo_list 链表上删除
struct net_device *dev= list_first_entry(&list, struct net_device, todo_list);
list_del(&dev->todo_list);
if (unlikely(dev->reg_state != NETREG_UNREGISTERING)) {
printk(KERN_ERR "network todo '%s' but state %d\n", dev->name, dev->reg_state);
dump_stack();
continue;
}
################### 如果某个 net_device 处于 NETREG_UNREGISTERING 状态
# 则 修改其状态为 NETREG_UNREGISTERED
dev->reg_state = NETREG_UNREGISTERED;
on_each_cpu(flush_backlog, dev, 1);
netdev_wait_allrefs(dev);
################### 等到 net_device.refcnt == 0 后,调用其 destructor 接口
# 来注销网络设备
/* paranoia */
BUG_ON(atomic_read(&dev->refcnt));
WARN_ON(dev->ip_ptr);
WARN_ON(dev->ip6_ptr);
WARN_ON(dev->dn_ptr);
if (dev->destructor)
dev->destructor(dev);
/* Free network device */
kobject_put(&dev->dev.kobj);
}
}
}
return err;
}
}
4.6.3.5、网络设备实例的注销 register_netdev unregister_netdevice
{
1、unregister_netdevice 接口
unregister_netdevice(struct net_device *dev)
{
unregister_netdevice_queue(dev, NULL);
{
// (struct net_device *dev, struct list_head *head)
if (head)
{
list_move_tail(&dev->unreg_list, head);
}
else
{
############# 回滚所有的注册过程。
rollback_registered(dev);
{
LIST_HEAD(single);
list_add(&dev->unreg_list, &single);
rollback_registered_many(&single);
{
// (struct list_head *head)
struct net_device *dev, *tmp;
BUG_ON(dev_boot_phase);
ASSERT_RTNL();
############### 以属主struct为游标, 从 single 表头开始,双指针遍历
# 从 single 表中删除未注册的 net_device 节点
# 对于 已经UP的网络设备,则关闭网络设备、关闭其发送/接收队列的 策略
# 将 此 net_device 从全局链表上 卸载掉
# 调用 ndo_stop 来停止网络设备传送队列、释放硬件资源,停止driver使用到的所有计时器
# 更新设备注册状态 dev->reg_state = NETREG_UNREGISTERING;
list_for_each_entry_safe(dev, tmp, head, unreg_list)
{
########### 从 single 表中删除未注册的 net_device 节点
if (dev->reg_state == NETREG_UNINITIALIZED) {
pr_debug("unregister_netdevice: device %s/%p never ""was registered\n", dev->name, dev);
WARN_ON(1);
list_del(&dev->unreg_list);
continue;
}
########### 对于 已经UP的网络设备,则关闭网络设备
dev_close(dev);
{
if (!(dev->flags & IFF_UP)) return 0;
__dev_close(dev);
{
const struct net_device_ops *ops = dev->netdev_ops;
ASSERT_RTNL();
might_sleep();
############# 通知那些 与此设备相关的子系统: 本设备将要关闭
call_netdevice_notifiers(NETDEV_GOING_DOWN, dev);
clear_bit(__LINK_STATE_START, &dev->state);
############# 清除 dev->state的__LINK_STATE_START位后,应该做 调度轮询的同步
# 由于poll列表可能由多个cpu共享,此时仅仅是提交 netif_running 的清除
# dev->stop() 会对 该设备上的所有的 napi_struct 实例调用 napi_disable
smp_mb__after_clear_bit(); /* Commit netif_running(). */
############# 设置 dev->_tx[x]->qdisc = noop_qdisc(默认的),并重启
dev_deactivate(dev);
{
// sch_generic.c
######### 对 dev->_tx[] ,每个发送队列,调用 dev_deactivate_queue
# 设置 dev->_tx[x]->qdisc = noop_qdisc(默认的),并重启
# 对于接收队列,同样如此。
netdev_for_each_tx_queue(dev, dev_deactivate_queue, &noop_qdisc);
{
/*
(struct net_device *dev,
void (*f)(struct net_device *,struct netdev_queue *,void *),
void *arg)
*/
unsigned int i;
for (i = 0; i < dev->num_tx_queues; i++)
f(dev, &dev->_tx[i], arg);
{
// dev_deactivate_queue (struct net_device *dev,struct netdev_queue *dev_queue,void *_qdisc_default)
struct Qdisc *qdisc_default = _qdisc_default;
struct Qdisc *qdisc;
qdisc = dev_queue->qdisc;
if (qdisc)
{
spin_lock_bh(qdisc_lock(qdisc));
if (!(qdisc->flags & TCQ_F_BUILTIN))
set_bit(__QDISC_STATE_DEACTIVATED, &qdisc->state);
rcu_assign_pointer(dev_queue->qdisc, qdisc_default);
qdisc_reset(qdisc);
spin_unlock_bh(qdisc_lock(qdisc));
}
}
}
dev_deactivate_queue(dev, &dev->rx_queue, &noop_qdisc);
dev_watchdog_down(dev);
/* Wait for outstanding qdisc-less dev_queue_xmit calls. */
synchronize_rcu();
/* Wait for outstanding qdisc_run calls. */
while (some_qdisc_is_busy(dev)) yield();
}
############# 调用设备提供的网卡关闭函数,主要功能
# 停止网络设备传送队列
# 释放硬件资源,停止driver使用到的所有计时器
# 此处使得 DETACH 热插拔事件发生后,此接口得以调用
if (ops->ndo_stop)
ops->ndo_stop(dev);
############# 修改设备 标识 为 DOWN,表示网卡已经关闭
dev->flags &= ~IFF_UP;
############# 关闭网卡所使用的DMA资源
net_dmaengine_put();
return 0;
}
rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP|IFF_RUNNING);
call_netdevice_notifiers(NETDEV_DOWN, dev);
return 0;
}
/* And unlink it from device chain. */
############# 将 此 net_device 如下链表上 卸载掉
# net->dev_base_head 链表
# net->dev_name_head[name_hash_index] 链表
# net->dev_name_head[index_hash_index] 链表
unlist_netdevice(dev);
{
ASSERT_RTNL();
/* Unlink dev from the device chain */
write_lock_bh(&dev_base_lock);
list_del_rcu(&dev->dev_list);
hlist_del_rcu(&dev->name_hlist);
hlist_del_rcu(&dev->index_hlist);
write_unlock_bh(&dev_base_lock);
}
dev->reg_state = NETREG_UNREGISTERING;
}
synchronize_net();
###############
# 取消与此设备相关的 队列策略实例
# 通知内核中其他子系统,网络设备已经注销
# 释放此 net_device 中关联的 组播地址、多播地址列表
# 清除掉/proc sys/ 中与此网络设备对应的所有文件。
#
list_for_each_entry(dev, head, unreg_list)
{
# 取消与此设备相关的 队列策略实例
dev_shutdown(dev);
{
netdev_for_each_tx_queue(dev, shutdown_scheduler_queue, &noop_qdisc);
shutdown_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
{
struct Qdisc *qdisc = dev_queue->qdisc_sleeping;
struct Qdisc *qdisc_default = _qdisc_default;
if (qdisc) {
rcu_assign_pointer(dev_queue->qdisc, qdisc_default);
dev_queue->qdisc_sleeping = qdisc_default;
qdisc_destroy(qdisc);
}
}
qdisc_destroy(dev->qdisc);
dev->qdisc = &noop_qdisc;
WARN_ON(timer_pending(&dev->watchdog_timer));
}
# 通知内核中其他子系统,网络设备已经注销
call_netdevice_notifiers(NETDEV_UNREGISTER, dev);
if (!dev->rtnl_link_ops || dev->rtnl_link_state == RTNL_LINK_INITIALIZED)
rtmsg_ifinfo(RTM_DELLINK, dev, ~0U);
# 释放此 net_device 中关联的 组播地址、多播地址列表
dev_uc_flush(dev);
dev_mc_flush(dev);
# 递减对网络设备实例的引用计数,一般用于虚拟设备,由本地提供
if (dev->netdev_ops->ndo_uninit)
dev->netdev_ops->ndo_uninit(dev);
/* Notifier chain MUST detach us from master device. */
WARN_ON(dev->master);
########### 清除掉/proc sys/ 中与此网络设备对应的所有文件。
netdev_unregister_kobject(dev);
}
/* Process any work delayed until the end of the batch */
dev = list_first_entry(head, struct net_device, unreg_list);
call_netdevice_notifiers(NETDEV_UNREGISTER_BATCH, dev);
synchronize_net();
############### 递减系统中对此设备的引用计数
list_for_each_entry(dev, head, unreg_list)
dev_put(dev);
}
}
############# 添加 net_device 到 net_todo_list 链表上
net_set_todo(dev);
{
list_add_tail(&dev->todo_list, &net_todo_list);
}
}
}
}
2、在driver中通常使用的包装接口 unregister_netdev
void unregister_netdev(struct net_device *dev)
{
rtnl_lock();
unregister_netdevice(dev);
rtnl_unlock();
{
netdev_run_todo
{
struct list_head list;
/* Snapshot list, allow later requests */
list_replace_init(&net_todo_list, &list);
__rtnl_unlock();
while (!list_empty(&list))
{
################### 对于注册,此时需要将 节点从 net_todo_list 链表上删除
struct net_device *dev= list_first_entry(&list, struct net_device, todo_list);
list_del(&dev->todo_list);
if (unlikely(dev->reg_state != NETREG_UNREGISTERING)) {
printk(KERN_ERR "network todo '%s' but state %d\n", dev->name, dev->reg_state);
dump_stack();
continue;
}
################### 如果某个 net_device 处于 NETREG_UNREGISTERING 状态
# 则 修改其状态为 NETREG_UNREGISTERED
dev->reg_state = NETREG_UNREGISTERED;
on_each_cpu(flush_backlog, dev, 1);
netdev_wait_allrefs(dev);
{
unsigned long rebroadcast_time, warning_time;
linkwatch_forget_dev(dev);
rebroadcast_time = warning_time = jiffies;
############### 只有当 net_device 引用计数 ==0 时,才推出循环。
while (atomic_read(&dev->refcnt) != 0)
{
########### 每隔一秒中,发送一次 NETDEV_UNREGISTER 事件通知
if (time_after(jiffies, rebroadcast_time + 1 * HZ))
{
rtnl_lock();
call_netdevice_notifiers(NETDEV_UNREGISTER, dev);
/* don't resend NETDEV_UNREGISTER_BATCH, _BATCH users
* should have already handle it the first time */
if (test_bit(__LINK_STATE_LINKWATCH_PENDING,&dev->state))
{
linkwatch_run_queue();
}
__rtnl_unlock();
rebroadcast_time = jiffies;
}
msleep(250);
########### 每隔十秒钟,打印一次控制台提示信息。
if (time_after(jiffies, warning_time + 10 * HZ)) {
printk(KERN_EMERG "unregister_netdevice: waiting for %s to become free. Usage count = %d\n",
dev->name, atomic_read(&dev->refcnt));
warning_time = jiffies;
}
}
}
################### 等到 net_device.refcnt == 0 后,调用其 destructor 接口
# 来注销网络设备
/* paranoia */
BUG_ON(atomic_read(&dev->refcnt));
WARN_ON(dev->ip_ptr);
WARN_ON(dev->ip6_ptr);
WARN_ON(dev->dn_ptr);
if (dev->destructor)
dev->destructor(dev);
/* Free network device */
kobject_put(&dev->dev.kobj);
}
}
}
}
3、三个注销相关的接口
{
1、ops->netdev_ops->ndo_stop
由本地driver提供
用来停止网络设备传送队列、释放硬件资源,停止driver使用到的所有计时器
调用流程:
unregister_netdevice
rollback_registered
rollback_registered_many
dev_close
2、dev->netdev_ops->ndo_uninit
由本地driver提供,用来递减网络设备引用计数,一般用于虚拟设备
调用流程
unregister_netdevice
rollback_registered
rollback_registered_many
3、dev->destructor
通常不对其做初始化,少数虚拟设备会用到
一般是 free_netdev 的包装。
4、一般设备驱动程序直接在 unregister_netdevice 之后调用 free_netdev
}
}
4.6.3.6、网络设备的激活与禁止 dev_open dev_close
{
1、dev_open ,如下功能
{
1、设置 dev->state.__LINK_STATE_START ,表明设备开始运行中
2、设置 dev->flags.IFF_UP 表明设备已经可以开始工作
3、调用 dev_activate 初始化 数据包传送队列侧路。
启动watchdog时钟
如果用户未配置队列策略,则默认采用 FIFO
4、通过网络子系统的通知链发送 netdev_chain(NETDEV_UP,net);
}
}
}
4.3.2、编译成Ko的driver加载,以broadcm_40181为实例
{
// 编译配置选项
{
// .bcmsdh_sdmmc_linux.o.cmd:
cmd_drivers/amlogic/wifi/broadcm_40181/bcmsdh_sdmmc_linux.o :=
arm-none-linux-gnueabi-gcc -Wp,-MD,drivers/amlogic/wifi/broadcm_40181/.bcmsdh_sdmmc_linux.o.d
-nostdinc -isystem /usr/local/arm-2010q1/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.1/include
-I/home/radway/T1D-STANDARD-1204/m3_sta/kernel_t1d/arch/arm/include
-Iinclude -include include/generated/autoconf.h
-D__KERNEL__
-mlittle-endian
-Iarch/arm/mach-meson3/include
-Wall
-Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common
-Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2
-marm -fno-omit-frame-pointer -mapcs -mno-sched-prolog
-mabi=aapcs-linux -mno-thumb-interwork
-D__LINUX_ARM_ARCH__=7
-march=armv7-a
-msoft-float -Uarm
-Wframe-larger-than=1024
-fno-stack-protector -fno-omit-frame-pointer -fno-optimize-sibling-calls -g
-Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fno-dwarf2-cfi-asm
-fconserve-stack
-Wall -Wstrict-prototypes
-Dlinux -DBCMDRIVER -DBCMDONGLEHOST -DUNRELEASEDCHIP -DBCMDMA32 -DWLBTAMP -DBCMFILEIMAGE
-DDHDTHREAD -DDHD_GPL -DDHD_SCHED -DDHD_DEBUG -DBDC -DTOE -DDHD_BCMEVENTS -DSHOW_EVENTS
-DDONGLEOVERLAYS -DBCMDBG -DMMC_SDIO_ABORT -DBCMSDIO -DBCMLXSDMMC -DBCMPLATFORM_BUS
-DWLP2P -DNEW_COMPAT_WIRELESS -DWIFI_ACT_FRAME -DKEEP_ALIVE -DCSCAN -DPKT_FILTER_SUPPORT
-DEMBEDDED_PLATFORM -DPNO_SUPPORT -DDHD_SDALIGN=64 -DMAX_HDR_READ=64 -DDHD_FIRSTREAD=64
-Idrivers/amlogic/wifi/broadcm_40181
-Idrivers/amlogic/wifi/broadcm_40181/include
-DCUSTOMER_HW_AMLOGIC -DENABLE_INSMOD_NO_FW_LOAD -DOOB_INTR_ONLY
-DCUSTOM_OOB_GPIO_NUM=INT_GPIO_5 -DHW_OOB -DSOFTAP -DWL_WIRELESS_EXT
-DMODULE -D"KBUILD_STR(s)=\#s"
-D"KBUILD_BASENAME=KBUILD_STR(bcmsdh_sdmmc_linux)"
-D"KBUILD_MODNAME=KBUILD_STR(dhd)"
-c -o drivers/amlogic/wifi/broadcm_40181/.tmp_bcmsdh_sdmmc_linux.o
drivers/amlogic/wifi/broadcm_40181/bcmsdh_sdmmc_linux.c
}
1、调用总流程
{
module_init(dhd_module_init);
bcmsdh_register(&dhd_sdio);
sdio_register_driver(&bcmsdh_sdmmc_driver);
bcmsdh_sdmmc_probe
bcmsdh_probe(&func->dev);
dhdsdio_probe((vendevid >> 16), (vendevid & 0xFFFF), 0, 0, 0, 0, (void *)regs, NULL, sdh);
/* Allocate private bus interface state */
bus->dhd = dhd_attach(osh, bus, SDPCM_RESERVE);
######################### 申请 net_device
net = alloc_etherdev(sizeof(dhd));
alloc_etherdev_mq(sizeof_priv, 1) // 1个发送队列
/* Ok, have the per-port tell the stack we're open for business */
dhd_net_attach(bus->dhd, 0);
######################### 第二次初始化 net_device
######################### 注册net_device 到内核
register_netdev(net);
}
2、probe接口的预处理
{
static bcmsdh_driver_t dhd_sdio = {
dhdsdio_probe,
dhdsdio_disconnect
};
// bcmsdh_linux.c
static bcmsdh_driver_t drvinfo = {NULL, NULL};
// bcmsdh_sdmmc_linux.h
typedef struct _BCMSDH_SDMMC_INSTANCE {
sdioh_info_t *sd;
struct sdio_func *func[SDIOD_MAX_IOFUNCS];
} BCMSDH_SDMMC_INSTANCE, *PBCMSDH_SDMMC_INSTANCE;
// bcmsdh_sdmmc_linux.c
PBCMSDH_SDMMC_INSTANCE gInstance;
static struct sdio_driver bcmsdh_sdmmc_driver =
{
.probe = bcmsdh_sdmmc_probe,
.remove = bcmsdh_sdmmc_remove,
.name = "bcmsdh_sdmmc",
.id_table = bcmsdh_sdmmc_ids,
.drv =
{
.pm = &bcmsdh_sdmmc_pm_ops,
},
};
// dhd_linux.c
static struct wake_lock dhd_lock;
struct semaphore dhd_registration_sem;
}
3、ko入口
// wifi/broadcm_40181/dhd_linux.c:module_init(dhd_module_init);
{
wake_lock_init(&dhd_lock, WAKE_LOCK_SUSPEND, "dhd_lock");
wake_lock(&dhd_lock);
wl_android_init();
dhd_customer_gpio_wlan_ctrl(WLAN_POWER_ON);
sema_init(&dhd_registration_sem, 0);
dhd_bus_register
return bcmsdh_register(&dhd_sdio);
{
int error = 0;
drvinfo = *driver;
error = sdio_function_init();
{
// bcmsdh_sdmmc_linux.c
gInstance = kzalloc(sizeof(BCMSDH_SDMMC_INSTANCE), GFP_KERNEL);
sdio_register_driver(&bcmsdh_sdmmc_driver);
}
return error;
}
/*
* Wait till MMC sdio_register_driver callback called and made driver attach.
* It's needed to make sync up exit from dhd insmod and
* Kernel MMC sdio device callback registration
*/
down_timeout(&dhd_registration_sem, msecs_to_jiffies(DHD_REGISTRATION_TIMEOUT));
goto exit_1;
exit_1 :
wake_lock_timeout(&dhd_lock, 20*HZ);
return error;
}
4、网络设备的注册
{
// 静态区
static bcmsdh_hc_t *sdhcinfo = NULL;
// dhd_linux.c
// 实际使用 dhd_ops_pri
static struct net_device_ops dhd_ops_pri = {
.ndo_open = dhd_open,
.ndo_stop = dhd_stop,
.ndo_get_stats = dhd_get_stats,
.ndo_do_ioctl = dhd_ioctl_entry,
.ndo_start_xmit = dhd_start_xmit,
.ndo_set_mac_address = dhd_set_mac_address,
.ndo_set_multicast_list = dhd_set_multicast_list,
};
static struct net_device_ops dhd_ops_virt = {
.ndo_get_stats = dhd_get_stats,
.ndo_do_ioctl = dhd_ioctl_entry,
.ndo_start_xmit = dhd_start_xmit,
.ndo_set_mac_address = dhd_set_mac_address,
.ndo_set_multicast_list = dhd_set_multicast_list,
};
struct ethtool_ops dhd_ethtool_ops = {
.get_drvinfo = dhd_ethtool_get_drvinfo
};
// bcmsdh_probe 的调用
{
bcmsdh_probe_workqueue
bcmsdh_probe(&sdioinfo->func->dev);
// 在 sdio_register_driver(&bcmsdh_sdmmc_driver); 执行之后,就开始调用
bcmsdh_sdmmc_probe
{
// struct sdio_func *func,
// const struct sdio_device_id *id)
if (func->num == 1)
if(func->device == 0x4)
ret = bcmsdh_probe(&func->dev);
{
// (struct device *dev)
osl_t *osh = NULL;
bcmsdh_hc_t *sdhc = NULL;
ulong regs = 0;
bcmsdh_info_t *sdh = NULL;
int irq = 0;
uint32 vendevid;
unsigned long irq_flags = 0;
/* allocate SDIO Host Controller state info */
osh = osl_attach(dev, PCI_BUS, FALSE);
sdhc = MALLOC(osh, sizeof(bcmsdh_hc_t));
bzero(sdhc, sizeof(bcmsdh_hc_t));
sdhc->osh = osh;
sdhc->dev = (void *)dev;
sdh = bcmsdh_attach(osh, (void *)r->start,(void **)®s, irq);
sdhc->sdh = sdh;
sdhc->oob_irq = irq;
sdhc->oob_flags = irq_flags;
sdhc->oob_irq_registered = FALSE; /* to make sure.. */
sdhc->oob_irq_enable_flag = FALSE;
sdhc->next = sdhcinfo;
sdhcinfo = sdhc;
/* Read the vendor/device ID from the CIS */
vendevid = bcmsdh_query_device(sdh);
sdhc->ch = drvinfo.attach((vendevid >> 16), (vendevid & 0xFFFF), 0, 0, 0, 0, (void *)regs, NULL, sdh);
{
// dhd_sdio.c dhdsdio_probe
/*
uint16 venid, uint16 devid, uint16 bus_no, uint16 slot,
uint16 func, uint bustype, void *regsva, osl_t * osh, void *sdh)
*/
int ret;
dhd_bus_t *bus;
dhd_cmn_t *cmn;
dhd_txbound = DHD_TXBOUND;
dhd_rxbound = DHD_RXBOUND;
dhd_alignctl = TRUE;
sd1idle = TRUE;
dhd_readahead = TRUE;
retrydata = FALSE;
dhd_doflow = FALSE;
dhd_dongle_memsize = 0;
dhd_txminmax = DHD_TXMINMAX;
forcealign = TRUE;
// VID有效性检查
switch (venid)
{
}
// 设备ID有效性检查
switch (devid)
{
}
/* Ask the OS interface part for an OSL handle */
if (osh == NULL) // yes
{
osh = dhd_osl_attach(sdh, DHD_BUS);
}
/* Allocate private bus interface state */
bus = MALLOC(osh, sizeof(dhd_bus_t));
bzero(bus, sizeof(dhd_bus_t));
bus->sdh = sdh;
bus->cl_devid = (uint16)devid;
bus->bus = DHD_BUS;
bus->tx_seq = SDPCM_SEQUENCE_WRAP - 1;
bus->usebufpool = FALSE; /* Use bufpool if allocated, else use locally malloced rxbuf */
/* attach the common module */
cmn = dhd_common_init(osh);
/* attempt to attach to the dongle */
dhdsdio_probe_attach(bus, osh, sdh, regsva, devid);
/* Attach to the dhd/OS/network interface */
bus->dhd = dhd_attach(osh, bus, SDPCM_RESERVE);
{
// dhd_linux.c (osl_t *osh, struct dhd_bus *bus, uint bus_hdrlen)
dhd_info_t *dhd = NULL;
struct net_device *net = NULL;
uint chip, chiprev;
dhd_attach_states_t dhd_state = DHD_ATTACH_STATE_INIT;
/* updates firmware */
// "firmware_path=/etc/wifi/sdio-g-cdc-full11n-reclaim-roml-wme-idsup.bin
if ((firmware_path != NULL) && (firmware_path[0] != '\0')) // yes
COPY_FW_PATH_BY_CHIP(bus, fw_path, firmware_path);
// nvram_path=/etc/wifi/nvram.txt
if ((nvram_path != NULL) && (nvram_path[0] != '\0')) // yes
strcpy(nv_path, nvram_path);
######################### 申请 net_device
/* Allocate etherdev, including space for private structure */
net = alloc_etherdev(sizeof(dhd));
{
alloc_etherdev_mq(sizeof_priv, 1) // 1个发送队列
alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);
{
// dev.c
/*
(int sizeof_priv,
const char *name,
void (*setup)(struct net_device *),
unsigned int queue_count)
*/
struct netdev_queue *tx;
struct net_device *dev;
############# 分配大小 指定为 net_device 的大小+ sizeof_priv , 并确保 32字节对齐
size_t alloc_size = sizeof(struct net_device);
struct net_device *p;
// 32字节对齐
if (sizeof_priv)
{
/* ensure 32-byte alignment of private area */
alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
alloc_size += sizeof_priv;
}
/* ensure 32-byte alignment of whole construct */
alloc_size += NETDEV_ALIGN - 1;
############# 分配内核内存
p = kzalloc(alloc_size, GFP_KERNEL);
tx = kcalloc(queue_count, sizeof(struct netdev_queue), GFP_KERNEL);
dev = PTR_ALIGN(p, NETDEV_ALIGN);
dev->padded = (char *)dev - (char *)p;
if (dev_addr_init(dev))
goto free_tx;
dev_unicast_init(dev);
dev_net_set(dev, &init_net);
dev->_tx = tx;
dev->num_tx_queues = queue_count;
dev->real_num_tx_queues = queue_count;
dev->gso_max_size = GSO_MAX_SIZE;
netdev_init_queues(dev);
INIT_LIST_HEAD(&dev->ethtool_ntuple_list.list);
dev->ethtool_ntuple_list.count = 0;
INIT_LIST_HEAD(&dev->napi_list);
INIT_LIST_HEAD(&dev->unreg_list);
INIT_LIST_HEAD(&dev->link_watch_list);
dev->priv_flags = IFF_XMIT_DST_RELEASE;
setup(dev);
strcpy(dev->name, name);
return dev;
free_tx:
kfree(tx);
free_p:
kfree(p);
return NULL;
}
}
dhd_state |= DHD_ATTACH_STATE_NET_ALLOC;
/* Allocate primary dhd_info */
dhd = MALLOC(osh, sizeof(dhd_info_t));
memset(dhd, 0, sizeof(dhd_info_t));
dhd->dhd_tasklet_create = FALSE;
dhd->thr_sysioc_ctl.thr_pid = DHD_PID_KT_INVALID;
dhd_state |= DHD_ATTACH_STATE_DHD_ALLOC;
######################### 保存 dhd_info_t 到 net_device.priv中
memcpy((void *)netdev_priv(net), &dhd, sizeof(dhd));
dhd->pub.osh = osh;
dhd->pub.info = dhd; /* Link to info module */
dhd->pub.info = dhd;
dhd->pub.bus = bus; /* Link to bus module */
dhd->pub.hdrlen = bus_hdrlen;
if (iface_name[0])// no,未将网卡名作为参数传给模块
{
}
dhd_add_if(dhd, 0, (void *)net, net->name, NULL, 0, 0);
{
/*
(dhd_info_t *dhd, int ifidx, void *handle, char *name,
uint8 *mac_addr, uint32 flags, uint8 bssidx)
*/
dhd_if_t *ifp;
ifp = dhd->iflist[ifidx];
if (ifp != NULL)//no
{}
else // 分配新的网口 dhd_if_t
{
ifp = MALLOC(dhd->pub.osh, sizeof(dhd_if_t));
memset(ifp, 0, sizeof(dhd_if_t));
}
memset(ifp, 0, sizeof(dhd_if_t));
ifp->info = dhd;
dhd->iflist[ifidx] = ifp;
strncpy(ifp->name, name, IFNAMSIZ);
ifp->name[IFNAMSIZ] = '\0';
if (handle == NULL)// NO
{
}
else
{
ifp->net = (struct net_device *)handle;
}
return 0;
}
dhd_state |= DHD_ATTACH_STATE_ADD_IF;
net->netdev_ops = NULL;
sema_init(&dhd->proto_sem, 1);
/* Initialize other structure content */
init_waitqueue_head(&dhd->ioctl_resp_wait);
init_waitqueue_head(&dhd->ctrl_wait);
/* Initialize the spinlocks */
spin_lock_init(&dhd->sdlock);
spin_lock_init(&dhd->txqlock);
spin_lock_init(&dhd->dhd_lock);
/* Initialize Wakelock stuff */
spin_lock_init(&dhd->wakelock_spinlock);
dhd->wakelock_counter = 0;
dhd->wakelock_timeout_enable = 0;
wake_lock_init(&dhd->wl_wifi, WAKE_LOCK_SUSPEND, "wlan_wake");
wake_lock_init(&dhd->wl_rxwake, WAKE_LOCK_SUSPEND, "wlan_rx_wake");
mutex_init(&dhd->dhd_net_if_mutex);
mutex_init(&dhd->dhd_init_mutex);
dhd_init_lock_local(&dhd->pub);
dhd_state |= DHD_ATTACH_STATE_WAKELOCKS_INIT;
/* Attach and link in the protocol */
dhd_prot_attach(&dhd->pub);
dhd_state |= DHD_ATTACH_STATE_PROT_ATTACH;
/* Attach and link in the iw */
GET_CHIP_VER(bus, &chip, &chiprev);
wl_iw_attach(net, (void *)&dhd->pub, chip, chiprev);
dhd_state |= DHD_ATTACH_STATE_WL_ATTACH;
/* Set up the watchdog timer */
init_timer(&dhd->timer);
dhd->timer.data = (ulong)dhd;
dhd->timer.function = dhd_watchdog;
/* Set up the bottom half handler */
tasklet_init(&dhd->tasklet, dhd_dpc, (ulong)dhd);
dhd->dhd_tasklet_create = TRUE;
/* Spawn a thread for system ioctls (set mac, set mcast) */
PROC_START(_dhd_sysioc_thread, dhd, &dhd->thr_sysioc_ctl, 0);
dhd_state |= DHD_ATTACH_STATE_THREADS_CREATED;
// Save the dhd_info into the priv
memcpy(netdev_priv(net), &dhd, sizeof(dhd));
// PM
register_pm_notifier(&dhd_sleep_pm_notifier);
// EARLYSUSPEND
dhd->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 20;
dhd->early_suspend.suspend = dhd_early_suspend;
dhd->early_suspend.resume = dhd_late_resume;
register_early_suspend(&dhd->early_suspend);
dhd_state |= DHD_ATTACH_STATE_EARLYSUSPEND_DONE;
dhd_state |= DHD_ATTACH_STATE_DONE;
dhd->dhd_state = dhd_state;
return &dhd->pub;
}
bus->dhd->cmn = cmn;
cmn->dhd = bus->dhd;
/* Allocate buffers */
dhdsdio_probe_malloc(bus, osh, sdh);
dhdsdio_probe_init(bus, osh, sdh);
if (bus->intr)// yes,采用中断模式
{
/* Register interrupt callback, but mask it (not operational yet). */
bcmsdh_intr_disable(sdh);
bcmsdh_intr_reg(sdh, dhdsdio_isr, bus);
}
/* if firmware path present try to download and bring up bus */
if (dhd_download_fw_on_driverload)//yes
{
dhd_bus_start(bus->dhd);
}
/* Ok, have the per-port tell the stack we're open for business */
dhd_net_attach(bus->dhd, 0);
{
// dhd_linux.c (dhd_pub_t *dhdp, int ifidx)
dhd_info_t *dhd = (dhd_info_t *)dhdp->info;
struct net_device *net = NULL;
int err = 0;
uint8 temp_addr[ETHER_ADDR_LEN] = { 0x00, 0x90, 0x4c, 0x11, 0x22, 0x33 };
net = dhd->iflist[ifidx]->net;
net->netdev_ops = &dhd_ops_virt;
/* Ok, link into the network layer... */
if (ifidx == 0) // yes
{
net->netdev_ops = &dhd_ops_pri;
}
net->hard_header_len = ETH_HLEN + dhd->pub.hdrlen;
net->ethtool_ops = &dhd_ethtool_ops;
dhd->pub.rxsz = DBUS_RX_BUFFER_SIZE_DHD(net);
// 设定MAC地址
memcpy(net->dev_addr, temp_addr, ETHER_ADDR_LEN);
// 注册网络设备
register_netdev(net);
if (ifidx == 0)
{
up(&dhd_registration_sem);
}
return 0;
}
wl_android_post_init(); // fix for preinit function missed called after resume
return bus;
}
return 0;
}
if (func->num == 2)
ret = bcmsdh_probe(&func->dev);
}
}
}
}
}
5.4、网络设备活动功能函数
{
1、ndo_open / dhd_open
net_device 创建并注册后,设备还不能开始收发数据包,此时还需激活。
ndo_open,用于激活网络设备:
申请网络设备活动所需的系统资源,并将相关的值写入到 设备寄存器来初始化适配器硬件
为网络设备分配IP地址,ifconfig -> ioctl(SIOCSIFADDR)
从设备硬件中读取MAC地址写入到 dev_dev_addr 中
设置net_device->flags的IFF_UP位,ifconfig ->ioctl(SIOCSIFFLAGS)
激活设备的 硬件发送队列,由 netif_start_queue 完成。
dhd_open(struct net_device *net)
{
dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net);
uint32 toe_ol;
int ifidx;
int32 ret = 0;
DHD_OS_WAKE_LOCK(&dhd->pub);
dhd_init_lock_local(&dhd->pub); // terence 20120530: fix for preinit function missed called after resume
/* Update FW path if it was changed */
if ((firmware_path != NULL) && (firmware_path[0] != '\0'))
{
if (firmware_path[strlen(firmware_path)-1] == '\n')
firmware_path[strlen(firmware_path)-1] = '\0';
COPY_FW_PATH_BY_CHIP( dhd->pub.bus, fw_path, firmware_path);
// firmware_path[0] = '\0';
}
dhd->pub.hang_was_sent = 0;
/*
* Force start if ifconfig_up gets called before START command
* We keep WEXT's wl_control_wl_start to provide backward compatibility
* This should be removed in the future
*/
wl_control_wl_start(net);
ifidx = dhd_net2idx(dhd, net);
if (ifidx < 0)
{
DHD_ERROR(("%s: Error: called with invalid IF\n", __FUNCTION__));
ret = -1;
goto exit;
}
if (!dhd->iflist[ifidx] || dhd->iflist[ifidx]->state == DHD_IF_DEL)
{
DHD_ERROR(("%s: Error: called when IF already deleted\n", __FUNCTION__));
ret = -1;
goto exit;
}
if (ifidx == 0)
{
atomic_set(&dhd->pend_8021x_cnt, 0);
if (dhd->pub.busstate != DHD_BUS_DATA)
{
/* try to bring up bus */
################### 安装中断处理函数
dhd_bus_start(&dhd->pub);
{
// (dhd_pub_t *dhdp)
// dhd_linux.c
int ret = -1;
dhd_info_t *dhd = (dhd_info_t*)dhdp->info;
unsigned long flags;
/* try to download image and nvram to the dongle */
if ((dhd->pub.busstate == DHD_BUS_DOWN) &&
(fw_path != NULL) && (fw_path[0] != '\0') &&
(nv_path != NULL) && (nv_path[0] != '\0'))
{
dhd_bus_download_firmware(dhd->pub.bus, dhd->pub.osh,fw_path, nv_path);
}
/* Start the watchdog timer */
dhd->pub.tickcnt = 0;
dhd_os_wd_timer(&dhd->pub, dhd_watchdog_ms);
/* Bring up the bus */
dhd_bus_init(&dhd->pub, FALSE));
/* Host registration for OOB interrupt */
############### 安装中断处理函数
bcmsdh_register_oob_intr(dhdp);
{
int error = 0;
/* IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE; */
dev_set_drvdata(sdhcinfo->dev, dhdp);
/* Refer to customer Host IRQ docs about proper irqflags definition */
request_irq(sdhcinfo->oob_irq, wlan_oob_irq, sdhcinfo->oob_flags, "bcmsdh_sdmmc", NULL);
enable_irq_wake(sdhcinfo->oob_irq);
sdhcinfo->oob_irq_registered = TRUE;
sdhcinfo->oob_irq_enable_flag = TRUE;
return 0;
}
/* Enable oob at firmware */
dhd_enable_oob_intr(dhd->pub.bus, TRUE);
/* Bus is ready, do any protocol initialization */
dhd_prot_init(&dhd->pub));
aoe_update_host_ipv4_table(&dhd->pub, dhd->pend_ipaddr, TRUE);
return 0;
}
}
/* dhd_prot_init has been called in dhd_bus_start or wl_android_wifi_on */
memcpy(net->dev_addr, dhd->pub.mac.octet, ETHER_ADDR_LEN);
/* Get current TOE mode from dongle */
if (dhd_toe_get(dhd, ifidx, &toe_ol) >= 0 && (toe_ol & TOE_TX_CSUM_OL) != 0)
dhd->iflist[ifidx]->net->features |= NETIF_F_IP_CSUM;
else
dhd->iflist[ifidx]->net->features &= ~NETIF_F_IP_CSUM;
}
if ((firmware_path != NULL) && (firmware_path[0] != '\0')) { // terence
// firmware_path[0] = '\0';
}
/* Allow transmit calls */
netif_start_queue(net);
dhd->pub.up = 1;
OLD_MOD_INC_USE_COUNT;
exit:
dhd_init_unlock_local(&dhd->pub); // terence 20120530: fix for preinit function missed called after resume
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return ret;
}
2、数据包发送接口 net->netdev_ops->ndo_start_xmit
// 数据包发送须知
内核准备好要发送的数据包后,链路层调用 ndo_start_xmit 将数据包放到 设备的硬缓冲区中,并启动硬件发送。
大多数设备的硬件缓冲区可以容纳多个数据包,在driver中,管理着一个 循环缓冲区(存放着16-64个指针,每个指针
指向一个skb)。当一个数据包准备后,相应的skb指针就放入到这个 循环队列中,一个数据包发送完毕,网络设备就产生
中断,此时刚发送完毕的skb就可以释放掉。
此函数之前,一般会调用 netif_queue_stopped 来查看设备发送队列是否已满。
并发访问控制锁 net_device.tx_global_lock
该函数把数据包扔给硬件后,实际的硬件发送操作尚未完成,如果硬件内部的缓冲区已满,driver必须调用
netif_stop_queue 来通知网络子系统不要再启动别的发送,直到缓冲区可用为止,而后driver须调用 netif_wake_queue
通知系统 重新启动数据包发送。
在发送函数中,当skb中数据包被复制到硬件内的缓冲区后,可以设置数据包开始发送时间 dev->trans_start = jiffies
标识着此时开始发送。如果复制成功,接口返回0。
// 发送超时处理
driver设置一个始终,在向硬件发出发送指令后,启动计时器。
如果超时后,仍未发送完成,则调用 发送超时处理函数来 处理这类错误,已确保数据包不会丢失。
dhd_start_xmit(struct sk_buff *skb, struct net_device *net)
{
int ret;
void *pktbuf;
dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net);
int ifidx;
uint8 htsfdlystat_sz = 0;
DHD_OS_WAKE_LOCK(&dhd->pub);
################# 如果网卡未UP 或者DHD状态为 DHD_BUS_DOWN,则停止传输
if (!dhd->pub.up || (dhd->pub.busstate == DHD_BUS_DOWN))
{
DHD_ERROR(("%s: xmit rejected pub.up=%d busstate=%d \n", __FUNCTION__, dhd->pub.up, dhd->pub.busstate));
netif_stop_queue(net);
/* Send Event when bus down detected during data session */
if (dhd->pub.busstate == DHD_BUS_DOWN) {
DHD_ERROR(("%s: Event HANG sent up\n", __FUNCTION__));
net_os_send_hang_message(net);
}
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return -ENODEV;
}
################# 确保使用了正确的网卡
ifidx = dhd_net2idx(dhd, net);
if (ifidx == DHD_BAD_IF)
{
DHD_ERROR(("%s: bad ifidx %d\n", __FUNCTION__, ifidx));
netif_stop_queue(net);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return -ENODEV;
}
/* Make sure there's enough room for any header */
if (skb_headroom(skb) < dhd->pub.hdrlen + htsfdlystat_sz)
{
struct sk_buff *skb2;
DHD_INFO(("%s: insufficient headroom\n", dhd_ifname(&dhd->pub, ifidx)));
dhd->pub.tx_realloc++;
skb2 = skb_realloc_headroom(skb, dhd->pub.hdrlen + htsfdlystat_sz);
dev_kfree_skb(skb);
if ((skb = skb2) == NULL)
{
DHD_ERROR(("%s: skb_realloc_headroom failed\n", dhd_ifname(&dhd->pub, ifidx)));
ret = -ENOMEM;
goto done;
}
}
/* Convert to packet */
if (!(pktbuf = PKTFRMNATIVE(dhd->pub.osh, skb)))
{
DHD_ERROR(("%s: PKTFRMNATIVE failed\n", dhd_ifname(&dhd->pub, ifidx)));
dev_kfree_skb_any(skb);
ret = -ENOMEM;
goto done;
}
ret = dhd_sendpkt(&dhd->pub, ifidx, pktbuf);
{
// (dhd_pub_t *dhdp, int ifidx, void *pktbuf)
int ret;
dhd_info_t *dhd = (dhd_info_t *)(dhdp->info);
struct ether_header *eh = NULL;
/* Reject if down */
if (!dhdp->up || (dhdp->busstate == DHD_BUS_DOWN))
{
PKTFREE(dhdp->osh, pktbuf, TRUE);
return -ENODEV;
}
/* Update multicast statistic */
if (PKTLEN(dhdp->osh, pktbuf) >= ETHER_HDR_LEN)
{
uint8 *pktdata = (uint8 *)PKTDATA(dhdp->osh, pktbuf);
eh = (struct ether_header *)pktdata;
if (ETHER_ISMULTI(eh->ether_dhost))
dhdp->tx_multicast++;
if (ntoh16(eh->ether_type) == ETHER_TYPE_802_1X)
atomic_inc(&dhd->pend_8021x_cnt);
} else {
PKTFREE(dhd->pub.osh, pktbuf, TRUE);
return BCME_ERROR;
}
/* Look into the packet and update the packet priority */
if (PKTPRIO(pktbuf) == 0)
pktsetprio(pktbuf, FALSE);
/* If the protocol uses a data header, apply it */
dhd_prot_hdrpush(dhdp, ifidx, pktbuf);
/* Use bus module to send data frame */
ret = dhd_bus_txdata(dhdp->bus, pktbuf);
return ret;
}
done:
if (ret)
dhd->pub.dstats.tx_dropped++;
else
dhd->pub.tx_packets++;
DHD_OS_WAKE_UNLOCK(&dhd->pub);
/* Return ok: we always eat the packet */
return 0;
}
3、数据包接收
// 处理时机:
设备中断处理程序收到 接收中断时
// 处理过程:
1、分配skb,将数据包从硬件内缓冲中,复制到skb中
2、对skb的必要数据域赋值
skb->dev、protocol
告诉上层协议如何对数据包做检验和。此处区别于 dev->features 域(它用来对发送出去的数据包进行检验和运算)
3、更新接收到的数据包的统计信息
rx_packets rx_bytes tx_pakets tx_bytes 接收和发送的数据包数、总的接收\发送的字节数
4、调用 netif_rx 将数据包扔给链路层。
// bcm40181 中断处理函数 wlan_oob_irq // bcmsdh_linux.c
/*
dhdsdio_readframes
*/
irqreturn_t wlan_oob_irq(int irq, void *dev_id)
{
dhd_pub_t *dhdp;
dhdp = (dhd_pub_t *)dev_get_drvdata(sdhcinfo->dev);
bcmsdh_oob_intr_set(0);
{
// (bool enable)
static bool curstate = 1;
unsigned long flags;
spin_lock_irqsave(&sdhcinfo->irq_lock, flags);
if (curstate != enable)
{
if (enable)
enable_irq(sdhcinfo->oob_irq);
else
disable_irq_nosync(sdhcinfo->oob_irq);
curstate = enable;
}
spin_unlock_irqrestore(&sdhcinfo->irq_lock, flags);
}
if (dhdp == NULL)
return IRQ_HANDLED;
################# 开始做中断处理
dhdsdio_isr((void *)dhdp->bus);
{
// dhd_sdio.c (void *arg)
dhd_bus_t *bus = (dhd_bus_t*)arg;
bcmsdh_info_t *sdh;
sdh = bus->sdh;
/* Count the interrupt call */
bus->intrcount++;
bus->ipend = TRUE;
/* Shouldn't get this interrupt if we're sleeping? */
if (bus->sleeping)
return;
/* Disable additional interrupts (is this needed now)? */
bcmsdh_intr_disable(sdh);
bus->intdis = TRUE;
bus->dpc_sched = TRUE;
dhd_sched_dpc(bus->dhd);
{
dhd_info_t *dhd = (dhd_info_t *)dhdp->info;
DHD_OS_WAKE_LOCK(dhdp);
tasklet_schedule(&dhd->tasklet);
{
// linuxver.h
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_schedule(t);
{
unsigned long flags;
local_irq_save(flags);
t->next = NULL;
*__get_cpu_var(tasklet_vec).tail = t;
__get_cpu_var(tasklet_vec).tail = &(t->next);
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
}
################### 此后开始执行 dhd_dpc->dhdsdio_dpc(dhd->pub.bus);
dhd_dpc(ulong data)
{
// dhd_linux.c
/* Call bus dpc unless it indicated down (then clean stop) */
if (dhd->pub.busstate != DHD_BUS_DOWN)
{
if (dhd_bus_dpc(dhd->pub.bus))
{
// dhd_bus_dpc(dhd->pub.bus)
// dhd_sdio.c (struct dhd_bus *bus)
bool resched;
resched = dhdsdio_dpc(bus);
{
bcmsdh_info_t *sdh = bus->sdh;
sdpcmd_regs_t *regs = bus->regs;
uint32 intstatus, newstatus = 0;
uint retries = 0;
uint rxlimit = dhd_rxbound; /* Rx frames to read before resched */
uint txlimit = dhd_txbound; /* Tx frames to send before resched */
uint framecnt = 0; /* Temporary counter of tx/rx frames */
bool rxdone = TRUE; /* Flag for no more read data */
bool resched = FALSE; /* Flag indicating resched wanted */
/* Start with leftover status bits */
intstatus = bus->intstatus;
dhd_os_sdlock(bus->dhd);
/* If waiting for HTAVAIL, check status */
if (bus->clkstate == CLK_PENDING)
{
int err;
uint8 clkctl, devctl = 0;
/* Read CSR, if clock on switch to AVAIL, else ignore */
clkctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err);
if (err) bus->dhd->busstate = DHD_BUS_DOWN;
if (SBSDIO_HTAV(clkctl))
{
devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err);
if (err) bus->dhd->busstate = DHD_BUS_DOWN;
devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl, &err);
if (err) bus->dhd->busstate = DHD_BUS_DOWN;
bus->clkstate = CLK_AVAIL;
}
else
goto clkwait;
}
BUS_WAKE(bus);
/* Make sure backplane clock is on */
dhdsdio_clkctl(bus, CLK_AVAIL, TRUE);
if (bus->clkstate != CLK_AVAIL) goto clkwait;
/* Pending interrupt indicates new device status */
if (bus->ipend)
{
bus->ipend = FALSE;
R_SDREG(newstatus, ®s->intstatus, retries);
bus->f1regdata++;
if (bcmsdh_regfail(bus->sdh)) newstatus = 0;
newstatus &= bus->hostintmask;
bus->fcstate = !!(newstatus & I_HMB_FC_STATE);
if (newstatus)
{
bus->f1regdata++;
if ((bus->rxint_mode == SDIO_DEVICE_RXDATAINT_MODE_0) &&(newstatus == I_XMTDATA_AVAIL))
{}
else
W_SDREG(newstatus, ®s->intstatus, retries);
}
}
/* Merge new bits with previous */
intstatus |= newstatus;
bus->intstatus = 0;
/* Handle flow-control change: read new state in case our ack
* crossed another change interrupt. If change still set, assume
* FC ON for safety, let next loop through do the debounce.
*/
if (intstatus & I_HMB_FC_CHANGE)
{
intstatus &= ~I_HMB_FC_CHANGE;
W_SDREG(I_HMB_FC_CHANGE, ®s->intstatus, retries);
R_SDREG(newstatus, ®s->intstatus, retries);
bus->f1regdata += 2;
bus->fcstate = !!(newstatus & (I_HMB_FC_STATE | I_HMB_FC_CHANGE));
intstatus |= (newstatus & bus->hostintmask);
}
/* Just being here means nothing more to do for chipactive */
if (intstatus & I_CHIPACTIVE) intstatus &= ~I_CHIPACTIVE;
/* Handle host mailbox indication */
if (intstatus & I_HMB_HOST_INT)
{
intstatus &= ~I_HMB_HOST_INT;
intstatus |= dhdsdio_hostmail(bus);
}
/* Generally don't ask for these, can get CRC errors... */
if (intstatus & I_WR_OOSYNC) intstatus &= ~I_WR_OOSYNC;
if (intstatus & I_RD_OOSYNC) intstatus &= ~I_RD_OOSYNC;
if (intstatus & I_SBINT) intstatus &= ~I_SBINT;
/* Would be active due to wake-wlan in gSPI */
if (intstatus & I_CHIPACTIVE) intstatus &= ~I_CHIPACTIVE;
/* Ignore frame indications if rxskip is set */
if (bus->rxskip) intstatus &= ~FRAME_AVAIL_MASK(bus);
####################### 在此处读取 数据包
/* On frame indication, read available frames */
if (PKT_AVAILABLE(bus, intstatus))
{
framecnt = dhdsdio_readframes(bus, rxlimit, &rxdone);
{
....
dhd_rx_frame(bus->dhd, ifidx, pkt, 1, chan);
{
if (in_interrupt())
// 在中断中,处理数据的读取
netif_rx(skb);
else
// 对于下半部,将在 softirqd 中处理
netif_rx_ni(skb);
}
}
if (rxdone || bus->rxskip)
intstatus &= ~FRAME_AVAIL_MASK(bus);
rxlimit -= MIN(framecnt, rxlimit);
}
/* Keep still-pending events for next scheduling */
bus->intstatus = intstatus;
clkwait:
/* Re-enable interrupts to detect new device events (mailbox, rx frame)
* or clock availability. (Allows tx loop to check ipend if desired.)
* (Unless register access seems hosed, as we may not be able to ACK...)
*/
if (bus->intr && bus->intdis && !bcmsdh_regfail(sdh))
{
bus->intdis = FALSE;
bcmsdh_oob_intr_set(1);
bcmsdh_intr_enable(sdh);
}
######################### 发送数据包
if (TXCTLOK(bus) && bus->ctrl_frame_stat && (bus->clkstate == CLK_AVAIL))
{
int ret, i;
ret = dhd_bcmsdh_send_buf(bus,
bcmsdh_cur_sbwad(sdh),
SDIO_FUNC_2,
F2SYNC,
(uint8 *)bus->ctrl_frame_buf,
(uint32)bus->ctrl_frame_len,
NULL, NULL, NULL);
ASSERT(ret != BCME_PENDING);
##################### 发送失败
/* On failure, abort the command and terminate the frame */
if(ret<0)
{
bus->tx_sderrs++;
bcmsdh_abort(sdh, SDIO_FUNC_2);
bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_FRAMECTRL,SFC_WF_TERM, NULL);
bus->f1regdata++;
for (i = 0; i < 3; i++) {
uint8 hi, lo;
hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,SBSDIO_FUNC1_WFRAMEBCHI, NULL);
lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,SBSDIO_FUNC1_WFRAMEBCLO, NULL);
bus->f1regdata += 2;
if ((hi == 0) && (lo == 0))
break;
}
}
if (ret == 0) {
bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP;
}
bus->ctrl_frame_stat = FALSE;
dhd_wait_event_wakeup(bus->dhd);
}
/* Send queued frames (limit 1 if rx may still be pending) */
else if ((bus->clkstate == CLK_AVAIL) && !bus->fcstate &&
pktq_mlen(&bus->txq, ~bus->flowcontrol) && txlimit && DATAOK(bus))
{
framecnt = rxdone ? txlimit : MIN(txlimit, dhd_txminmax);
framecnt = dhdsdio_sendfromq(bus, framecnt);
txlimit -= framecnt;
}
/* Resched the DPC if ctrl cmd is pending on bus credit */
if (bus->ctrl_frame_stat)
resched = TRUE;
/* Resched if events or tx frames are pending, else await next interrupt */
/* On failed register access, all bets are off: no resched or interrupts */
if ((bus->dhd->busstate == DHD_BUS_DOWN) || bcmsdh_regfail(sdh))
{
bus->dhd->busstate = DHD_BUS_DOWN;
bus->intstatus = 0;
}
else if (bus->clkstate == CLK_PENDING)
{
/* Awaiting I_CHIPACTIVE; don't resched */
}
else if (bus->intstatus || bus->ipend ||
(!bus->fcstate && pktq_mlen(&bus->txq, ~bus->flowcontrol) && DATAOK(bus)) ||
PKT_AVAILABLE(bus, bus->intstatus))
{ /* Read multiple frames */
resched = TRUE;
}
bus->dpc_sched = resched;
/* If we're done for now, turn off clock request. */
if ((bus->idletime == DHD_IDLE_IMMEDIATE) && (bus->clkstate != CLK_PENDING))
{
bus->activity = FALSE;
dhdsdio_clkctl(bus, CLK_NONE, FALSE);
}
dhd_os_sdunlock(bus->dhd);
return resched;
}
return resched;
}
{
tasklet_schedule(&dhd->tasklet);
}
else
DHD_OS_WAKE_UNLOCK(&dhd->pub);
}
else
{
dhd_bus_stop(dhd->pub.bus, TRUE);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
}
}
}
}
return IRQ_HANDLED;
}
4、网络设备定制的ioctl命令
ioctl(sd,SIOCSIADDR,&ifreq);
sock_ioctl
// 特定协议族的 ioctl 接口
// 对于索引在[SIOCDEVPRIVATE,SIOCDEPRIVATE+15] 之间的命令会调用
dev_ioctl
dev->do_ioctl
5、统计信息的更新
由接收函数、发送函数做对应的更新,之后存储到 net_device_stats 中
}
5.5、在适配器中支持组发送
{
// 组发送数据包:
可以由多个主机接收的数据包,通过为一组主机分配特殊的硬件地址来实现
送往某个特殊硬件地址的数据包可以被这个组中的所有主机收到。
数据包中的目的地址,由内核分配。
任何一个数据包,如果其目的地址 包含在已设置的组发送地址列表中,driver应该将其发给内核。
// 网路设备对组发送的支持,三类设备
设备自己不能处理组发送
或接受 目的地址与本MAC一致的数据包,或接受所有数据包。
dev->flags.IFF_MULTICAST 清零
设备能够区分数据包的类型(组发送、广播、点到点)
接收所有的数据包,由driver来判断是否为本机需要接收的组发送包
设备本身能够检测组发送的MAC地址
它们保存了一个组发送地址列表。忽略非列表中的 组发送包
// 组发送地址列表的管理
列表中的地址,APP可能会随时更改他们,内核需要对其做更新,并通知driver,将新的地址列表发送给设备。
网络设备,需要接收内核发送的 组发送地址列表,并存储。
// 组发送相关的设备标识 dev->flags
IFF_MULTICAST 设置此标识后,接口才会处理组发送包
IFF_ALLMULTI 设置后,可以让driver查看所有从网络上到达的数据包,此时,不应该再用mc_list表来过滤
IFF_PROMISC 混杂模式,接收所有的网络数据包。
}
5.6、事件通知链
{
1、网络子系统为什么需要通知链
由于网络设备的状态在系统运行过程中,有可能随时变化(注册、注销、其他参数的变化)。
为了提高网络传输的性能,简化协议处理,协议栈实例总是将其使用到的网络设备的引用
存放到自己的struct中,当设备状态发生变化后,这些信息将无效。此时,协议栈不应该再使用
无效的设备信息,去传送数据包。
而网络设备本身并不知道,那种子系统会用到它。因而这里采用:基于回调函数通知链来实现。
2、事件通知链及其构成
一个事件处理函数的列表。
通知方:负责创建通知链,发生事件后,去调用此事件对应的处理函数(其他子系统的回调函数)
去通知 其他子系统。
被通知方:实现处理函数,并将其注册到 通知链上。
3、notifier_block
// notifier.h
struct notifier_block {
int (*notifier_call)
(
struct notifier_block *, // 事件来自系统的 哪个通知链
unsigned long, // 事件编码
void * // 传给事件处理回调函数的 参数
);
struct notifier_block *next; // 维护者 事件通知链中 下一个成员
int priority; // 处理优先级。一般按照注册顺序去调用
};
4、其他子系统向通知链注册
1、编写事件处理回调函数
2、声明 notifier_block,保存回调函数
3、调用特定事件通知链的注册函数
// 通用注册函数 notifier_chain_register
static int notifier_chain_register
(struct notifier_block **nl,
struct notifier_block *n)
{
while ((*nl) != NULL) {
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
n->next = *nl;
rcu_assign_pointer(*nl, n);
return 0;
}
5、事件处理函数的回调
通知方有事件发生时,调用 notifier_call_chain 依次调用本通知链上已注册的节点中的 notifier_call 接口
即通知给所有的 对此系统感兴趣的 其他子系统。
int __kprobes notifier_call_chain
(struct notifier_block **nl, // 事件通知链表头
unsigned long val, // 事件编号
void *v, // 额外参数
int nr_to_call,
int *nr_calls)
{
int ret = NOTIFY_DONE;
struct notifier_block *nb, *next_nb;
nb = rcu_dereference_raw(*nl);
while (nb && nr_to_call)
{
next_nb = rcu_dereference_raw(nb->next);
ret = nb->notifier_call(nb, val, v);
if (nr_calls) (*nr_calls)++;
if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
break;
nb = next_nb;
nr_to_call--;
}
return ret;
}
6、网络子系统中常用的事件通知链
netdev_chain 发送关于网络设备注册、注销、状态变化的通知
inetaddr_chain 如果本接口IPV4地址有变化,则发此通知到其他系统(比如路由子系统)
inde6addr_cahin 同上。
7、内核中其他通知链
reboot_notifier_list 系统将要重启时,通知其他子系统
}
}
6、数据链路层的收发处理
{
5.1、NAPI接收模式
{
//--------------------------------- 比较
//
传统模式:每接收到一个网络数据包就会产生一个中断,对于高带宽网络,每秒产生的中断会太会,会降低系统性能。
NAPI模式:基于poll,同时结合中断处理。
当网卡从网络上接收到数据帧后,向CPU发出中断请求,内核执行中断处理函数开始接收数据帧。
在内核处理完前边的数据帧前,如果设备又收到新的数据帧,此时不需产生中断,内核调用poll接口从设备缓冲区
中联系读入数据帧,直到设备缓冲区空为止,再重新打开设备中断。
传统poll:即使设备没有数据交换,CPU也会不断查询设备的状态
高速设备:在高流量网络中,网络设备中总会有更多的数据包需要处理。
//--------------------------------- 支持NAPI的网络设备。
必须能存放多个数据包(在硬件上或接收队列中)。
在连续读接口的数据包时,接口应能关闭 自己设备的接收中断,而打开发送或别的事件中断。
// 其 net_device 的设定
net_device.poll = xxx_poll ,用于NAPI的poll
net_device.weight = x,CPU一次可以从网络设备中读入多少个数据包。
对于10M的以太网,设定为16
快速网络设备,设定为 64个数据包
//--------------------------------- 修改driver
1、当产生接收中断时,接收数据包接口
闭接收中断,
让内核开始轮询网络设备
netif_rx_schedule
dev->poll 83222401
2、xxx_poll
比较 设备能传给内核的数据包数量最大值(weight) 与 内核一次可读入的最大值之间 的 最小值 quota
从网络设备的接收队列中,循环读入 quota 个数据包,做如下处理
分配sk_buff,复制数据包到其中
修改skb的(dev,protocol,ipsumed)
调用 netif_recive_skb 将skb传给上层协议
修改统计信息
如果硬件接收队列为空,则退出循环。
如果已读入了 quota 个数据包,也退出循环。
将设备放入 数据接收完成设备队列
开接收中断。
返回
3、poll过程中,虽然关闭了接收中断,但数据包仍然可以从网络进入到 设备接口的缓冲区中。
4、网络子系统可以保证 poll接口不会在多个CPU上同时被调用,从而避免了并发访问控制的复杂性。
//--------------------------------- NAPI模式的实现
//
1、CPU 输入输出队列 softnet_data 及其实例化
// 每个CPU都对应着一个此结构,因而访问时,不需要加锁机制
struct softnet_data
{
struct Qdisc *output_queue; // 管理网络设备输出队列
struct sk_buff_head input_pkt_queue; // 存放网络输入数据帧skb的链表
struct list_head poll_list; // 以NAPI方式接收网络数据的设备链表,这些设备实现了poll接口
// 当链表中的设备 有从网络收入数据帧,则其对应的poll接口会在 net_rx_action 处理
//NET_RX_SOFTIRQ软件中断时被调用,用于将 设备硬件缓冲区中的数据帧 复制到 内核地址
//空间的skb,放到cpu输入队列。
struct sk_buff *completion_queue; // 存放 已经成功发送/接收的skb,这些skb会在 NET_TX_SOFTIRQ 处理程序 net_tx_action 中进行。
struct napi_struct backlog; // 其 poll 接口在网络子系统初始化时,被初始化为 process_baklog接口
// (用于将cpu输入队列input_pkt_queue中的数据帧向上层协议 推送)
};
// 输入队列长度相关的全局变量
int netdev_max_backlog __read_mostly = 1000; // CPU 输入队列最大长度
int netdev_budget __read_mostly = 300; // 软件中断 一次可以处理的最大数据帧数目
int weight_p __read_mostly = 64; // poll 可以从设备读入的最大帧数
// cpu 全局 softnet_data 的实例化
net_dev_init
##################### 初始化 每个CPU的 数据包输入\输出队列
for_each_possible_cpu(i)
{
struct softnet_data *queue;
queue = &per_cpu(softnet_data, i);
skb_queue_head_init(&queue->input_pkt_queue); // 初始化输入数据包队列
queue->completion_queue = NULL; // 完成队列为空
INIT_LIST_HEAD(&queue->poll_list); // 建立轮询设备队列
queue->backlog.poll = process_backlog;
queue->backlog.weight = weight_p;
queue->backlog.gro_list = NULL;
queue->backlog.gro_count = 0;
}
2、网络设备对应的NAPI管理器
struct napi_struct {
### 用于将网络设备对应的此结构挂载到 cpu poll_list 队列上。
# poll_list 队列中的网络设备均已产生了接收中断,通知CPU它们已经收到了数据包
struct list_head poll_list;
unsigned long state; // 该网络设备的状态为:NAPI_STATE_SCHED 时,该设备的
// napi_struct(即为本结构)才加入到当前CPU的poll_list 链表中
// 当 NAPI_STATE_SCHED 位清除后,从队列中移除。
int weight; // 每个设备的poll函数可以从设备缓冲区中读入的数据帧数量,默认为64个
int (*poll)(struct napi_struct *, int);
// NAPI网络设备驱动程序的poll接口
// 该接口由,net_rx_action 调用执行,满足如下条件时,退出
// 1、一次时间片用完
// 2、软件中断执行预算用完
// 3、CPU的输入队列已满
#ifdef CONFIG_NETPOLL
spinlock_t poll_lock;
int poll_owner;
#endif
unsigned int gro_count; // 分片数量
struct net_device *dev; // 每个napi_struct 对应着一个网卡:net_device
struct list_head dev_list; // 内核全局网络设备链表,
struct sk_buff *gro_list; // 一个数据包被分片后,所有分片数据的skb链表
struct sk_buff *skb; // 未分片时,存放单一的数据帧
};
3、数据帧接收处理
{
1、网络设备接收到网络数据帧,存入硬件缓冲区,向CPU发出中断请求。
2、CPU响应设备中断请求后,执行接收中断处理处理程序
将数据帧复制到内核空间的skb中
调用 netif_rx->napi_schedule ,
3、 napi_schedule ,执行过程
将数skb挂载到CPU的输入队列;
查看设备中是否新的数据帧进入
将设备的 napi_struct 实例挂载到 softnet_data.poll_list 队列
挂起接收软件中断 NET_RX_SOFTIRQ
4、接收软件中断被调度执行,执行 net_rx_action,其过程:
调用 softnet_data.poll_list 中设备的poll函数。
xxx_poll 从网络设备读入 新接收的数据帧,
5、xxx_poll
1、查看设备中是否有新的数据帧到达
若有,则复制到内核空间的 skb中,并放到 cpu 输入队列上
2、直到满足以下条件才推出:
一次时间片用完。
软件中断的执行预算用完
CPU输入队列已满
6、netif_rx // 2.6.34
int netif_rx(struct sk_buff *skb)
{
struct softnet_data *queue;
unsigned long flags;
// netpool让内核在整个网络和I/O子系统都失效的情况下发送和接收数据帧,用于远程调试
if (netpoll_rx(skb))
return NET_RX_DROP;
// 将接收数据帧的时间标志到skb->tstamp中,表示netif_rx开始执行
if (!skb->tstamp.tv64)
net_timestamp(skb);
// 关闭CPU本地中断
local_irq_save(flags);
// 获取当前CPU的softnet_data
queue = &__get_cpu_var(softnet_data);
__get_cpu_var(netdev_rx_stat).total++;
################# 队列未满,输入队列最多挂载1000个skb
if (queue->input_pkt_queue.qlen <= netdev_max_backlog)
{
// 队列非空,则只需要将数据帧添加到输入队列即可。
if (queue->input_pkt_queue.qlen)
{
enqueue:
// 将数据帧放入到CPU的输入队列
__skb_queue_tail(&queue->input_pkt_queue, skb);
// 开中断
local_irq_restore(flags);
// 返回接收成功
return NET_RX_SUCCESS;
}
### 如果队列为空,则说明是第一次往CPU输入队列中添加数据帧,即为第一个帧,此时需要标记 软中断
napi_schedule(&queue->backlog);
{
// (struct napi_struct *n)
// 如果满足(未设置 NAPI_STATE_DISABLE , 且之前未设置 NAPI_STATE_SCHED),则说明可用
if (napi_schedule_prep(n))
{
// if
return !napi_disable_pending(n) &&
!test_and_set_bit(NAPI_STATE_SCHED, &n->state);
}
// 将 CPU队列的napi_struct(softnet_data.backlog)挂载到 cpu的poll_list链表上,并标记软件中断 NET_RX_SOFTIRQ
__napi_schedule(n);
{
unsigned long flags;
local_irq_save(flags);
list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
local_irq_restore(flags);
}
}
### 标记软件中断后,还需将本次skb 加到输入队列中。
goto enqueue;
}
################# 如果队列已满,则扔掉数据帧,释放skb
__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
}
}
}
5.2、网络接收软件中断 NET_RX_SOFTIRQ 的调度执行
{
1、主要任务
将 cpu输入队列中的skb推送给网络层协议处理函数
2、net_rx_action 的实现
void net_rx_action(struct softirq_action *h)
{
##################### 函数初始化阶段
struct list_head *list = &__get_cpu_var(softnet_data).poll_list; // 获取当前CPU的数据队列
unsigned long time_limit = jiffies + 2; // 设置此接口执行的最长时间 2个tick
int budget = netdev_budget; // 一次能处理的最大数据帧数 netdev_budget
void *have;
// 禁止本地所有中断
local_irq_disable();
##################### 遍历 softnet_data.poll_list 链表中的 每个 napi_struct 实例
while (!list_empty(list))
{
struct napi_struct *n;
int work, weight;
################# 如果处理的数据帧数 达到上限 300,或者执行时间到期,则退出循环
if (unlikely(budget <= 0 || time_after(jiffies, time_limit)))
goto softnet_break;
################# 开中断
local_irq_enable();
// 获取一个 napi_struct 实例
n = list_first_entry(list, struct napi_struct, poll_list);
have = netpoll_poll_lock(n);
// 此 napi_struct 实例对应的poll 函数一次可以处理的 数据帧最大数目
weight = n->weight;
################# 在 netif_rx->napi_schedule 之前已经设置过此标识位
# 执行 poll
work = 0;
if (test_bit(NAPI_STATE_SCHED, &n->state))
{
############# 执行 poll-> process_backlog
work = n->poll(n, weight);
{
// net/core/dev.c
// (struct napi_struct *napi, int quota)
int work = 0;
struct softnet_data *queue = &__get_cpu_var(softnet_data);
unsigned long start_time = jiffies;
napi->weight = weight_p;
################### 依次取出cpu数据队列中的skb,将其扔给网络层特定协议处理函数
# 满足以下条件之一后退出
# 所取skb的个数已达上限
# 函数执行时间已达上限
# 队列已为空。
do
{
struct sk_buff *skb;
############### 由于执行poll接口时,本地中断是打开的,因而此过程可能被打断
# 所以在访问 cpu队列,从中取出skb时,应该禁止本地中断。
local_irq_disable();
// 取出一个 skb
skb = __skb_dequeue(&queue->input_pkt_queue);
if (!skb)
{
__napi_complete(napi);
local_irq_enable();
break;
}
############### 再次打开本地中断,如果此时网卡有接收新的数据包,本函数会被打断
# 网卡会将新接收到的数据包skb放到 队列 queue->input_pkt_queue 中
local_irq_enable();
netif_receive_skb(skb);
} while (++work < quota && jiffies == start_time);
return work;
}
trace_napi_poll(n);
}
WARN_ON_ONCE(work > weight);
budget -= work;
local_irq_disable();
################# 如果处理的帧数已达上限,则执行如下处理
if (unlikely(work == weight))
{
// 如果有调用 napi_disable 设置了 NAPI_STATE_DISABLE ,则
// 清除 NAPI_STATE_SCHED
// 将此 napi_struct 实例从 cpu数据队列.poll_list 上移除
if (unlikely(napi_disable_pending(n)))
{
local_irq_enable();
napi_complete(n);
local_irq_disable();
}
else // 否则将其移到 poll_list 链表的尾部。
list_move_tail(&n->poll_list, list);
}
netpoll_poll_unlock(have);
}
out:
local_irq_enable();
#ifdef CONFIG_NET_DMA
/*
* There may not be any more sk_buffs coming right now, so push
* any pending DMA copies to hardware
*/
dma_issue_pending_all();
#endif
return;
##################### 如果队列中仍有数据帧,但是 net_rx_action 被强迫返回,则
# 重新标记网络接收软件中断 NET_RX_SOFTIRQ,以便下次内核调用时,处理余下的数据。
softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
}
3、netif_receive_skb
netif_receive_skb(skb);
{
struct packet_type *ptype, *pt_prev;
struct net_device *orig_dev;
struct net_device *master;
struct net_device *null_or_orig;
struct net_device *null_or_bond;
int ret = NET_RX_DROP;
__be16 type;
##################### 设置时间戳
if (!skb->tstamp.tv64)
net_timestamp(skb);
##################### 如果wlan跟踪器要接收此帧,
if (vlan_tx_tag_present(skb) && vlan_hwaccel_do_receive(skb))
return NET_RX_SUCCESS;
##################### 是否将此帧传给 netpoll
if (netpoll_receive_skb(skb))
return NET_RX_DROP;
##################### 判断接收此帧的 网络设备是否属于 某个网络接口组
# 对于接口组:
# 如果其中任何一个成员网卡收到数据帧,在上传给网络层处理之前,
# 此skb中 对接收数据帧设备的 引用计数 必须修改为 对组中主设备的
if (!skb->skb_iif)
skb->skb_iif = skb->dev->ifindex;
null_or_orig = NULL;
orig_dev = skb->dev;
master = ACCESS_ONCE(orig_dev->master);
if (master)
{
if (skb_bond_should_drop(skb, master))
null_or_orig = orig_dev; /* deliver only exact match */
else
skb->dev = master;
}
##################### 更新 已收到数据帧数目 统计信息
__get_cpu_var(netdev_rx_stat).total++;
##################### 初始化skb结构体中,MAC层、网络层、传输层的协议头指针
skb_reset_network_header(skb); // skb->network_header = skb->data;
skb_reset_transport_header(skb); // skb->transport_header = skb->data;
skb->mac_len = skb->network_header - skb->mac_header;
pt_prev = NULL;
##################### 开始数据帧继续传递的过程
#
rcu_read_lock();
#ifdef CONFIG_NET_CLS_ACT // yes
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
}
#endif
##################### 给所有的协议嗅探器 发送一个数据帧的 拷贝
list_for_each_entry_rcu(ptype, &ptype_all, list)
{
if (ptype->dev == null_or_orig || ptype->dev == skb->dev || ptype->dev == orig_dev)
{
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
##################### 如果配置了 输入数据帧过滤特性,
# 则由 ing_filter 决定如何对数据帧进一步处理(或丢掉)
#ifdef CONFIG_NET_CLS_ACT
skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
ncls:
#endif
##################### 查看桥代码 是否应该处理 此输入数据帧。是则返回1,否则返回0
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
/*
* Make sure frames received on VLAN interfaces stacked on
* bonding interfaces still make their way to any base bonding
* device that may have registered for a specific ptype. The
* handler may have to adjust skb->dev and orig_dev.
*/
null_or_bond = NULL;
if ((skb->dev->priv_flags & IFF_802_1Q_VLAN) &&
(vlan_dev_real_dev(skb->dev)->priv_flags & IFF_BONDING)) {
null_or_bond = vlan_dev_real_dev(skb->dev);
}
##################### 将数据帧发送给 网络层所有注册了 接受处理程序的协议实例
type = skb->protocol;
list_for_each_entry_rcu(ptype,&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list)
{
if (ptype->type == type &&
(ptype->dev == null_or_orig ||
ptype->dev == skb->dev ||
ptype->dev == orig_dev ||
ptype->dev == null_or_bond
)
)
{
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
{
atomic_inc(&skb->users);
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
pt_prev = ptype;
}
}
if (pt_prev)
{
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
else
{
kfree_skb(skb);
ret = NET_RX_DROP;
}
out:
rcu_read_unlock();
return ret;
}
}
5.3、数据帧的发送 dev_queue_xmit
{
1、启动/停止设备发送队列
{
// 通过 清除/设定 netdev_queue.state.__QUEUE_STATE_XOFF 来实现 启动/停止
1、启动 ndo_open -> netif_start_queue
// 其他启动场合: 如果网络设备产生的中断是 上次发送完成中断,则driver应该调用之
{
// netdevice.h
netif_tx_start_queue(netdev_get_tx_queue(dev, 0));
clear_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
2、停止 netif_stop_queue
// 当driver发现 设备硬件没有足够的空间来缓存新的数据帧时,须停止发送队列
{
netif_tx_stop_queue(netdev_get_tx_queue(dev, 0));
set_bit(__QUEUE_STATE_XOFF, &dev_queue->state);
}
}
2、dev_queue_xmit 的调用须知
调用此函数之前
网络层需要对 数据帧做如下信息保存
输出网络地址
下一跳IP地址
MAC地址
链路层需要做如下信息保存:
3、dev_queue_xmit的实现
dev_queue_xmit(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct netdev_queue *txq;
struct Qdisc *q;
int rc = -ENOMEM;
##################### 满足如下条件,则直接使用GSO模式
# skb_shared_info.gso_size 非0,且
# 或者 dev->features 未设置 skb_shinfo(skb)->gso_type
# 或者 (skb_shared_info.frag_list 非空 且 dev->features 未设置 NETIF_F_FRAGLIST )
# 或者 skb_shared_info.gso_size 非0,且
# skb->ip_summed != CHECKSUM_PARTIAL
#
if (netif_needs_gso(dev, skb))
{
// if
return skb_is_gso(skb) &&
(!skb_gso_ok(skb, dev->features) ||
unlikely(skb->ip_summed != CHECKSUM_PARTIAL)
);
## 1、 skb_is_gso(skb)
return skb_shinfo(skb)->gso_size;
## 2、 skb_gso_ok(skb, dev->features)
return net_gso_ok(features, skb_shinfo(skb)->gso_type) && // dev->features 设置了 skb_shinfo(skb)->gso_type
(!skb_has_frags(skb) || // skb_shinfo(skb)->frag_list != NULL;
(features & NETIF_F_FRAGLIST) // dev->features 设置了 NETIF_F_FRAGLIST
);
## 3、 net_gso_ok(features, skb_shinfo(skb)->gso_type)
int feature = gso_type << NETIF_F_GSO_SHIFT;
return (features & feature) == feature;
}
goto gso;
##################### 如果负载数据被分片,满足如下条件之一,则将分片组装成一个完整的数据块
# 设备不支持 Scatter/gather DMA 特性。或者
# 设备支持DMA特性,但分片数据存储在高端地址,设备不能访问这些地址
# 调用 __skb_linearize 将分片组装起来
#
if (skb_needs_linearize(skb, dev) && __skb_linearize(skb))
{
// if
## 1、 skb_needs_linearize(skb, dev) &&
return (skb_has_frags(skb) && // skb_shinfo(skb)->frag_list != NULL;且
!(dev->features & NETIF_F_FRAGLIST) // dev->features 未设置 NETIF_F_FRAGLIST
) || // 或者
(skb_shinfo(skb)->nr_frags && // skb_shared_info.nr_frags 非空,且
(!(dev->features & NETIF_F_SG) || // dev->features 未设置 NETIF_F_SG 或者
illegal_highdma(dev, skb) ) // dev->features 设置了 NETIF_F_HIGHDMA 或者
// skb_shared_info.frags[x].page 确实为高端内存
);
## 2、 illegal_highdma(dev, skb)
#ifdef CONFIG_HIGHMEM
int i;
if (dev->features & NETIF_F_HIGHDMA) return 0;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
if (PageHighMem(skb_shinfo(skb)->frags[i].page)) return 1;
#endif
return 0;
## 2、 __skb_linearize(skb)
return __pskb_pull_tail(skb, skb->data_len) ? 0 : -ENOMEM;
{
// skb 分片数据长度
int i, k;
################### tail+delta个字节 到 缓冲区末尾的距离
int eat = (skb->tail + delta) - skb->end;
################### 缓冲区空间扩展
// 如果此skb缓冲区的尾部没有足够的空间,则分配128字节的用于将来扩展。
// 如果有空间,当且仅当skb是克隆型的,才对tail后续空间进行充分配(不扩展)
if (eat > 0 || skb_cloned(skb))
{
if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0,GFP_ATOMIC))
return NULL;
}
if (skb_copy_bits(skb, skb_headlen(skb), skb_tail_pointer(skb), delta))
{
// if (const struct sk_buff *skb, int offset, void *to, int len)
// skb , skb->len - skb->data_len; ,
int start = skb_headlen(skb); // 此skb中除开 负载数据之外的 总长度
struct sk_buff *frag_iter;
int i, copy;
//
if (offset > (int)skb->len - len)
goto fault;
############### 如果 起始地址高于 所需偏移,应该复制header 到 目的地址(skb->tail)
if ((copy = start - offset) > 0)
{
if (copy > len)
copy = len;
skb_copy_from_linear_data_offset(skb, offset, to, copy);
if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
############### 扫描每个TCP分段 ,将其依次复制到 skb->tail处
# 通常情况下,初始传递的 offset = start = hsize ,则有
# 第一次: offset == start = hsize
# _end = start + size1
# copy = size1
# to = skb->tail
# from = frags[0].page_offset
# 第二次: offset = hsize + size1
# start = hsize + size1
# _end = start + size2 = hsize + size1 + size2
# copy = size2
# to = skb->tail + size1
# from = frags[1].page_offset
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
{
int end;
WARN_ON(start > offset + len);
########### 每次复制的字节数: frags[i].size
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0)
{
u8 *vaddr;
if (copy > len) copy = len;
// 获取分片数据包所在页的虚拟地址,将分片页中的数据负载 copy 到 skb->tail指向的内存处
vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
memcpy(to,vaddr + skb_shinfo(skb)->frags[i].page_offset+ offset - start, copy);
kunmap_skb_frag(vaddr);
if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
start = end;
}
############### 扫描所有的IP分片对应的skb,对每个skb的分段做递归处理
skb_walk_frags(skb, frag_iter)
// for (iter = skb_shinfo(skb)->frag_list; frag_iter; frag_iter = frag_iter->next)
{
int end;
WARN_ON(start > offset + len);
end = start + frag_iter->len;
if ((copy = end - offset) > 0)
{
if (copy > len) copy = len;
if (skb_copy_bits(frag_iter, offset - start, to, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
start = end;
}
if (!len)
return 0;
fault:
return -EFAULT;
}
BUG();
################### 如果没有 IP 分片,
if (!skb_has_frags(skb)) // 如果 skb_shinfo(skb)->frag_list == NULL
goto pull_pages;
################### 决定 要 pull_pages的 字节数
# 如果某个段对应页的size 超过了 总的需求量,则退出去做 pull_pages 处理
# 否则,eat -= 多个 size 的和
eat = delta;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
{
if (skb_shinfo(skb)->frags[i].size >= eat)
goto pull_pages;
eat -= skb_shinfo(skb)->frags[i].size;
}
###################
# 通常情况下,pulling操作是被认为十分罕见的,因而要坚决阻止对skb的头部做频繁处理
#而是尽量在此处做处理。
if (eat)
{
struct sk_buff *list = skb_shinfo(skb)->frag_list;
struct sk_buff *clone = NULL;
struct sk_buff *insp = NULL;
do
{
BUG_ON(!list);
if (list->len <= eat) {
/* Eaten as whole. */
eat -= list->len;
list = list->next;
insp = list;
} else {
/* Eaten partially. */
if (skb_shared(list)) {
/* Sucks! We need to fork list. :-( */
clone = skb_clone(list, GFP_ATOMIC);
if (!clone) return NULL;
insp = list->next;
list = clone;
} else {
/* This may be pulled without problems. */
insp = list;
}
if (!pskb_pull(list, eat))
{
kfree_skb(clone);
return NULL;
}
break;
}
} while (eat);
/* Free pulled out fragments. */
while ((list = skb_shinfo(skb)->frag_list) != insp) {
skb_shinfo(skb)->frag_list = list->next;
kfree_skb(list);
}
/* And insert new clone at head. */
if (clone) {
clone->next = list;
skb_shinfo(skb)->frag_list = clone;
}
}
}
}
goto out_kfree_skb;
/* If packet is not checksummed and device does not support
* checksumming for this protocol, complete checksumming here.
*/
##################### 如果数据包在此未做 检验和计算,下列情况中需要调用 skb_checksum_help 来做计算
# 设备硬件不支持 对检验和的硬计算
# 设备只支持在 IP协议上发送的 TCP/UDP数据帧的检验和计算,
# 然而当前发送的数据帧不采用IP协议,或传输层使用其他协议
if (skb->ip_summed == CHECKSUM_PARTIAL)
{
skb_set_transport_header(skb, skb->csum_start -skb_headroom(skb));
{
// skb , return skb->data - skb->head;
skb->transport_header = skb->data + offset;
}
if (!dev_can_checksum(dev, skb) && skb_checksum_help(skb))
goto out_kfree_skb;
}
gso:
##################### 禁止软件中断、禁止RCU抢占优先,获取设备发送队列
rcu_read_lock_bh();
txq = dev_pick_tx(dev, skb);
{
u16 queue_index;
struct sock *sk = skb->sk;
if (sk_tx_queue_recorded(sk)) // (sk && sk->sk_tx_queue_mapping >= 0);
{
queue_index = sk_tx_queue_get(sk); // sk->sk_tx_queue_mapping;
}
else
{
const struct net_device_ops *ops = dev->netdev_ops;
if (ops->ndo_select_queue) // 对于 BCM40181 NO
{
queue_index = ops->ndo_select_queue(dev, skb);
queue_index = dev_cap_txqueue(dev, queue_index);
}
else
{
queue_index = 0;
if (dev->real_num_tx_queues > 1) // 对于 BCM40181 NO
queue_index = skb_tx_hash(dev, skb);
if (sk)
{
struct dst_entry *dst = rcu_dereference_bh(sk->sk_dst_cache);
if (dst && skb_dst(skb) == dst)
sk_tx_queue_set(sk, queue_index);
}
}
}
skb_set_queue_mapping(skb, queue_index); // skb->queue_mapping = queue_mapping;
return netdev_get_tx_queue(dev, queue_index); // return &dev->_tx[0];
}
q = rcu_dereference_bh(txq->qdisc); // Qdisc noop_qdisc
#ifdef CONFIG_NET_CLS_ACT
skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_EGRESS);
#endif
##################### 如果设备支持发送队列,则将数据帧放入到 设备发送队列中
if (q->enqueue) // noop_qdisc.noop_enqueue,
{
rc = __dev_xmit_skb(skb, q, dev, txq);
{
/*
(struct sk_buff *skb,
struct Qdisc *q,
struct net_device *dev,
struct netdev_queue *txq)
*/
// 获取 数据缓冲区队列qdisc 保护锁
spinlock_t *root_lock = qdisc_lock(q);
int rc;
spin_lock(root_lock);
############# 如果 数据帧缓冲区 处于非激活态,则扔掉数据包
if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state)))
{
kfree_skb(skb);
rc = NET_XMIT_DROP;
}
else if ( (q->flags & TCQ_F_CAN_BYPASS) && // 设备队列有标识 TCQ_F_CAN_BYPASS
!qdisc_qlen(q) && // q->q.qlen == 0
!test_and_set_bit(__QDISC_STATE_RUNNING, &q->state)) // q->state 中不包含 __QDISC_STATE_RUNNING 标识
{
__qdisc_update_bstats(q, skb->len);
################### 这是一个保守型的工作队列(其中没有旧的的skb残留,且qdisc未运行)
# 此时直接调用 __qdisc_run 发送数据帧
if (sch_direct_xmit(skb, q, dev, txq, root_lock))
{
// if
int ret = NETDEV_TX_BUSY;
// 释放 数据缓冲区队列 qdisc 锁
spin_unlock(root_lock);
############### 如果driver未实现自己的锁定机制,则需 获取 网络设备发送队列保护锁 txq->_xmit_lock ,
# 如果driver实现了,则会设置 dev->features.NETIF_F_LLTX 位,从而告诉内核不需额外获取此锁
HARD_TX_LOCK(dev, txq, smp_processor_id());
############### 如果设备发送队列未停止,则调用 dev_hard_start_xmit 将数据帧放入设备发送队列中。
if (!netif_tx_queue_stopped(txq) && !netif_tx_queue_frozen(txq))
ret = dev_hard_start_xmit(skb, dev, txq);
{
const struct net_device_ops *ops = dev->netdev_ops;
int rc = NETDEV_TX_OK;
################# 通常 skb 未组织成链表
if (likely(!skb->next))
{
if (!list_empty(&ptype_all))
dev_queue_xmit_nit(skb, dev);
if (netif_needs_gso(dev, skb)) {
if (unlikely(dev_gso_segment(skb)))
goto out_kfree_skb;
if (skb->next)
goto gso;
}
/*
* If device doesnt need skb->dst, release it right now while
* its hot in this cpu cache
*/
if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
skb_dst_drop(skb);
rc = ops->ndo_start_xmit(skb, dev);
if (rc == NETDEV_TX_OK)
txq_trans_update(txq);
/*
* TODO: if skb_orphan() was called by
* dev->hard_start_xmit() (for example, the unmodified
* igb driver does that; bnx2 doesn't), then
* skb_tx_software_timestamp() will be unable to send
* back the time stamp.
*
* How can this be prevented? Always create another
* reference to the socket before calling
* dev->hard_start_xmit()? Prevent that skb_orphan()
* does anything in dev->hard_start_xmit() by clearing
* the skb destructor before the call and restoring it
* afterwards, then doing the skb_orphan() ourselves?
*/
return rc;
}
gso:
do
{
struct sk_buff *nskb = skb->next;
skb->next = nskb->next;
nskb->next = NULL;
/*
* If device doesnt need nskb->dst, release it right now while
* its hot in this cpu cache
*/
if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
skb_dst_drop(nskb);
rc = ops->ndo_start_xmit(nskb, dev);
if (unlikely(rc != NETDEV_TX_OK)) {
if (rc & ~NETDEV_TX_MASK)
goto out_kfree_gso_skb;
nskb->next = skb->next;
skb->next = nskb;
return rc;
}
txq_trans_update(txq);
if (unlikely(netif_tx_queue_stopped(txq) && skb->next))
return NETDEV_TX_BUSY;
} while (skb->next);
out_kfree_gso_skb:
if (likely(skb->next == NULL))
skb->destructor = DEV_GSO_CB(skb)->destructor;
out_kfree_skb:
kfree_skb(skb);
return rc;
}
HARD_TX_UNLOCK(dev, txq);
spin_lock(root_lock);
############### 查看 dev_hard_start_xmit 的返回结果
#
## 发送成功,返回队列数据长度 q->q.qlen;
if (dev_xmit_complete(ret))
{
/* Driver sent out skb successfully or skb was consumed */
ret = qdisc_qlen(q);
}
## 驱动程序获取锁失败,说明当前有别的CPU正在通过该网络设备发送数据。
# 此时需要将skb重新放回原来队列中
else if (ret == NETDEV_TX_LOCKED) {
ret = handle_dev_cpu_collision(skb, txq, q);
}
## 设备中无足够空间缓存此skb,应将数据帧重新放回队列
# 此时需要将skb重新放回原来队列中
else
{
if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit()))
printk(KERN_WARNING "BUG %s code %d qlen %d\n",dev->name, ret, q->q.qlen);
ret = dev_requeue_skb(skb, q);
}
if (ret && (netif_tx_queue_stopped(txq) || netif_tx_queue_frozen(txq)))
ret = 0;
return ret;
}
################### 当一个设备被调度来发送数据帧时,下一个要发送的数据帧由 此接口来选中。
# 满足如下条件时,才会执行到此
# (设备发送队列未被停止 || 设备发送队列未被冻结) 且
# 当前skb发送成功,ret = 发送的数据包长度 , 或者
# 驱动程序获取锁失败,skb被重新放回 缓冲区队列, 或者
# 设备中无足够空间缓存此skb,应将数据帧重新放回队列
__qdisc_run(q);
{
unsigned long start_time = jiffies;
while (qdisc_restart(q))
{
{
// while
struct netdev_queue *txq;
struct net_device *dev;
spinlock_t *root_lock;
struct sk_buff *skb;
############# 从 数据帧缓冲区中 读取一个 skb
skb = dequeue_skb(q);
if (unlikely(!skb))
return 0;
############# 获取 数据缓冲区队列 qdisc 锁,获取 设备发送队列 dev->_tx[0]
root_lock = qdisc_lock(q);
dev = qdisc_dev(q);
txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb)/*skb->queue_mapping;*/);
############# 执行发送
return sch_direct_xmit(skb, q, dev, txq, root_lock);
}
################# 当其他进程需要此CPU,或者时间片到期,则做如下处理:
# 将网络设备放到CPU发送队列 softnet_data->output_queue 上
# 标识网络发送软件中断 NET_TX_SOFTIRQ
if (need_resched() || jiffies != start_time) {
__netif_schedule(q);
{
################### 检查设备是否已被调度过,如没有,则标记 __QDISC_STATE_SCHED
if (!test_and_set_bit(__QDISC_STATE_SCHED, &q->state))
__netif_reschedule(q);
{
struct softnet_data *sd;
unsigned long flags;
local_irq_save(flags);
########### 挂载 当前设备输出队列 到 CPU 的发送队列上
sd = &__get_cpu_var(softnet_data);
q->next_sched = sd->output_queue;
sd->output_queue = q;
########### 标记网络发送软件中断
raise_softirq_irqoff(NET_TX_SOFTIRQ);
local_irq_restore(flags);
}
}
break;
}
}
clear_bit(__QDISC_STATE_RUNNING, &q->state);
}
else
clear_bit(__QDISC_STATE_RUNNING, &q->state);
rc = NET_XMIT_SUCCESS;
} else {
rc = qdisc_enqueue_root(skb, q);
qdisc_run(q);
}
spin_unlock(root_lock);
return rc;
}
goto out;
}
##################### 如果设备无发送队列,对于lookback设备,做以下处理
# 立即将数据发送出去,如果发送成功,则在另一端会有调用 netif_rx 接收之
# 如果发送不成功,则直接将数据扔掉: 如果上层使用了TCP传输,则上层协议最终会重新传送数据帧。
loopback, all the sorts of tunnels...
Really, it is unlikely that netif_tx_lock protection is necessary
here. (f.e. loopback and IP tunnels are clean ignoring statistics
counters.)
However, it is possible, that they rely on protection
made by us here.
Check this and shot the lock. It is not prone from deadlocks.
Either shot noqueue qdisc, it is even simpler 8)
*/
if (dev->flags & IFF_UP)
{
int cpu = smp_processor_id(); /* ok because BHs are off */
if (txq->xmit_lock_owner != cpu)
{
HARD_TX_LOCK(dev, txq, cpu);
if (!netif_tx_queue_stopped(txq)) {
rc = dev_hard_start_xmit(skb, dev, txq);
if (dev_xmit_complete(rc)) {
HARD_TX_UNLOCK(dev, txq);
goto out;
}
}
HARD_TX_UNLOCK(dev, txq);
if (net_ratelimit())
printk(KERN_CRIT "Virtual device %s asks to " "queue packet!\n", dev->name);
}
else {
/* Recursion is detected! It is possible,
* unfortunately */
if (net_ratelimit())
printk(KERN_CRIT "Dead loop on virtual device " "%s, fix it urgently!\n", dev->name);
}
}
rc = -ENETDOWN;
rcu_read_unlock_bh();
out_kfree_skb:
kfree_skb(skb);
return rc;
out:
rcu_read_unlock_bh();
return rc;
}
}
5.4、网络发送软件中断 NET_TX_SOFTIRQ
{
1、触发地点
当网络设备有足够缓冲区时,调用 netif_wake_queue
当发送结束,driver通知内核相关的skb可以被释放时,调用 dev_kfree_skb_irq
dev_kfree_skb 将skb释放回每个CPU的高速缓冲存储器中
dev_kfree_skb_irq 将skb指针存放到 softnet_data.completion_queue 队列中
后续由 netif_tx_action 来做真实释放
2、netif_tx_action
void net_tx_action(struct softirq_action *h)
{
struct softnet_data *sd = &__get_cpu_var(softnet_data);
##################### 回收已发送完成的 skb 缓冲区
if (sd->completion_queue)
{
struct sk_buff *clist;
################# 由于此接口运行过程中,可能有中断发生,
# driver可能会放入新的skb指针到 completion_queue 中,因此访问此队列时要先关中断
# 保存 队列头指针到 clist , 并将 completion_queue 清空
local_irq_disable();
clist = sd->completion_queue;
sd->completion_queue = NULL;
local_irq_enable();
################# 遍历上一次发送完毕后需要释放的skb链表,释放所有的 skb 缓冲区
while (clist) {
struct sk_buff *skb = clist;
clist = clist->next;
WARN_ON(atomic_read(&skb->users));
__kfree_skb(skb);
}
}
##################### 发送数据帧
if (sd->output_queue)
{
################# 保存 发送设备队列 的地址
struct Qdisc *head;
local_irq_disable();
head = sd->output_queue;
sd->output_queue = NULL;
local_irq_enable();
################# 遍历 输出设备队列,获取每个设备输出队列锁,调用 qdisc_run 尝试发送
while (head)
{
struct Qdisc *q = head;
spinlock_t *root_lock;
head = head->next_sched;
root_lock = qdisc_lock(q);
############# 如果获取 设备输出队列锁成功,则调用 qdisc_run 尝试发送
if (spin_trylock(root_lock))
{
smp_mb__before_clear_bit();
clear_bit(__QDISC_STATE_SCHED,&q->state);
qdisc_run(q);
spin_unlock(root_lock);
}
############# 获取设备输出队列锁失败,则重新申请调度,并将此设备放入到 CPU 输出设备队列中。
else
{
if (!test_bit(__QDISC_STATE_DEACTIVATED, &q->state)) {
__netif_reschedule(q);
}
else
{
smp_mb__before_clear_bit();
clear_bit(__QDISC_STATE_SCHED,&q->state);
}
}
}
}
}
}
}
7、网络层实现
{
6.1、链路层与网络层之间 接收数据包的 接口
{
1、 netif_receive_skb 将数据帧上传给谁。
通过 sk_buff->protocol 来确定应该将输入数据帧上传给网络层中哪个协议的接收处理程序。
此过程,即为: 链路层处理程序 解析MAC头信息,据此判断应该把数据帧上传给谁。
2、上传之前的准备工作
// 移动 网络层协议头指针、传输层协议头指针
netif_receive_skb
{
##################### 初始化skb结构体中,网络层、传输层的协议头指针
skb_reset_network_header(skb); // skb->network_header = skb->data;
skb_reset_transport_header(skb); // skb->transport_header = skb->data;
skb->mac_len = skb->network_header - skb->mac_header;
}
3、接口的实现与组织
{
//-----------------------------------------------------------------------------------------
//----------------------------- 每个网络层协议实例,都具备一个 packet_type 结构
//
struct packet_type
{
__be16 type; // 协议标识符 ETH_P_XX , 例如IP协议: ETH_P_IP
struct net_device *dev; // 如果非空,表明只有从指定的网络设备接收到的数据帧才会传给此协议实例
// 如果为空,链路层在选择协议处理程序时,网络设备将不起作用。
// 协议实例实现的 数据帧接收处理函数,由 netif_receive_skb 调用。例如IP协议,为 ip_rcv
int (*func) (struct sk_buff *,
struct net_device *,
struct packet_type *,
struct net_device *);
// 如果设备支持 Scatter/gather DMA 特性,则用此接口来做IP分片
struct sk_buff *(*gso_segment)(struct sk_buff *skb, int features);
int (*gso_send_check)(struct sk_buff *skb);
struct sk_buff **(*gro_receive)(struct sk_buff **head, struct sk_buff *skb);
int (*gro_complete)(struct sk_buff *skb);
void *af_packet_priv; // 协议自身使用的私有数据,通常无意义
struct list_head list; //
};
//----------------------------- IPV4 packet_type 结构实例
// af_inet.c
static struct packet_type ip_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
.gso_send_check = inet_gso_send_check,
.gso_segment = inet_gso_segment,
.gro_receive = inet_gro_receive,
.gro_complete = inet_gro_complete,
};
//-----------------------------------------------------------------------------------------
//----------------------------- 组织结构 网络层协议实例接口向量表
// dev.c
##### 此组协议实例接受处理函数:接收所有的数据帧,用于网络工具、网络探测器
static struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
##### 只接收与协议标识符相匹配的 数据帧
static struct list_head ptype_all __read_mostly; /* Taps */
##### 纵向组织结构:
以skb->protocol=packet_type.type的hash值为索引的,链表头数组。
##### 横向组织结构:
同一个 list_head 链表上,每个节点挂载着一个 packet_type 结构体
表明某个数据帧,可以被多个网络协议实例的接收处理函数处理。
}
4、网接口向量表 ptype_all 中 增加/删除新的网络协议
dev_add_pack
dev_remove_pack
}
6.2、IP协议头结构体 iphdr
{
// ip.h
struct iphdr
{
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD) // yes
__u8 version:4, // IP协议版本 V4/V6
ihl:4; // 协议头长度 共16个单元/ 每个单元4Byte,共 64Byte
#else
#error "Please fix "
#endif
__u8 tos; // type of services 服务类型,告诉路由器 数据包发送者最关心的发送特性(最小延迟、最大吞吐量)
/* RFC1349定义:
0------2----------------6--7
优先级 TOS 0
RFC2474定义:
0-----------------------6--7
Diff Serv Code Point CU(当前未使用)
DSCP查分服务类型:描述如何处理数据包
*/
__be16 tot_len; // IP数据包总长度(IP协议头 + 负载数据)
__be16 id; // 数据包标识符:一个数据包分成多个片之后,属于同一个包的片具有相同的id值
// 数据片传输到目标主机后,IP函数根据id值将上述数据片组装成一个完整的包
__be16 frag_off; // 分片数据在数据包中的偏移量
__u8 ttl; /* 数据包存活期:从发送开始经过多少秒之后,尚未到达目标主机,则扔掉该包
每个路由器在将接收到得数据包前送之前,都会将其TTL值--,当TTL==0,则扔掉包
默认值为 64,如果数据包被扔掉,则数据包发送方会受到一条 ICMP 消息
*/
__u8 protocol; // 传输层使用的协议,IP层协议处理程序 根据该字段将数据包 传递给 上层协议处理程序
// 定义位于 include/linux/in.h
__sum16 check; // 检验和 对IP协议头信息 做的检验和
__be32 saddr; // 数据包源IP地址
__be32 daddr; // 目的IP地址
/*The options start here. */
//-------------------- 从此以后为 IP 选项区
/*
0 / N 个 4字节单元
*/
};
//----------------------- 关于protocol字段
/* Standard well-defined IP protocols. */
enum {
IPPROTO_IP = 0, /* Dummy protocol for TCP */
IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
IPPROTO_TCP = 6, /* Transmission Control Protocol */
IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
IPPROTO_PUP = 12, /* PUP protocol */
IPPROTO_UDP = 17, /* User Datagram Protocol */
IPPROTO_IDP = 22, /* XNS IDP protocol */
IPPROTO_DCCP = 33, /* Datagram Congestion Control Protocol */
IPPROTO_RSVP = 46, /* RSVP protocol */
IPPROTO_GRE = 47, /* Cisco GRE tunnels (rfc 1701,1702) */
IPPROTO_IPV6 = 41, /* IPv6-in-IPv4 tunnelling */
IPPROTO_ESP = 50, /* Encapsulation Security Payload protocol */
IPPROTO_AH = 51, /* Authentication Header protocol */
IPPROTO_BEETPH = 94, /* IP option pseudo header for BEET */
IPPROTO_PIM = 103, /* Protocol Independent Multicast */
IPPROTO_COMP = 108, /* Compression Header protocol */
IPPROTO_SCTP = 132, /* Stream Control Transport Protocol */
IPPROTO_UDPLITE = 136, /* UDP-Lite (RFC 3828) */
IPPROTO_RAW = 255, /* Raw IP packets */
IPPROTO_MAX
};
}
6.3、IP层数据包接收处理
{
6.4.1、ip_rcv 对数据包进行有效性检查,并交由nfilter去过滤
int ip_rcv( struct sk_buff *skb,
struct net_device *dev,
struct packet_type *pt,
struct net_device *orig_dev)
{
struct iphdr *iph;
u32 len;
/* When the interface is in promisc. mode, drop all the crap
* that it receives, do not try to analyse it.
*/
##################### 如果数据类型为 PACKET_OTHERHOST(网络设备工作在混杂模式时,
# 不管MAC地址是否与本机相同,直接扔给传输层,因而此处不做处理)
if (skb->pkt_type == PACKET_OTHERHOST) goto drop;
IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);
##################### 数据包共享处理
# 如果 skb 引用计数>1,说明内核中有其他进程在使用这个数据包,因而要clone一份给 ip_rcv 处理
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
{
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
goto out;
}
##################### IP 协议头信息的正确性检查
#
### 1、判断 skb->data 指向的数据区中 须存在一块区域 与 IP协议头中指定的长度一致
if (!pskb_may_pull(skb, sizeof(struct iphdr))) goto inhdr_error;
{
// if (struct sk_buff *skb, unsigned int len)
if (likely(len <= skb_headlen(skb))) return 1; // 如果(skb->len-skb->data_len) >= sizeof(struct iphdr)
if (unlikely(len > skb->len)) return 0;
return __pskb_pull_tail(skb, len - skb_headlen(skb)) != NULL;
{
// (struct sk_buff *skb, int delta) // skb ,
int i, k;
################### tail+delta个字节 到 缓冲区末尾的距离
int eat = (skb->tail + delta) - skb->end;
################### 缓冲区空间扩展
// 如果此skb缓冲区的尾部没有足够的空间,则分配128字节的用于将来扩展。
// 如果有空间,当且仅当skb是克隆型的,才对tail后续空间进行充分配(不扩展)
if (eat > 0 || skb_cloned(skb))
{
if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0,GFP_ATOMIC))
return NULL;
}
if (skb_copy_bits(skb, skb_headlen(skb), skb_tail_pointer(skb), delta))
{
// if (const struct sk_buff *skb, int offset, void *to, int len)
// skb , skb->len - skb->data_len; ,
int start = skb_headlen(skb); // 此skb中除开 负载数据之外的 总长度
struct sk_buff *frag_iter;
int i, copy;
//
if (offset > (int)skb->len - len)
goto fault;
############### 如果 起始地址高于 所需偏移,应该复制header 到 目的地址(skb->tail)
if ((copy = start - offset) > 0)
{
if (copy > len)
copy = len;
skb_copy_from_linear_data_offset(skb, offset, to, copy);
if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
############### 扫描每个TCP分段 ,将其依次复制到 skb->tail处
# 通常情况下,初始传递的 offset = start = hsize ,则有
# 第一次: offset == start = hsize
# _end = start + size1
# copy = size1
# to = skb->tail
# from = frags[0].page_offset
# 第二次: offset = hsize + size1
# start = hsize + size1
# _end = start + size2 = hsize + size1 + size2
# copy = size2
# to = skb->tail + size1
# from = frags[1].page_offset
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
{
int end;
WARN_ON(start > offset + len);
########### 每次复制的字节数: frags[i].size
end = start + skb_shinfo(skb)->frags[i].size;
if ((copy = end - offset) > 0)
{
u8 *vaddr;
if (copy > len) copy = len;
// 获取分片数据包所在页的虚拟地址,将分片页中的数据负载 copy 到 skb->tail指向的内存处
vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
memcpy(to,vaddr + skb_shinfo(skb)->frags[i].page_offset+ offset - start, copy);
kunmap_skb_frag(vaddr);
if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
start = end;
}
############### 扫描所有的IP分片对应的skb,对每个skb的分段做递归处理
skb_walk_frags(skb, frag_iter)
// for (iter = skb_shinfo(skb)->frag_list; frag_iter; frag_iter = frag_iter->next)
{
int end;
WARN_ON(start > offset + len);
end = start + frag_iter->len;
if ((copy = end - offset) > 0)
{
if (copy > len) copy = len;
if (skb_copy_bits(frag_iter, offset - start, to, copy))
goto fault;
if ((len -= copy) == 0)
return 0;
offset += copy;
to += copy;
}
start = end;
}
if (!len)
return 0;
fault:
return -EFAULT;
}
BUG();
################### 如果没有 IP 分片,
if (!skb_has_frags(skb)) // 如果 skb_shinfo(skb)->frag_list == NULL
goto pull_pages;
################### 决定 要 pull_pages的 字节数
# 如果某个段对应页的size 超过了 总的需求量,则退出去做 pull_pages 处理
# 否则,eat -= 多个 size 的和
eat = delta;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
{
if (skb_shinfo(skb)->frags[i].size >= eat)
goto pull_pages;
eat -= skb_shinfo(skb)->frags[i].size;
}
###################
# 通常情况下,pulling操作是被认为十分罕见的,因而要坚决阻止对skb的头部做频繁处理
#而是尽量在此处做处理。
if (eat)
{
struct sk_buff *list = skb_shinfo(skb)->frag_list;
struct sk_buff *clone = NULL;
struct sk_buff *insp = NULL;
do
{
BUG_ON(!list);
if (list->len <= eat) {
/* Eaten as whole. */
eat -= list->len;
list = list->next;
insp = list;
} else {
/* Eaten partially. */
if (skb_shared(list)) {
/* Sucks! We need to fork list. :-( */
clone = skb_clone(list, GFP_ATOMIC);
if (!clone) return NULL;
insp = list->next;
list = clone;
} else {
/* This may be pulled without problems. */
insp = list;
}
if (!pskb_pull(list, eat))
{
kfree_skb(clone);
return NULL;
}
break;
}
} while (eat);
/* Free pulled out fragments. */
while ((list = skb_shinfo(skb)->frag_list) != insp) {
skb_shinfo(skb)->frag_list = list->next;
kfree_skb(list);
}
/* And insert new clone at head. */
if (clone) {
clone->next = list;
skb_shinfo(skb)->frag_list = clone;
}
}
}
}
### 2、确保协议头中基本信息(除开 选项之外)正确,其长度>=5,版本号为 4
iph = ip_hdr(skb);
if (iph->ihl < 5 || iph->version != 4) goto inhdr_error;
### 3、对整个协议头 进行正确性检查
if (!pskb_may_pull(skb, iph->ihl*4)) goto inhdr_error;
### 4、计算检验和,查看结果是否与 协议头中保存的原校验和数值相匹配
iph = ip_hdr(skb);
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl))) goto inhdr_error;
##################### 数据包正确性检查
#
### 1、确保整个数据包长度 不小于 IP协议头中tot_len 给出的实际数据包总长度
# 例如:对于以太网协议,可能会在负载数据后加补丁,以保证总长度不小于链路层允许的最小数据包长度。
### 2、确保 IP协议头不能被分割
# 即 iph->ihl*4 <= IP 数据包的总长度
len = ntohs(iph->tot_len);
if (skb->len < len)
{
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
goto drop;
}
else if (len < (iph->ihl*4)) goto inhdr_error;
##################### 获取一个干净的(无补丁的)skb
if (pskb_trim_rcsum(skb, len))
{
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
goto drop;
}
##################### 清空 控制缓冲区 skb->cb 供IP层处理使用
memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
##################### 如果此skb->destructor 非空,则调用之
skb_orphan(skb);
{
if (skb->destructor) skb->destructor(skb);
skb->destructor = NULL;
skb->sk = NULL;
}
##################### 调用网络过滤子系统的回调函数,对数据包进行安全过滤
# 通过检查后,如果数据包仍未被扔掉,则调用 ip_rcv_finish 做继续处理
return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish);
{
/*
uint8_t pf,
unsigned int hook,
struct sk_buff *skb,
struct net_device *in,
struct net_device *out,
int (*okfn)(struct sk_buff *)
*/
return NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN);
{
/*
uint8_t pf,
unsigned int hook,
struct sk_buff *skb,
struct net_device *in,
struct net_device *out,
int (*okfn)(struct sk_buff *),
int thresh
*/
int ret = nf_hook_thresh(pf, hook, skb, in, out, okfn, thresh);
return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
{
struct list_head *elem;
unsigned int verdict;
int ret = 0;
/* We may already have this, but read-locks nest anyway */
rcu_read_lock();
elem = &nf_hooks[pf][hook];// [PF_INET][NF_INET_PRE_ROUTING]
next_hook:
verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev, outdev, &elem, okfn, hook_thresh);
if (verdict == NF_ACCEPT || verdict == NF_STOP)
{
ret = 1;
}
else if (verdict == NF_DROP)
{
kfree_skb(skb);
ret = -EPERM;
}
else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE)
{
if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,verdict >> NF_VERDICT_BITS))
goto next_hook;
}
rcu_read_unlock();
return ret;
}
if (ret == 1)
ret = okfn(skb);
return ret;
}
}
inhdr_error:
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
out:
return NET_RX_DROP;
}
6.4.2、ip_rcv_finish 将数据包上传给网络层。
int ip_rcv_finish(struct sk_buff *skb)
{
##################### 获取IP 协议头指针、路由表等
const struct iphdr *iph = ip_hdr(skb);
struct rtable *rt;
#################### 如果skb->_skb_dst 为空,则通过路由子系统判断是 本地上传/前送到网络
if (skb_dst(skb) == NULL)
{
int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);
if (unlikely(err))
{
if (err == -EHOSTUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),IPSTATS_MIB_INADDRERRORS);
else if (err == -ENETUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),IPSTATS_MIB_INNOROUTES);
goto drop;
}
}
#################### 处理IP选项
### 1、如果包含有选项,则调用 ip_rcv_options 处理
# 处理失败,则丢掉
if (iph->ihl > 5 && ip_rcv_options(skb))
goto drop;
#################### 统计收到的各类数据包计数:组传送 、 处理选项
rt = skb_rtable(skb);
if (rt->rt_type == RTN_MULTICAST)
{
IP_UPD_PO_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INMCAST,skb->len);
}
else if (rt->rt_type == RTN_BROADCAST)
IP_UPD_PO_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INBCAST,skb->len);
#################### 决定将数据包做如下哪两种处理
# 是前送 ip_forward
# 还是送给传输层 ip_local_deliver
return dst_input(skb);
return skb_dst(skb)->input(skb);
skb->_skb_dst->input
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
6.4.3、IP数据包的本地上传
{
1、主要任务
本地数据包的重组
上传至传输层
2、 ip_local_deliver
int ip_local_deliver(struct sk_buff *skb)
{
// 如果数据包有分段,则调用 ip_defrag 重组之
if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET))
{
if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
{
// if ip_fragment.c (struct sk_buff *skb, u32 user)
struct ipq *qp;
struct net *net;
net = skb->dev ? dev_net(skb->dev) : dev_net(skb_dst(skb)->dev); // init_net
IP_INC_STATS_BH(net, IPSTATS_MIB_REASMREQDS);
/* Start by cleaning up the memory. */
if (atomic_read(&net->ipv4.frags.mem) > net->ipv4.frags.high_thresh)
ip_evictor(net);
/* Lookup (or create) queue header */
/* Find the correct entry in the "incomplete datagrams" queue for
* this IP datagram, and create new one, if nothing is found.
*/
if ((qp = ip_find(net, ip_hdr(skb), user)) != NULL)
{
// if ip_find(struct net *net, struct iphdr *iph, u32 user)
struct inet_frag_queue *q;
struct ip4_create_arg arg;
unsigned int hash;
arg.iph = iph;
arg.user = user;
read_lock(&ip4_frags.lock);
hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
################### 如果未找到,则创建新的 段队列
q = inet_frag_find(&net->ipv4.frags, &ip4_frags, &arg, hash);
{
// (struct netns_frags *nf, struct inet_frags *f, void *key, unsigned int hash)
struct inet_frag_queue *q;
struct hlist_node *n;
hlist_for_each_entry(q, n, &f->hash[hash], list)
{
if (q->net == nf && f->match(q, key))
{
atomic_inc(&q->refcnt);
read_unlock(&f->lock);
return q;
}
}
read_unlock(&f->lock);
############### 如果未找到,则创建
return inet_frag_create(nf, f, key);
{
// (struct netns_frags *nf, struct inet_frags *f, void *arg)
struct inet_frag_queue *q;
q = inet_frag_alloc(nf, f, arg);
########### 创建 inet_frag_queue 成功后,添加到 ip4_frags.hash[x] 链表上
# 再挂载到 init_net.ipv4.frags.lru_list 链表上
return inet_frag_intern(nf, q, f, arg);
{
/*
struct netns_frags *nf,
struct inet_frag_queue *qp_in,
struct inet_frags *f,
void *arg
*/
struct inet_frag_queue *qp;
unsigned int hash;
write_lock(&f->lock);
hash = f->hashfn(qp_in);
qp = qp_in;
if (!mod_timer(&qp->timer, jiffies + nf->timeout))
atomic_inc(&qp->refcnt);
atomic_inc(&qp->refcnt);
hlist_add_head(&qp->list, &f->hash[hash]);
list_add_tail(&qp->lru_list, &nf->lru_list);
nf->nqueues++;
write_unlock(&f->lock);
return qp;
}
}
}
if (q == NULL)
goto out_nomem;
################### 返回此段队列所属的 ipq 结构体指针
return container_of(q, struct ipq, q);
}
{
int ret;
spin_lock(&qp->q.lock);
################### /* Add new segment to existing queue. */
ret = ip_frag_queue(qp, skb);
{
// ip_fragment.c (struct ipq *qp, struct sk_buff *skb)
struct sk_buff *prev, *next;
struct net_device *dev;
int flags, offset;
int ihl, end;
int err = -ENOENT;
if (qp->q.last_in & INET_FRAG_COMPLETE) goto err;
if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
unlikely(ip_frag_too_far(qp)) &&
unlikely(err = ip_frag_reinit(qp))
)
{
ipq_kill(qp);
goto err;
}
############### 获取此 skb数据包 相对完整包的偏移量(Byte为单位)
offset = ntohs(ip_hdr(skb)->frag_off);
flags = offset & ~IP_OFFSET;
offset &= IP_OFFSET;
offset <<= 3; /* offset is in 8-byte chunks */
ihl = ip_hdrlen(skb); // IP 协议头长度 ip_hdr(skb)->ihl * 4;
/* Determine the position of this fragment. */
############### 确定此分片的位置
end = offset + skb->len - ihl;
err = -EINVAL;
############### 如果此skb是最后一个分片
if ((flags & IP_MF) == 0)
{
// 如果在end处仍有一些数据、或者end不匹配,则出错
if (end < qp->q.len ||
((qp->q.last_in & INET_FRAG_LAST_IN) && end != qp->q.len))
goto err;
qp->q.last_in |= INET_FRAG_LAST_IN;
qp->q.len = end;
}
############### 此skb是中间分片
else
{
if (end&7)
{
end &= ~7;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
if (end > qp->q.len)
{
/* Some bits beyond end -> corruption. */
// 如果在end处仍有一些数据,则出错
if (qp->q.last_in & INET_FRAG_LAST_IN)
goto err;
qp->q.len = end;
}
}
if (end == offset) goto err;
###############
err = -ENOMEM;
if (pskb_pull(skb, ihl) == NULL) goto err;
{
// if
return unlikely(len > skb->len) ? NULL : __pskb_pull(skb, len);
{
// __pskb_pull
if (len > skb_headlen(skb) && // 如果(ihl=IP协议头长度) (skb->len - skb->data_len)
!__pskb_pull_tail(skb, len - skb_headlen(skb)))
return NULL;
skb->len -= len;
return skb->data += len;
}
}
}
spin_unlock(&qp->q.lock);
ipq_put(qp);
return ret;
}
IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
kfree_skb(skb);
return -ENOMEM;
}
return 0;
}
return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);
}
3、 ip_local_deliver_finish(struct sk_buff *skb)
{
struct net *net = dev_net(skb->dev);
__skb_pull(skb, ip_hdrlen(skb));
/* Point into the IP datagram, just past the header. */
skb_reset_transport_header(skb);
rcu_read_lock();
{
int protocol = ip_hdr(skb)->protocol;
int hash, raw;
const struct net_protocol *ipprot;
resubmit:
raw = raw_local_deliver(skb, protocol);
hash = protocol & (MAX_INET_PROTOS - 1);
ipprot = rcu_dereference(inet_protos[hash]);
if (ipprot != NULL) {
int ret;
if (!net_eq(net, &init_net) && !ipprot->netns_ok) {
if (net_ratelimit())
printk("%s: proto %d isn't netns-ready\n",
__func__, protocol);
kfree_skb(skb);
goto out;
}
if (!ipprot->no_policy) {
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
goto out;
}
nf_reset(skb);
}
ret = ipprot->handler(skb);
{
// 对于TCP,则为 tcp_v4_rcv
}
if (ret < 0) {
protocol = -ret;
goto resubmit;
}
IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
} else {
if (!raw) {
if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
IP_INC_STATS_BH(net, IPSTATS_MIB_INUNKNOWNPROTOS);
icmp_send(skb, ICMP_DEST_UNREACH,
ICMP_PROT_UNREACH, 0);
}
} else
IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
kfree_skb(skb);
}
}
out:
rcu_read_unlock();
return 0;
}
}
}
6.4、IP 数据包的分段与重组
{
1、网络命名空间的初始化
{
// net_namespace.c
static LIST_HEAD(pernet_list);
static struct list_head *first_device = &pernet_list;
extern struct list_head net_namespace_list;
LIST_HEAD(net_namespace_list);
//
static int __init net_ns_init(void) // pure_initcall
{
struct net_generic *ng;
ng = net_alloc_generic();
{
struct net_generic *ng;
size_t generic_size = sizeof(struct net_generic) + INITIAL_NET_GEN_PTRS * sizeof(void *);
ng = kzalloc(generic_size, GFP_KERNEL);
if (ng) ng->len = INITIAL_NET_GEN_PTRS;
return ng;
}
rcu_assign_pointer(init_net.gen, ng); // init_net.gen = ng
mutex_lock(&net_mutex);
setup_net(&init_net)
{
const struct pernet_operations *ops, *saved_ops;
int error = 0;
LIST_HEAD(net_exit_list);
atomic_set(&net->count, 1);
list_for_each_entry(ops, &pernet_list, list)
{
// 由于 pernet_list 为空,此处不做处理
error = ops_init(ops, net);
if (error < 0) goto out_undo;
}
// 在此处便直接返回
out:
return error;
}
##################### 添加 init_net 到 net_namespace_list 全局链表中
rtnl_lock();
list_add_tail_rcu(&init_net.list, &net_namespace_list);
rtnl_unlock();
mutex_unlock(&net_mutex);
return 0;
}
}
2、IP分段管理器初始化 inet_init->ipfrag_init
{
// 静态区
//----------------------- ip_fragment.c
static struct inet_frags ip4_frags;
static struct pernet_operations ip4_frags_ops = {
.init = ipv4_frags_init_net,
.exit = ipv4_frags_exit_net,
};
static struct ctl_table ip4_frags_ns_ctl_table[] =
{
{
.procname = "ipfrag_high_thresh",
.data = &init_net.ipv4.frags.high_thresh,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec
},
{
.procname = "ipfrag_low_thresh",
.data = &init_net.ipv4.frags.low_thresh,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec
},
{
.procname = "ipfrag_time",
.data = &init_net.ipv4.frags.timeout,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_jiffies,
},
{ }
};
//-----------------------
struct ctl_path net_ipv4_ctl_path[] = {
{ .procname = "net", },
{ .procname = "ipv4", },
{ },
};
//----------------------- 接口实现
void __init ipfrag_init(void)
{
ip4_frags_ctl_register();
register_pernet_subsys(&ip4_frags_ops);
{
// net_namespace.c
int error;
mutex_lock(&net_mutex);
error = register_pernet_operations(first_device, ops);
{
int error;
error = __register_pernet_operations(list, ops);
{
int err = 0;
######### 再一次初始化 init_net
err = ops_init(ops, &init_net);
{
//
int err;
if (ops->id && ops->size) // no
{
}
if (ops->init) // yes
return ops->init(net);
{
// ip_fragment.c ipv4_frags_init_net
/*
* Fragment cache limits. We will commit 256K at one time. Should we
* cross that limit we will prune down to 192K. This should cope with
* even the most extreme cases without allowing an attacker to
* measurably harm machine performance.
*/
net->ipv4.frags.high_thresh = 256 * 1024; // 一次最多可以提交256K
net->ipv4.frags.low_thresh = 192 * 1024; // 最少提交 192K,如要分片
net->ipv4.frags.timeout = IP_FRAG_TIME; // 30 个时钟频率
inet_frags_init_net(&net->ipv4.frags);
{
// inet_fragment.c
nf->nqueues = 0;
atomic_set(&nf->mem, 0);
INIT_LIST_HEAD(&nf->lru_list);
}
########### 注册 init_net 到 sysctl 中
return ip4_frags_ns_ctl_register(net);
{
struct ctl_table *table;
struct ctl_table_header *hdr;
table = ip4_frags_ns_ctl_table;
if (!net_eq(net, &init_net)) // NO
{
table = kmemdup(table, sizeof(ip4_frags_ns_ctl_table), GFP_KERNEL);
if (table == NULL)
goto err_alloc;
table[0].data = &net->ipv4.frags.high_thresh;
table[1].data = &net->ipv4.frags.low_thresh;
table[2].data = &net->ipv4.frags.timeout;
}
hdr = register_net_sysctl_table(net, net_ipv4_ctl_path, table);
if (hdr == NULL) goto err_reg;
net->ipv4.frags_hdr = hdr;
return 0;
}
}
}
if (err)
ops_free(ops, &init_net);
return err;
}
}
mutex_unlock(&net_mutex);
return error;
}
##################### 安装操作集
ip4_frags.hashfn = ip4_hashfn;
ip4_frags.constructor = ip4_frag_init;
ip4_frags.destructor = ip4_frag_free;
ip4_frags.skb_free = NULL;
ip4_frags.qsize = sizeof(struct ipq);
ip4_frags.match = ip4_frag_match;
ip4_frags.frag_expire = ip_expire;
ip4_frags.secret_interval = 10 * 60 * HZ;
##################### 初始化 ip4_frags.hash[] 数组
inet_frags_init(&ip4_frags);
{
// (struct inet_frags *f)
int i;
for (i = 0; i < INETFRAGS_HASHSZ; i++)
INIT_HLIST_HEAD(&ip4_frags->hash[i]);
rwlock_init(&ip4_frags->lock);
ip4_frags->rnd = (u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6)));
################# 添加计时器
setup_timer(&ip4_frags->secret_timer, inet_frag_secret_rebuild, (unsigned long)ip4_frags);
ip4_frags->secret_timer.expires = jiffies + ip4_frags->secret_interval;
add_timer(&ip4_frags->secret_timer);
}
}
}
}
6.5、网络层发送处理接口, ip_queue_xmit
{
1、ip_queue_xmit,TCP SCTP 协议调用此接口,将数据包扔给IP层
传输层调用此接口时, skb->data 指向 缓冲区中 网络层负载数据的起始位置(传输层协议头起始地址处)
2、具体实现
// net/ipv4/ip_output.c
int ip_queue_xmit(struct sk_buff *skb)
{
##################### 获取 发送数据包的 控制信息
# 包括:所属套接字、协议族套接字、应用程序设定的IP选项
// 如果数据包是由 本地AP产生,则它一定属于某个sock,此域非空
struct sock *sk = skb->sk;
struct inet_sock *inet = inet_sk(sk);
struct ip_options *opt = inet->opt;
struct rtable *rt;
struct iphdr *iph;
int res;
rcu_read_lock();
##################### 如果skb已分配了适当的路由信息,则不需要参考路由表
rt = skb_rtable(skb);
if (rt != NULL)
goto packet_routed;
##################### 否则,skb没有分配路由,则查看路由是否已缓存在了套接字结构中
# 如果该缓存有效,则须确定它仍有效,这项检查由 __sk_dst_check 完成。
rt = (struct rtable *)__sk_dst_check(sk, 0);
##################### 如果套接字中缓存无效,则需做如下查询
# 假如IP选项中配置了源路由选项,则需要从IP选项的IP地址列表中,读取下一跳的IP地址
# 在路由表中查询,查询 路由的目标IP地址区在inet->daddr数据域。
if (rt == NULL)
{
__be32 daddr;
daddr = inet->inet_daddr; // 获取 数据包的目标IP地址
################# 如果IP选项中设置了 源路由选项,则 daddr = 源路由IP地址列表中
if(opt && opt->srr)
daddr = opt->faddr;
{
############# 构建路由查询结构
struct flowi fl =
{ .oif = sk->sk_bound_dev_if,
.mark = sk->sk_mark,
.nl_u = {
.ip4_u ={
.daddr = daddr, // 数据包 目的地址
.saddr = inet->inet_saddr, // 发送数据包的源地址
.tos = RT_CONN_FLAGS(sk) // 服务类型
}
},
.proto = sk->sk_protocol, // 传输层协议
.flags = inet_sk_flowi_flags(sk), //
.uli_u = {
.ports ={
.sport = inet->inet_sport, // 源端口号
.dport = inet->inet_dport // 目的端口号
}
}
};
/* If this fails, retransmit mechanism of transport layer will
* keep trying until route appears or the connection times
* itself out.
*/
security_sk_classify_flow(sk, &fl);
############# 获取新的路由
# 此时查找到的 下一跳IP地址,必须与 源路由列表中下一跳的IP地址 一致。
# 如果查找失败,则 扔掉数据包
#
if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
goto no_route;
}
################# 将发送数据包的 网络设备信息 , 存储到 inet_sock 中。
# 下次发送数据包时,就可以不用再查找。
sk_setup_caps(sk, &rt->u.dst);
}
skb_dst_set_noref(skb, &rt->u.dst);
packet_routed:
##################### 确保 查找到的 下一跳IP地址,必须与 源路由列表中下一跳的IP地址 一致。
if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
goto no_route;
##################### 构建 IP 协议头
# 目前为止,skb 只包含了如下内容
# 常规负载数据
# (传输层)协议头
# 传输层 送下来的 负载数据
# TCP /SCTP 分配 skb 缓冲区时,总是按照最大可能的需要去申请内存,它通常包含了下层 协议头所需的空间
#从而避免,IP层及其以下的协议 在skb空间不够时 做缓冲区的复制或者充分配之额外开销。
#
### skb->data 前移 iphdr及选项的总长度 个 byte
# 调整网络层协议头指针,指向缓冲区中 iphdr起始处
# linux/skbuff.h
skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
skb_reset_network_header(skb); // skb->network_header = skb->data;
### 初始化 iphdr 的如下域
# version:4(IP协议版本,4),ihl:4(协议头长度,5个单元) 0x45xx(xx,tos)
iph = ip_hdr(skb); // ip 协议头指针
*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
### 是否允许对数据包进行分片(DF/MF域)
if (ip_dont_fragment(sk, &rt->u.dst) && !skb->local_df)
{
// if ip_dont_fragment
return (inet_sk(sk)->pmtudisc == IP_PMTUDISC_DO ||
(inet_sk(sk)->pmtudisc == IP_PMTUDISC_WANT &&
!(dst_metric_locked(dst, RTAX_MTU))
)
);
}
iph->frag_off = htons(IP_DF);
else
iph->frag_off = 0;
iph->ttl = ip_select_ttl(inet, &rt->u.dst);
iph->protocol = sk->sk_protocol; // 传输层协议类型
iph->saddr = rt->rt_src;
iph->daddr = rt->rt_dst;
##################### 如果此 skb对应的 inet_sock 中有设置 IP选项
# 则在此,需要构建 iphdr中选项数据块
# 根据 inet_sock.opt指向的配置块,将选项中要求的值添加到 iphdr 中
if (opt && opt->optlen)
{
iph->ihl += opt->optlen >> 2;
ip_options_build(skb, opt, inet->inet_daddr, rt, 0);
{
// ip_options.c
/*
struct sk_buff * skb,
struct ip_options * opt,
__be32 daddr,
struct rtable *rt,
int is_frag // 为0,表示 协议头不属于分片数据。
*/
unsigned char *iph = skb_network_header(skb);
############# 将 inet_sock.opt指向的配置块 复制到 skb->cb[48] 缓冲区中。
memcpy(&(IPCB(skb)->opt), opt, sizeof(struct ip_options));
############# 将 选项具体内容【inet_sock.opt->__data指向的数据块】
# 复制到 skb->network_header(ip层协议区)中iphdr块之后。
memcpy(iph+sizeof(struct iphdr), opt->__data, opt->optlen);
opt = &(IPCB(skb)->opt);
if (opt->srr)
memcpy(iph+opt->srr+iph[opt->srr+1]-4, &daddr, 4);
############# 此处,表明 协议头数据 不属于 分片数据
if (!is_frag)
{
if (opt->rr_needaddr)
ip_rt_get_source(iph+opt->rr+iph[opt->rr+2]-5, rt);
if (opt->ts_needaddr)
ip_rt_get_source(iph+opt->ts+iph[opt->ts+2]-9, rt);
if (opt->ts_needtime) {
struct timespec tv;
__be32 midtime;
getnstimeofday(&tv);
midtime = htonl((tv.tv_sec % 86400) * MSEC_PER_SEC + tv.tv_nsec / NSEC_PER_MSEC);
memcpy(iph+opt->ts+iph[opt->ts+2]-5, &midtime, 4);
}
return;
}
if (opt->rr)
{
memset(iph+opt->rr, IPOPT_NOP, iph[opt->rr+1]);
opt->rr = 0;
opt->rr_needaddr = 0;
}
if (opt->ts)
{
memset(iph+opt->ts, IPOPT_NOP, iph[opt->ts+1]);
opt->ts = 0;
opt->ts_needaddr = opt->ts_needtime = 0;
}
}
}
##################### 根据“是否对IP数据包进行分片”,在 iphdr中设置 IP数据包的标识符 ID
ip_select_ident_more(iph, &rt->u.dst, sk,(skb_shinfo(skb)->gso_segs ?: 1) - 1);
skb->priority = sk->sk_priority;
skb->mark = sk->sk_mark;
res = ip_local_out(skb);
rcu_read_unlock();
return res;
no_route:
##################### 此处,数据包会被扔掉,IP层会返回一个错误代码
# 更新 SNMP 相关的统计信息
# 不需要想 源地址发送 ICMP消息。
rcu_read_unlock();
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
kfree_skb(skb);
return -EHOSTUNREACH;
}
}
6.6、eth层
{
1、以太网header及protol类型定义 ethhdr
{
// if_ether.h
#define ETH_ALEN 6 /* Octets in one ethernet addr */
#define ETH_HLEN 14 /* Total octets in header. */
#define ETH_ZLEN 60 /* Min. octets in frame sans FCS */
#define ETH_DATA_LEN 1500 /* Max. octets in payload */
#define ETH_FRAME_LEN 1514 /* Max. octets in frame sans FCS */
#define ETH_FCS_LEN 4 /* Octets in the FCS */
/*
* These are the defined Ethernet Protocol ID's.
*/
#define ETH_P_LOOP 0x0060 /* Ethernet Loopback packet */
#define ETH_P_PUP 0x0200 /* Xerox PUP packet */
#define ETH_P_PUPAT 0x0201 /* Xerox PUP Addr Trans packet */
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
#define ETH_P_X25 0x0805 /* CCITT X.25 */
#define ETH_P_ARP 0x0806 /* Address Resolution packet */
#define ETH_P_BPQ 0x08FF /* G8BPQ AX.25 Ethernet Packet [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_IEEEPUP 0x0a00 /* Xerox IEEE802.3 PUP packet */
#define ETH_P_IEEEPUPAT 0x0a01 /* Xerox IEEE802.3 PUP Addr Trans packet */
#define ETH_P_DEC 0x6000 /* DEC Assigned proto */
#define ETH_P_DNA_DL 0x6001 /* DEC DNA Dump/Load */
#define ETH_P_DNA_RC 0x6002 /* DEC DNA Remote Console */
#define ETH_P_DNA_RT 0x6003 /* DEC DNA Routing */
#define ETH_P_LAT 0x6004 /* DEC LAT */
#define ETH_P_DIAG 0x6005 /* DEC Diagnostics */
#define ETH_P_CUST 0x6006 /* DEC Customer use */
#define ETH_P_SCA 0x6007 /* DEC Systems Comms Arch */
#define ETH_P_TEB 0x6558 /* Trans Ether Bridging */
#define ETH_P_RARP 0x8035 /* Reverse Addr Res packet */
#define ETH_P_ATALK 0x809B /* Appletalk DDP */
#define ETH_P_AARP 0x80F3 /* Appletalk AARP */
#define ETH_P_8021Q 0x8100 /* 802.1Q VLAN Extended Header */
#define ETH_P_IPX 0x8137 /* IPX over DIX */
#define ETH_P_IPV6 0x86DD /* IPv6 over bluebook */
#define ETH_P_PAUSE 0x8808 /* IEEE Pause frames. See 802.3 31B */
#define ETH_P_SLOW 0x8809 /* Slow Protocol. See 802.3ad 43B */
#define ETH_P_WCCP 0x883E /* Web-cache coordination protocol
* defined in draft-wilson-wrec-wccp-v2-00.txt */
#define ETH_P_PPP_DISC 0x8863 /* PPPoE discovery messages */
#define ETH_P_PPP_SES 0x8864 /* PPPoE session messages */
#define ETH_P_MPLS_UC 0x8847 /* MPLS Unicast traffic */
#define ETH_P_MPLS_MC 0x8848 /* MPLS Multicast traffic */
#define ETH_P_ATMMPOA 0x884c /* MultiProtocol Over ATM */
#define ETH_P_LINK_CTL 0x886c /* HPNA, wlan link local tunnel */
#define ETH_P_ATMFATE 0x8884 /* Frame-based ATM Transport
* over Ethernet
*/
#define ETH_P_PAE 0x888E /* Port Access Entity (IEEE 802.1X) */
#define ETH_P_AOE 0x88A2 /* ATA over Ethernet */
#define ETH_P_8021AD 0x88A8 /* 802.1ad Service VLAN */
#define ETH_P_TIPC 0x88CA /* TIPC */
#define ETH_P_8021AH 0x88E7 /* 802.1ah Backbone Service Tag */
#define ETH_P_1588 0x88F7 /* IEEE 1588 Timesync */
#define ETH_P_FCOE 0x8906 /* Fibre Channel over Ethernet */
#define ETH_P_TDLS 0x890D /* TDLS */
#define ETH_P_FIP 0x8914 /* FCoE Initialization Protocol */
#define ETH_P_QINQ1 0x9100 /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_QINQ2 0x9200 /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_QINQ3 0x9300 /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_EDSA 0xDADA /* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_AF_IUCV 0xFBFB /* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */
/*
* Non DIX types. Won't clash for 1500 types.
*/
#define ETH_P_802_3 0x0001 /* Dummy type for 802.3 frames */
#define ETH_P_AX25 0x0002 /* Dummy protocol id for AX.25 */
#define ETH_P_ALL 0x0003 /* Every packet (be careful!!!) */
#define ETH_P_802_2 0x0004 /* 802.2 frames */
#define ETH_P_SNAP 0x0005 /* Internal only */
#define ETH_P_DDCMP 0x0006 /* DEC DDCMP: Internal only */
#define ETH_P_WAN_PPP 0x0007 /* Dummy type for WAN PPP frames*/
#define ETH_P_PPP_MP 0x0008 /* Dummy type for PPP MP frames */
#define ETH_P_LOCALTALK 0x0009 /* Localtalk pseudo type */
#define ETH_P_CAN 0x000C /* Controller Area Network */
#define ETH_P_PPPTALK 0x0010 /* Dummy type for Atalk over PPP*/
#define ETH_P_TR_802_2 0x0011 /* 802.2 frames */
#define ETH_P_MOBITEX 0x0015 /* Mobitex ([email protected]) */
#define ETH_P_CONTROL 0x0016 /* Card specific control frames */
#define ETH_P_IRDA 0x0017 /* Linux-IrDA */
#define ETH_P_ECONET 0x0018 /* Acorn Econet */
#define ETH_P_HDLC 0x0019 /* HDLC frames */
#define ETH_P_ARCNET 0x001A /* 1A for ArcNet :-) */
#define ETH_P_DSA 0x001B /* Distributed Switch Arch. */
#define ETH_P_TRAILER 0x001C /* Trailer switch tagging */
#define ETH_P_PHONET 0x00F5 /* Nokia Phonet frames */
#define ETH_P_IEEE802154 0x00F6 /* IEEE802.15.4 frame */
#define ETH_P_CAIF 0x00F7 /* ST-Ericsson CAIF protocol */
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
unsigned char h_source[ETH_ALEN]; /* source ether addr */
__be16 h_proto; /* packet type ID field */
} __attribute__((packed));
}
2、arphdr
{
// include/linux/if_arp.h:
struct arphdr
{
__be16 ar_hrd; /* format of hardware address */
__be16 ar_pro; /* format of protocol address */
unsigned char ar_hln; /* length of hardware address */
unsigned char ar_pln; /* length of protocol address */
__be16 ar_op; /* ARP opcode (command) */
#if 0
/*
* Ethernet looks like this : This bit is variable sized however...
*/
unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */
unsigned char ar_sip[4]; /* sender IP address */
unsigned char ar_tha[ETH_ALEN]; /* target hardware address */
unsigned char ar_tip[4]; /* target IP address */
#endif
}
ARP请求包的分析实例
{
ARP请求包的分析:如下所示为一个ARP请求包
0000 ff ff ff ff ff ff 00 0c f1 d4 d9 60 08 06 00 01 ...........`....
0010 08 00 06 04 00 01 00 0c f1 d4 d9 60 c0 a8 01 0f ...........`....
0020 00 00 00 00 00 00 c0 a8 01 02 ..........
根据定义,头6个字节是以太网目的地址 ff ff ff ff ff ff 这是一个广播地址,全网下的所有终端都能接收到,
紧跟着的6个字节是以太网源地址,即发送者的MAC地址( 00 0c f1 d4 d9 60 是我的MAC地址)。
帧类型0806占两个字节,到这里以太网帧头就结束了。0806指的是后面的数据是属于arp包的。
接着分析ARP包头。头两个字节是硬件类型 00 01,接着两个字节是协议类型,即ARP使用的是IP协议代号08 00。
硬件地址长度和协议地址长度分别是6和4。这与ARP报文格式是对应的。
后面的2个字节OP指示当前包是请求包还是应答包,对应的值分别是0x0001和0x0002。
原始数据里是00 01所以这是一个请求包,然后6个字节又是发送者MAC地址00 0c f1 d4 d9 60 ,后面4个字节是发送者IP地址c0 a8 01 0f ,
转换成点分十进制格式即192.168.1.15,这是我的IP,接下来的6个字节留空,00 00 00 00 00 00 在arp请求包里也可以是其他数据,
因为稍后IP地址为c0 a8 01 02 (192.168.1.2)会把自己的MAC地址填充进这6个字节中。
填充完后,arp包里的发送者硬件地址|目标硬件地址和以太网首部的以太网目的地址|以太网源地址正好对调。
最后把这个封装好的ARP包发送出去,这样一个来回就可以让两台终端互相知道对方的IP和MAC。
ARP欺骗的3种基本方式:
1. 主机C冒充网关欺骗主机B;
2. 主机c冒充主机B欺骗网关;
3. 主机C同时欺骗主机B和网关,实现数据中转,并监听到所有主机B的数据。
}
}
}
}
8、传输层 TCP
{
8.1、TCP Type & Header
{
//----------------------- type
// include/linux/in.h
IPPROTO_TCP
//----------------------- header
// tcp.h
struct tcp_sock {
/* inet_connection_sock has to be the first member of tcp_sock */
struct inet_connection_sock inet_conn;
u16 tcp_header_len; /* Bytes of tcp header to send */
u16 xmit_size_goal_segs; /* Goal for segmenting output packets */
/*
* Header prediction flags
* 0x5?10 << 16 + snd_wnd in net byte order
*/
__be32 pred_flags;
/*
* RFC793 variables by their proper names. This means you can
* read the code and the spec side by side (and laugh ...)
* See RFC793 and RFC1122. The RFC writes these in capitals.
*/
u32 rcv_nxt; /* What we want to receive next */
u32 copied_seq; /* Head of yet unread data */
u32 rcv_wup; /* rcv_nxt on last window update sent */
u32 snd_nxt; /* Next sequence we send */
u32 snd_una; /* First byte we want an ack for */
u32 snd_sml; /* Last byte of the most recently transmitted small packet */
u32 rcv_tstamp; /* timestamp of last received ACK (for keepalives) */
u32 lsndtime; /* timestamp of last sent data packet (for restart window) */
/* Data for direct copy to user */
struct {
struct sk_buff_head prequeue;
struct task_struct *task;
struct iovec *iov;
int memory;
int len;
#ifdef CONFIG_NET_DMA
/* members for async copy */
struct dma_chan *dma_chan;
int wakeup;
struct dma_pinned_list *pinned_list;
dma_cookie_t dma_cookie;
#endif
} ucopy;
u32 snd_wl1; /* Sequence for window update */
u32 snd_wnd; /* The window we expect to receive */
u32 max_window; /* Maximal window ever seen from peer */
u32 mss_cache; /* Cached effective mss, not including SACKS */
u32 window_clamp; /* Maximal window to advertise */
u32 rcv_ssthresh; /* Current window clamp */
u32 frto_highmark; /* snd_nxt when RTO occurred */
u16 advmss; /* Advertised MSS */
u8 frto_counter; /* Number of new acks after RTO */
u8 nonagle : 4,/* Disable Nagle algorithm? */
thin_lto : 1,/* Use linear timeouts for thin streams */
thin_dupack : 1,/* Fast retransmit on first dupack */
unused : 2;
/* RTT measurement */
u32 srtt; /* smoothed round trip time << 3 */
u32 mdev; /* medium deviation */
u32 mdev_max; /* maximal mdev for the last rtt period */
u32 rttvar; /* smoothed mdev_max */
u32 rtt_seq; /* sequence number to update rttvar */
u32 packets_out; /* Packets which are "in flight" */
u32 retrans_out; /* Retransmitted packets out */
u16 urg_data; /* Saved octet of OOB data and control flags */
u8 ecn_flags; /* ECN status bits. */
u8 reordering; /* Packet reordering metric. */
u32 snd_up; /* Urgent pointer */
u8 keepalive_probes; /* num of allowed keep alive probes */
/*
* Options received (usually on last packet, some only on SYN packets).
*/
struct tcp_options_received rx_opt;
/*
* Slow start and congestion control (see also Nagle, and Karn & Partridge)
*/
u32 snd_ssthresh; /* Slow start size threshold */
u32 snd_cwnd; /* Sending congestion window */
u32 snd_cwnd_cnt; /* Linear increase counter */
u32 snd_cwnd_clamp; /* Do not allow snd_cwnd to grow above this */
u32 snd_cwnd_used;
u32 snd_cwnd_stamp;
u32 prior_cwnd; /* Congestion window at start of Recovery. */
u32 prr_delivered; /* Number of newly delivered packets to
* receiver in Recovery. */
u32 prr_out; /* Total number of pkts sent during Recovery. */
u32 rcv_wnd; /* Current receiver window */
u32 write_seq; /* Tail(+1) of data held in tcp send buffer */
u32 pushed_seq; /* Last pushed seq, required to talk to windows */
u32 lost_out; /* Lost packets */
u32 sacked_out; /* SACK'd packets */
u32 fackets_out; /* FACK'd packets */
u32 tso_deferred;
u32 bytes_acked; /* Appropriate Byte Counting - RFC3465 */
/* from STCP, retrans queue hinting */
struct sk_buff* lost_skb_hint;
struct sk_buff *scoreboard_skb_hint;
struct sk_buff *retransmit_skb_hint;
struct sk_buff_head out_of_order_queue; /* Out of order segments go here */
/* SACKs data, these 2 need to be together (see tcp_build_and_update_options) */
struct tcp_sack_block duplicate_sack[1]; /* D-SACK block */
struct tcp_sack_block selective_acks[4]; /* The SACKS themselves*/
struct tcp_sack_block recv_sack_cache[4];
struct sk_buff *highest_sack; /* highest skb with SACK received
* (validity guaranteed only if
* sacked_out > 0)
*/
int lost_cnt_hint;
u32 retransmit_high; /* L-bits may be on up to this seqno */
u32 lost_retrans_low; /* Sent seq after any rxmit (lowest) */
u32 prior_ssthresh; /* ssthresh saved at recovery start */
u32 high_seq; /* snd_nxt at onset of congestion */
u32 retrans_stamp; /* Timestamp of the last retransmit,
* also used in SYN-SENT to remember stamp of
* the first SYN. */
u32 undo_marker; /* tracking retrans started here. */
int undo_retrans; /* number of undoable retransmissions. */
u32 total_retrans; /* Total retransmits for entire connection */
u32 urg_seq; /* Seq of received urgent pointer */
unsigned int keepalive_time; /* time before keep alive takes place */
unsigned int keepalive_intvl; /* time interval between keep alive probes */
int linger2;
/* Receiver side RTT estimation */
struct {
u32 rtt;
u32 seq;
u32 time;
} rcv_rtt_est;
/* Receiver queue space */
struct {
int space;
u32 seq;
u32 time;
} rcvq_space;
/* TCP-specific MTU probe information. */
struct {
u32 probe_seq_start;
u32 probe_seq_end;
} mtu_probe;
#ifdef CONFIG_TCP_MD5SIG
/* TCP AF-Specific parts; only used by MD5 Signature support so far */
const struct tcp_sock_af_ops *af_specific;
/* TCP MD5 Signature Option information */
struct tcp_md5sig_info *md5sig_info;
#endif
/* When the cookie options are generated and exchanged, then this
* object holds a reference to them (cookie_values->kref). Also
* contains related tcp_cookie_transactions fields.
*/
struct tcp_cookie_values *cookie_values;
}
}
8.1、TCP到IP层的数据发送接口 注册
{
//---------------------------------------------------------------------------------------------
//--------------------------------- TCP层到IP层数据包发送接口
//
const struct inet_connection_sock_af_ops ipv4_specific = {
.queue_xmit = ip_queue_xmit,
.send_check = tcp_v4_send_check,
.rebuild_header = inet_sk_rebuild_header,
.conn_request = tcp_v4_conn_request,
.syn_recv_sock = tcp_v4_syn_recv_sock,
.remember_stamp = tcp_v4_remember_stamp,
.net_header_len = sizeof(struct iphdr),
.setsockopt = ip_setsockopt,
.getsockopt = ip_getsockopt,
.addr2sockaddr = inet_csk_addr2sockaddr,
.sockaddr_len = sizeof(struct sockaddr_in),
.bind_conflict = inet_csk_bind_conflict,
};
##################### 初始化 TCP层到IP层数据包发送接口
struct proto tcp_prot = {
...
.init = tcp_v4_init_sock,
...
}
int tcp_v4_init_sock(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
skb_queue_head_init(&tp->out_of_order_queue);
tcp_init_xmit_timers(sk);
tcp_prequeue_init(tp);
icsk->icsk_rto = TCP_TIMEOUT_INIT;
tp->mdev = TCP_TIMEOUT_INIT;
/* So many TCP implementations out there (incorrectly) count the
* initial SYN frame in their delayed-ACK and congestion control
* algorithms that we must have the following bandaid to talk
* efficiently to them. -DaveM
*/
tp->snd_cwnd = 2;
/* See draft-stevens-tcpca-spec-01 for discussion of the
* initialization of these values.
*/
tp->snd_ssthresh = TCP_INFINITE_SSTHRESH;
tp->snd_cwnd_clamp = ~0;
tp->mss_cache = TCP_MSS_DEFAULT;
tp->reordering = sysctl_tcp_reordering;
icsk->icsk_ca_ops = &tcp_init_congestion_ops;
sk->sk_state = TCP_CLOSE;
sk->sk_write_space = sk_stream_write_space;
sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
icsk->icsk_af_ops = &ipv4_specific;
icsk->icsk_sync_mss = tcp_sync_mss;
#ifdef CONFIG_TCP_MD5SIG
tp->af_specific = &tcp_sock_ipv4_specific;
#endif
/* TCP Cookie Transactions */
if (sysctl_tcp_cookie_size > 0) {
/* Default, cookies without s_data_payload. */
tp->cookie_values =
kzalloc(sizeof(*tp->cookie_values),
sk->sk_allocation);
if (tp->cookie_values != NULL)
kref_init(&tp->cookie_values->kref);
}
/* Presumed zeroed, in order of appearance:
* cookie_in_always, cookie_out_never,
* s_data_constant, s_data_in, s_data_out
*/
sk->sk_sndbuf = sysctl_tcp_wmem[1];
sk->sk_rcvbuf = sysctl_tcp_rmem[1];
local_bh_disable();
percpu_counter_inc(&tcp_sockets_allocated);
local_bh_enable();
return 0;
}
}
}
9、传输层 UDP
{
9.1、IP_TYPE & UDP Header
{
//----------------------- Type
IPPROTO_UDP
//----------------------- header // include/linux/udp.h
struct udphdr {
__be16 source;
__be16 dest;
__be16 len;
__sum16 check;
};
}
}
9、网络设备实例的注册 register_netdev register_netdevice
{
1、静态区
{
// sch_generic.c
struct Qdisc noop_qdisc = {
.enqueue = noop_enqueue,
.dequeue = noop_dequeue,
.flags = TCQ_F_BUILTIN,
.ops = &noop_qdisc_ops,
.list = LIST_HEAD_INIT(noop_qdisc.list),
.q.lock = __SPIN_LOCK_UNLOCKED(noop_qdisc.q.lock),
.dev_queue = &noop_netdev_queue,
};
}
2、wlan接口的通知
{
// core.c (net\wireless): .name = "wlan",
static struct device_type wiphy_type = {
.name = "wlan",
};
static struct notifier_block cfg80211_netdev_notifier = {
.notifier_call = cfg80211_netdev_notifier_call,
};
// notifier.c
subsys_initcall(cfg80211_init);
{
register_netdevice_notifier(&cfg80211_netdev_notifier);
{
// (struct notifier_block *nb)
struct net_device *dev;
struct net_device *last;
struct net *net;
int err;
// 添加到全局链表 netdev_chain 上
err = raw_notifier_chain_register(&netdev_chain, nb);
for_each_net(net)
{
for_each_netdev(net, dev)
{
err = nb->notifier_call(nb, NETDEV_REGISTER, dev);
{
// notifier.c:cfg80211_netdev_notifier_call
struct net_device *dev = ndev;
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_registered_device *rdev;
if (!wdev)
return NOTIFY_DONE;
}
err = notifier_to_errno(err);
if (err) goto rollback;
if (!(dev->flags & IFF_UP)) continue;
nb->notifier_call(nb, NETDEV_UP, dev);
}
}
}
}
}
3、实际注册接口 register_netdevice
int register_netdev(struct net_device *dev)
{
int err;
##################### 获取 routing netlink 锁
rtnl_lock();
if (strchr(dev->name, '%'))
err = dev_alloc_name(dev, dev->name);
##################### 将 net_device 放到全局链表 dev_base_head 和 两个 hash 表中
err = register_netdevice(dev);
{
// sch_generic.c
int ret;
struct net *net = dev_net(dev);
BUG_ON(dev_boot_phase);
ASSERT_RTNL();
might_sleep();
/* When net_device's are persistent, this will be fatal. */
BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
BUG_ON(!net);
spin_lock_init(&dev->addr_list_lock);
netdev_set_addr_lockdep_class(dev);
netdev_init_queue_locks(dev);
dev->iflink = -1;
#ifdef CONFIG_RPS // no
if (!dev->num_rx_queues) {
/*
* Allocate a single RX queue if driver never called
* alloc_netdev_mq
*/
dev->_rx = kzalloc(sizeof(struct netdev_rx_queue), GFP_KERNEL);
if (!dev->_rx) {
ret = -ENOMEM;
goto out;
}
dev->_rx->first = dev->_rx;
atomic_set(&dev->_rx->count, 1);
dev->num_rx_queues = 1;
}
#endif
################# 如果设备驱动程序提供了 init 接口,则调用它来初始化 私有数据
/* Init, if this function is available */
if (dev->netdev_ops->ndo_init) {
ret = dev->netdev_ops->ndo_init(dev);
if (ret) {
if (ret > 0)
ret = -EIO;
goto out;
}
}
ret = dev_get_valid_name(dev, dev->name, 0);
{
if (!dev_valid_name(name))
{
// if
if (*name == '\0')
return 0;
if (strlen(name) >= IFNAMSIZ)
return 0;
if (!strcmp(name, ".") || !strcmp(name, ".."))
return 0;
while (*name) {
if (*name == '/' || isspace(*name))
return 0;
name++;
}
return 1;
}
return -EINVAL;
if (fmt && strchr(name, '%'))
return __dev_alloc_name(net, name, buf);
else if (__dev_get_by_name(net, name))
return -EEXIST;
else if (buf != name)
strlcpy(buf, name, IFNAMSIZ);
return 0;
}
################# 为设备分配唯一的标识符
# 找到第一个未被使用过的 index
dev->ifindex = dev_new_index(net);
{
// (struct net *net)
static int ifindex;
for (;;)
{
if (++ifindex <= 0) ifindex = 1;
// 如果以此索引,返回的 dev为空,则说明此slot未被使用
if (!__dev_get_by_index(net, ifindex)) return ifindex;
{
######## 从 net->dev_name_head[index_hash_index] 链表上获取 存储有此 ifindex 的 节点 net_device 结构体指针
struct hlist_node *p;
struct net_device *dev;
struct hlist_head *head = dev_index_hash(net, ifindex);
hlist_for_each_entry(dev, p, head, index_hlist)
if (dev->ifindex == ifindex) return dev;
return NULL;
}
}
}
if (dev->iflink == -1)
dev->iflink = dev->ifindex;
################# 查看 dev->features 是否为无效组合
/* Fix illegal checksum combinations */
if ((dev->features & NETIF_F_HW_CSUM) &&
(dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
printk(KERN_NOTICE "%s: mixed HW and IP checksum settings.\n",dev->name);
dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM);
}
if ((dev->features & NETIF_F_NO_CSUM) &&
(dev->features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
printk(KERN_NOTICE "%s: mixed no checksumming and other settings.\n",dev->name);
dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM);
}
dev->features = netdev_fix_features(dev->features, dev->name);
/* Enable software GSO if SG is supported. */
if (dev->features & NETIF_F_SG)
dev->features |= NETIF_F_GSO;
ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev);
{
return raw_notifier_call_chain(&netdev_chain, val, dev);
return __raw_notifier_call_chain(nh, val, v, -1, NULL);
{
/*
struct raw_notifier_head *nh,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
*/
// notifier.c
return notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
{
cfg80211_netdev_notifier_call
{
switch (state) {
case NETDEV_POST_INIT:
SET_NETDEV_DEVTYPE(dev, &wiphy_type);
((net)->dev.type = (devtype))
break;
}
}
}
}
ret = notifier_to_errno(ret);
if (ret) goto err_uninit;
ret = netdev_register_kobject(dev);
if (ret) goto err_uninit;
dev->reg_state = NETREG_REGISTERED;
/*
* Default initial state at registry is that the
* device is present.
*/
################# 设置dev->state的 __LINK_STATE_PRESENT 位,表明设备有效
# 当从系统拔掉一个设备/或者系统PMU支持suspend,且设备suspend了,则该标志位会被清除
set_bit(__LINK_STATE_PRESENT, &dev->state);
################# 初始化设备的 队列策略(策略定义了:数据包如何从队列中 取出及放入、多少数据包可放入)
# 发送/接收队列的 如下域
# qdisc 队列操作函数与 skb 链表
# qdisc_sleeping 休眠队列
#
dev_init_scheduler(dev);
{
dev->qdisc = &noop_qdisc;
// 设置每一个 发送队列 tx->qdisc tx->qdisc_sleeping
netdev_for_each_tx_queue(dev, dev_init_scheduler_queue, &noop_qdisc);
{
// ....
// dev_init_scheduler_queue(struct net_device *dev,struct netdev_queue *dev_queue, void *_qdisc)
(&dev->_tx[x])->qdisc = _qdisc; // noop_qdisc
(&dev->_tx[x])->qdisc_sleeping = _qdisc; // noop_qdisc
}
dev_init_scheduler_queue(dev, &dev->rx_queue, &noop_qdisc);
dev->rx_queue.qdisc = noop_qdisc;
dev->rx_queue.qdisc_sleeping = noop_qdisc;
setup_timer(&dev->watchdog_timer, dev_watchdog, (unsigned long)dev);
}
dev_hold(dev);
################# 将 net_device 放到全局链表 dev_base_head 和 两个 hash 表中
list_netdevice(dev);
{
struct net *net = dev_net(dev); // return &init_net;
ASSERT_RTNL();
write_lock_bh(&dev_base_lock);
############# 将 此 net_device 挂载到如下链表上
# net->dev_base_head 链表
# net->dev_name_head[name_hash_index] 链表
# net->dev_name_head[index_hash_index] 链表
list_add_tail_rcu(&dev->dev_list, &net->dev_base_head);
hlist_add_head_rcu(&dev->name_hlist, dev_name_hash(net, dev->name));
hlist_add_head_rcu(&dev->index_hlist,dev_index_hash(net, dev->ifindex));
write_unlock_bh(&dev_base_lock);
return 0;
}
################# 将 NETDEV_REGISTER 事件通知给 所有需要获知网络设备已经注册消息的 子系统
ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
ret = notifier_to_errno(ret);
if (ret) {
rollback_registered(dev);
dev->reg_state = NETREG_UNREGISTERED;
}
/*
* Prevent userspace races by waiting until the network
* device is fully setup before sending notifications.
*/
if (!dev->rtnl_link_ops ||
dev->rtnl_link_state == RTNL_LINK_INITIALIZED)
rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U);
out:
return ret;
err_uninit:
if (dev->netdev_ops->ndo_uninit)
dev->netdev_ops->ndo_uninit(dev);
goto out;
}
##################### 释放 routing netlink 锁
# 通过 netdev_run_todo 遍历 net_todo_list 列表,完成所有网络设备实例的注册(或注销)
# 任何时候,只有一个CPU可以运行 netdev_run_todo
rtnl_unlock();
{
netdev_run_todo
{
struct list_head list;
/* Snapshot list, allow later requests */
list_replace_init(&net_todo_list, &list);
__rtnl_unlock();
while (!list_empty(&list))
{
################### 对于注册,此时需要将 节点从 net_todo_list 链表上删除
struct net_device *dev= list_first_entry(&list, struct net_device, todo_list);
list_del(&dev->todo_list);
if (unlikely(dev->reg_state != NETREG_UNREGISTERING)) {
printk(KERN_ERR "network todo '%s' but state %d\n", dev->name, dev->reg_state);
dump_stack();
continue;
}
################### 如果某个 net_device 处于 NETREG_UNREGISTERING 状态
# 则 修改其状态为 NETREG_UNREGISTERED
dev->reg_state = NETREG_UNREGISTERED;
on_each_cpu(flush_backlog, dev, 1);
netdev_wait_allrefs(dev);
################### 等到 net_device.refcnt == 0 后,调用其 destructor 接口
# 来注销网络设备
/* paranoia */
BUG_ON(atomic_read(&dev->refcnt));
WARN_ON(dev->ip_ptr);
WARN_ON(dev->ip6_ptr);
WARN_ON(dev->dn_ptr);
if (dev->destructor)
dev->destructor(dev);
/* Free network device */
kobject_put(&dev->dev.kobj);
}
}
}
return err;
}
}
5、
{
static const struct xfrm_type esp_type =
{
.description = "ESP4",
.owner = THIS_MODULE,
.proto = IPPROTO_ESP,
.flags = XFRM_TYPE_REPLAY_PROT,
.init_state = esp_init_state,
.destructor = esp_destroy,
.get_mtu = esp4_get_mtu,
.input = esp_input,
.output = esp_output
};
pfkey_add
pfkey_msg2xfrm_state
// af_key.c (net\key):
err = xfrm_init_state(x);
// net/xfrm/xfrm_state.c:2063:
err = x->type->init_state(x);
esp_init_state
esp_init_authenc
// ./net/ipv4/esp4.c:
}
netlink_rcv_skb
if (nlh->nlmsg_flags & NLM_F_ACK || err)
netlink_ack(skb, nlh, err);
rep = __nlmsg_put(skb, NETLINK_CB(in_skb).pid, nlh->nlmsg_seq,NLMSG_ERROR, payload, 0);
}
10、socket系统调用的实现,以AF_INET协议族为例
{
1、create
{
### 调用特定协议族的套接字创建函数
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
int flags = type & ~SOCK_TYPE_MASK;
type &= SOCK_TYPE_MASK;
struct socket *sock;
/*-----------------------------------/
* 创建INET层socket、网络层struct sock
*/
sock_create(family, type, protocol, &sock);
{
// return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
int err;
struct socket *sock;
const struct net_proto_family *pf;
// 确保套接字族有效
if (family < 0 || family >= NPROTO) return -EAFNOSUPPORT;
if (type < 0 || type >= SOCK_MAX) return -EINVAL;
/* Compatibility. This uglymoron is moved from INET layer to here to avoid deadlock in module load.
*/
if (family == PF_INET && type == SOCK_PACKET)
{
static int warned;
if (!warned) {
warned = 1;
printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n", current->comm);
}
family = PF_PACKET;
}
// 如果SE模块未被加载,则该接口是空函数
err = security_socket_create(family, type, protocol, kern);
if (err) return err;
/*创建套接字socket
*/
sock = sock_alloc();
sock->type = type;
#ifdef CONFIG_MODULES
/* Attempt to load a protocol module if the find failed.
*
* 12/09/1996 Marcin: But! this makes REALLY only sense, if the user
* requested real, full-featured networking support upon configuration.
* Otherwise module support will break!
*/
if (net_families[family] == NULL)
request_module("net-pf-%d", family);
#endif
rcu_read_lock();
#################################
// 根据协议族类型获取 net_families[ inet_family_ops->family ],即为net_proto_family结构体
pf = rcu_dereference(net_families[family]);
err = -EAFNOSUPPORT;
if (!pf)
goto out_release;
/*
* We will call the ->create function, that possibly is in a loadable
* module, so we have to bump(碰到) that loadable module refcnt first.
*/
if (!try_module_get(pf->owner))
goto out_release;
/* Now protected by module ref count */
rcu_read_unlock();
#################################
/*
1、创建网络层的struct sock,用来保存打开的连接的状态信息。它一般定义的变量叫 sk。
通过调用协议族常量对应的create接口,如果是PF_INET,则为inet_create
2、安装INET层socket操作集
inet_create 从inetsw_array中获取与该协议族\协议类型对应的p_inet_protosw结构体,如
tcp udp raw,然后执行socket->ops = p_inet_protosw->ops,从而为将VFS层sockfs操作集----->INET层的socket 操作的过渡
做准备。
3、安装网络层sock操作集
使得INET层socket操作过渡到网络层的某个特定协议对应的操作。
*/
err = pf->create(net, sock, protocol, kern);
{
// 对于INET 为 inet_family_ops.inet_create
struct sock *sk;
struct inet_protosw *answer;
struct inet_sock *inet;
struct proto *answer_prot;
unsigned char answer_flags;
char answer_no_check;
int try_loading_module = 0;
int err;
if (unlikely(!inet_ehash_secret))
if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM) build_ehash_secret();
sock->state = SS_UNCONNECTED;
/* Look for the requested type/protocol pair. */
lookup_protocol:
err = -ESOCKTNOSUPPORT;
rcu_read_lock();
//------ 1、在inetsw 查找与该传输层协议相匹配的交换结构 answer
// inetsw 数组 10 个元素
// 初始化时,其成员链表只有1个节点
// 其中只有 inetsw_array[SOCK_RAW].protocol == IPPROTO_IP
list_for_each_entry_rcu(answer, &inetsw[sock->type], list)
{
err = 0;
/* 如果socket p2为 SOCK_RAW, p3 为IPPROTO_IP,则走36*/
if (protocol == answer->protocol) {
if (protocol != IPPROTO_IP) break;
} else {
/* Check for the two wild cases. */
/* socket ,p2为非SOCK_RAW, p3 为IPPROTO_IP,则走此路
如:ipconfig命令
*/
if (IPPROTO_IP == protocol) {
protocol = answer->protocol;
break;
}
/* 对于ping,socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);会走该分支*/
if (IPPROTO_IP == answer->protocol) break;
}
err = -EPROTONOSUPPORT;
}
if (unlikely(err)) {
if (try_loading_module < 2) {
rcu_read_unlock();
/*
* Be more specific, e.g. net-pf-2-proto-132-type-1 (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
*/
if (++try_loading_module == 1)
request_module("net-pf-%d-proto-%d-type-%d", PF_INET, protocol, sock->type);
/*
* Fall back to generic, e.g. net-pf-2-proto-132 (net-pf-PF_INET-proto-IPPROTO_SCTP)
*/
else
request_module("net-pf-%d-proto-%d",PF_INET, protocol);
goto lookup_protocol;
} else
goto out_rcu_unlock;
}
err = -EPERM;
if (sock->type == SOCK_RAW && !kern && !capable(CAP_NET_RAW)) goto out_rcu_unlock;
err = -EAFNOSUPPORT;
if (!inet_netns_ok(net, protocol)) goto out_rcu_unlock;
/* 对于ifconfig ,将inet_dgram_ops、udp_prot存储到socket 和 sock中的对应成员里 */
/*
f_op->unlocked_ioctl
sock_ioctl
sock->ops->ioctl
// ops会根据要打开的socket类型分别指向
// inet_stream_ops 、 inet_dgram_ops 、 inet_sockram_ops
// 而这几个xx_ops 的ioctl 均指向 inet_ioctl
inet_ioctl
// 根据ioctl的cmd类型分别调用如下:
ip_rt_ioctl
arp_ioctl
devinet_ioctl
sk->sk_prot->ioctl
// 根据协议的类型sk->sk_prot注册为不同的inetsw_array[sock_type]->prot
tcp_ioctl
udp_ioctl
raw_ioctl
// 具体过程:
SYSCALL_DEFINE3(ioctl // ioctl.c
// 首先根据FD获取file指针filp
do_vfs_ioctl(filp, fd, cmd, arg);
vfs_ioctl(filp, cmd, arg);
filp->f_op->unlocked_ioctl(filp, cmd, arg);
//
以下过程在sys_socket __ sock_map_fd __ sock_alloc_file 中完成。
每次打开一个socket文件时,都会SOCK_INODE(sock)->i_fop = &socket_file_ops;
将socket_file_ops操作集赋给socket_alloc结构体中的vfs_inode的子成员。
因而filp->f_op->unlocked_ioctl 即为socket_file_ops.sock_ioctl
//
sock_ioctl(filp, cmd, arg);
struct socket * sock = file->private_data;
struct sock *sk = sock->sk;
struct net *net = sock_net(sk);
sock_do_ioctl(net, sock, cmd, arg);
//以下便将VFS层的ioctl过渡到了INET层的socket操作集
sock->ops->ioctl(sock, cmd, arg);
struct sock *sk = sock->sk;
sk->sk_prot->ioctl(sk, cmd, arg);
//以下将INET层socket操作集过渡到网络层具体的协议对应的操作集
*/
//------ 2、从交换结构 answer中获取
// 套接字层对应系统调用套接字操作接口集
// 内核协议相关套接字操作函数集
sock->ops = answer->ops;
answer_prot = answer->prot;
answer_no_check = answer->no_check;
answer_flags = answer->flags;
rcu_read_unlock();
WARN_ON(answer_prot->slab == NULL);
err = -ENOBUFS;
//------ 3、根据协议的类型得到不同的obj_size,从而分配与之对应的sock结构
// 并将answer->prot 存储到 sk->sk_prot 中。
// 对于inetsw_array数组,其元素对应的该成员分别是 tcp_prot、udp_prot、raw_prot
// 对于TCP协议 为 inetsw_array[IPPROTO_TCP].prot == tcp_prot
sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
{
struct sock *sk;
sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
if (sk) {
sk->sk_family = family;
/*
* See comment in struct sock definition to understand
* why we need sk_prot_creator -acme
*/
sk->sk_prot = sk->sk_prot_creator = prot;
sock_lock_init(sk);
sock_net_set(sk, get_net(net));
atomic_set(&sk->sk_wmem_alloc, 1);
sock_update_classid(sk);
}
return sk;
}
if (sk == NULL) goto out;
err = 0;
sk->sk_no_check = answer_no_check;
if (INET_PROTOSW_REUSE & answer_flags) sk->sk_reuse = 1;
//------ 3、获取INET套接字 inet_sock,并设置
inet = inet_sk(sk);
inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;
/* 对于原始套接字类型、此时protocol表示端口号 */
if (SOCK_RAW == sock->type) {
inet->inet_num = protocol;
if (IPPROTO_RAW == protocol) inet->hdrincl = 1;
}
if (ipv4_config.no_pmtu_disc) inet->pmtudisc = IP_PMTUDISC_DONT;
else inet->pmtudisc = IP_PMTUDISC_WANT;
inet->inet_id = 0;
//------ 4、为每个打开的socket设置其等待队列
sock_init_data(sock, sk);
sk->sk_destruct = inet_sock_destruct;
sk->sk_protocol = protocol;
sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
inet->uc_ttl = -1;
inet->mc_loop = 1;
inet->mc_ttl = 1;
inet->mc_all = 1;
inet->mc_index = 0;
inet->mc_list = NULL;
sk_refcnt_debug_inc(sk);
if (inet->inet_num) {
/* It assumes that any protocol which allows the user to assign a number at socket
* creation time automatically shares.
*/
inet->inet_sport = htons(inet->inet_num);
/* Add to protocol hash chains. */
sk->sk_prot->hash(sk);
}
//------ 5、初始化新创建的套接字,注册tcp协议特定的AF_INET函数,包含了数据收发函数,位于ipv4_specific中
// 调用 tcp_v4_init_sock
if (sk->sk_prot->init) {
err = sk->sk_prot->init(sk);
if (err) sk_common_release(sk);
}
out:
return err;
out_rcu_unlock:
rcu_read_unlock();
goto out;
}
if (err < 0)
goto out_module_put;
/*
* Now to bump the refcnt of the [loadable] module that owns this
* socket at sock_release time we decrement its refcnt.
*/
if (!try_module_get(sock->ops->owner))
goto out_module_busy;
/*
* Now that we're done with the ->create function, the [loadable]
* module can have its refcnt decremented
*/
module_put(pf->owner);
err = security_socket_post_create(sock, family, type, protocol, kern);
if (err)
goto out_sock_release;
*res = sock;
return 0;
}
/*-----------------------------------/
为socket分配文件描述符FD、并分配一个文件
以FD为索引,查找用户空间的文件描述符表,得到内核空间中文件描述符的地址
文件描述符,分为两部分: inode节点数据 特定文件数据。
比如sockfs文件描述符等价于:
struct socket_alloc {
struct socket socket;
struct inode vfs_inode;
};
*/
retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
{
struct file *newfile;
// 分配file结构并安装inode、path、设置sockfs文件操作集socket_file_ops到file结构
int fd = sock_alloc_file(sock, &newfile, flags);
{
struct qstr name = { .name = "" };
struct path path;
struct file *file;
int fd;
// 从当前进程的文件描述表中返回一个空闲的索引
fd = get_unused_fd_flags(flags);
if (unlikely(fd < 0))
return fd;
// 套接字文件的根目录项
path.dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name);
if (unlikely(!path.dentry)) {
put_unused_fd(fd);
return -ENOMEM;
}
// 套接字文件的挂载点结构,此时会增加sockfs挂载点结构的引用计数
path.mnt = mntget(sock_mnt);
path.dentry->d_op = &sockfs_dentry_operations;
// 将path.dentry实例与套接字文件对应的inode关联起来,设置path.dentry.d_inode字段,并将dentry添加到inode.i_dentry表中。
d_instantiate(path.dentry, SOCK_INODE(sock));
// 设置用于操作文件数据的函数集---->inode.i_fop
SOCK_INODE(sock)->i_fop = &socket_file_ops;
// 根据 套接字文件的根目录项、挂载点目录 分配一个套接字文件
// 从slab cache filp_cachep中分配 file结构体;
// 设置 文件名与inode的关联、文件所在文件系统的有关信息;
// 设置与文件相关的inode实例的地址空间实例,通常设置为inode->i_maaping
// 设置文件数据 操作集到file->f_op
file = alloc_file(&path, FMODE_READ | FMODE_WRITE,
&socket_file_ops);
if (unlikely(!file)) {
/* drop dentry, keep inode */
atomic_inc(&path.dentry->d_inode->i_count);
path_put(&path);
put_unused_fd(fd);
return -ENFILE;
}
// file结构关联到socket;保存open系统调用时传递的额外参数;文件数据偏移为0;保存socket指针到file.private_data
sock->file = file;
file->f_flags = O_RDWR | (flags & O_NONBLOCK);
file->f_pos = 0;
file->private_data = sock;
*f = file;
// 返回文件描述符
return fd;
}
// 将file结构与FD关联起来,即执行如下操作
// current->files->fdt->fd[FD] = newfile;
if (likely(fd >= 0))
fd_install(fd, newfile);
return fd;
}
return retval;
}
}
2、send
{
SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len,unsigned, flags)
{
return sys_sendto(fd, buff, len, flags, NULL, 0);
{
/*
int, fd
void __user *, buff
size_t, len
unsigned, flags
struct sockaddr __user *, addr
int addr_len
*/
struct socket *sock;
struct sockaddr_storage address;
int err;
struct msghdr msg;
struct iovec iov;
int fput_needed;
if (len > INT_MAX)
len = INT_MAX;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
iov.iov_base = buff;
iov.iov_len = len;
msg.msg_name = NULL;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_namelen = 0;
if (addr)
{
err = move_addr_to_kernel(addr, addr_len, (struct sockaddr *)&address);
msg.msg_name = (struct sockaddr *)&address;
msg.msg_namelen = addr_len;
}
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
msg.msg_flags = flags;
err = sock_sendmsg(sock, &msg, len);
{
// struct socket *sock, struct msghdr *msg, size_t size
struct kiocb iocb;
struct sock_iocb siocb;
int ret;
init_sync_kiocb(&iocb, NULL);
iocb.private = &siocb;
ret = __sock_sendmsg(&iocb, sock, msg, size);
{
int err = security_socket_sendmsg(sock, msg, size);
return err ?: __sock_sendmsg_nosec(iocb, sock, msg, size);
{
struct sock_iocb *si = kiocb_to_siocb(iocb);
sock_update_classid(sock->sk);
si->sock = sock;
si->scm = NULL;
si->msg = msg;
si->size = size;
// 调用 inet_stream_ops.tcp_sendmsg
return sock->ops->sendmsg(iocb, sock, msg, size);
{
struct iovec *iov;
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
int iovlen, flags;
int mss_now, size_goal;
int sg, err, copied;
long timeo;
lock_sock(sk);
flags = msg->msg_flags;
timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
/* Wait for a connection to finish. */
if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
goto out_err;
/* This should be in poll */
clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
mss_now = tcp_send_mss(sk, &size_goal, flags);
/* Ok commence sending. */
iovlen = msg->msg_iovlen;
iov = msg->msg_iov;
copied = 0;
err = -EPIPE;
if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
goto out_err;
sg = sk->sk_route_caps & NETIF_F_SG;
while (--iovlen >= 0) {
size_t seglen = iov->iov_len;
unsigned char __user *from = iov->iov_base;
iov++;
while (seglen > 0) {
int copy = 0;
int max = size_goal;
skb = tcp_write_queue_tail(sk);
{
return skb_peek_tail(&sk->sk_write_queue);
{
struct sk_buff *list = ((struct sk_buff *)list_)->prev;
if (list == (struct sk_buff *)list_)
list = NULL;
return list;
}
}
if (tcp_send_head(sk)) {
if (skb->ip_summed == CHECKSUM_NONE)
max = mss_now;
copy = max - skb->len;
}
if (copy <= 0) {
new_segment:
/* Allocate new segment. If the interface is SG,
* allocate skb fitting to single page.
*/
if (!sk_stream_memory_free(sk))
goto wait_for_sndbuf;
skb = sk_stream_alloc_skb(sk,
select_size(sk, sg),
sk->sk_allocation);
if (!skb)
goto wait_for_memory;
/*
* Check whether we can use HW checksum.
*/
if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
skb->ip_summed = CHECKSUM_PARTIAL;
skb_entail(sk, skb);
copy = size_goal;
max = size_goal;
}
/* Try to append data to the end of skb. */
if (copy > seglen)
copy = seglen;
/* Where to copy to? */
if (skb_tailroom(skb) > 0) {
/* We have some space in skb head. Superb! */
if (copy > skb_tailroom(skb))
copy = skb_tailroom(skb);
err = skb_add_data_nocache(sk, skb, from, copy);
if (err)
goto do_fault;
} else {
int merge = 0;
int i = skb_shinfo(skb)->nr_frags;
struct page *page = TCP_PAGE(sk);
int off = TCP_OFF(sk);
if (skb_can_coalesce(skb, i, page, off) &&
off != PAGE_SIZE) {
/* We can extend the last page
* fragment. */
merge = 1;
} else if (i == MAX_SKB_FRAGS || !sg) {
/* Need to add new fragment and cannot
* do this because interface is non-SG,
* or because all the page slots are
* busy. */
tcp_mark_push(tp, skb);
goto new_segment;
} else if (page) {
if (off == PAGE_SIZE) {
put_page(page);
TCP_PAGE(sk) = page = NULL;
off = 0;
}
} else
off = 0;
if (copy > PAGE_SIZE - off)
copy = PAGE_SIZE - off;
if (!sk_wmem_schedule(sk, copy))
goto wait_for_memory;
if (!page) {
/* Allocate new cache page. */
if (!(page = sk_stream_alloc_page(sk)))
goto wait_for_memory;
}
/* Time to copy data. We are close to
* the end! */
err = skb_copy_to_page_nocache(sk, from, skb,
page, off, copy);
if (err) {
/* If this page was new, give it to the
* socket so it does not get leaked.
*/
if (!TCP_PAGE(sk)) {
TCP_PAGE(sk) = page;
TCP_OFF(sk) = 0;
}
goto do_error;
}
/* Update the skb. */
if (merge) {
skb_shinfo(skb)->frags[i - 1].size +=
copy;
} else {
skb_fill_page_desc(skb, i, page, off, copy);
if (TCP_PAGE(sk)) {
get_page(page);
} else if (off + copy < PAGE_SIZE) {
get_page(page);
TCP_PAGE(sk) = page;
}
}
TCP_OFF(sk) = off + copy;
}
if (!copied)
TCP_SKB_CB(skb)->flags &= ~TCPHDR_PSH;
tp->write_seq += copy;
TCP_SKB_CB(skb)->end_seq += copy;
skb_shinfo(skb)->gso_segs = 0;
from += copy;
copied += copy;
if ((seglen -= copy) == 0 && iovlen == 0)
goto out;
if (skb->len < max || (flags & MSG_OOB))
continue;
if (forced_push(tp)) {
tcp_mark_push(tp, skb);
__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
} else if (skb == tcp_send_head(sk))
tcp_push_one(sk, mss_now);
continue;
wait_for_sndbuf:
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:
if (copied)
tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
goto do_error;
mss_now = tcp_send_mss(sk, &size_goal, flags);
}
}
out:
if (copied)
tcp_push(sk, flags, mss_now, tp->nonagle);
release_sock(sk);
if (copied > 0)
uid_stat_tcp_snd(current_uid(), copied);
return copied;
do_fault:
if (!skb->len) {
tcp_unlink_write_queue(skb, sk);
/* It is the one place in all of TCP, except connection
* reset, where we can be unlinking the send_head.
*/
tcp_check_send_head(sk, skb);
sk_wmem_free_skb(sk, skb);
}
do_error:
if (copied)
goto out;
out_err:
err = sk_stream_error(sk, flags, err);
release_sock(sk);
return err;
}
}
}
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&iocb);
return ret;
}
out_put:
fput_light(sock->file, fput_needed);
out:
return err;
}
}
}
}
11、route子系统
{
inet_init
ip_init
{
ip_rt_init();
{
int rc = 0;
#ifdef CONFIG_IP_ROUTE_CLASSID
ip_rt_acct = __alloc_percpu(256 * sizeof(struct ip_rt_acct), __alignof__(struct ip_rt_acct));
if (!ip_rt_acct)
panic("IP: failed to allocate ip_rt_acct\n");
#endif
########################### 分配内核内存分配器
# ipv4_dst_ops.kmem_cachep ----> rtable
# ipv4_dst_blackhole_ops.kmem_cachep = ipv4_dst_ops.kmem_cachep
#
ipv4_dst_ops.kmem_cachep = kmem_cache_create("ip_dst_cache", sizeof(struct rtable), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
ipv4_dst_blackhole_ops.kmem_cachep = ipv4_dst_ops.kmem_cachep;
########################### 初始化 ipv4_dst_ops.pcpuc_entries
# ipv4_dst_ops.pcpuc_entries.lock
# .count = 0;
# .counters = 0; // percpu 变量
# .list = 0; // 热插拔表项
# ipv4_dst_ops.pcpuc_entries,挂载到 percpu_counters 链表
dst_entries_init(&ipv4_dst_ops);
{
// dst_ops.h
return percpu_counter_init(&dst->pcpuc_entries, 0);
{
static struct lock_class_key __key;
__percpu_counter_init(&dst->pcpuc_entries, value/*0*/, &__key);
{
// lib/percpu_counter.c
// struct percpu_counter *fbc, s64 amount, struct lock_class_key *key
spin_lock_init(&fbc->lock);
lockdep_set_class(&fbc->lock, key);
fbc->count = amount;
fbc->counters = alloc_percpu(s32);
{
// mm/percpu.c
}
if (!fbc->counters)
return -ENOMEM;
debug_percpu_counter_activate(fbc);
#ifdef CONFIG_HOTPLUG_CPU
INIT_LIST_HEAD(&fbc->list);
mutex_lock(&percpu_counters_lock);
list_add(&fbc->list, &percpu_counters);
mutex_unlock(&percpu_counters_lock);
#endif
return 0;
}
}
}
dst_entries_init(&ipv4_dst_blackhole_ops);
########################### 初始化路由HASH表
# rt_hash_table 信息如下
rt_hash_table = (struct rt_hash_bucket *)alloc_large_system_hash(
"IP route cache",
sizeof(struct rt_hash_bucket),
rhash_entries,
(totalram_pages >= 128 * 1024) ?15 : 17,
0,
&rt_hash_log,
&rt_hash_mask,
rhash_entries ? 0 : 512 * 1024);
{
// mm/page_alloc.c
/*
const char *tablename, "IP route cache"
unsigned long bucketsize, rt_hash_bucket
unsigned long numentries,
int scale,
int flags,
unsigned int *_hash_shift,
unsigned int *_hash_mask,
unsigned long limit
*/
}
memset(rt_hash_table, 0, (rt_hash_mask + 1) * sizeof(struct rt_hash_bucket));
rt_hash_lock_init();
ipv4_dst_ops.gc_thresh = (rt_hash_mask + 1);
ip_rt_max_size = (rt_hash_mask + 1) * 16;
devinet_init();
ip_fib_init();
INIT_DELAYED_WORK_DEFERRABLE(&expires_work, rt_worker_func);
expires_ljiffies = jiffies;
schedule_delayed_work(&expires_work,
net_random() % ip_rt_gc_interval + ip_rt_gc_interval);
if (ip_rt_proc_init())
printk(KERN_ERR "Unable to create route proc files\n");
#ifdef CONFIG_XFRM
xfrm_init();
xfrm4_init(ip_rt_max_size);
#endif
rtnl_register(PF_INET, RTM_GETROUTE, inet_rtm_getroute, NULL, NULL);
#ifdef CONFIG_SYSCTL
register_pernet_subsys(&sysctl_route_ops);
#endif
register_pernet_subsys(&rt_genid_ops);
return rc;
}
inet_initpeers();
#if defined(CONFIG_IP_MULTICAST) && defined(CONFIG_PROC_FS)
igmp_mc_proc_init();
#endif
}
}