zoukankan      html  css  js  c++  java
  • Linux 网卡驱动学习(六)(应用层、tcp 层、ip 层、设备层和驱动层作用解析)

    本文将介绍网络连接建立的过程、收发包流程,以及当中应用层、tcp层、ip层、设备层和驱动层各层发挥的作用。

    1、应用层

    对于使用socket进行网络连接的server端程序。我们会先调用socket函数创建一个套接字:

    1. fd = socket(AF_INET, SOCK_STREAM, 0);

    以上指定了连接协议,socket调用返回一个文件句柄,与socket文件相应的inode不在磁盘上,而是存在于内存。

     

    之后我们指定监听的port、同意与哪些ip建立连接,并调用bind完毕port绑定:

    1. server_addr.sin_family = AF_INET;
    2. server_addr.sin_port   = htons(PORT);
    3. server_addr.sin_addr.s_addr = INADDR_ANY;
    4. bind(fd, (struct sockaddr_in *)&server_addr, sizeof(struct sockaddr_in));

    port作为进程的标识,client依据serverip和port号就能找到对应进程。

     

    接着我们调用listen函数。对port进行监听:

    1. listen(fd, backlog);

    backlog值指定了监听队列的长度,下面内核參数限制了backlog可设定的最大值:

    1. linux # sysctl -a | grep somaxconn
    2. net.core.somaxconn = 128

    监听port在listen调用后变为LISTEN状态:

    1. linux # netstat -antp | grep 9999
    2. Proto  Recv-Q Send-Q Local Address  Foreign Address  State  PID/Program name
    3. tcp         0      0  0.0.0.0:9999        0.0.0.0:* LISTEN       8709/server

    对应地。client调用connect进行连接,tcp三次握手在connect调用返回之前完毕:

    假设server端向client发送SYNACK后。client不返回ACK,则server保持半连接(SYN_RECV)状态:

    1. linux # netstat -np | grep SYN_RECV
    2. tcp      0        0     0.0.0.0:9999  127.0.0.0.1:5334   SYN_RECV  - 

    若队列中的连接均处于半连接状态。server将不能处理正常的请求,syn泛洪攻击(syn flood)就是利用这个特点完毕DoS(拒绝服务攻击)


    当连接数超过队列长度backlog时。超出的连接也保持为半连接状态。直到数量达到内核參数tcp_max_syn_backlog值,超出该值的连接请求将被丢弃:

    1. linux # sysctl -a | grep tcp_max_syn
    2. net.ipv4.tcp_max_syn_backlog = 1024


    accept调用用于处理新到来的连接:

    1. new_fd = accept(fd, (struct sockaddr*)&client_addr, &sin_size);

    其返回一个文件描写叙述符。兴许我们能够对该文件描写叙述符调用writeread等操作函数。原监听port仍处于LISTEN状态:

    1. linux # netstat -antp | grep 9999
    2. tcp     0    0    0.0.0.0:9999       0.0.0.0:*      LISTEN  8709/server
    3. tcp     0    0  127.0.0.1:9999 127.0.0.1:52274 ESTABLISHED  -

    以上为网络连接建立过程中。应用层所做的工作,server端完毕了socket创建、port绑定、port监听、连接和收发包任务,而client端相对简单,仅仅需包括连接和收发包。

     

    2、tcp

    内核代码中。tcp_sendmsgtcp发包的主入口函数,该函数中struct sk_buff结构用于描写叙述一个数据包。

    对于超过MTU(maximum transmission unit, 最大传输单元)的数据包。tcp层会对数据包进行拆分,若开启了网口的tcp segmentation offload功能,则拆分工作由网卡完毕:

    1. linux # ethtool -k ether
    2. Offload parameters for eth1:
    3. rx-checksumming: on
    4. tx-checksumming: on
    5. scatter-gather: on
    6. tcp segmentation offload: on

    下面内核參数是内核为tcp socket预留的用于发送数据包的缓冲区大小,单位为byte

    1. linux # sysctl -a | grep tcp_wmem
    2. net.ipv4.tcp_wmem = 4096 16384 131072

    默认的用于包发送的缓冲区大小为16M

     

    除了用于缓冲收发数据包。对于每一个socket,内核还要分配一些数据结构用于保持连接状态,内核对tcp层可使用的内存大小进行了限制:

    1. linux # sysctl -a | grep tcp_mem
    2. net.ipv4.tcp_mem = 196608 262144 393216

    以上值以页为单位。分别相应最小值、压力值和最大值,并在系统启动、tcp栈初始化时依据内存总量设定。通过proc提供的接口,我们能够查到tcp已用的内存页数:

    1. linux # cat /proc/net/sockstat
    2. sockets : used 91
    3. TCP : inuse 8 orphan 0 tw 11 alloc 13 mem 2


    3、ip

    内核代码中。ip_queue_xmit函数是ip层的主入口函数,注意ip层与tcp层操作的都是同一块内存(sk_buff结构)。期间并没有发生数据包相关的内存拷贝。

    ip层主要完毕查找路由的任务,其依据路由表配置,决定数据包发往哪个网口,另外,该层实现netfilter的功能。

     

    4、网络设备层

    dev_queue_xmit是网络设备层的主入口函数,该层为每一个网口维护一条数据包队列。由ip层下发的数据包放入相应网口的队列中。在该层中。数据包不是直接交给网卡,而是先缓冲起来,再通过软中断(NET_TX_SOFTIRQ)调用qdisc_run函数。该函数将数据包进一步交由网卡处理。

    我们运行ifconfig时。txqueuelen指示了网络设备层中。网口队列的长度。

     

    5、驱动层

    使用不同驱动的网卡,对应的驱动层代码就不一样。这里以e1000网卡为例。e1000_xmit_frame是该层的主入口函数。该层利用环形队列进行数据包管理,由两个指针负责维护环形队列。运行ethtool命令,我们能够查询网口驱动层环形队列长度:

    1. linux # ethtool -g eth1
    2. Ring parameters for ether
    3. Pre-set maximums:
    4. RX : 511
    5. RX Mini : 0
    6. RX Jumbo : 0
    7. TX : 511
    8. Current hardware settings:
    9. RX : 200
    10. RX Mini : 0
    11. RX Jumbo : 0
    12. TX : 511

    以上RXTX分别指示收包队列与发包队列长度,单位为包个数。

     

    网卡接收到数据包时将产生中断,以通知cpu数据包到来的消息。而网卡接收包又很繁忙,假设每次收发包都向cpu发送硬中断,那cpu将忙于处理网卡中断。

    对应的优化方案是NAPI(New API)模式,其关闭网卡硬中断,使网卡不发送中断。而非使cpu不接收网卡中断。在e1000驱动代码中。由e1000_clean函数实现NAPI模式。

     

    不像写文件的过程,磁盘设备层完毕内存数据到磁盘拷贝后,会将消息层层上报,这里的网卡驱动层发包后不会往上层发送通知消息。

     

    收包过程

    以上为网络发包所需经过的层次结构,以及各层的大体功能,以下我们简单看下收包过程。

     

    网卡接收到数据包后,通知上层,该过程不会发生拷贝,数据包丢给ip层。

    内核代码中,ip_rcvip层收包的主入口函数,该函数由软中断调用。

    存放数据包的sk_buff结构包括有目的地ip和port信息,此时ip层进行检查,假设目的地ip不是本机。则将包丢弃,假设配置了netfilter。则依照配置规则对包进行转发。

     

    tcp_v4_rcvtcp层收包的接收入口。其调用__inet_lookup_skb函数查到数据包须要往哪个socket传送。之后将数据包放入tcp层收包队列中,假设应用层有read之类的函数调用。队列中的包将被取出。

     

    tcp层收包使用的内存相同有限制:

    1. linux # sysctl -a | grep rmem
    2. net.ipv4.tcp_rmem = 4096 16384 131072
  • 相关阅读:
    14 break
    13 for循环
    Python 3.7 将引入 dataclass 装饰器
    工程师如何在面试中脱颖而出
    如何避免 async/await 地狱
    命令行里打 cd 简直是浪费生命
    GitHub 十大 CI 工具
    GitHub CEO:GitHub 十年,感谢有你
    如何在 2 分钟内入睡(二战时期美国飞行员训练法)
    一分钟了解 TCP/IP 模型
  • 原文地址:https://www.cnblogs.com/yxysuanfa/p/7010429.html
Copyright © 2011-2022 走看看