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

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

核心组件总结

1. 初始化阶段 (kobject_uevent_init)

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

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

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

关键步骤:

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

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

核心机制:

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

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

初始化 kobject uevent 子系统kobject_uevent_init

c 复制代码
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 函数

函数声明

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

创建 Netlink Socket

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

错误检查

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

成功返回

c 复制代码
return 0;
  • 成功创建 socket,返回 0 表示初始化成功

函数声明和参数

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

变量声明

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

初始检查

c 复制代码
if (!nl_table)
    return NULL;

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

创建轻量级 Socket

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

创建 Netlink 特定结构

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

设置回调函数

c 复制代码
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

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

成功返回

c 复制代码
return sk;
  • 返回创建的 socket 核心结构指针

函数功能总结

kobject_uevent_init

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

关键作用

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

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

关键特性

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

通信机制

  • 使用 NETLINK_KOBJECT_UEVENT 协议类型(值为 15)
  • 基于数据报(SOCK_DGRAM)的通信方式
  • 支持广播机制,允许一个内核事件被多个用户空间进程接收
c 复制代码
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;
}

代码逐行解释

函数声明和参数

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

变量声明

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

Socket 状态初始化

c 复制代码
sock->state = SS_UNCONNECTED;

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

Socket 类型检查

c 复制代码
if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
    return -ESOCKTNOSUPPORT;

检查 socket 类型是否支持:

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

协议范围检查

c 复制代码
if (protocol<0 || protocol >= MAX_LINKS)
    return -EPROTONOSUPPORT;

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

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

设置 Socket 操作

c 复制代码
sock->ops = &netlink_ops;

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

分配 Socket 核心结构

c 复制代码
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 数据

c 复制代码
sock_init_data(sock, sk);

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

设置模块所有者

c 复制代码
sk_set_owner(sk, THIS_MODULE);

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

分配 Netlink 特定结构

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

初始化 Netlink 结构

c 复制代码
memset(nlk, 0, sizeof(*nlk));

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

初始化自旋锁

c 复制代码
spin_lock_init(&nlk->cb_lock);

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

初始化等待队列

c 复制代码
init_waitqueue_head(&nlk->wait);

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

设置 Socket 析构函数

c 复制代码
sk->sk_destruct = netlink_sock_destruct;

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

设置协议类型

c 复制代码
sk->sk_protocol = protocol;

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

成功返回

c 复制代码
return 0;

返回 0 表示 Netlink socket 创建成功

函数功能总结

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

关键作用

  1. 参数验证

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

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

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

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

    • 设置模块所有者关系
    • 确保正确的引用计数管理
c 复制代码
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;
}

代码逐行解释

函数声明和参数

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

变量声明

c 复制代码
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: 哈希桶链表的长度

获取表锁

c 复制代码
netlink_table_grab();

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

计算哈希桶位置

c 复制代码
head = nl_pid_hashfn(hash, pid);

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

初始化长度计数

c 复制代码
len = 0;

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

遍历哈希桶查找冲突

c 复制代码
sk_for_each(osk, node, head) {
    if (nlk_sk(osk)->pid == pid)
        break;
    len++;
}

遍历哈希桶中的socket链表:

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

检查pid冲突

c 复制代码
if (node)
    goto err;

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

检查socket是否已分配pid

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

检查哈希表条目数限制

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

哈希表稀释检查

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

更新哈希表统计

c 复制代码
hash->entries++;

增加哈希表的条目计数器

设置socket的pid

c 复制代码
nlk_sk(sk)->pid = pid;

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

将socket插入链表

c 复制代码
sk_add_node(sk, head);

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

设置成功状态

c 复制代码
err = 0;

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

错误处理标签

c 复制代码
err:
    netlink_table_ungrab();
    return err;

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

函数功能总结

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

  1. 并发安全

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

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

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

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

向用户空间发送 uevent通知send_uevent

c 复制代码
/**
 * 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(用户事件)通知,通常用于内核对象的热插拔事件通知

代码详细解释

函数声明和参数

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

变量声明

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

初始检查

c 复制代码
if (!uevent_sock)
    return -EIO;

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

计算基本长度

c 复制代码
len = strlen(signal) + 1;
len += strlen(obj) + 1;

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

分配 SKB 缓冲区

c 复制代码
skb = alloc_skb(len + BUFFER_SIZE, gfp_mask);
if (!skb)
    return -ENOMEM;

分配 socket buffer:

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

构建基本消息

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

添加环境变量

c 复制代码
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

广播消息

c 复制代码
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
...
c 复制代码
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;
}

代码逐行解释

函数声明和参数

c 复制代码
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: 内存分配标志
  • 返回整型状态码

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

c 复制代码
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大小

c 复制代码
netlink_trim(skb, allocation);

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

锁定Netlink

c 复制代码
netlink_lock_table();

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

遍历多播socket列表并广播

c 复制代码
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函数发送数据

释放表锁

c 复制代码
netlink_unlock_table();

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

清理克隆的SKB

c 复制代码
if (info.skb2)
    kfree_skb(info.skb2);

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

释放原始SKB

c 复制代码
kfree_skb(skb);

释放原始的socket缓冲区

处理成功投递情况

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

处理失败情况

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

函数功能总结

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

关键特性

  1. 多播机制

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

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

    • 记录成功投递数量
    • 检测拥塞情况
    • 统计失败次数
c 复制代码
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;
}

代码逐行解释

函数声明

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

获取Netlink选项

c 复制代码
struct netlink_opt *nlk = nlk_sk(sk);

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

排除自身检查

c 复制代码
if (p->exclude_sk == sk)
    goto out;

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

目标过滤检查

c 复制代码
if (nlk->pid == p->pid || !(nlk->groups & p->group))
    goto out;

检查是否匹配目标条件:

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

失败状态检查

c 复制代码
if (p->failure) {
    netlink_overrun(sk);
    goto out;
}

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

增加socket引用计数

c 复制代码
sock_hold(sk);

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

准备发送的SKB

c 复制代码
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,但增加其引用计数

克隆失败处理

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

如果SKB克隆失败:

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

尝试投递消息

c 复制代码
else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0) {
    netlink_overrun(sk);
}

尝试投递消息到socket:

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

投递成功处理

c 复制代码
else {
    p->congested |= val;
    p->delivered = 1;
    p->skb2 = NULL;
}

投递成功:

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

减少socket引用计数

c 复制代码
sock_put(sk);

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

退出标签

c 复制代码
out:
    return 0;

所有退出路径都返回0

函数功能总结

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

  1. 过滤机制

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

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

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

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

SKB共享策略

  • 如果原始SKB只有一个使用者,直接共享使用
  • 如果多个使用者需要,创建克隆
  • 避免不必要的内存拷贝
相关推荐
belldeep3 小时前
网络安全:FOFA , Hunter 是什么?
网络·安全·fofa·hunter
autism_cx3 小时前
TCP/IP协议栈
服务器·网络·笔记·网络协议·tcp/ip·ios·osi
汪汪大队u3 小时前
CSMA/CA 协议和CSMA/CD的区别
网络
艾莉丝努力练剑3 小时前
【C++:继承】C++面向对象继承全面解析:派生类构造、多继承、菱形虚拟继承与设计模式实践
linux·开发语言·c++·人工智能·stl·1024程序员节
报错小能手4 小时前
项目——基于C/S架构的预约系统平台(3)
linux·开发语言·笔记·学习·架构·1024程序员节
星空的资源小屋4 小时前
Tuesday JS,一款可视化小说编辑器
运维·网络·人工智能·编辑器·电脑·excel
心寒丶4 小时前
Linux基础知识(三、Linux常见操作目录命令)
linux·运维·服务器·1024程序员节
ajassi20004 小时前
开源 Linux 服务器与中间件(十二)FRP内网穿透应用
linux·服务器·开源·frp
Bruce_Liuxiaowei4 小时前
[特殊字符] 排查VMnet1无IP的步骤
网络·网络协议·1024程序员节