一、socket 层到网络接口的驱动程序之间的函数调用过程概述
在 Linux 操作系统中,socket 层到网络接口的驱动程序之间的函数调用过程相对复杂,涉及多个层次的交互。以下是一个简化的概述,描述数据从 socket 传递到硬件驱动,再到硬件设备的基本调用过程:
1. 用户空间到内核空间:
用户程序通过 socket API 发起网络操作。当用户空间要发送数据时,它会调用如 sendto 或 write 等系统调用,这些调用将数据从用户空间传递到内核空间。
2. Socket 层处理:
根据 socket 类型(例如 TCP、UDP 或者其他协议),内核将调用相应的协议族处理函数。例如对于一个 TCP socket,传输控制协议 (TCP) 相关的函数将被使用。
3. 协议栈:
数据通过网络协议栈传递,进行必要的处理,如 IP 分段、TCP 分段、添加头部等。每一层协议栈都对数据进行加工,直到形成可以在网络上传输的包。
4. 路由决策:
内核根据 IP 层的路由表进行路由决策,确定用于发送数据的 net_device 结构体。此结构体代表了相应的网络接口,并包含了用于发送数据的接口函数。
5. 队列与传输:
数据包被放入到对应网络设备的发送队列中等待传输。这通常涉及到内核中的排队机制,如 Qdisc(排队规则)层。
6. 网络设备驱动程序的发送函数:
被排队的数据包最终将被网络设备驱动程序的发送函数处理。这一函数通常被称为 hard_start_xmit 或类似的名称。驱动程序将数据包转换成适合该网络设备的形式,并通过硬件接口发送出去。
7. 硬件传输:
最后,数据被传递到网卡的硬件,由网卡负责将电子信号或光信号发送到网络中。
这个过程可能会因为具体的配置和使用的协议栈而不同。在某些情况下,例如使用虚拟化技术或软件定义网络(SDN),过程中的某些步骤可能被跳过或者是在不同的层次上执行。此外,接收数据的过程也会涉及类似但相反的步骤,以将数据从硬件传输上来,经过内核处理,然后递送到用户态的应用程序中。
二、路由子系统将 IP 数据发送到正确的网络接口过程
在 Linux 内核中,IP层路由决策主要是由内核的路由子系统来完成的。路由子系统负责将 IP 数据包根据目的地址发送到正确的网络接口。这个过程大体上可以分为几个步骤:
1. 查找路由表:
当一个 IP 数据包需要被发送的时候,内核会根据这个数据包的目的 IP 地址在路由表中查找最匹配的路由项。Linux 中的路由表可以通过 ip route show 命令查看。
2. 路由表条目:
每一条路由表项包含了几个重要的信息字段,例如目的网络、子网掩码、网关、接口名称等。这些信息用来决定数据包的转发路径。
3. 选择最合适的路由:
如果路由表中有多条路由都可以匹配到一个目的地址,那么将根据优先级和其他标准选择最合适的一条。通常,最具体(最长前缀匹配)的路由将被选择用来转发数据包。
4. 确定 net_device:
一旦选择了一条路由,内核将使用与之相关联的 net_device 结构体。`net_device` 结构体代表了一个网络接口设备,包含了很多网络设备的信息,包括设备名称、接口状态、传输队列、以及用于发送和接收数据的函数指针等。
5. 处理网关(如果必要):
如果数据包需要通过网关(跳)来到达目标,那么路由表项中也会包含网关的信息。在这种情况下,数据包的实际目的硬件地址将会是网关设备的地址,而不是最终目的地的地址。
6. 引用程序:
在最终确定了`net_device`结构体和下一跳信息(next hop—not necessarily the final destination)之后,数据包将被送到网络接口的发送队列准备发送(例如通过调用`dev_queue_xmit`函数)。
7. Neighbor Subsystem (ARP):
如果还没有目的地的 MAC 地址,则需要进行地址解析(例如使用 ARP 协议解析 IPv4 地址)。这是在发送前必须完成的,除非目的地就是本地网络接口。
路由决策是一个动态的过程,可以被多种因素影响,例如路由协议、策略路由规则、网络接口的状态更改等。管理员或者网络管理系统可以修改路由表来更改路由决策的结果,以适应不断变化的网络环境。在进行网络通信时,确保高性能和正确的路由决策是很重要的,这是由内核的路由子系统来保证的。
三、选择路由的过程中涉及的步骤
当 Linux 内核需要发送一个网络数据包时,它会通过路由子系统来确定该往哪个方向发送数据包。选择路由之后,内核会使用与这条路由相关联的 net_device 结构体来处理接下来的数据包发送。`net_device` 结构体是 Linux 内核网络子系统的一个基础结构,代表一个网络接口,包含了与该网络设备相关的所有信息。
在选择路由的过程中,以下步骤通常会涉及:
1. 路由查找:
内核使用数据包的目的 IP 地址,通过路由查找函数(如 ip_route_output())在路由表中查找匹配的路由项。路由表包含了多个条目,每一个条目都包含目的网络、掩码、网关、接口等信息。
2. 选择路由项:
如果找到了匹配的路由项,内核就会获取相应的路由决策,这包括了数据包应该从哪个网络接口发送,以及是否需要通过网关来转发等信息。
3. 关联 net_device:
路由项包含了一个指向相关 net_device 结构体的指针。`net_device` 结构体定义在 <linux/netdevice.h> 头文件中,包含了网络接口的诸多信息和操作函数指针,如设备名(`name` 字段)、接口索引(`ifindex` 字段)、操作函数(`netdev_ops` 字段)等。
4. 数据包的处理:
一旦确定了 net_device,内核就会将数据包传递给这个结构体中的操作函数进行处理。`net_device_ops` 结构体中包含了多个操作数据包的函数指针,例如 ndo_start_xmit 用于发送数据包,`ndo_open` 和 ndo_stop 用于打开和关闭设备等。
5. 发送数据包:
ndo_start_xmit 函数(通常对应该网卡驱动的 hard_start_xmit 函数)被调用并且被传入待发送的 sk_buff(包含网络数据包的结构体)以及 net_device 结构体。该函数负责将数据包加入到传输队列,并触发数据包的传输流程,这可能会直接向硬件发送数据包,或者向硬件排队以便于稍后发送。
这个过程中的一个重要概念是 sk_buff,它是内核中用于存储和控制数据包的主要数据结构。当内核层的各种网络子系统处理数据包时,`sk_buff` 结构会随数据包传递,最终到达用于实际发送数据的网络设备驱动程序。
四、 IP 层数据包映射到相应的 net_device
结构体的过程
在 Linux 内核中,路由决策是基于路由表来进行的,其将 IP 层数据包映射到相应的 net_device 结构体的过程通常如下:
1. 网络接口注册:
当网络接口(或称为网络设备)如以太网卡在系统启动或者后来被加载时,它的驱动会通过调用 register_netdev() 函数将该设备注册到内核网络子系统中。在注册过程中,`net_device` 结构体会被初始化和填充,它包含了用于操作该网络设备的信息,包括发送和接收函数指针、设备名、以及设备的硬件地址等。
2. 路由表的初始化和更新:
路由表是内核中用于存储路由信息的数据结构,它包含了多个路由项,每个路由项包含了网络目的地址、网络掩码、网关地址、标志等信息。路由表可以手动通过例如 ip route add 命令配置,也可以自动通过 DHCP、路由协议如 OSPF、BGP 或其他机制来更新。
当路由项被创建或更新时,每个路由项也会关联到一个 net_device 结构体。例如,如果一个路由项指定了通过特定的网关(也就是下一跳)来达到目的网络,则该路由项将包含指向该网关对应的网络接口(`net_device` 结构)的指针。
3. 路由选择:
当内核需要转发或发送数据包时,它会根据数据包的目的 IP 地址执行路由选择(路由查找)。这通常涉及查询路由表并且找到最合适的路由项。路由选择的算法会考虑多个因素,例如目的地址、子网掩码和度量值(metric)。
4. 结构体关联:
当路由选择过程确定了合适的路由项后,它会使用该路由项中指向 net_device 结构体的指针来确定数据包应该通过哪个网络接口发送。这个指针在路由项被添加到路由表时就已经建立起了。
路由表和网络设备通常是可以动态管理的,管理员可以使用包括 ifconfig、`ip` 等命令来配置网络接口和路由表,内核也会使用诸如 rtnetlink 接口来进行这些信息的更新和同步。
五、`net_device` 结构体中的设备名
`net_device` 结构体确实包含网络接口的名称,它在 Linux 内核网络子系统中表示一个网络接口。在 net_device 结构体中,通常有一个名为 name 的成员用于存储设备的名称。这个名称通常与在用户空间看到的设备名称(如 eth0, wlan0, ens33 等)相对应。
下面是一个简化版的 net_device 结构体的例子,展示了包含设备名称的部分:
struct net_device {
char name[IFNAMSIZ]; // 网络设备名称
// ... 其他很多网络设备相关的成员变量和函数指针 ...
// 这里仅展示了名称部分,并省略了大量其它细节
};
在这个结构体中,`IFNAMSIZ` 是一个定义在 <linux/if.h> 中的宏,它规定了接口名的最大长度,便于内核和用户空间的统一。
net_device 结构提供了内核中对网络接口的抽象,包含了很多网络操作的接口函数、统计信息、队列状态,以及一些特定于设备类型和配置的参数。
当网络接口注册到网络子系统时,例如在网卡驱动初始化的时候,这个结构会被填充并加入到内核的网络设备列表中。用户可以通过指令如 ifconfig 或 ip link show 在用户空间查看网络接口的详细信息。
六、IP 地址
在 Linux 内核中,`net_device` 结构体代表一个网络接口,它主要包含了与网络设备相关的信息,如设备状态、硬件地址(如 MAC 地址),以及指向设备驱动函数的指针等。`net_device` 结构体本身不直接保存 IP 地址信息。
IP 地址(无论是 IPv4 还是 IPv6)是在网络层管理的,具体而言,在 Linux 内核中,IP 地址是与网络接口相关联的,但存储在不同于 net_device 的结构体中。对于 IPv4,地址信息通常存储在 in_device 结构体中;对于 IPv6,地址信息通常存储在 inet6_dev 结构体中。
这两个结构体(`in_device` 和 inet6_dev)包含了与 IP 层相关的配置和状态信息,包括分配给接口的 IP 地址列表、子网掩码、任播和广播地址等。这些结构通过指针与对应的 net_device 实例相连。这样的设计允许网络接口能够同时配置和管理 IPv4 和 IPv6 地址。
网络接口的 IP 地址通常是通过系统调用(如 ioctl 或 socket 操作)和网络配置工具(如 ifconfig、`ip` 命令行工具等)来设置和管理的。在内核层面,此类操作会通过各种内核函数来修改和查看与 in_device 以及 inet6_dev 相关联的 IP 地址信息。