数据的发送
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层实现系列— send()类发送函数的实现