TCP receive 接收流程
- 网卡驱动注册到内核,方便内核与网卡进行交互。
- 内核启动网卡,为网卡工作分配资源(ring buffer)和注册硬中断处理 e1000_intr。
- 网卡(NIC)接收数据。
- 网卡通过 DMA 方式将接收到的数据写入主存(步骤 2 内核通过网卡驱动将 DMA 内存地址信息写入网卡寄存器,使得网卡获得 DMA 内存信息)。
- 网卡触发硬中断,通知 CPU 已接收数据。
- CPU 收到网卡的硬中断,调用对应的处理函数 e1000_intr。
- 网卡驱动函数先禁止网卡中断,避免频繁硬中断,降低内核的工作效率。
- 网卡驱动将 napi_struct.poll_list 挂在 softnet_data.poll_list 上,方便后面软中断调用 napi_struct.poll 获取网卡数据。
- 然后启用 NET_RX_SOFTIRQ -> net_rx_action 内核软中断。
- 内核软中断线程消费网卡 DMA 方式写入主存的数据。
- 内核软中断遍历 softnet_data.poll_list,调用对应的 napi_struct.poll -> e1000_clean 读取网卡 DMA 方式写入主存的数据。
- e1000_clean 遍历 ring buffer 通过 dma_sync_single_for_cpu 接口读取 DMA 方式写入主存的数据,并将数据拷贝到 e1000_copybreak 创建的 skb 包。
- 网卡驱动读取到 skb 包后,需要将该包传到网络层处理。在这过程中,需要通过 GRO (Generic receive offload) 接口:napi_gro_receive 进行处理,将小包合并成大包,然后通过 __netif_receive_skb 将 skb 包交给 TCP/IP 协议逐层处理,最后将 skb 包追加到 socket.sock.sk_receive_queue 队列,等待应用处理;如果 read / epoll_wait 阻塞等待读取数据,那么唤醒进程/线程。
- skb 包需要传到网络层,如果内核开启了 RPS (Receive Package Steering) 功能,为了利用多核资源,(enqueue_to_backlog)需要将数据包负载均衡到各个 CPU,那么这个 skb 包将会通过哈希算法,挂在某个 cpu 的接收队列上(softnet_data.input_pkt_queue),然后等待软中断调用 softnet_data 的 napi 接口 process_backlog(softnet_data.backlog.poll)将接收队列上的数据包通过 __netif_receive_skb 交给网络层处理。
- 网卡驱动读取了网卡写入的数据,并将数据包交给协议栈处理后,需要通知网卡已读(ring buffer)数据的位置,将位置信息写入网卡 RDT 寄存器(writel(i, hw->hw_addr + rx_ring->rdt)),方便网卡继续往 ring buffer 填充数据。
- 网卡驱动重新设置允许网卡触发硬中断(e1000_irq_enable),重新执行步骤 3。
- 用户程序(或被唤醒)调用 read 接口读取 socket.sock.sk_receive_queue 上的数据并拷贝到用户空间
网卡 PCI 驱动,NAPI 中断缓解技术,软硬中断,DMA
kernel对于TCP包得处理大致可以分为两类:
如果处理in-sequence的包时,application正阻塞在read操作中,则接收到的数据包的数据会被直接拷贝到user buffer。
否则,in-sequence包会被放在receive queue中,out-of-order包会放置于out-of-order queue中。
TCP层处理IPV4-TCP数据包的第一个函数是tcp_v4_rcv(),首先从这个函数开始理解整个流程:
tcp_v4_rcv() // net/ipv4/tcp_ipv4.c => sk = __inet_lookup_skb() // 找到skb属于的sock结构体 => if (!sock_owned_by_user(sk)) // sock未被加锁 => if (!tcp_prequeue(sk, skb)) // 如果符合加入prequeue的原则,则加入prequeue,返回true;反之返回false => ret = tcp_v4_do_rcv(sk, skb) => tcp_rcv_established() // receive function for the ESTABLISHED state => if (len == tcp_header_len) // 如果是纯ack包 => tcp_ack(sk, skb, 0) // dealing with incoming acks => flag |= tcp_clean_rtx_queue() // see if we can take anything off of the retransmit queue => if (tcp_ack_is_dubious(sk, flag) // 判断时候出现可疑情况,具体看下代码吧。 => tcp_fastretrans_alert() // 进入快速重传 => tcp_cwnd_down() // decrease cwnd each second ack, 该函数就是快速重传对cwnd操作的关键函数 => tcp_xmit_retransmit_queue(sk) // 在重传阶段,该函数负责找到合适的数据进行重传 => __kfree_skb(skb) // free an sk_buff => tcp_data_snd_check(sk) // 如果有数据需要发送,则发送数据到对端 => tcp_push_pending_frames(sk) // 发送pending的数据 => tcp_write_xmit() // writes packets to the network, 这部分在上一章已经分析过了 => tcp_check_space(sk) // 如果有内存释放,则唤醒等待内存的队列 /* when incoming ACK allows to free some skb from write_queue, * we remember this event in flag SOCK_QUEUE_SHRUNK and wake up socket * on the exit from tcp input hander. * * PROBLEM: sndbuf expansion does not work well with largesend. */ => tcp_new_space(sk) => sk->sk_sndbuf = min(sndmem, sysctl_tcp_wmem[2]) // expand the sndbuf if possible => else // 如果是带数据的包 /* 此数据包刚好是下一个读取的数据,并且用户空间可存放下该数据包 */ => if (tp->copied_seq == tp->rcv_nxt && len - tcp_header_len <= tp->ucopy.len) /* 如果函数在进程上下文调用并且sock被用户占用的话 */ => if (tp->ucopy.task == current && sock_owned_by_user(sk) && !copied_early) => tcp_copy_to_iovec() // 直接copy 到用户空间 => if (!eaten) // 没有直接读到用户空间 /* 当truesize大于sk_forward_alloc时,表示已分配的限额已经用完,不能直接放到receive queue中, 此时往往要重新计算sk_forward_alloc */ => if (skb->truesize > sk->sk_forward_alloc) goto step5 => eaten = tcp_queue_rcv() => tcp_try_coalesce() // try merge skb to prior one => if (!eaten) __skb_queue_tail() // 如果上一步未成功,则将skb放入receive queue中 => tcp_event_data_recv(sk, skb) // 数据包接收后续处理 /* 每次收到超过128字节的数据报后,需要调用tcp_grow_window增加rcv_ssthresh的值 */ => if (skb->len >= 128) tcp_grow_window(sk, skb) => __tcp_ack_snd_check(sk, 0) // check if sending an ack is needed => tcp_validate_incoming(sk, skb, th, 1) // standard slow path, [details ignored] => tcp_data_queue(sk, skb) // 对数据包进行处理 => if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) // 如果是待接收的报文 => if (tcp_receive_window(tp) == 0) goto out_of_window; // 如果超出rwnd,则直接丢掉 => //如果正在读,且正是要读的数据,那么直接拷贝到用户空间 => else eaten = tcp_queue_rcv() // 将数据放入receive queue中 => if (!skb_queue_empty(&tp->out_of_order_queue)) // 如果out of ordre queue不为空 => tcp_ofo_queue(sk) // This one checks to see if we can put data from the out-of-order queue into the receive-queue => tcp_fast_path_check(sk) // 检查是否可以从slowpath回到fastpath => else tcp_data_queue_ofo(sk, skb) // 将数据包放到out-of-order queue中 => tcp_data_snd_check(sk) // 如果有数据需要发送,则发送数据到对端 => tcp_ack_snd_check(sk) // 判断是否有必要发送一个ack
TCP的接收队列的处理主要是在tcp_recvmsg()函数中,所以先从这个函数入手
tcp_recvmsg() // this routine copies from a sock struct into the user buffer => lock_sock(sk) // become a socket user => skb_queue_walk() // get a skb => //如果有skb可供拷贝 => err = skb_copy_datagram_iovec() // copy data into iovec if found_ok_skb /* This function should be called every time data is copied to user space. * It calculates the appropriate TCP receive buffer space. */ => tcp_rcv_space_adjust(sk) => //调整至少每隔一个RTT才进行一次 => space = 2 * (tp->copied_seq - tp->rcvq_space.seq) // 一个RTT内,接收并复制到用户空间的数量的2倍 ... => sk->sk_rcvbuf = space // 调整接收缓冲区的大小 => sk_eat_skb(sk, skb, copied_early) // 如果一个skb内数据被拷贝完了,则释放掉该skb =>// 如果没有skb可供拷贝 /* 如果设置了MSG_WAITALL,target == len; 否则target == 1 */ => if (copied >= target && !sk->sk_backlog.tail) break; // 如果读够了target,且backlog queue 为空则直接return => tcp_cleanup_rbuf(sk, copied) /* 注意区分这个函数与sk_eat_skb() 后者是清掉某个skb及其内存, 前者的主要功能是发送一个接收窗口更新的ACK--因为用户进程消费了读缓存中的数据 */ => if (inet_csk_ack_scheduled(sk)) // if the ack is scheduled by calling tcp_ack_scheduled() => if delayed ACK was blocked by socket lock, send an ACK => if we have not ACKed data of length > 1mss, send an ACK => if we have emptied the receive buffer, and there is data flow only in one direction, send an ACK => rcv_window_now = tcp_receive_window(tp) // 计算当前的应该通知对方的receive window => win = tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt // 左边界 + 当前receive_window - 已用 => new_window = __tcp_select_window(sk) // 计算新的接收窗口大小, 约为rcvbuf空闲部分的一半 => if (new_window && new_window >= 2 * rcv_window_now) send an ACK => if (time_to_ack) tcp_send_ack(sk) // 如果上面有需要发送ack的需求,则发送一个ACK /* if prequeue is not empty, we have to process it before releasing socket * queue的处理优先级如下: * receive queue 最高 * prequeue queue 次之 * backlog queue 最低 */ => if prequeue is not empty, goto do_prequeue => tcp_prequeue_process(sk) => sk_backlog_rcv(sk, skb) == tcp_v4_do_rcv() => if (copied >= target) // 下面两个步骤主要就是为了处理backlog queue => release_sock(sk) => if (sk->sk_backlog.tail) => __release_sock(sk) => sk_backlog_rcv(sk, skb) == tcp_v4_do_rcv() => lock_sock(sk) => else => sk_wait_data(sk, &timeo) // 睡眠等待新数据的到来
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子