Linux 三层、四层接收数据处理函数相关的链表

本文详细介绍了在网络协议栈中,数据包如何从底层网卡驱动传递到上层应用的过程。具体包括二层MAC子层如何处理数据包并调用三层协议处理函数,以及三层协议处理完毕后如何根据协议类型调用相应的四层处理函数。

在底层接收到数据后,通过底层网卡驱动处理完成后,会调用函数netif_receive_skb进行二层mac子层进行处理,对于需要本机处理的三层数据包,是如何调用各三层处理函数的呢?

对于ip子层处理完以后,需要发送到本地上层继续处理的数据包,又是如何调用相应四层处理函数的呢?

 

三层数据接收处理相关的链表及处理流程

1、  三层的协议有arp、ipv4、ipv6、ipx等,我比较熟悉且用到的是arp、ip这两个协议的数据包接收处理函数,下面我们看一下三层协议接收处理函数相关的数据结构

 

三层数据包协议处理

structpacket_type {

__be16                  type;       /*三层协议类型*/

struct net_device    *dev;      /* 与该协议相关联的设备,可以对不同的设备绑定不同的三层协议,我们一般将该值设置为NULL,即匹配所有设备*/

 

/*协议接收处理函数,处理接收的三层数据包的协议处理函数*/

int                 (*func)(struct sk_buff *,

                              struct net_device *,

                              struct packet_type *,

                              struct net_device *);

       /*gso相关的处理函数,对gso不熟悉*/

struct sk_buff         *(*gso_segment)(structsk_buff *skb,

                                    intfeatures);

int                 (*gso_send_check)(structsk_buff *skb);

struct sk_buff         **(*gro_receive)(structsk_buff **head,

                                    struct sk_buff *skb);

int                 (*gro_complete)(structsk_buff *skb);

void               *af_packet_priv;/*用于PF_SOCKET类型的socket。它指向相关的sock数据结构*/

struct list_head       list;/*将该三层协议处理结构添加到三层协议处理链表ptype_base 中*/

};

 static struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;

 

思考:二层协议处理函数为netif_receive_skb,在二层数据处理完以后,对于需要发送到本机的三层数据包,该如何发送到相应的协议层呢?三层协议不止一个,应该怎样处理呢?

 

相对来说,使用一个链表,对于需要发送到本机的三层数据包,遍历链表ptype_base,找到与数据包的三层协议对应的三层协议packet_type,然后就调用packet_type->(*func),由相应的三层处理函数进行后续处理。对于ipv4,即为ip_rcv;对于arp即为arp_rcv;对于ipv6,即为ipv6_rcv。

 

将三层协议packet_type添加到链表ptype_base中的函数为

void dev_add_pack(structpacket_type *pt)

{

int hash;

 

spin_lock_bh(&ptype_lock);

if (pt->type == htons(ETH_P_ALL))

        list_add_rcu(&pt->list,&ptype_all);

else {

        hash = ntohs(pt->type) &PTYPE_HASH_MASK;

        list_add_rcu(&pt->list,&ptype_base[hash]);

}

spin_unlock_bh(&ptype_lock);

}

 

将三层协议packet_type添加到hash链表ptype_base,就是调用链表的处理函数,还是比较简单的。遍历hash链表ptype_base,可以使用list_for_each_entry_rcu实现。

 

 

 

 

四层数据接收处理相关的链表及处理流程

1、  对于ipv4、ipv6的三层处理函数ip_rcv、ipv6_rcv处理完以后,需要由四层协议处理的函数,其处理流程与三层协议链表ptype类似。下面分析四层数据接收协议相关的数据结构

/*四层协议注册相关的数据结构 */

struct net_protocol {

       int                 (*handler)(struct sk_buff*skb);/*四层协议相关的函数*/

       void               (*err_handler)(struct sk_buff*skb, u32 info);/*错误处理*/

       /*gso相关*/

       int                 (*gso_send_check)(structsk_buff *skb);

       structsk_buff         *(*gso_segment)(struct sk_buff *skb,

                                          int features);

       structsk_buff        **(*gro_receive)(struct sk_buff **head,

                                          struct sk_buff *skb);

       int                 (*gro_complete)(struct sk_buff*skb);

       unsignedint           no_policy:1,

                            netns_ok:1;

};

 

与三层协议相关的数据结构packet_type相比,该数据结构中并没有标记协议的类型,那四层协议数据结构是如何查找的呢?

 

因为四层协议相关的数据结构,主要是链接到hash链表structnet_protocol *inet_protos[MAX_INET_PROTOS]。虽说是hash数组,但是该数组的最大值为256,而三、四层协议相关的定义如下,而hash数组的最大值即为256,即退化为了数组指针。所以每一个协议均对应一个net_protocol指针,此时只要从ip头部周获取到相应的四层的类型,即可从数组中取出相应的四层协议结构。而在三层结构,有可能出现同一个链表存在多个三层协议,此时就需要在三层协议中增加一个协议类型成员了。

enum {

 IPPROTO_IP = 0,             /*Dummy protocol for TCP         */

 IPPROTO_ICMP = 1,              /*Internet Control Message Protocol    */

 IPPROTO_IGMP = 2,              /*Internet Group Management Protocol      */

 IPPROTO_IPIP = 4,         /* IPIPtunnels (older KA9Q tunnels use 94) */

 IPPROTO_TCP = 6,          /*Transmission Control Protocol  */

 IPPROTO_EGP = 8,         /*Exterior Gateway Protocol              */

 IPPROTO_PUP = 12,        /* PUPprotocol                          */

 IPPROTO_UDP = 17,              /*User Datagram Protocol           */

 IPPROTO_IDP = 22,        /* XNS IDPprotocol                   */

 IPPROTO_RSVP = 46,            /*RSVP protocol                 */

 IPPROTO_GRE = 47,              /*Cisco GRE tunnels (rfc 1701,1702)  */

 

 IPPROTO_IPV6 = 41,            /* IPv6-in-IPv4 tunnelling           */

 

 IPPROTO_ESP = 50,            /*Encapsulation Security Payload protocol */

 IPPROTO_AH = 51,             /*Authentication Header protocol       */

 IPPROTO_PIM    = 103,              /* Protocol Independent Multicast */

 

 IPPROTO_COMP   = 108,                /* Compression Header protocol*/

 IPPROTO_SCTP   = 132,              /* Stream Control TransportProtocol   */

 

 IPPROTO_RAW = 255,          /* Raw IP packets                 */

 IPPROTO_MAX

};

 

 

四层协议添加到v4数组net_protocol和从数组中删除的函数为,主要是将四层协议结构添加到数组中去。

int inet_add_protocol(const structnet_protocol *prot, unsigned char protocol)

{

       inthash, ret;

 

       hash= protocol & (MAX_INET_PROTOS - 1);

 

       spin_lock_bh(&inet_proto_lock);

       if(inet_protos[hash]) {

              ret= -1;

       }else {

              inet_protos[hash]= prot;

              ret= 0;

       }

       spin_unlock_bh(&inet_proto_lock);

 

       returnret;

}

 

/*

 *    Remove a protocol from the hash tables.

 */

 

int inet_del_protocol(const structnet_protocol *prot, unsigned char protocol)

{

       inthash, ret;

 

       hash= protocol & (MAX_INET_PROTOS - 1);

 

       spin_lock_bh(&inet_proto_lock);

       if(inet_protos[hash] == prot) {

              inet_protos[hash]= NULL;

              ret= 0;

       }else {

              ret= -1;

       }

       spin_unlock_bh(&inet_proto_lock);

 

       synchronize_net();

 

       returnret;

}

 

 

 

 

 

在ip_local_deliver_finish中根据从三层协议头中获取的四层协议类型,从数组inet_protos中取出相应的四层协议net_protocol,执行net_protocol->handler,即将数据包交由四层协议处理。对于udp为udp_rcv;对于tcp为tcp_v4_rcv;对于icmp为icmp_rcv;对于igmp为igmp_rcv。这样即实现了将三层数据包交由上层继续处理。

<think>我们正在查找Linux内核中`__dev_close_many`函数相关信息。这个函数网络设备关闭机制的一部分,通常在内核的网络子系统中使用。 ### 函数作用 `__dev_close_many`函数用于关闭多个网络设备。它遍历给定的网络设备列表,并对每个设备执行关闭操作。该函数通常被`dev_close_many`和`dev_ifclose`等函数调用,用于在需要关闭多个设备时(例如系统关机、网络命名空间关闭等)进行批量处理。 ### 函数原型 根据Linux内核源代码(以5.x版本为例),该函数的原型通常位于`net/core/dev.c`文件中: ```c static void __dev_close_many(struct list_head *head); ``` 或者在某些版本中,可能带有第二个参数(如`bool unlink`): ```c static void __dev_close_many(struct list_head *head, bool unlink); ``` ### 关键行为 1. **遍历设备列表**:函数遍历传入的设备链表,对每个设备执行关闭操作。 2. **设备状态检查**:在关闭设备前,会检查设备是否处于运行状态(`__LINK_STATE_START`),只有运行中的设备才需要关闭。 3. **调用设备关闭操作**:对于每个需要关闭的设备,调用`__dev_close`函数(内部会调用设备特定的`ndo_stop`操作)。 4. **从命名空间中移除**:如果`unlink`参数为真,则设备关闭后将其从所属的网络命名空间中移除(通过调用`dev_unregister`)。 ### 调用关系 该函数通常被以下函数调用: - `dev_close_many`:这是`__dev_close_many`的封装,提供更简单的接口。 - `dev_ifclose`:用于关闭一个网络设备,可能会调用到`__dev_close_many`(当需要关闭多个设备时)。 ### 代码示例(简化版) ```c static void __dev_close_many(struct list_head *head, bool unlink) { struct net_device *dev, *tmp; list_for_each_entry_safe(dev, tmp, head, close_list) { if (unlink) list_del_init(&dev->close_list); if (dev->flags & IFF_UP) { __dev_close(dev); if (unlink) dev->reg_state = NETREG_UNREGISTERING; } } } ``` ### 相关函数 - `__dev_close`:实际执行单个设备关闭操作的内部函数- `dev_close`:关闭单个设备,是`__dev_close`的封装。 - `dev_change_flags`:用于改变设备标志,当需要关闭设备时也会调用关闭函数。 ### 使用场景 1. **系统关机或重启**:关闭所有网络设备。 2. **网络命名空间删除**:当删除一个网络命名空间时,需要关闭并清理其中的所有设备。 3. **容器技术**:在容器停止时,关闭容器内的网络设备。 ### 注意事项 -函数是静态函数(`static`),因此只能在定义它的文件(`net/core/dev.c`)中被调用。 - 在遍历设备列表时,使用了`list_for_each_entry_safe`宏,确保在遍历过程中删除设备是安全的。 ### 调试与问题排查 如果遇到网络设备关闭相关的问题,可以: 1. 检查内核日志,看是否有设备关闭时的错误信息(如`ndo_stop`操作失败)。 2. 使用`ftrace`或`kprobe`跟踪`__dev_close_many`的调用情况。 ### 相关问题 1. Linux内核中网络设备的打开和关闭流程是怎样的? 2. `__dev_close`函数具体做了哪些工作? 3. 网络设备驱动中的`ndo_stop`操作通常需要实现哪些功能? 4. 如何调试Linux内核网络设备的关闭问题? 5. 网络命名空间的关闭过程涉及哪些关键函数? 以上信息基于Linux内核5.x版本,具体实现可能随内核版本变化而略有不同。</think>### Linux 内核 `__dev_close_many` 函数详解 `__dev_close_many` 是 Linux 内核网络子系统中的核心函数,用于**批量关闭多个网络设备**。它位于 `net/core/dev.c` 文件中,是网络设备管理的关键底接口。 --- #### 一、功能作用 1. **批量设备关闭** 同时关闭多个网络设备,避免逐个关闭的性能开销。 2. **状态同步** 确保设备状态从 `UP` 切换到 `DOWN` 时同步更新内核数据结构。 3. **资源释放** 触发网络队列停止(`netif_tx_disable()`)并释放相关资源。 --- #### 二、函数原型与关键参数 ```c static void __dev_close_many(struct list_head *head, bool unlink); ``` - **`head`**:网络设备链表头(`struct list_head`),包含待关闭的设备 - **`unlink`**:布尔值,决定是否从内核全局设备列表中移除设备 - **调用关系**: ```mermaid graph TD A[dev_close_many] --> B[__dev_close_many] B --> C[__dev_close] // 关闭单个设备 C --> D[dev->netdev_ops->ndo_stop] // 驱动关闭操作 ``` --- #### 三、核心逻辑流程 1. **遍历设备链表** 使用 `list_for_each_entry_safe` 宏安全遍历设备链表: ```c list_for_each_entry_safe(dev, tmp, head, close_list) { if (unlink) list_del_init(&dev->close_list); __dev_close(dev); // 核心关闭操作 } ``` 2. **执行设备关闭** 在 `__dev_close()` 中: - 设置设备状态为 `__LINK_STATE_START` 清除 - 调用 `netif_tx_disable()` 停止数据包传输 - 执行驱动注册的关闭回调:`dev->netdev_ops->ndo_stop()` 3. **资源清理** - 刷新数据包队列(`qdisc_flush()`) - 释放套接字缓冲区(`skb_queue_purge()`) - 更新邻居子系统状态 --- #### 四、典型应用场景 1. **网络命名空间删除** 当销毁网络命名空间时,关闭其所有设备: ```c // net/core/net_namespace.c cleanup_net() -> __dev_close_many() ``` 2. **系统关机流程** 在 `kernel_restart` 中关闭所有网络设备: ```c dev_close_many(&dev_close_list, true); ``` 3. **容器虚拟化** 容器退出时通过 `veth` 设备关闭调用此函数[^1]。 --- #### 五、性能优化设计 1. **批处理优势** 相比逐个调用 `dev_close()`,批量处理减少: - 锁竞争次数(减少 `rtnl_lock()` 获取/释放) - 上下文切换开销 - 缓存局部性优化 2. **安全遍历机制** 使用 `_safe` 版本链表宏(`list_for_each_entry_safe`)确保在设备被移除时遍历不崩溃。 --- #### 六、调试与问题排查 1. **常见问题** - 设备关闭后仍接收数据包(状态同步失败) - 内存泄漏(资源未完全释放) - 死锁(错误持有 `rtnl_lock`) 2. **调试方法** - 启用内核动态调试: ```bash echo &#39;file dev.c +p&#39; > /sys/kernel/debug/dynamic_debug/control ``` - 检查内核日志: ```bash dmesg | grep "__dev_close_many" ``` - 使用 `ftrace` 跟踪调用栈: ```bash echo function_graph > /sys/kernel/debug/tracing/current_tracer echo __dev_close_many > /sys/kernel/debug/tracing/set_ftrace_filter ``` --- #### 七、关联函数对比 | 函数 | 作用 | 调用场景 | |------|------|----------| | `dev_close()` | 关闭单个设备 | 用户空间 `ifconfig eth0 down` | | `dev_close_many()` | 批量关闭接口 | 内核空间批量操作 | | `unregister_netdevice()` | 注销设备 | 设备热拔插 | --- ### 相关问题 1. Linux 内核中网络设备的状态机如何管理 `UP/DOWN` 状态转换? 2. 当调用 `__dev_close_many` 时网络数据包如何处理? 3. 网络设备驱动中的 `ndo_stop` 回调需要实现哪些功能? 4. 如何在内核模块中安全地调用 `__dev_close_many`? 5. 网络命名空间销毁时资源清理的完整流程是怎样的?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值