免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 12052 | 回复: 10
打印 上一主题 下一主题

深入剖析网络发送过程 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-12-20 17:23 |只看该作者 |倒序浏览
Layer 5:应用层(Application Layer)

在TCP协议上,当通过三方握手建立了连接之后,就进入数据包的实质发送阶段,在本文中以send命令来阐述。当通过send将数据包发送之后,glibc函数库会启用另外一个其定义的别用名函数__libc_sendto(),该函数最后会间接执行到sendto系统调用:
inline_syscall##nr(name, args);// ##nr说明是该系统调用带有nr个args参数sendto系统调用的参数值是6,而name就是sendto
从上面的分析可以看出glibc将要执行的下面一条语句是
inline_syscall6(name,arg1,arg2,arg3,arg4,arg5,arg6)
在该函数中一段主要功能实现代码如下:
__asm__ __volatile__                                    \
          ("callsys # %0 %1 <= %2 %3 %4 %5 %6 %7 %8"            \
           : inline_syscall_r0_out_constraint (_sc_0),          \
             "=r"(_sc_19), "=r"(_sc_16), "=r"(_sc_17),          \
             "=r"(_sc_18), "=r"(_sc_20), "=r"(_sc_21)           \
           : "0"(_sc_0), "2"(_sc_16), "3"(_sc_17), "4"(_sc_18), \
             "1"(_sc_19), "5"(_sc_20), "6"(_sc_21)              \
           : inline_syscall_clobbers);                          \
        _sc_ret = _sc_0, _sc_err = _sc_19;  
该代码采用了嵌入汇编(详细介绍查阅嵌入汇编相关书籍),其中:
_sc_0=sendto;
_sc_19 --_sc_21分别是arg1—arg6;
inline_syscall_r0_out_constraint:功能相当于"=r",选用一个寄存器来存储输出变量。
"0"--"6"分别是%0--%6,代表_sc_0--_sc_21
接下来函数最终通过Linux中顶顶有名的INT 0X80陷入系统核心。具体的过程可以参考内核相关书籍。下面是一个兄弟对INT 0X80的简要介绍:
http://blog.chinaunix.net/u2/65427/showart_712571.html
在陷入系统内核以后,最终会调用系统所提供的系统调用函数sys_sendto(),该函数直接调用了__sock_sendmsg(),该函数对进程做一个简单的权限检查之后就触发套接字(socket)中定义的虚拟sendmsg的函数,进而进入到下一层传输层处理。

Layer 4: 传输层(Transport Layer)

由上层的讨论可知,系统触发了sendmsg虚拟接口函数,其实就是传输层中的tcp_sendmsg或是udp_sendmsg,看你所使用的协议而定。本文介绍tcp_sendmsg().
该函数需要做如下工作:
1)        为sk_buff(后面简称skb)分配空间,该函数首先尝试在套接字缓冲队列中寻找空闲空间,如果找不到就使用tcp_alloc_pskb()为其重新分配空间。
2)        下面这步就会tcp_sendmsg函数的主要部分了,将数据拷贝到缓冲区。它分为如下两种情况:
2.1)如果skb还有剩余空间的话,就使用skb_add_data()来向skb尾部添加数据包。代码如下:
if (skb_tailroom(skb) > 0) {
                                /* We have some space in skb head. Superb! */
                                if (copy > skb_tailroom(skb))
                                        copy = skb_tailroom(skb);
                                if ((err = skb_add_data(skb, from, copy)) != 0)
                                        goto do_fault;
                        }
2.2)如果skb没有了可用空间,内核会使用TCP_PAGE宏来为发送的数据包分配一个高速缓存页空间,当该页被正确地分配后就调用Copy_from_user(to(page地址),from(usr空间),n)将用户空间数据包复制到page所在的地址空间。
但是我们都知道数据包在协议层之间的传输是通过skb的,难道将数据包复制到这个新分配的page中,内核就可以去睡大觉了吗?当然不是!接下来内核就要来处理这个问题了,那么怎样来处理呢?
此时就需要使用到skb中的另外一个数据区struct skb_shared_info[],但是该数据区在创建skb时是没有为其分配空间的,也就是说它开始纯粹就是个指针,而没有具体的告诉它要指向什么地方。这时大家应该知道它可以指向什么地方了,对,就是page!在内核中对这种情况的具体是通过fill_page_desc(struct sk_buff *skb,int I,struct page *page,int off,int size)来实现的,代码如下:
static inline void fill_page_desc(struct sk_buff *skb, int i,
                                  struct page *page, int off, int size)
{
        skb_frag_t *frag = &skb_shinfo(skb)->frags;
        frag->page = page;
        frag->page_offset = off;
        frag->size = size;
        skb_shinfo(skb)->nr_frags = i + 1;
}
这里需要注意的是struct skb_shared_info[]只能通过skb_shinfo来获取,在该结构体中skb_flag_t类型的flags就是具体指向page的数组。
2.3)至此skb数据包的装载工作算是结束了,接下来就需要做一些后续工作,包括是否要分片,以及后来的TCP协议头的添加。先看在tcp_sendmsg()中的最后一个重要函数tcp_push,它的调用格式如下:
static inline void tcp_push(struct sock *sk, struct tcp_opt *tp, int flags,
                            int mss_now, int nonagle)
细心的朋友会发现,在该函数中传输的竟然不是skb,而是一个名为sock的结构体,那这又是什么东东呢?个人理解是它在顶层协议层之间(例如:应用层和传输层之间)的传输起着非常重要的作用,相当于沟通两层之间的纽带。再深入查找下该结构体的构成,我们很容易发现这样一个结构体变量:struct sk_buff_head,有名称我们可以知道它是用来描述skb头部信息的一个结构体,它指向了buffer的数据区。这下我们也明白了点,这个结构体其实还充当了一个队列作用,是用来存储skb的数据区。协议层之间传输完之后,具体到该层处理时内核就会从sk_buff_head逐个中取出skb数据区来处理,例如添加协议头等。
好了,tcp_sendmsg到此结束了它的使命了,下面将要需要的一个函数就是在tcp_push()中直接用到的一个函数:__tcp_push_pending_frames(),该函数又直接调用tcp_write_xmit()函数来进一步对数据包处理,它包括一下两步:
1)        检查是否需要对数据包进行分片,条件是只要skb中全部数据长度大于当前路由负荷量就需要分片。
2)        采用skb_clone(skb,GFP_ATOMIC)为TCP_HEAD分配一个sk_buff空间,这里需要注意的是skb_clone分配空间的特点,它首先是依照参数skb来来复制出一个新的sk_buff,新的skb和旧的skb共享数据变量缓存区,但是结构体缓冲区不是共享的,这似乎和copy on write机制有些相似。
3)        在分配了一个新的skb之后,内核就会执行tcp_transmit_skb().其实内核中是将2,3步合在一起的,如下:
tcp_transmit_skb(sk, skb_clone(skb, GFP_ATOMIC))
接下来就是tcp_transmit_skb函数的实现过程了。
1)        通过skb_push()在skb前面加入tcp协议头信息。这包括序列号,源地址,目的地址,校验和等。
2)        通过tcp_opt结构体(它是在该函数的开始部分从sock结构体中获得的)来访问tcp_func结构体中的.queue_xmit虚拟功能函数,在IPV4中是调用了ip_queue_xmit(),这样就进入了下一层——网络层。

未贴完,完整版在附件中。
关于驱动程序部分的声明:
由于本人没有实际开发过驱动程序,都是看了别人的驱动程序之后的体会和总结,所以难免会有错误,请各位不啬赐教!
接收过程待续。。。。


[ 本帖最后由 dreamice 于 2008-12-20 19:32 编辑 ]

深入剖析网络发送过程.rar

20.86 KB, 下载次数: 1258

评分

参与人数 1可用积分 +15 收起 理由
dreamice + 15 原创内容

查看全部评分

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
2 [报告]
发表于 2008-12-20 19:34 |只看该作者
呵呵,不错,多多分享经验

论坛徽章:
0
3 [报告]
发表于 2008-12-21 17:12 |只看该作者
太好了,不过要是有图版的就更好了。
没有图全是问题理解起来还是比较困难的

论坛徽章:
0
4 [报告]
发表于 2008-12-24 14:31 |只看该作者

回复 #1 letueo 的帖子

很好的资料
谢谢分享

论坛徽章:
0
5 [报告]
发表于 2008-12-25 11:23 |只看该作者

回复 #1 letueo 的帖子

挺好, 写的挺详细的

论坛徽章:
0
6 [报告]
发表于 2008-12-28 17:24 |只看该作者
呵呵,谢谢各位的支持。。。。。。。。。。。

论坛徽章:
0
7 [报告]
发表于 2008-12-28 17:30 |只看该作者
虽然不懂,还是支持。

论坛徽章:
0
8 [报告]
发表于 2008-12-29 13:34 |只看该作者
接收和发送一并看了~

论坛徽章:
0
9 [报告]
发表于 2008-12-29 15:28 |只看该作者
内核对现在的我来说还是不可触摸的,不过还是支持,顶一下

论坛徽章:
0
10 [报告]
发表于 2009-01-03 00:30 |只看该作者
期待接收部分
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP