当前位置: 首页 > news >正文

Linux中内核和用户空间通信send_uevent函数的实现

Netlink 整体架构概述

Netlink 是 Linux 内核中用于内核空间与用户空间通信的机制,特别适合事件通知配置管理

核心组件总结

1. 初始化阶段 (kobject_uevent_init)

功能: 建立内核与用户空间的通信基础

  • 创建专用的 Netlink socket (NETLINK_KOBJECT_UEVENT)
  • 初始化全局 uevent_sock 变量
  • 为后续 uevent 通知建立通道

2. Socket 创建阶段 (netlink_kernel_create + netlink_create)

功能: 创建和配置内核端 Netlink socket

关键步骤:

  • 协议验证和范围检查
  • 分配 socket 核心数据结构
  • 设置 Netlink 特定选项
  • 初始化锁和等待队列
  • 注册到全局 Netlink

3. Socket 注册阶段 (netlink_insert)

功能: 管理 socket 的寻址和路由

核心机制:

  • 基于 PID 的哈希表管理
  • 冲突检测和避免
  • 并发安全保护
  • 哈希表性能优化(稀释机制)

4. 消息发送阶段 (send_ueventnetlink_broadcastdo_one_broadcast)

功能: 实现高效可靠的消息广播

初始化 kobject uevent 子系统kobject_uevent_init

static int __init kobject_uevent_init(void)
{uevent_sock = netlink_kernel_create(NETLINK_KOBJECT_UEVENT, NULL);if (!uevent_sock) {printk(KERN_ERR"kobject_uevent: unable to create netlink socket!\n");return -ENODEV;}return 0;
}
struct sock *
netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len))
{struct socket *sock;struct sock *sk;if (!nl_table)return NULL;if (unit<0 || unit>=MAX_LINKS)return NULL;if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))return NULL;if (netlink_create(sock, unit) < 0) {sock_release(sock);return NULL;}sk = sock->sk;sk->sk_data_ready = netlink_data_ready;if (input)nlk_sk(sk)->data_ready = input;if (netlink_insert(sk, 0)) {sock_release(sock);return NULL;}return sk;
}

代码逐行解释

kobject_uevent_init 函数

函数声明

static int __init kobject_uevent_init(void)
  • __init: 表示这是内核初始化函数,初始化完成后会释放其内存
  • 无参数,返回整型状态码

创建 Netlink Socket

uevent_sock = netlink_kernel_create(NETLINK_KOBJECT_UEVENT, NULL);
  • uevent_sock: 全局变量,存储创建的 netlink socket
  • NETLINK_KOBJECT_UEVENT: Netlink 协议类型,专门用于 kobject uevent
  • NULL: 输入回调函数,这里设为 NULL 表示这是纯发送端socket

错误检查

if (!uevent_sock) {printk(KERN_ERR"kobject_uevent: unable to create netlink socket!\n");return -ENODEV;
}
  • 检查 socket 创建是否成功
  • 如果失败,打印错误信息到内核日志
  • 返回 -ENODEV 表示没有这样的设备

成功返回

return 0;
  • 成功创建 socket,返回 0 表示初始化成功

netlink_kernel_create 函数

函数声明和参数

struct sock *netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len))
  • unit: Netlink 协议类型(如 NETLINK_KOBJECT_UEVENT
  • input: 数据到达时的回调函数指针
  • 返回创建的 struct sock 指针

变量声明

struct socket *sock;
struct sock *sk;
  • sock: 套接字结构,包含协议相关的操作
  • sk: 套接字核心结构,包含网络层状态

初始检查

if (!nl_table)return NULL;if (unit<0 || unit>=MAX_LINKS)return NULL;
  • 检查全局 netlink 表是否已初始化
  • 验证协议类型是否在有效范围内

创建轻量级 Socket

if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))return NULL;
  • PF_NETLINK: 协议族,表示使用 Netlink
  • SOCK_DGRAM: 套接字类型,数据报套接字
  • unit: 协议类型
  • &sock: 输出参数,返回创建的 socket
  • 如果创建失败返回 NULL

创建 Netlink 特定结构

if (netlink_create(sock, unit) < 0) {sock_release(sock);return NULL;
}
  • netlink_create(): 初始化 netlink 特定的数据结构
  • 如果失败,释放之前创建的 socket 并返回 NULL

设置回调函数

sk = sock->sk;
sk->sk_data_ready = netlink_data_ready;
if (input)nlk_sk(sk)->data_ready = input;
  • sk = sock->sk: 获取 socket 的核心结构
  • sk->sk_data_ready = netlink_data_ready: 设置通用的数据就绪回调
  • 如果提供了自定义 input 回调,设置到 netlink 特定结构中

插入 Netlink

if (netlink_insert(sk, 0)) {sock_release(sock);return NULL;
}
  • netlink_insert(): 将 socket 插入全局 netlink
  • 第二个参数 0 表示协议号
  • 如果插入失败,释放 socket 并返回 NULL

成功返回

return sk;
  • 返回创建的 socket 核心结构指针

函数功能总结

kobject_uevent_init

主要功能:初始化 kobject uevent 子系统,创建用于发送 ueventnetlink socket

关键作用

  • 为内核对象事件通知建立通信通道
  • 初始化全局变量 uevent_sock,供后续 send_uevent 函数使用

netlink_kernel_create

主要功能:创建内核态的 netlink socket,用于内核与用户空间的通信

关键特性

  • 创建的是内核端 socket,不同于用户空间的 socket
  • 支持设置数据到达回调函数(input 参数)
  • 管理 netlink 协议族的内部数据结构
  • 维护全局的 netlink socket

通信机制

  • 使用 NETLINK_KOBJECT_UEVENT 协议类型(值为 15)
  • 基于数据报(SOCK_DGRAM)的通信方式
  • 支持广播机制,允许一个内核事件被多个用户空间进程接收

初始化 Netlink socket netlink_create

static int netlink_create(struct socket *sock, int protocol)
{struct sock *sk;struct netlink_opt *nlk;sock->state = SS_UNCONNECTED;if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)return -ESOCKTNOSUPPORT;if (protocol<0 || protocol >= MAX_LINKS)return -EPROTONOSUPPORT;sock->ops = &netlink_ops;sk = sk_alloc(PF_NETLINK, GFP_KERNEL, 1, NULL);if (!sk)return -ENOMEM;sock_init_data(sock,sk);sk_set_owner(sk, THIS_MODULE);nlk = sk->sk_protinfo = kmalloc(sizeof(*nlk), GFP_KERNEL);if (!nlk) {sk_free(sk);return -ENOMEM;}memset(nlk, 0, sizeof(*nlk));spin_lock_init(&nlk->cb_lock);init_waitqueue_head(&nlk->wait);sk->sk_destruct = netlink_sock_destruct;sk->sk_protocol = protocol;return 0;
}

代码逐行解释

函数声明和参数

static int netlink_create(struct socket *sock, int protocol)
  • sock: 已经创建的 socket 结构指针
  • protocol: Netlink 协议类型
  • 返回整型状态码(0表示成功,负值表示错误)

变量声明

struct sock *sk;
struct netlink_opt *nlk;
  • sk: socket 的核心网络结构
  • nlk: Netlink 特定的选项和状态结构

Socket 状态初始化

sock->state = SS_UNCONNECTED;

将 socket 状态设置为未连接状态,因为 Netlink socket 通常是无连接的

Socket 类型检查

if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)return -ESOCKTNOSUPPORT;

检查 socket 类型是否支持:

  • SOCK_RAW: 原始套接字
  • SOCK_DGRAM: 数据报套接字
  • 如果不支持这两种类型,返回"socket 类型不支持"错误

协议范围检查

if (protocol<0 || protocol >= MAX_LINKS)return -EPROTONOSUPPORT;

验证协议号是否在有效范围内:

  • protocol 必须大于等于 0 且小于 MAX_LINKS
  • 如果超出范围,返回"协议不支持"错误

设置 Socket 操作

sock->ops = &netlink_ops;

设置 socket 的操作函数表为 netlink_ops,这定义了 Netlink socket 的各种操作(绑定、发送、接收等)

分配 Socket 核心结构

sk = sk_alloc(PF_NETLINK, GFP_KERNEL, 1, NULL);
if (!sk)return -ENOMEM;
  • sk_alloc(): 分配 struct sock 内存
  • PF_NETLINK: 协议族标识
  • GFP_KERNEL: 内存分配标志(可睡眠)
  • 1: 把struct sock结构体清零
  • 如果分配失败,返回内存不足错误

初始化 Socket 数据

sock_init_data(sock, sk);

初始化 socket 和 sock 结构之间的关联关系,设置各种初始状态和回调函数

设置模块所有者

sk_set_owner(sk, THIS_MODULE);

设置 sock 结构的所有者为当前内核模块,用于模块引用计数管理。

分配 Netlink 特定结构

nlk = sk->sk_protinfo = kmalloc(sizeof(*nlk), GFP_KERNEL);
if (!nlk) {sk_free(sk);return -ENOMEM;
}
  • Netlink 特定选项分配内存
  • sk->sk_protinfo: 指向协议特定数据的指针
  • 如果分配失败,释放之前分配的 sock 结构并返回内存不足错误

初始化 Netlink 结构

memset(nlk, 0, sizeof(*nlk));

Netlink 结构清零,确保所有字段初始化为 0。

初始化自旋锁

spin_lock_init(&nlk->cb_lock);

初始化回调锁,用于保护 Netlink 回调相关的数据结构的并发访问

初始化等待队列

init_waitqueue_head(&nlk->wait);

初始化等待队列头,用于进程在 Netlink socket 上等待数据时的睡眠/唤醒机制

设置 Socket 析构函数

sk->sk_destruct = netlink_sock_destruct;

设置 socket 的析构函数,当 socket 被释放时会调用此函数进行清理工作。

设置协议类型

sk->sk_protocol = protocol;

将协议类型保存到 sock 结构中

成功返回

return 0;

返回 0 表示 Netlink socket 创建成功

函数功能总结

主要功能:初始化 Netlink socket 的协议特定数据结构和状态

关键作用

  1. 参数验证

    • 验证 socket 类型兼容性
    • 检查协议号有效性
  2. 内存管理

    • 分配 sock 核心结构
    • 分配 Netlink 特定选项结构
    • 设置适当的析构函数
  3. 并发控制

    • 初始化自旋锁保护回调数据
    • 初始化等待队列管理阻塞操作
  4. 协议设置

    • 设置 Netlink 特定的操作函数表
    • 保存协议类型标识
  5. 模块管理

    • 设置模块所有者关系
    • 确保正确的引用计数管理

Netlink socket插入到全局哈希表netlink_insert

static int netlink_insert(struct sock *sk, u32 pid)
{struct nl_pid_hash *hash = &nl_table[sk->sk_protocol].hash;struct hlist_head *head;int err = -EADDRINUSE;struct sock *osk;struct hlist_node *node;int len;netlink_table_grab();head = nl_pid_hashfn(hash, pid);len = 0;sk_for_each(osk, node, head) {if (nlk_sk(osk)->pid == pid)break;len++;}if (node)goto err;err = -EBUSY;if (nlk_sk(sk)->pid)goto err;err = -ENOMEM;if (BITS_PER_LONG > 32 && unlikely(hash->entries >= UINT_MAX))goto err;if (len && nl_pid_hash_dilute(hash, len))head = nl_pid_hashfn(hash, pid);hash->entries++;nlk_sk(sk)->pid = pid;sk_add_node(sk, head);err = 0;err:netlink_table_ungrab();return err;
}

代码逐行解释

函数声明和参数

static int netlink_insert(struct sock *sk, u32 pid)
  • sk: 要插入的 socket 结构指针
  • pid: 进程ID(在Netlink中用作地址标识)
  • 返回整型状态码(0表示成功,负值表示错误)

变量声明

struct nl_pid_hash *hash = &nl_table[sk->sk_protocol].hash;
struct hlist_head *head;
int err = -EADDRINUSE;
struct sock *osk;
struct hlist_node *node;
int len;
  • hash: 获取对应协议号的Netlink哈希表
  • head: 哈希桶头指针
  • err: 错误码,初始化为地址已使用
  • osk: 用于遍历的临时socket指针
  • node: 哈希链表节点
  • len: 哈希桶链表的长度

获取表锁

netlink_table_grab();

获取Netlink表的自旋锁,保护对全局Netlink数据结构的并发访问

计算哈希桶位置

head = nl_pid_hashfn(hash, pid);

根据pid计算在哈希表中的桶位置,nl_pid_hashfn是哈希函数

初始化长度计数

len = 0;

初始化链表长度计数器为0

遍历哈希桶查找冲突

sk_for_each(osk, node, head) {if (nlk_sk(osk)->pid == pid)break;len++;
}

遍历哈希桶中的socket链表:

  • sk_for_each: 宏定义,遍历链表中的每个socket
  • 检查每个socket的pid是否与要插入的pid冲突
  • 如果找到相同pid,跳出循环
  • 同时统计链表长度

检查pid冲突

if (node)goto err;

如果node不为NULL,说明在链表中找到了相同pid的socket,存在冲突,跳转到错误处理

检查socket是否已分配pid

err = -EBUSY;
if (nlk_sk(sk)->pid)goto err;
  • 将错误码改为"设备忙"
  • 检查要插入的socket是否已经分配了pid
  • 如果已经分配了pid,跳转到错误处理

检查哈希表条目数限制

err = -ENOMEM;
if (BITS_PER_LONG > 32 && unlikely(hash->entries >= UINT_MAX))goto err;
  • 将错误码改为"内存不足"
  • 在64位系统上检查哈希表条目数是否超过32位无符号整型最大值
  • 如果超过限制,跳转到错误处理

哈希表稀释检查

if (len && nl_pid_hash_dilute(hash, len))head = nl_pid_hashfn(hash, pid);
  • 如果当前桶的链表不为空且需要稀释(链表过长)
  • 调用nl_pid_hash_dilute检查是否需要重新哈希
  • 如果需要,重新计算哈希桶位置

更新哈希表统计

hash->entries++;

增加哈希表的条目计数器

设置socket的pid

nlk_sk(sk)->pid = pid;

pid分配给要插入的socket的Netlink特定数据

将socket插入链表

sk_add_node(sk, head);

将socket添加到哈希桶链表的头部

设置成功状态

err = 0;

将错误码设置为0,表示插入成功

错误处理标签

err:netlink_table_ungrab();return err;

释放Netlink表的自旋锁,并返回相应的错误码

函数功能总结

主要功能:将Netlink socket插入到全局哈希表中,管理socket的注册和寻址

  1. 并发安全

    • 使用自旋锁保护全局Netlink
    • 确保多CPU环境下的安全操作
  2. 冲突检测

    • 检查pid是否已被其他socket使用
    • 防止地址冲突
  3. 资源管理

    • 检查哈希表容量限制
    • 管理哈希表条目计数
  4. 性能优化

    • 实现哈希稀释机制防止链表过长
    • 动态重新哈希保持性能

向用户空间发送 uevent通知send_uevent

/*** send_uevent - notify userspace by sending event trough netlink socket** @signal: signal name* @obj: object path (kobject)* @envp: possible hotplug environment to pass with the message* @gfp_mask:*/
static int send_uevent(const char *signal, const char *obj,char **envp, int gfp_mask)
{struct sk_buff *skb;char *pos;int len;if (!uevent_sock)return -EIO;len = strlen(signal) + 1;len += strlen(obj) + 1;/* allocate buffer with the maximum possible message size */skb = alloc_skb(len + BUFFER_SIZE, gfp_mask);if (!skb)return -ENOMEM;pos = skb_put(skb, len);sprintf(pos, "%s@%s", signal, obj);/* copy the environment key by key to our continuous buffer */if (envp) {int i;for (i = 2; envp[i]; i++) {len = strlen(envp[i]) + 1;pos = skb_put(skb, len);strcpy(pos, envp[i]);}}return netlink_broadcast(uevent_sock, skb, 0, 1, gfp_mask);
}

函数功能概述

这个函数用于通过 netlink socket 向用户空间发送 uevent(用户事件)通知,通常用于内核对象的热插拔事件通知

代码详细解释

函数声明和参数

static int send_uevent(const char *signal, const char *obj,char **envp, int gfp_mask)
  • signal: 事件信号名称(如 “add”, “remove”, “change”)
  • obj: 内核对象路径
  • envp: 环境变量数组,包含要传递的附加信息
  • gfp_mask: 内存分配标志

变量声明

struct sk_buff *skb;
char *pos;
int len;
  • skb: socket buffer 指针,用于存储要发送的网络数据
  • pos: 指向 skb 数据区当前写入位置的指针
  • len: 用于计算各种字符串长度

初始检查

if (!uevent_sock)return -EIO;

检查全局的 uevent netlink socket 是否已初始化。如果没有,返回 IO 错误

计算基本长度

len = strlen(signal) + 1;
len += strlen(obj) + 1;

计算信号名称和对象路径的总长度,每个字符串都包含终止符 \0

分配 SKB 缓冲区

skb = alloc_skb(len + BUFFER_SIZE, gfp_mask);
if (!skb)return -ENOMEM;

分配 socket buffer:

  • len + BUFFER_SIZE: 为环境变量预留额外空间
  • gfp_mask: 控制内存分配行为(如是否可睡眠)
  • 如果分配失败,返回内存不足错误

构建基本消息

pos = skb_put(skb, len);
sprintf(pos, "%s@%s", signal, obj);
  • skb_put(): 在 skb 尾部扩展指定长度的空间,返回写入位置
  • sprintf(): 将信号和对象路径格式化为 signal@obj 的形式

添加环境变量

if (envp) {int i;for (i = 2; envp[i]; i++) {len = strlen(envp[i]) + 1;pos = skb_put(skb, len);strcpy(pos, envp[i]);}
}
  • envp[2] 开始遍历环境变量数组(跳过前两个元素)
  • 对每个环境变量字符串:
    • 计算长度(包含终止符)
    • 扩展 skb 空间
    • 复制字符串到 skb

广播消息

return netlink_broadcast(uevent_sock, skb, 0, 1, gfp_mask);

通过 netlink socket 广播消息:

  • uevent_sock: 发送用的 netlink socket
  • skb: 包含消息数据的 socket buffer
  • 0: 目标组(0 表示所有组)
  • 1: 分配失败时是否克隆 skb
  • gfp_mask: 内存分配标志

函数功能总结

主要功能:通过 netlink 机制向用户空间发送内核对象事件通知

典型应用场景

  • 设备热插拔(device add/remove)
  • 模块加载/卸载
  • 文件系统挂载/卸载
  • 电源管理事件

消息格式

signal@obj
env_var1=value1
env_var2=value2
...

向多个Netlink socket广播消息netlink_broadcast

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid,u32 group, int allocation)
{struct netlink_broadcast_data info;struct hlist_node *node;struct sock *sk;info.exclude_sk = ssk;info.pid = pid;info.group = group;info.failure = 0;info.congested = 0;info.delivered = 0;info.allocation = allocation;info.skb = skb;info.skb2 = NULL;netlink_trim(skb, allocation);/* While we sleep in clone, do not allow to change socket list */netlink_lock_table();sk_for_each_bound(sk, node, &nl_table[ssk->sk_protocol].mc_list)do_one_broadcast(sk, &info);netlink_unlock_table();if (info.skb2)kfree_skb(info.skb2);kfree_skb(skb);if (info.delivered) {if (info.congested && (allocation & __GFP_WAIT))yield();return 0;}if (info.failure)return -ENOBUFS;return -ESRCH;
}

代码逐行解释

函数声明和参数

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid,u32 group, int allocation)
  • ssk: 发送源socket(通常为内核socket)
  • skb: 要广播的socket缓冲区
  • pid: 目标进程ID(0表示广播给所有)
  • group: 目标组ID
  • allocation: 内存分配标志
  • 返回整型状态码

变量声明和初始化广播数据结构

struct netlink_broadcast_data info;
struct hlist_node *node;
struct sock *sk;info.exclude_sk = ssk;
info.pid = pid;
info.group = group;
info.failure = 0;
info.congested = 0;
info.delivered = 0;
info.allocation = allocation;
info.skb = skb;
info.skb2 = NULL;
  • info: 广播过程中使用的状态数据结构
  • node, sk: 用于遍历socket列表
  • 初始化info结构的所有字段:
    • exclude_sk: 排除的socket(不向自己发送)
    • pid, group: 目标过滤条件
    • failure, congested, delivered: 状态计数器
    • allocation: 内存分配标志
    • skb: 原始数据包
    • skb2: 克隆的数据包

调整SKB大小

netlink_trim(skb, allocation);

根据内存分配标志调整skb的大小,可能释放不必要的尾部空间

锁定Netlink

netlink_lock_table();

获取Netlink表的读写锁,防止在广播过程中socket列表发生变化

遍历多播socket列表并广播

sk_for_each_bound(sk, node, &nl_table[ssk->sk_protocol].mc_list)do_one_broadcast(sk, &info);
  • sk_for_each_bound: 宏定义,遍历绑定到指定协议的多播socket列表
  • nl_table[ssk->sk_protocol].mc_list: 获取对应协议的多播socket链表
  • 对每个socket调用do_one_broadcast函数发送数据

释放表锁

netlink_unlock_table();

释放Netlink表的读写锁,允许其他操作修改socket列表

清理克隆的SKB

if (info.skb2)kfree_skb(info.skb2);

如果创建了克隆的skb(用于重试等情况),释放它

释放原始SKB

kfree_skb(skb);

释放原始的socket缓冲区

处理成功投递情况

if (info.delivered) {if (info.congested && (allocation & __GFP_WAIT))yield();return 0;
}
  • 如果有数据成功投递:
    • 检查是否有拥塞且允许等待,如果有则让出CPU
    • 返回0表示成功

处理失败情况

if (info.failure)return -ENOBUFS;
return -ESRCH;
  • 如果有发送失败,返回"无缓冲区空间"错误
  • 如果没有找到任何接收者,返回"无此进程"错误

函数功能总结

主要功能:向多个Netlink socket广播消息,实现一对多的通信模式

关键特性

  1. 多播机制

    • 遍历协议的多播socket列表
    • 向所有符合条件的socket发送消息
  2. 过滤条件

    • pid: 可以指定特定进程
    • group: 可以指定组播组
    • exclude_sk: 排除发送者自身
  3. 状态跟踪

    • 记录成功投递数量
    • 检测拥塞情况
    • 统计失败次数

向单个Netlink socket投递广播消息do_one_broadcast

static inline int do_one_broadcast(struct sock *sk,struct netlink_broadcast_data *p)
{struct netlink_opt *nlk = nlk_sk(sk);int val;if (p->exclude_sk == sk)goto out;if (nlk->pid == p->pid || !(nlk->groups & p->group))goto out;if (p->failure) {netlink_overrun(sk);goto out;}sock_hold(sk);if (p->skb2 == NULL) {if (atomic_read(&p->skb->users) != 1) {p->skb2 = skb_clone(p->skb, p->allocation);} else {p->skb2 = p->skb;atomic_inc(&p->skb->users);}}if (p->skb2 == NULL) {netlink_overrun(sk);/* Clone failed. Notify ALL listeners. */p->failure = 1;} else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0) {netlink_overrun(sk);} else {p->congested |= val;p->delivered = 1;p->skb2 = NULL;}sock_put(sk);out:return 0;
}

代码逐行解释

函数声明

static inline int do_one_broadcast(struct sock *sk,struct netlink_broadcast_data *p)
  • static inline: 内联函数,减少函数调用开销
  • sk: 目标socket
  • p: 广播数据状态结构指针
  • 返回整型(总是返回0)

获取Netlink选项

struct netlink_opt *nlk = nlk_sk(sk);

从socket中获取Netlink特定的选项结构,包含pid、groups等信息

排除自身检查

if (p->exclude_sk == sk)goto out;

如果当前socket是排除的socket(即发送者自身),跳过处理

目标过滤检查

if (nlk->pid == p->pid || !(nlk->groups & p->group))goto out;

检查是否匹配目标条件:

  • nlk->pid == p->pid: 如果指定了特定pid且匹配
  • !(nlk->groups & p->group): 如果socket不在目标组中
    满足任一条件则跳过处理

失败状态检查

if (p->failure) {netlink_overrun(sk);goto out;
}

如果之前已经发生过失败(如内存不足),标记当前socket为溢出状态并跳过

增加socket引用计数

sock_hold(sk);

增加socket的引用计数,防止在发送过程中socket被释放

准备发送的SKB

if (p->skb2 == NULL) {if (atomic_read(&p->skb->users) != 1) {p->skb2 = skb_clone(p->skb, p->allocation);} else {p->skb2 = p->skb;atomic_inc(&p->skb->users);}
}

处理SKB的共享:

  • 如果还没有克隆的SKB:
    • 如果原始SKB的引用计数不为1(被多个使用者共享),创建克隆
    • 否则直接使用原始SKB,但增加其引用计数

克隆失败处理

if (p->skb2 == NULL) {netlink_overrun(sk);/* Clone failed. Notify ALL listeners. */p->failure = 1;
}

如果SKB克隆失败:

  • 标记当前socket溢出
  • 设置全局失败标志,后续所有socket都会跳过

尝试投递消息

else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0) {netlink_overrun(sk);
}

尝试投递消息到socket:

  • 如果返回负值(投递失败),标记socket溢出

投递成功处理

else {p->congested |= val;p->delivered = 1;p->skb2 = NULL;
}

投递成功:

  • p->congested |= val: 记录拥塞状态(val可能表示是否拥塞)
  • p->delivered = 1: 标记至少有一个消息成功投递
  • p->skb2 = NULL: 重置克隆SKB指针,下一个socket需要重新克隆

减少socket引用计数

sock_put(sk);

减少socket的引用计数,与之前的sock_hold对应

退出标签

out:return 0;

所有退出路径都返回0

函数功能总结

主要功能:向单个Netlink socket投递广播消息,处理各种边界条件和错误情况

  1. 过滤机制

    • 排除发送者自身
    • 基于pid和group的目标过滤
    • 提前跳过不匹配的socket
  2. 内存管理

    • SKB引用计数管理
    • 智能克隆策略(仅在需要时克隆)
    • socket引用计数保护
  3. 错误处理

    • 克隆失败时的全局失败标记
    • 单个socket溢出不影响其他socket
    • 拥塞状态记录
  4. 状态跟踪

    • 记录成功投递状态
    • 跟踪拥塞情况
    • 管理SKB共享状态

SKB共享策略

  • 如果原始SKB只有一个使用者,直接共享使用
  • 如果多个使用者需要,创建克隆
  • 避免不必要的内存拷贝
http://www.dtcms.com/a/527982.html

相关文章:

  • Python设计模式实战:用Pythonic的方式实现单例、工厂模式
  • 智能规模效应:解读ChatGPT Atlas背后的数据边界之战
  • 网站建设雨点国家防疫政策最新
  • RabbitMQ Unacked 消息深度解析:机制、问题与解决方案
  • LabVIEW超高温高压流变仪开发
  • 理解面向问题域的需求分析(PDOA)方法
  • 肥东住房和城乡建设部网站WordPress国外赚钱
  • Dify从入门到精通 第31天 在 Dify 中构建智能天气查询机器人
  • 【机器人】RViz中LaserScan的参数信息说明
  • QXlsx操作Excel深度解析:核心类接口与 Qt C++ 功能解析
  • 今日Reddit AI高价值讨论分析 10.25
  • 福州百度网站快速优化郑州新闻最新消息今天
  • AI云“分野”:阿里云们“卖铲”,火山引擎奇袭“MaaS”
  • 阿里云渠道商:服务器操作系统怎么选?
  • 阿里云代理商:怎么通过ACL实现网络分层安全?
  • Go语言实现的简易远程传屏工具:让你的屏幕「飞」起来
  • 哪些网站做翻译可以赚钱织梦网站更改标题长度
  • 阮一峰《TypeScript 教程》学习笔记——declare关键字
  • Flutter 异步编程:Future 与 Stream 深度解析
  • 代码训练LeetCode(48)字母异位词分组
  • 每日算法刷题Day79:10.25:leetcode 一般树5道题,用时1h30min
  • 数据分析核心术语略解
  • 南宁网站设计和开发大赛诚信通开了网站谁给做
  • MATLAB基于云模型时间序列预测
  • 【成长纪实】HarmonyOS Next学习地图:新手避坑指南与核心知识点拆解
  • wordpress不适合大型网站网站建设对宣传的意义
  • 大良营销网站建设教程写网站建设需求文档
  • CICD实战(13) - 使用Arbess+GitLab实现.Net core项目自动化部署
  • KingbaseES赋能多院区医院信创转型:浙江省人民医院异构多活数据底座实践解析
  • 微硕WSF2N65 650V N沟MOSFET:汽车PTC辅助加热器“高压启动核”