LINUX下PING与TCP_IP协议栈学习笔记(2)
时间:2009-04-02 来源:superfight
ip_append_data在/net/ipv4/ip_output.c中
int ip_append_data(struct sock *sk, |
sock_wmalloc负责从高速缓存中分配一个skb
sock_wmalloc在/net/core/sock.c中
struct sk_buff *sock_wmalloc(struct sock *sk, unsigned long size, int force, |
alloc_skb负责调用分配函数
alloc_skb在/include/linux/skbuff.h中
static inline struct sk_buff *alloc_skb(unsigned int size, |
__alloc_skb负责实际的分配
__alloc_skb在/net/core/skbuff.c中
struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, |
由于我们传递的fclone为0,所以是不会进入到if (fclone)中的
好,返回到sock_wmalloc,分配skb成功之后进入skb_set_owner_w
skb_set_owner_w在/include/net/sock.h中
static inline void skb_set_owner_w(struct sk_buff *skb, struct sock *sk) |
执行完后一个skb便分配好了
结构图如下
500)this.width=500;" border=0>
然后到data = skb_put(skb, fraglen)
放入数据和IP报头,并且返回起初的data指针位置,skb的len加上放入的大小
如下图
接着设置设置IP层指针的位置,这里exthdrlen为0 如下图
500)this.width=500;" border=0> skb->transport_header = (skb->network_header + fragheaderlen);
接着设置运输层指针的位置
如下图
500)this.width=500;" border=0>
然后到data += fragheaderlen;
使得data(不是skb呢个)指向运输层头部,如下图
来到if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) 这里getfrag为调用ip_append_data时所传递的参数ip_generic_getfrag
ip_generic_getfrag就不分析了,他负责把数据从用户空间拷贝到data指针(不是skb呢个)的位置 接着到__skb_queue_tail(&sk->sk_write_queue, skb);
把这个skb挂接到sk的发送队列中 然后conninue回到while头,因为我们把length长的数据拷贝完成了,这里length为0,跳出while循环 return 0 退出 回到raw_sendmsg,现在进入到ip_push_pending_frames
ip_push_pending_frames负责skb的重新排列
ip_push_pending_frames在/net/ipv4/ip_output.c中
int ip_push_pending_frames(struct sock *sk) |
由于skb_shared_info-> frag_list为NULL,所以这里tail_skb为空
这里data和network_header指针相等
所以不会进入到if (skb->data < skb_network_header(skb))中
因为我们的发送队列中只有一个skb,在之前已经出队了
所以这里不会进入到while ((tmp_skb = __skb_dequeue(&sk->sk_write_queue)) != NULL)中
接下来设置ip头部信息,设置好之后的结构如下
ip_local_out在/net/ipv4/ip_output.c中
int ip_local_out(struct sk_buff *skb) |
继续进入__ip_local_out
__ip_local_out在/net/ipv4/ip_output.c中
int __ip_local_out(struct sk_buff *skb) |
NF_HOOK和Netfilter有关,我们 不关心Netfilter,直接进入dst_output
dst_output在/include/net/dst.h中
static inline int dst_output(struct sk_buff *skb) |
看看之前的结构图,得知dst->output为ip_output
ip_output在/net/ipv4/ip_output.c中
int ip_output(struct sk_buff *skb) |
继续进入到ip_finish_output
ip_finish_output在/net/ipv4/ip_output.c中
static int ip_finish_output(struct sk_buff *skb) |
这里我们的数据大小不会超过MTU,所以不需要分片,进入到ip_finish_output2
ip_finish_output2在/net/ipv4/ip_output.c中
static inline int ip_finish_output2(struct sk_buff *skb) |
这里一开始我们的dst->hh为NULL,但是当第二次发送ICMP包的时候这里的hh就已经分配好了,也就是说只有第一次会进入dst->neighbour->output,以后都是neigh_hh_output
两个走向都会分析,现在先看hh为NULL时
neighbour->output为neigh_resolve_output
neigh_resolve_output在/net/core/neighbour.c中
int neigh_resolve_output(struct sk_buff *skb)
|
由于现在data和network_header指针指向同一个位置,所以skb_network_offset(skb)为0,
__skb_pull(skb, skb_network_offset(skb))并没有实际改变skb结构
之后是创建hh结构,和arp有关,也和路由表查询有关,我这里就不分析了
创建好的hh如下
然后到err = dev_hard_header(skb, dev, ntohs(skb->protocol),neigh->ha, NULL, skb->len)
dev_hard_header在/include/linux/netdevice.h中
static inline int dev_hard_header(struct sk_buff *skb, struct net_device *dev, |
我们的lo设备是有头部操作集的,eth_header_ops,结构如下
const struct header_ops eth_header_ops ____cacheline_aligned = { |
可以看见是有create函数的
eth_header在/net/ethernet/eth.c中
int eth_header(struct sk_buff *skb, struct net_device *dev, |
主要是注意struct ethhdr *eth = (struct ethhdr *)skb_push(skb, ETH_HLEN);
这里会进行数据的压入
压入后的结构如下
来到rc = neigh->ops->queue_xmit(skb)
neigh->ops->queue_xmit为dev_queue_xmit
在进入dev_queue_xmit之前我们回到ip_finish_output2中看看有hh时的流程
neigh_hh_output在/include/net/neighbour.h中
static inline int neigh_hh_output(struct hh_cache *hh, struct sk_buff *skb) |
neigh_hh_output也会对skb进行数据压入的操作然后调用hh_output
在之前hh的结构中hh_output正是dev_queue_xmit
所以最后两边都会回到dev_queue_xmit