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_uevent → netlink_broadcast → do_one_broadcast)
功能: 实现高效可靠的消息广播
初始化 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 socketNETLINK_KOBJECT_UEVENT:Netlink协议类型,专门用于kobject ueventNULL: 输入回调函数,这里设为 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 表示初始化成功
netlink_kernel_create 函数
函数声明和参数
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: 协议族,表示使用NetlinkSOCK_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 子系统,创建用于发送 uevent 的 netlink 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
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 的协议特定数据结构和状态
关键作用:
-
参数验证:
- 验证 socket 类型兼容性
- 检查协议号有效性
-
内存管理:
- 分配 sock 核心结构
- 分配
Netlink特定选项结构 - 设置适当的析构函数
-
并发控制:
- 初始化自旋锁保护回调数据
- 初始化等待队列管理阻塞操作
-
协议设置:
- 设置
Netlink特定的操作函数表 - 保存协议类型标识
- 设置
-
模块管理:
- 设置模块所有者关系
- 确保正确的引用计数管理
将Netlink socket插入到全局哈希表netlink_insert
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的注册和寻址
-
并发安全:
- 使用自旋锁保护全局
Netlink表 - 确保多CPU环境下的安全操作
- 使用自旋锁保护全局
-
冲突检测:
- 检查
pid是否已被其他socket使用 - 防止地址冲突
- 检查
-
资源管理:
- 检查哈希表容量限制
- 管理哈希表条目计数
-
性能优化:
- 实现哈希稀释机制防止链表过长
- 动态重新哈希保持性能
向用户空间发送 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 socketskb: 包含消息数据的 socket buffer0: 目标组(0 表示所有组)1: 分配失败时是否克隆skbgfp_mask: 内存分配标志
函数功能总结
主要功能 :通过 netlink 机制向用户空间发送内核对象事件通知
典型应用场景:
- 设备热插拔(device add/remove)
- 模块加载/卸载
- 文件系统挂载/卸载
- 电源管理事件
消息格式:
signal@obj
env_var1=value1
env_var2=value2
...
向多个Netlink socket广播消息netlink_broadcast
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: 目标组IDallocation: 内存分配标志- 返回整型状态码
变量声明和初始化广播数据结构
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广播消息,实现一对多的通信模式
关键特性:
-
多播机制:
- 遍历协议的多播socket列表
- 向所有符合条件的socket发送消息
-
过滤条件:
pid: 可以指定特定进程group: 可以指定组播组exclude_sk: 排除发送者自身
-
状态跟踪:
- 记录成功投递数量
- 检测拥塞情况
- 统计失败次数
向单个Netlink socket投递广播消息do_one_broadcast
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: 目标socketp: 广播数据状态结构指针- 返回整型(总是返回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投递广播消息,处理各种边界条件和错误情况
-
过滤机制:
- 排除发送者自身
- 基于
pid和group的目标过滤 - 提前跳过不匹配的socket
-
内存管理:
- SKB引用计数管理
- 智能克隆策略(仅在需要时克隆)
- socket引用计数保护
-
错误处理:
- 克隆失败时的全局失败标记
- 单个socket溢出不影响其他socket
- 拥塞状态记录
-
状态跟踪:
- 记录成功投递状态
- 跟踪拥塞情况
- 管理SKB共享状态
SKB共享策略:
- 如果原始SKB只有一个使用者,直接共享使用
- 如果多个使用者需要,创建克隆
- 避免不必要的内存拷贝