文章目录

  • 1. 前言
  • 2. 背景
  • 3. 网卡数据收发流程
    • 3.1 网络数据接收流程
      • 3.1.1 网卡数据接收流程
      • 3.1.2 网卡数据向上传递给L3,L4的流程
    • 3.2 网卡数据发送流程

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文基于 linux-4.14.132 内核代码进行分析。

3. 网卡数据收发流程

我们先来明确下,Linux 下网络数据通信流程:

         Host A                            Host B---------------------            ---------------------|       应用数据       |         |       应用数据      ||        |    ^        |         |        |    ^       ||        v    |        |         |        v    |       ||  -----------------   |         |  -----------------  |
L4  | | 传输层(TCP/UDP) |  | <- - -> | | 传输层(TCP/UDP) | ||  -----------------   |         |  -----------------  ||       |     ^        |         |       |     ^       ||       v     |        |         |       v     |       ||  -----------------   |         |  -----------------  |
L3  | |    网络层(IP)   |  | <- - -> | |    网络层(IP)   | ||  -----------------   |         |  -----------------  ||       |     ^        |         |       |     ^       ||       v     |        |         |       v     |       ||  -----------------   |         |  -----------------  |
L2  | | 数据链路层(MAC) |  | <- - -> | | 数据链路层(MAC) | ||  -----------------   |         |  -----------------  ||       |     ^        |         |       |     ^       ||       v     |        |         |       v     |       ||  -----------------   |         |  -----------------  |
L1  | |   物理层(PHY)   |  | <- - -> | |   物理层(PHY)   | ||  -----------------   |         |  -----------------  |----------------------           --------------------- /\                              /\||                              ||||______________________________|||________________________________|

Host A 和 B 的数据通信流经各自的物理层(L1的PHY),数据链路层(L2的MAC),网络层(L3),传输层(L4),最后到达应用层的缓冲。

3.1 网络数据接收流程

网络收据的接收流程,分为两个部分:第1部分,是网卡数据接收流程;第2部分,是网卡将接收的数据,向上传递给协议栈的网络层(L3),再由网络层向上传递给协议栈的传输层(L4)处理

3.1.1 网卡数据接收流程

当数据通过网线,再经物理层(网卡PHY),最后传递给数据链路层的MAC,会生成中断信息,分析流程正是从网卡中断开始。还是以一个实际的网卡驱动例子,来开始分析:

static int sun8i_dwmac_probe(struct platform_device *pdev)
{...ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);...return ret;
}static const struct net_device_ops stmmac_netdev_ops = {.ndo_open = stmmac_open,.ndo_start_xmit = stmmac_xmit,.ndo_stop = stmmac_release,.ndo_change_mtu = stmmac_change_mtu,....ndo_do_ioctl = stmmac_ioctl,....ndo_set_mac_address = stmmac_set_mac_address,
};int stmmac_dvr_probe(struct device *device,struct plat_stmmacenet_data *plat_dat,struct stmmac_resources *res)
{struct net_device *ndev = NULL;/* 创建网卡对象 */ndev = alloc_etherdev_mqs(sizeof(struct stmmac_priv),MTL_MAX_TX_QUEUES,MTL_MAX_RX_QUEUES);...ndev->netdev_ops = &stmmac_netdev_ops; /* 配置网卡接口 */.../* 网卡使用的所有接收队列的 NAPI 初始化 */for (queue = 0; queue < priv->plat->rx_queues_to_use; queue++) {struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];netif_napi_add(ndev, &rx_q->napi, stmmac_poll,(8 * priv->plat->rx_queues_to_use));}...ret = register_netdev(ndev); /* 注册网卡设备 */...return ret;
}void netif_napi_add(struct net_device *dev, struct napi_struct *napi,int (*poll)(struct napi_struct *, int), int weight)
{INIT_LIST_HEAD(&napi->poll_list);hrtimer_init(&napi->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED);napi->timer.function = napi_watchdog;...napi->poll = poll; /* stmmac_poll() */list_add(&napi->dev_list, &dev->napi_list); /* 添加到网络设备的 napi_struct 对象列表 */napi->dev = dev; /* 设定 napi_struct 对象关联的网络设备 */...set_bit(NAPI_STATE_SCHED, &napi->state); /* 标记 napi_struct 对象为已调度状态(NAPI_STATE_SCHED) */napi_hash_add(napi); /* 添加到 napi_struct 对象 全局哈希表 */
}

上面的代码,创建网卡设备对象,然后设置了其操作接口,接着是所有接收队列的NAPI初始化,最后注册网卡设备对象到系统。应用程序使用网卡设备时,先打开网卡设备,并在此时注册网卡数据接收处理中断接口:

dev_open()__dev_open()ops->ndo_open(dev) = stmmac_open(dev)
struct stmmac_rx_queue {.../* 使用 NAPI */struct napi_struct napi ____cacheline_aligned_in_smp;
};static int stmmac_open(struct net_device *dev)
{struct stmmac_priv *priv = netdev_priv(dev);int ret;...ret = request_irq(dev->irq, stmmac_interrupt,IRQF_SHARED, dev->name, dev);/* 启动 MAC 收发 */stmmac_enable_all_queues(priv);stmmac_start_all_queues(priv);return 0;
}static void stmmac_enable_all_queues(struct stmmac_priv *priv)
{u32 rx_queues_cnt = priv->plat->rx_queues_to_use;u32 queue;for (queue = 0; queue < rx_queues_cnt; queue++) {struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];napi_enable(&rx_q->napi);}
}static void stmmac_start_all_queues(struct stmmac_priv *priv)
{u32 tx_queues_cnt = priv->plat->tx_queues_to_use;u32 queue;for (queue = 0; queue < tx_queues_cnt; queue++)netif_tx_start_queue(netdev_get_tx_queue(priv->dev, queue));
}static inline void napi_enable(struct napi_struct *n)
{...clear_bit(NAPI_STATE_SCHED, &n->state);clear_bit(NAPI_STATE_NPSVC, &n->state);
}

上面代码流程完成了网卡接收中断接口的注册、网卡收发数据队列的启动、NAPI 的使能。接下来,我们看看,在网卡的中断接口中,它是怎样接收处理数据的:

static irqreturn_t stmmac_interrupt(int irq, void *dev_id)
{struct stmmac_priv *priv = netdev_priv(dev);.../* To handle DMA interrupts */stmmac_dma_interrupt(priv);return IRQ_HANDLED;
}static void stmmac_dma_interrupt(struct stmmac_priv *priv)
{u32 tx_channel_count = priv->plat->tx_queues_to_use;int status;u32 chan;/* 查询网卡 MAC 芯片的所有 DMA 通道的数据传输状态 */for (chan = 0; chan < tx_channel_count; chan++) {struct stmmac_rx_queue *rx_q = &priv->rx_queue[chan]; /* 用于第 @chan 号 DMA 通道 的 接收队列 *//* 读取 MAC 芯片寄存器,查询数据传输的 DMA 中断状态 */status = priv->hw->dma->dma_interrupt(priv->ioaddr,&priv->xstats, chan);if (likely((status & handle_rx)) || (status & handle_tx)) { /* 有完成的 DMA 数据传输 */if (likely(napi_schedule_prep(&rx_q->napi))) { /* DMA 通道 @chan 对应数据接收队列可进行 NAPI 调度 */stmmac_disable_dma_irq(priv, chan); /* 禁用 DMA 通道 @chan 的中断 */__napi_schedule(&rx_q->napi);}}...}
}void __napi_schedule(struct napi_struct *n)
{unsigned long flags;local_irq_save(flags);____napi_schedule(this_cpu_ptr(&softnet_data)/*当前CPU的softnet_data*/, n);local_irq_restore(flags);
}/** 添加 napi_struct(@napi) 到 softnet_data(@sd) 的轮询列表,* 然后触发 NET_RX_SOFTIRQ 软中断:* 在软中断中将触发 napi_struct::poll(), 完成网络包收取。*/
static inline void ____napi_schedule(struct softnet_data *sd,struct napi_struct *napi)
{/** 添加到 softnet_data 的 napi_struct 轮询列表:** . 传统的 netif_rx() 收包方式*   a. 初始化 backlog*        net_dev_init()*         for_each_possible_cpu(i) {*             struct softnet_data *sd = &per_cpu(softnet_data, i);*              ...*                sd->backlog.poll = process_backlog;*                sd->backlog.weight = weight_p;*         }*   b. 触发 poll 接口 process_backlog()*    netif_rx()*        netif_rx_internal()*            enqueue_to_backlog()*               ___napi_schedule(sd, &sd->backlog) // 本函数** . NAPI 方式*   a. 注册 NAPI 接口*      netif_napi_add(ndev, &priv->napi, sun8i_emac_poll, 64)*          list_add(&napi->dev_list, &dev->napi_list); // 添加的网络设备的 napi_struct 列表*           napi->dev = dev; // 设定 napi_struct 关联的网络设备*  b. 触发 NAPI 接口*     stmmac_interrupt()*         stmmac_dma_interrupt()*             __napi_schedule()*/list_add_tail(&napi->poll_list, &sd->poll_list);/** 触发 NET_RX_SOFTIRQ 软中断,NET_RX_SOFTIRQ 软中断 net_rx_action() 处理接口* 来触发 napi_struct::poll():* process_backlog() / stmmac_poll() 完成网络包收取 */__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

从前面的代码了解到,进入网卡MAC数据接收中断后,最后会调度 NAPI 来收取数据,收取数据流程接下来会进入到 softirq 的流程:

/* 注册软中断 NET_RX_SOFTIRQ 处理接口 net_rx_action() */
// net/core/dev.c
net_dev_init()open_softirq(NET_RX_SOFTIRQ, net_rx_action)/* 退出中断时调用,调用 NET_RX_SOFTIRQ 软中断接口 net_rx_action() */
irq_exit()__do_softirq()...softirq_vec[nr].action(h) = net_rx_action()
static __latent_entropy void net_rx_action(struct softirq_action *h)
{struct softnet_data *sd = this_cpu_ptr(&softnet_data);unsigned long time_limit = jiffies +usecs_to_jiffies(netdev_budget_usecs);int budget = netdev_budget;LIST_HEAD(list);LIST_HEAD(repoll);local_irq_disable();list_splice_init(&sd->poll_list, &list); /* 链表拼接: sd->poll_list(napi_struct列表) + list */local_irq_enable();for (;;) {struct napi_struct *n;if (list_empty(&list)) {if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))goto out;break;}n = list_first_entry(&list, struct napi_struct, poll_list); /* 取当前 CPU 的 softnet_data 的 napi_struct 列表首节点 */budget -= napi_poll(n, &repoll);/* If softirq window is exhausted then punt.* Allow this to run for 2 jiffies since which will allow* an average latency of 1.5/HZ.*/if (unlikely(budget <= 0 ||time_after_eq(jiffies, time_limit))) {sd->time_squeeze++;break;}}local_irq_disable();list_splice_tail_init(&sd->poll_list, &list);list_splice_tail(&repoll, &list);list_splice(&list, &sd->poll_list);if (!list_empty(&sd->poll_list))__raise_softirq_irqoff(NET_RX_SOFTIRQ);net_rps_action_and_irq_enable(sd);
out:__kfree_skb_flush();
}static int napi_poll(struct napi_struct *n, struct list_head *repoll)
{void *have;int work, weight;list_del_init(&n->poll_list); /* 从所属列表移出 */.../* This NAPI_STATE_SCHED test is for avoiding a race* with netpoll's poll_napi().  Only the entity which* obtains the lock and sees NAPI_STATE_SCHED set will* actually make the ->poll() call.  Therefore we avoid* accidentally calling ->poll() when NAPI is not scheduled.*/work = 0;if (test_bit(NAPI_STATE_SCHED, &n->state)) {/* 触发 napi_struct::poll(), 如:* . 默认的 softnet_data::backlog 的 poll 接口 process_backlog()* . 使用了 NAPI 的 stmmac_poll() */work = n->poll(n, weight);...}...return work;
}

兜兜转转,终于进入 MAC 驱动的 NAPI 接收数据 poll 处理接口 stmmac_poll()

static int stmmac_poll(struct napi_struct *napi, int budget)
{struct stmmac_rx_queue *rx_q =container_of(napi, struct stmmac_rx_queue, napi);...u32 chan = rx_q->queue_index;int work_done = 0;....../* 收取数据 */work_done = stmmac_rx(priv, budget, rx_q->queue_index);/** 本次轮询目标量为 @budget, 但实际只取了 @work_done , * 说明当前没有更多数据,因此标记此处数据轮询结束,并* 重启 DMA 通道 @chan 的中断,以便继续接收数据。*/if (work_done < budget) {napi_complete_done(napi, work_done); /*  */stmmac_enable_dma_irq(priv, chan);}return work_done;
}

到此,网络数据接收流程的第1部分:网卡数据接收流程分析完毕。接下来,看处在L2层MAC数据沿着协议栈,流经L3,最终到达L4层的过程。

3.1.2 网卡数据向上传递给L3,L4的流程

static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
{struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue];...unsigned int count = 0;...while (count < limit) {if (unlikely(status == discard_frame)) {...}  else {int frame_len;frame_len = priv->hw->desc->get_rx_frame_len(p, coe);.../* 分配 skb 缓冲 */skb = netdev_alloc_skb_ip_align(priv->dev, frame_len);.../* 拷贝接收的数据到 skb 缓冲 */skb_copy_to_linear_data(skb,rx_q->rx_skbuff[entry]->data,frame_len);.../* 提取数据帧的 协议类型 */skb->protocol = eth_type_trans(skb, priv->dev);.../* 将数据传递给 网络层 (L3) 处理:L2 -> L3 */napi_gro_receive(&rx_q->napi, skb);...}...}...return count;
}gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{...return napi_skb_finish(dev_gro_receive(napi, skb), skb);
}static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb)
{switch (ret) {case GRO_NORMAL:if (netif_receive_skb_internal(skb))ret = GRO_DROP;break;...}return ret;
}static int netif_receive_skb_internal(struct sk_buff *skb)
{/* 这里不列举 XDP 和 RPS 情形,仅说明数据传输流程 */...rcu_read_lock();ret = __netif_receive_skb(skb);rcu_read_unlock();return ret;
}static int __netif_receive_skb(struct sk_buff *skb)
{int ret;...ret = __netif_receive_skb_core(skb, false);return ret;
}static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{struct packet_type *ptype, *pt_prev;...int ret = NET_RX_DROP;.../* * 这里会根据数据报不同协议类型,进入对应协议类型数据包的处理接口。* 我们这里以 ETH_P_IP 协议类型数据报为例,来说明接下来的类型。*/// net/ipv4/ip_input.c:// ip_packet_type.func = ip_rcv() ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);...return ret;
}

ip_rcv() 进入 网络层(L3) 的数据处理流程:

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{const struct iphdr *iph;...iph = ip_hdr(skb); /* IP 协议数据报头 */...len = ntohs(iph->tot_len);...skb->transport_header = skb->network_header + iph->ihl*4; /* 传出层(L4) 数据报头部指针 *//* Remove any debris in the socket control block */memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));IPCB(skb)->iif = skb->skb_iif;.../* * 在这里将应用 pre routing 路由规则。* 像用户层 ip, iptables 等工具,可以通过这里,加入规则过滤数据报。*/return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,net, NULL, skb, dev, NULL,ip_rcv_finish);
}static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{.../* * 为收到数据包 @skb 设置路由管理信息和路由处理接口:* (ip_local_deliver(), ip_forward(), ...) */err = ip_route_input_noref(skb, iph->daddr, iph->saddr,iph->tos, dev);.../* 数据报路由投递处理 */return dst_input(skb);
}/* Input packet from network to transport.  */
static inline int dst_input(struct sk_buff *skb)
{/* 这里假设数据报的目标地址是当前机器,所以不走 ip_forward() 数据报转发流程 */return skb_dst(skb)->input(skb); /* ip_local_deliver(), ip_forward(), ... */
}int ip_local_deliver(struct sk_buff *skb)
{/**    Reassemble IP fragments.*/struct net *net = dev_net(skb->dev);.../* * 在这里将应用 local in 路由规则。* 像用户层 ip, iptables 等工具,可以通过这里,加入规则过滤数据报。*/return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,net, NULL, skb, skb->dev, NULL,ip_local_deliver_finish);
}static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{__skb_pull(skb, skb_network_header_len(skb)); /* 移动 skb 数据指针,指向传输层(L4)的数据报 */rcu_read_lock();{int protocol = ip_hdr(skb)->protocol; /* 获取传输层数据报的协议类型 */...ipprot = rcu_dereference(inet_protos[protocol]);if (ipprot) {.../* * 这里按传输层(L4)的数据报协议类型,进入不同协议数据报处理接口。 * 我们这里以 TCP/IP v4 数据报为例,说明接下来的流程。*/ret = ipprot->handler(skb); /* tcp_v4_rcv() */} else {...}}
out:rcu_read_unlock();return 0;
}

网络层(L3)的数据报处理流程结束,接下来进入传输层(L4)的数据报处理入口 tcp_v4_rcv()

// net/ipv4/tcp_ipv4.c
int tcp_v4_rcv(struct sk_buff *skb)
{...const struct tcphdr *th;...struct sock *sk;...th = (const struct tcphdr *)skb->data; /* TCP协议数据报头 */iph = ip_hdr(skb); /* IP协议数据报头 */
lookup:/* 用 【源、目标IP】和【源、目标端口】等找到接收 @skb 的目标通信套接字 */sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,th->dest, sdif, &refcounted);.../* * 我们假设已经完成了 socket 链接的 3 次握手过程,* 本次通信,为普通数据传输。*/.../* IPsec 包过滤 */if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))goto discard_and_relse;.../* 运行 eBPF 包过滤程序 */if (tcp_filter(sk, skb))goto discard_and_relse;th = (const struct tcphdr *)skb->data;iph = ip_hdr(skb);tcp_v4_fill_cb(skb, iph, th); /* 从 TCP 数据报头提取序列号、flags、时间戳到 TCP skb 控制块 (tcp_skb_cb) */skb->dev = NULL;/* 目标套接字处于监听状态: 通常是用作 server 端的套接字 */if (sk->sk_state == TCP_LISTEN) {ret = tcp_v4_do_rcv(sk, skb); /* 接收 TCP 数据报 */goto put_and_return;}...if (!sock_owned_by_user(sk)) {ret = tcp_v4_do_rcv(sk, skb); /* 接收 TCP 数据报 */} else if (tcp_add_backlog(sk, skb)) {goto discard_and_relse;}
}/* @sk: 接收 @skb 的目标套接字 */
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{/* 发送数据 @skb 的源头,已经和 @sk 建立了连接 */if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */tcp_rcv_established(sk, skb, tcp_hdr(skb)); /* @sk 接收 @skb 数据 */return 0;}/* socket 尚未建立连接情形,读者可自行分析 */...
}void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th)
{.../* Bulk data transfer: receiver */eaten = tcp_queue_rcv(sk, skb, tcp_header_len,&fragstolen);...
}static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, int hdrlen,bool *fragstolen)
{int eaten;struct sk_buff *tail = skb_peek_tail(&sk->sk_receive_queue);__skb_pull(skb, hdrlen);eaten = (tail &&tcp_try_coalesce(sk, RCV_QUEUE, tail,skb, fragstolen)) ? 1 : 0;tcp_rcv_nxt_update(tcp_sk(sk), TCP_SKB_CB(skb)->end_seq);if (!eaten) {__skb_queue_tail(&sk->sk_receive_queue, skb); /* 将接收的数据,写入 socket 的缓冲 */skb_set_owner_r(skb, sk);}return eaten;
}

到此,我们接收的数据终于到达了 socket 的缓冲,整个流程总结如下:

网卡接收缓冲 -> 网络层(L3) -> TCP(L4) -> socket 缓冲

好了,网络数据的接收流程已经分析完毕,接下来我们分析网络数据的发送流程

3.2 网卡数据发送流程

网络数据的发送流程,对比 网络数据的接收流程 ,数据流动方向刚好是相反的:

socket 缓冲 -> TCP(L4) -> 网络层(L3) -> 网卡发送缓冲

所以发送流程,从 socket 的发送接口开始,我们仍然以 TCP/IP v4 数据报为例,来分析网络数据报的发送流程。我们假设通信双方的 socket 已经经历了三次握手的连接建立过程。从系统调用 sys_send() 开始:

sys_send(fd, buff, len, flags)sys_sendto(fd, buff, len, flags, NULL, 0)sock_sendmsg(sock, &msg)sock_sendmsg_nosec(sock, msg)sock->ops->sendmsg(sock, msg, msg_data_left(msg)) = inet_sendmsg()sk->sk_prot->sendmsg(sk, msg, size) = tcp_sendmsg()tcp_sendmsg_locked(sk, msg, size)
int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
{...timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT); /* 发送超时时间 */...out:if (copied) {tcp_tx_timestamp(sk, sockc.tsflags, tcp_write_queue_tail(sk));tcp_push(sk, flags, mss_now, tp->nonagle, size_goal); /* 传递数据到 网络层 */}...return copied + copied_syn;...
}static void tcp_push(struct sock *sk, int flags, int mss_now,int nonagle, int size_goal)
{...__tcp_push_pending_frames(sk, mss_now, nonagle);
}void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,int nonagle)
{...if (tcp_write_xmit(sk, cur_mss, nonagle, 0,sk_gfp_mask(sk, GFP_ATOMIC)))tcp_check_probe_timer(sk);
}/* 将 sock @sk 发送队列的数据报向下传递给 网络层 */
static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,int push_one, gfp_t gfp)
{...max_segs = tcp_tso_segs(sk, mss_now);while ((skb = tcp_send_head(sk))) {...if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp))) /* 将数据报向下传递给 网络层 */break;...}...
}/* 将数据报向下传递给 网络层 */
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,gfp_t gfp_mask)
{return __tcp_transmit_skb(sk, skb, clone_it, gfp_mask,tcp_sk(sk)->rcv_nxt);
}static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{.../* 将数据报向下传递给 网络层 */err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl); /* ip_queue_xmit() */...return err;
}

好,数据已经从传输层(L4)向下传送给了网络层(L3)接口 ip_queue_xmit() ,我们来看网络层(L3)数据报向外发送的流程。

/* net/ipv4/ip_output.c */
int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
{struct inet_sock *inet = inet_sk(sk);...struct rtable *rt;struct iphdr *iph;int res;...rt = skb_rtable(skb);.../* Make sure we can route this packet. */rt = (struct rtable *)__sk_dst_check(sk, 0);if (!rt) {.../* If this fails, retransmit mechanism of transport layer will* keep trying until route appears or the connection times* itself out.*//* 输出路由 */ rt = ip_route_output_ports(net, fl4, sk,daddr, inet->inet_saddr,inet->inet_dport,inet->inet_sport,sk->sk_protocol,RT_CONN_FLAGS(sk),sk->sk_bound_dev_if);...}/* OK, we know where to send it, allocate and build IP header. */skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));skb_reset_network_header(skb);iph = ip_hdr(skb);...iph->protocol = sk->sk_protocol;ip_copy_addrs(iph, fl4);...res = ip_local_out(net, sk, skb); /* 将数据包传递给网络设备 */...return res;...
}int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{int err;err = __ip_local_out(net, sk, skb);if (likely(err == 1))err = dst_output(net, sk, skb);return err;
}/* Output packet to network from transport.  */
static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{return skb_dst(skb)->output(net, sk, skb); /* ip_output() */
}int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{struct net_device *dev = skb_dst(skb)->dev;skb->dev = dev;skb->protocol = htons(ETH_P_IP);/* * 在这里将应用 post routing 路由规则。* 像用户层 ip, iptables 等工具,可以通过这里,加入规则过滤数据报。*/return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,net, sk, skb, NULL, dev,ip_finish_output,!(IPCB(skb)->flags & IPSKB_REROUTED));
}static int ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{unsigned int mtu;int ret;ret = BPF_CGROUP_RUN_PROG_INET_EGRESS(sk, skb); /* 运行发送包的 eBPF 钩子 */......return ip_finish_output2(net, sk, skb);
}static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{...neigh = __ipv4_neigh_lookup_noref(dev, nexthop);if (unlikely(!neigh)) /* 第1次??? */neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);if (!IS_ERR(neigh)) {int res;sock_confirm_neigh(skb, neigh);res = neigh_output(neigh, skb); /* 输出数据 @skb: neigh_direct_output(), neigh_resolve_output(), ... */...return res;}
}int neigh_direct_output(struct neighbour *neigh, struct sk_buff *skb)
{return dev_queue_xmit(skb);
}int dev_queue_xmit(struct sk_buff *skb)
{return __dev_queue_xmit(skb, NULL);
}static int __dev_queue_xmit(struct sk_buff *skb, void *accel_priv)
{struct net_device *dev = skb->dev;/* 没有 Qdisc 队列, 直接调用驱动接口 ndo_start_xmit() 传送数据  */ if (dev->flags & IFF_UP) {int cpu = smp_processor_id(); /* ok because BHs are off */if (txq->xmit_lock_owner != cpu) {...if (!netif_xmit_stopped(txq)) {...skb = dev_hard_start_xmit(skb, dev, txq, &rc); /* 调用网卡驱动接口 .ndo_start_xmit = stmmac_xmit() 传送数据 */...if (dev_xmit_complete(rc)) { /* 传输完成 */...goto out;}}...}}
}

到此为止,网络子系统数据发送流程的公共代码流程已经完成,接下来的是网卡MAC驱动的数据发送逻辑。接着看 stmmac_xmit()

static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
{/* 使用 DMA 通道来传送 @skb 缓冲数据 */...return NETDEV_TX_OK;
}

到此,网络数据包的发送流程也分析完毕了。

Linux: 网络数据收发流程简析相关推荐

  1. linux内核源码分析之网络数据收发流程

    目录 一.TCP/IP 模型与 ISO模型 二.内核中分层模型的结构 三.数据帧的封装 四.协议栈收发包流程 1.网络包接收流程 2.网络包发送流程 一.TCP/IP 模型与 ISO模型 内核中使用的 ...

  2. linux网络数据发送流程

    网络套接字如何将数据发生出去的呢?这个需要从网络协议初始化开始分析. 网络协议初始化: 所在文件net/ipv4/af_inet.c static int __init inet_init(void) ...

  3. linux网络数据包流程

    一.介绍 对于调试linux网卡驱动或者wifi驱动性能,或者排查网络数据丢包的时候,需要对内核处理包要与基本的了解,从而排查出丢包出现在哪个环节,这里给出大致流程和常用排查方法 二.基本框架 1.硬 ...

  4. Linux的启动流程简析(以Debian为例)

    Linux的启动流程简析(以Debian为例) 正文: 前面的文章探讨BIOS和主引导记录的作用.那篇文章不涉及操作系统,只与主板的板载程序有关.今天,我想接着往下写,探讨操作系统接管硬件以后发生的事 ...

  5. Linux网络 - 数据包的接收过程

    Linux网络 - 数据包的接收过程 嵌入式Linux中文站 嵌入式Linux中文站 微信号 emblinux 功能介绍 嵌入式Linux中文站提供专业嵌入式Linux开发技术资讯 Table of ...

  6. uboot源码分析(1)uboot 命令解析流程简析

    uboot 命令解析流程简析 uboot正常启动后,会调用main_loop(void)函数,进入main_loop()之后,如果在规定的时间(CONFIG_BOOTDELAY)内,没有检查到任何按键 ...

  7. Android开机启动流程简析

    Android开机启动流程简析 (一) 文章目录 Android开机启动流程简析 (一) 前言 一.开机启动的流程概述 二.Android的启动过程分析 (1).总体流程 init简述 Zygote简 ...

  8. UDT 最新源码分析(五) -- 网络数据收发

    UDT 最新源码分析 -- 网络数据收发 从接口实现看 UDT 网络收发 UDT 发送 send / sendmsg / sendfile UDT 接收 recv /recvmsg /recvfile ...

  9. CAS流程简析 服务端校验Ticket

    相关阅读 CAS基础组件 简介 CAS流程简析 服务端处理未携带Service登录请求 CAS流程简析 服务端处理携带Service登录请求 CAS基础组件 客户端过滤器 简介 用户访问客户端的请求若 ...

最新文章

  1. django 中文乱码或不识别
  2. 智能车竞赛创意组别对应的FQA
  3. coco数据集大小分类_如何处理不平衡数据集的分类任务
  4. C#线程--5.0之前时代(一)--- 原理和基本使用
  5. 静态页面cors跨域问题
  6. 基于VTK的MFC应用程序开发(3)
  7. MQTT再学习 -- 交叉编译与移植
  8. 使用RMAN备份控制文件(control file)和系统参数文件(spfile)
  9. [转]js实现简单的双向数据绑定
  10. strict=False 但还是size mismatch for []: copying a param with shape [] from checkpoint,the shape in cur
  11. ASP.NET企业开发框架IsLine FrameWork系列之十二--使用Session、Cookie与安全支持
  12. python开发软件的实例-Python 开发工具和框架安装实例步骤
  13. aspectjweaver:关于Spring注解AOP的注意点
  14. 上古卷轴5无法启动 因为计算机丢失,《上古卷轴5:天际》无法启动nmm解决方法...
  15. C盘空间莫名丢失20G?
  16. tensorflow笔记之二十八——带掩码的损失函数
  17. 钢琴作品常用体裁名称及曲式
  18. NFT的未来:RFT、数据经济和Web3堆栈创新的推动者
  19. 《当咖啡与甘蓝汁竞争》:产品是负熵,帮助客户更好进化
  20. 如何用七牛云上传音乐生成外链

热门文章

  1. 前端控制文本展开收起效果
  2. MC9S12XS128实现超声波测距
  3. EXCEL2016版本的三维地图试玩...
  4. 【嵌入式】跑马灯实验
  5. 《写给企业家的经济学》读书笔记 - Part 6
  6. python 操作PPT练习
  7. OPPO连续点击android版本9,oppo游戏中心下载安装正版
  8. 开发人员的5种IFTTT替代品
  9. 编程如何修改磁盘上的一个族文件(*.rfa)文件中的参数值
  10. 胜者举杯相庆,败者拼死相救