skb数据的发送

数据的发送

skb结构和分配

skb分配释放的频率非常高,用kmem_cache分配skb_buf的头部,alloc_skb分配其数据区,alloc_skb最终调用了_kmalloc分配连续物理内存,skb_buf结构体中重要成员:

head指向已分配空间的头部,end指向该空间尾部,data指向有效数据头部,tail指向有效数据尾部,当skb在各层流动时,head和end是不变的。

skb_put:向后移动tail来增加有效数据空间的大小,将skb->tail移动到新的位置 后,老的tail将作为返回值返回;

skb_push:将data前移来增大有效数据空间的大小,将移动后的skb->data返回;

skb_headroom:返回head和data之间的空间大小

skb_tailroom:返回tail和end的空间大小

skb_reserve:将有效数据空间向后移动len,相等于减少了tail空间,拓展了同样大小的head空间

alloc_skb是__alloc_skb的封装,它分配sk_buff结构及数据缓存块:(a)通过kmem_cache_alloc函数从缓存里分配sk_buff数据结构;(b)通过kmalloc分配数据缓冲区。dev_alloc_skb函数主要用于设备驱动中,并在中断上下文中使用。它包装了skb_alloc函数并且在分配空间上多分了16字节空间,当skb数据头需要增加而又长度又小于16字节时,这样就可利用skb_reserve出来的空间,避免了重新分配,由于该函数主要在中断里使用,其在分配空间要求置上原子标志位GFP_ATOMIC。

layer4:传输层(Transport)

tcp_sendmsg##net/ipv4/tcp.c

int tcp_sendmsg(structsock *sk, struct msghdr *msg, size_t size)

Layer 3:网络层(Network Layer)

ip_queue_xmit## net/ipv4/ip_output.c

int ip_queue_xmit(structsock *sk, struct sk_buff *skb, struct flowi *fl)

{

struct inet_sock *inet = inet_sk(sk);

struct net *net = sock_net(sk);

struct ip_options_rcu *inet_opt;

struct flowi4 *fl4;

struct rtable *rt;

struct iphdr *iph;

int res;

rt = skb_rtable(skb);

if (rt)

            goto packet_routed;如果已经路由

/* Make sure we can route this packet.*/

rt = (struct rtable*)__sk_dst_check(sk, 0);如果没被路由

   if (!rt){      

/* If this fails, retransmitmechanism of transport layer will

* keep trying until routeappears or the connection times

* itself out.

*/

rt = ip_route_output_ports(net,fl4, sk,

daddr, inet->inet_saddr,

  inet->inet_dport,

inet->inet_sport,

sk->sk_protocol,

RT_CONN_FLAGS(sk),

sk->sk_bound_dev_if);

}

res = ip_local_out(net, sk, skb); //触使包发到数据链路层

}

在分配路由表的时候关联netdev

ip_route_output_ports

---> ip_route_output_flow

--->__ip_route_output_key

--->__ip_route_output_key_hash

--->__mkroute_output

--->rt_dst_alloc

struct rtable*__ip_route_output_key_hash(struct net *net, struct flowi4 *fl4,

intmp_hash)

{

struct net_device *dev_out = NULL;

dev_out = dev_get_by_index_rcu(net,fl4->flowi4_oif);

rth = __mkroute_output(&res, fl4, orig_oif,dev_out, flags);

}

net/ipv4/route.c

static struct rtable*rt_dst_alloc(struct net_device *dev,

unsigned intflags, u16 type,

boolnopolicy, bool noxfrm, bool will_cache)

{

struct rtable *rt;

rt = dst_alloc(&ipv4_dst_ops, dev,1, DST_OBSOLETE_FORCE_CHK,

(will_cache ? 0 :(DST_HOST | DST_NOCACHE)) |

(nopolicy ? DST_NOPOLICY: 0) |

(noxfrm ? DST_NOXFRM :0));

if (rt) {

rt->rt_genid =rt_genid_ipv4(dev_net(dev));

rt->rt_flags = flags;

rt->rt_type = type;

rt->rt_is_input = 0;

rt->rt_iif = 0;

   rt->rt_pmtu = 0;

rt->rt_gateway = 0;

rt->rt_uses_gateway = 0;

rt->rt_table_id = 0;

INIT_LIST_HEAD(&rt->rt_uncached);

     rt->dst.output = ip_output;

if (flags & RTCF_LOCAL)

rt->dst.input =ip_local_deliver;

}

return rt;

}

int ip_local_out(structnet *net, struct sock *sk, struct sk_buff *skb)

{      

int err;

err = __ip_local_out(net, sk, skb);

if (likely(err == 1))

 err = dst_output(net, sk, skb);

return err;

}

/* Output packet tonetwork from transport. */

static inline intdst_output(struct net *net, struct sock *sk, struct sk_buff *skb)

{

return skb_dst(skb)->output(net, sk,skb);调用rt_dst_alloc中的ip_output

}

/*ip_output单播包,ip_mc_output组播包*/

int ip_output(struct net*net, struct sock *sk, struct sk_buff *skb)

{      

struct net_device *dev =skb_dst(skb)->dev;    

IP_UPD_PO_STATS(net, IPSTATS_MIB_OUT,skb->len);

     skb->dev = dev;

skb->protocol =htons(ETH_P_IP);  

return NF_HOOK_COND(NFPROTO_IPV4,NF_INET_POST_ROUTING,

net, sk, skb, NULL,dev,

ip_finish_output,

!(IPCB(skb)->flags& IPSKB_REROUTED));

}

ip_output会调用netfilter的钩子函数

ip_finish_output

--->ip_finish_output2

---->dst->neighbour->output

在IPv4中,neighbour subsystem使用的是arp,所以一般情况下,dst->neighbour->outpu指向的是arp_generic_ops->neigh_resolve_output。

neigh_resolve_output

---->dev_queue_xmit###net/core/dev.c

---->dev_hard_start_xmit

----> ndo_start_xmit

/* dev_queue_xmit是对skb做些最后的处理并且第一次尝试发送,软中断是将前者发送失败或者没发完的包发送出去*/

数据链路层

在驱动中实现ndo_start_xmit函数

static structnet_device_ops xxx_netdev_ops = {

.ndo_init = xxx_init,

.ndo_uninit = xxx_uninit,

.ndo_open = xxx_open,

.ndo_stop = xxx_close,

.ndo_start_xmit = xxx_start_xmit,

.ndo_get_stats = xxx_get_stats,

.ndo_tx_timeout = xxx_tx_timeout,

.ndo_do_ioctl = xxx_ioctl,

};

网络子系统部分实现一个传输队列qdisc,系统中每个CPU都拥有自己的传输独立,每个要发送到数据包都先放到传输队列中,驱动调用ndo_start_xmit将包发到硬件缓存后,当网络子系统有新的包需要发送时,可能会再次调用ndo_start_xmit,但这是硬件可能还没有来得及将包发出,因此这时需要内核子系统维护一个发送队列。当驱动感知到硬件无法再接收更多数据时,netif_stop_queue通知内核网络子系统停止包的发送,当可以继续传输数据时用netif_wake_queue触发继续发送,netif_start_queue用在驱动最初简单地设置发送标志,另外当连接状态有变时,需要告知内核子系统:

netif_carrier_on

作用告诉内核子系统网络链接完整。

netif_carrier_off

作用告诉内核子系统网络断开。

netif_carrier_ok

作用:查询网络断开还是链接。

以上函数主要是改变net_device dev的state状态来告知内核链路状态的变化,网络设备将数据包发送完成之后,将向主机产生一个硬件中断,此后需要释放skb等系列后续操作

reference:

socket数据发送过程zz

Socket层实现系列— send()类发送函数的实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值