Linux内核网络udp数据包发送(一)
扫描二维码
随时随地手机看文章
1. 前言
本文首先从宏观上概述了数据包发送的流程,接着分析了协议层注册进内核以及被socket的过程,最后介绍了通过 socket 发送网络数据的过程。2. 数据包发送宏观视角
从宏观上看,一个数据包从用户程序到达硬件网卡的整个过程如下:- 使用系统调用(如
sendto
,sendmsg
等)写数据 - 数据穿过socket 子系统,进入socket 协议族(protocol family)系统
- 协议族处理:数据穿过协议层,这一过程(在许多情况下)会将数据(data)转换成数据包(packet)
- 数据穿过路由层,这会涉及路由缓存和 ARP 缓存的更新;如果目的 MAC 不在 ARP 缓存表中,将触发一次 ARP 广播来查找 MAC 地址
- 穿过协议层,packet 到达设备无关层(device agnostic layer)
- 使用 XPS(如果启用)或散列函数选择发送队列
- 调用网卡驱动的发送函数
- 数据传送到网卡的
qdisc
(queue discipline,排队规则) - qdisc 会直接发送数据(如果可以),或者将其放到队列,下次触发NET_TX 类型软中断(softirq)的时候再发送
- 数据从 qdisc 传送给驱动程序
- 驱动程序创建所需的DMA 映射,以便网卡从 RAM 读取数据
- 驱动向网卡发送信号,通知数据可以发送了
- 网卡从 RAM 中获取数据并发送
- 发送完成后,设备触发一个硬中断(IRQ),表示发送完成
- 硬中断处理函数被唤醒执行。对许多设备来说,这会触发 NET_RX 类型的软中断,然后 NAPI poll 循环开始收包
- poll 函数会调用驱动程序的相应函数,解除 DMA 映射,释放数据
3. 协议层注册
协议层分析我们将关注 IP 和 UDP 层,其他协议层可参考这个过程。我们首先来看协议族是如何注册到内核,并被 socket 子系统使用的。当用户程序像下面这样创建 UDP socket 时会发生什么?sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
简单来说,内核会去查找由 UDP 协议栈导出的一组函数(其中包括用于发送和接收网络数据的函数),并赋给 socket 的相应字段。准确理解这个过程需要查看 AF_INET
地址族的代码。内核初始化的很早阶段就执行了 inet_init
函数,这个函数会注册 AF_INET
协议族 ,以及该协议族内的各协议栈(TCP,UDP,ICMP 和 RAW),并调用初始化函数使协议栈准备好处理网络数据。inet_init
定义在net/ipv4/af_inet.c 。AF_INET
协议族导出一个包含 create
方法的 struct net_proto_family
类型实例。当从用户程序创建 socket 时,内核会调用此方法:static const struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
inet_create
根据传递的 socket 参数,在已注册的协议中查找对应的协议:/* Look for the requested type/protocol pair. */
lookup_protocol:
err = -ESOCKTNOSUPPORT;
rcu_read_lock();
list_for_each_entry_rcu(answer,