
图1:Network-API-and-data-flow-within-Linux-kernel
图1原图链接:https://thelinuxchannel.org/2024/10/network_data_flow_through_kernel-api-map/
注,本文基于 2.6.20 内核。
https://elixir.bootlin.com/linux/v2.6.20/source
接收流程
第 2 层:链路层(例如以太网)
网络设备会预先分配一定数量的套接字缓冲区(sk_buff)用于接收数据。具体分配的数量是针对每个设备进行配置的。通常,这些 sk_buff 中数据空间的地址会直接被配置为设备的直接内存访问(DMA)区域。设备中断处理程序会获取 sk_buff 并对其进行接收处理。在网络 API(NAPI)出现之前,这一过程是通过 netif_rx() 函数完成的。而在 NAPI 中,该过程分两个阶段进行。
-
从中断处理程序来看,设备驱动程序只需调用
netif_rx_schedule()函数,然后从中断中返回。netif_rx_schedule()会将设备添加到softnet_data的轮询列表中,并触发NET_RX_SOFTIRQ软件中断。 -
软中断守护进程
ksoftirqd会运行net_rx_action()函数,该函数会调用设备的轮询虚拟方法。轮询(poll)方法会进行特定于设备的缓冲区管理,为每个sk_buff调用netif_receive_skb()函数,根据需要分配新的sk_buff,并以netif_rx_complete()函数结束操作。
netif_receive_skb() 函数会确定如何将 sk_buff 传递给上层。
-
会调用
netpoll_rx()函数以支持网络轮询(Netpoll)应用程序编程接口(API): -
调用针对
ETH_P_ALL协议的数据包处理程序(用于tcpdump工具)。 -
为入站(ingress)队列调用
handle_ing()函数。 -
为网桥调用
handle_bridge()函数。 -
为虚拟局域网调用
handle_macvlan()函数。 -
调用为数据包指定的第 3 层(L3)协议所注册的数据包处理程序。
数据包处理程序是通过 deliver_skb() 函数来调用的,该函数会调用协议的 func 虚拟方法来处理数据包。
第 3 层:网络层(IPv4、ARP)
ARP(地址解析协议)
ARP 数据包由 arp_rcv() 函数处理。它会处理 ARP 信息,将其存储在邻居缓存中,并在需要时发送回复。如果需要发送回复,会为回复分配一个新的套接字缓冲区(sk_buff,带有新的数据空间)。
IPv4(互联网协议第 4 版)
IPv4 数据包由 ip_rcv() 函数处理。它会解析首部,检查其有效性,并在需要时发送 ICMP 回复或错误消息。它还会使用 ip_route_input() 函数查找目的地址。会使用 sk_buff 调用目的地址的输入虚拟方法。
-
对于组播地址,会调用
ip_mr_input()函数。数据包可能会使用ip_mr_forward()函数进行转发,也可能会使用ip_local_delivery()函数进行本地交付。 -
对于有不同目的地址且本机有路由的数据包,会调用
ip_forward()函数。它会直接调用邻居的输出虚拟方法。 -
如果本机是数据包的目的地址,则会调用
ip_local_deliver()函数。这里会收集数据报分片。
ip_local_deliver() 函数首先会使用 raw_local_deliver() 函数将数据交付给该连接的任何原始套接字。然后,它会调用数据报中指定协议的第 4 层(L4)协议处理程序。即使存在原始套接字,也会调用 L4 协议处理程序。
在整个过程中,会包含对 xfrm4_policy_check 的调用,以支持 IPsec(互联网协议安全)。
第 4 层:传输层(TCP)
TCP 的网络协议处理函数是 tcp_v4_rcv()。这里的大部分代码用于处理 TCP 协议相关操作,例如建立连接、执行流量控制等。
接收到的 TCP 数据包可能包含对之前发送数据包的确认信息,这可能会触发进一步的数据包发送(tcp_data_snd_check())或确认信息发送(tcp_ack_snd_check())。
将接收到的数据包传递给上层的操作在 tcp_rcv_established() 和 tcp_data_queue() 函数中完成。这些函数会维护 TCP 连接的乱序队列,以及套接字的接收队列(sk_receive_queue)和异步等待队列(sk_async_wait_queue)。如果有用户进程已经在等待数据到达,会使用 skb_copy_datagram_iovec() 函数立即将数据复制到用户空间。否则,sk_buff 会被添加到套接字的某个队列中,稍后再进行复制。
最后,接收函数会调用套接字的 sk_data_ready 虚拟方法,以表明有数据可用。这会唤醒正在等待的进程。
第 5 层:会话层(套接字和文件)
有三种系统调用可用于从网络接收数据:
-
read(从文件描述符读取内存数据) -
recvfrom(从套接字读取内存数据) -
recvmsg(从套接字读取复合消息)
所有这些系统调用最终都会调用 __sock_recvmsg() 函数。该函数会先调用 security_sock_recvmsg() 进行权限检查,然后使用套接字的 recvmsg 虚拟方法向更下一层请求消息。通常调用的是 sock_common_recvmsg() 函数,它会调用套接字所使用协议的 recvmsg 虚拟方法。
tcp_recvmsg() 函数要么使用 skb_copy_datagram_iovec() 从套接字队列复制数据,要么使用 sk_wait_data() 等待数据到达。后一种方式会使进程阻塞,并且会在第 4 层的处理流程中被唤醒。
附录部分函数源码
int netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
struct net_device *orig_dev;
int ret = NET_RX_DROP;
__be16 type;
/* if we've gotten here through NAPI, check netpoll */
if (skb->dev->poll && netpoll_rx(skb))
return NET_RX_DROP;
if (!skb->tstamp.off_sec)
net_timestamp(skb);
if (!skb->input_dev)
skb->input_dev = skb->dev;
orig_dev = skb_bond(skb);
if (!orig_dev)
return NET_RX_DROP;
__get_cpu_var(netdev_rx_stat).total++;
skb->h.raw = skb->nh.raw = skb->data;
skb->mac_len = skb->nh.raw - skb->mac.raw;
pt_prev = NULL;
rcu_read_lock();
#ifdef CONFIG_NET_CLS_ACT
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
}
#endif
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
#ifdef CONFIG_NET_CLS_ACT
if (pt_prev) {
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL; /* noone else should process this after*/
} else {
skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
}
ret = ing_filter(skb);
if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {
kfree_skb(skb);
goto out;
}
skb->tc_verd = 0;
ncls:
#endif
if (handle_bridge(&skb, &pt_prev, &ret, orig_dev))
goto out;
type = skb->protocol;
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
if (ptype->type == type &&
(!ptype->dev || ptype->dev == skb->dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} else {
kfree_skb(skb);
/* Jamal, now you will not able to escape explaining
* me how you were going to use this. :-)
*/
ret = NET_RX_DROP;
}
out:
rcu_read_unlock();
return ret;
}
/* Try to reschedule poll. Called by irq handler. */
static inline void netif_rx_schedule(struct net_device *dev)
{
if (netif_rx_schedule_prep(dev))
__netif_rx_schedule(dev);
}
static __inline__ int deliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev,
struct net_device *orig_dev)
{
atomic_inc(&skb->users);
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
Source
https://wiki.linuxfoundation.org/networking/kernel_flow


1万+

被折叠的 条评论
为什么被折叠?



