TCP/IP协议栈在Linux内核中的运行时序分析

可选题目三:TCP/IP协议栈在Linux内核中的运行时序分析

 

在深入理解Linux内核任务调度(中断处理、softirg、tasklet、wq、内核线程等)机制的基础上,分析梳理send和recv过程中TCP/IP协议栈相关的运行任务实体及相互协作的时序分析。

编译、部署、运行、测评、原理、源代码分析、跟踪调试等

应该包括时序

 

一, 基础概念简介

1.什么是TCP/IP?

TCP/IP 也即传输控制协议/网际协议(Transmission Control Protocol / Internet Protocol),是一类通信协议,也是因特网种最根本的协议,用于提供已连接因特网的计算机进行通信。TCP/IP 定义了电子设备(比如计算机)如何连入因特网,以及数据如何在它们之间传输的标准。当然,TCP/IP协议不仅仅是指TCP和IP两个协议,而是指一类协议,如下图所示,对于四层模型,应用层中Telnet、FTP、SMTP等协议及传输层TCP/UDP协议也包括其中。以此命名,也只是因为在TCP/IP协议中TCP协议和IP协议最具代表性。

 

 

 

 

2.osi七层模型

OSI参考模型将网络结构划分为七层,即物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。每一层均有自己的一套功能集,并与紧邻的上层和下层交互作用。在顶层,应用层与用户使用的软件进行交互,在七层模型中,每个分层都接受由它下一层所提供的特定服务,并且负责为自己的上一层提供特定的服务。需要注意的是OSI为理论参考模型,现在普遍采用的是下面介绍的四层模型。

 

 

 

 

3. TCP/IP四层模型

基于TCP/IP的参考模型将网络结构分成四层,分别是网络访问层、网际层、传输层和应用层。相对于OSI参考模型,去掉了会话层和表示层(这两层的功能被合并到应用层实现)。同时将OSI参考模型中的数据链路层和物理层合并为网络访问层。

 

 

 

4.Socket

       Socket接口是TCP/IP网络的API,独立于具体协议的网络编程接口,在TCP/IP模型中,socket套接字主要位于传输层和应用层之间。Socket接口定义了许多函数或例程,可以用它们来开发TCP/IP网络上的应用程序,事实上,它是设计模式的一种应用,用于让编程变的更简单。

 

二、套接口层相关结构

1.socket结构

     

 

 

 

2.proto_ops 结构:套接口系统调用到传输层函数的跳转表,完成从套接口层到传输层的映射功能

3.sock结构:公用的网络描述块,定义了基本的传输控制块结构。与具体的协议无关。

4.proto结构:在proto_ops 结构的基础上,进一步将传输层和网络层之间进行映射

5.net_proto_family:每一个协议族都会用net_porto_family结构的一个实例进行表示,在初始化时,会调用sock_register函数进行统一的注册

 

三、          Socket系统调用

1.sys_socketcall

在Linux系统中,所有的socket系统的调用总入口都为sys_socketcall,其函数原型为:

 

函数的两个形参含义如下:

*call:每个数字代表一个操作码,一共17种,具体操作码对应情况如下:

 

对一些常用操作的说明:

SYS_SOCKET: 创建一个套接口,若创建成功,返回一个打开的文件描述符

SYS_BIND:将套接字地址与套接字号相绑定

SYS_CONNECT: 建立连接

SYS_LISTEN: 仅在TCP服务器端调用,将套接字转换到LISTEN状态

SYS_ACCEPT: 用于面向对象的连接器,用于接受新的连接

SYS_SEND:见下分析

SYS_RECV:见下分析

 

*args参数:为一个指针,指向数组,其可以根据不同的操作码要求,从用户态复制相应长度的数据,结构如下:

 

2.SYS_SOCKET()函数分析:

  前面有同学对该函数进行了一定的分析,但是仍不够全面,在此做一定的补充。首先对源码进行分析:

 

 

 

  我们发现在sys_socket函数中,还调用了两个函数:socket_create()以及socket_mp_fd(),下面我们进一步对这两个函数进行进一步的解析:

  1. sock_create()

  

  事实上,我们可以看到该函数只是将__socket_create()进行了简单封装,并将最后一个参数默认设置为0,所以我们这里将对__socket_create()做解析即可。

__socket_create()函数解析:

  

 

 

 

 

 

 

  2.socket_mp_fd()

   socket_map_fd的功能实际上是将socket套接口与文件描述符进行绑定(实际上我们从函数名也可以推测出)。

  综上所述,由此我们可以得到sys_socket的函数调用时序图大致如下:

   

       跟踪调试结果:

  

 

   

四、 Send系统调用分析

  事实上sys_send只是对sys_sendto函数的简单封装:

  

  同时未指定数据输出的目的地址(参数为NULL),所以采取默认地址,即connect函数连接的那个地址。

  所以我们对sys_sendto函数进行分析即可,实际上该函数的作用是将数据报发送至指定的目的地址,下面对源码进行分析:

  

 

   

 

   

       我们发现在sys_sendto函数种最终还是通过调用SYS_SENDMSG函数进行发送数据,所以我们接着对SYS_SENDMSG函数做分析。该函数的主要工作是将用户空间的信息复制到内核空间中,然后再逐级调用发包接口发送数据。具体到源码的解析如下:

   

 

   

 

   

 

   

  通过上述源码的分析,我们大致可以画出sendmsg函数的系统调用过程

   

 

 

  调试结果:

  

五、RECV系统调用分析:

  与send过程类似,相当于镜像操作。这里就没必要详细展开了。直接给出调用过程图如下:

   

 

 

  调试结果:

  

五、 传输层部分

网际层向传输层方向:

   

各调用函数作用说明:

1.tcp_v4_rcv():充当网络层与传输层的接口,传输层报文处理入口函数

2.__inet_lookup_v4_lookup():在ehash或者bhask中查找传输控制块,若无找到则进行退出, 并通过tcp_v4_send_reset(skb)发送RST段给对方,如果报文被损坏则无法发送rst,直接丢包

3.xfrm4_policy_check():进行安全检查

4.sk_filter():看是否符合过滤器规则

5.tcp_v4_do_rcv():传输层处理TCP段的主入口

6.tcp_rcv_established():当连接已经建立时,用快速路径处理报文

7. tcp_v4_hnd_req():为侦听套口,处理半连接状态的ACK消息

8. tcp_child_process():不是侦听套接字,说明已经建立了半连接。调用此函数初始化子传输控制块,如果失败则向客户端发送rst段,即tcp_v4_send_reset()

 

调试结果:

 

 

 

  传输层向网际层方向:

  大致经历了以下几个步骤:

  调用Tcp_sendmsg函数检查链接状态,并同时获取链接的MSS。创建该数据包的 sk_buffer 数据结构实例 skb,从 userspace buffer 中拷贝 packet 的数据到 skb 的 buffer。构造数据包头部,接而计算 TCP 校验和(ack)和顺序号(seq)。最后调用ip_queue_xmit函数将数据包传输到网际层进行处理。

  这里主要对Tcp_sendmsg函数的调用逻辑进行补充分析,该函数只要检查已经建立的 TCP connection 的状态,然后获取有效的 MSS,Tcp_sendmsg函数的内部调用顺序如下:

   

 

 

各调用函数作用说明:

1.Tcp_sendmsg分析:sendmsg系统调用在TCP层的实现

2.lock_sock():获取套接口的锁

3.sock_sndtimeo()根据标志计算阻塞超时时间

4.sk_stream_wait_connect():对于不能发送信息状态须等待连接正确建立,超时

5.tcp_current_mss():获得有效的MSS

 

六、网际层:

网际层到传输层方向:   

    

 

 

各调用函数作用说明:

1.Ip_rcv函数:对IP头部合法性进行严格检查,如数据报文长度、首部长度、是否为共享数据包,然后把具体功能交给ip_rcv_finish

2.ip_rcv_finish函数:如果还未为该数据报查找输入路由缓存,调用ip_route_input为其查找输入的路由缓存,接着处理IP数据报首部中的选项,最后根据输入路由缓存输入到本地或转发。

3.Ip_forward: 数据报转发的接口函数。

4.Ip_forward_finish:完成输入ip数据报的转发

5.Ip_local_deliver:处理输入到本地的IP数据报,将分片进行重组,获得完整数据报,之后调用ip_local_deliver_finish函数进行数据传输。

6.ip_local_deliver_finish:将数据报从网络层传递到传输层。

调试结果:

   

同时发现了传输层与网络层接口部分·:

  

 

 

 

 

 

网际层到网络访问层方向

  

各调用函数作用说明:

Ip_queue_xmit:将TCP端打包成IP数据报

Dst_output:封装了输出数据报目的路由缓存项中的输出端口(分为两类:单播,组播)

Ip_output: 处理单播数据报,设置数据报的输出网络设备以及网络层协议类型参数。

Ip_finish_output:观察数据报长度是否大于MTU,若大于,则调用ip_fragment分片,否则调用ip_finish_output2输出;

Ip_ finish_output2: 对skb的头部空间进行检查,看是否能够容纳下二层头部,若空间不足,则需要重新申请skb;然后,获取邻居子系统,并通过邻居子系统输出

 

调试结果:

  

网络层与传输层的接口部分:

   

七、网络访问层:

从网际层接受数据方向:

数据包传输过程:

       启用软中断后,调用dev_queue_xmit函数对数据包进行处理,主要处理依据为是否采用了Generic Segmentation Offload技术,是否采用了QoS技术进行处理。对函数dev_queue_xmit的源码分析如下:

      

 

   

 

  

 

   

 

   

从网络接口层向网际层方向:

       主要实现函数为softnet_data,前面有同学也提到,但未做出详细分析,所以下面部分就softnet_data结构及其工作原理加以说明补充。该结构实质上描述的是与软中断相关的输入及输出队列。该结构的源码如下:

   

结构的参数说明:

Throttle:该参数与后面avg_blog、cng_level参数配合使用,实现拥塞管理算法,throttle的值实际为bool值,其含义是,当CPU是超载时,为true,否则为false。

cng_level:用于表示拥塞级别,在处理每一帧时会重新进行计算。

avg_blog:表示的是后面参数input_pkt_queue队列的平均长度

input_pkt_queue:数据包排列形成的队列结构

poll_list:等待处理入帧的双向设备链表。

output_queue:记录了需要发送数据包的设备列表。

completion_queue:记录了已经成功发送可以释放的缓冲区。

backlog_dev:表示与CPU相关的的设备

 

数据包传输过程:

  若采取非NAPI方式,首先会通过硬中断读取数据包。再调用netif_rx函数将收到的数据包添加到input_pkt_queue队列结构中,最后通过产生一个软中断的方式,依次将数据传输到网络层。

       若采取NAPI方式,相较于非NAPI方式,可以有效的减少硬中断的数量。首先会将网络设备添加到poll_list结构中,再通过软中断的方式将网络设备中的报文传输到网络层中。

posted @ 2021-01-26 21:57  Panteng0623  阅读(351)  评论(0编辑  收藏  举报