内核连接器¶
内核连接器 - 基于 netlink 的新的用户空间 <-> 内核空间易于使用的通信模块。
连接器驱动程序使得使用基于 netlink 的网络连接各种代理变得容易。 必须注册一个回调和一个标识符。当驱动程序收到带有适当标识符的特殊 netlink 消息时,将调用适当的回调。
从用户空间的角度来看,它非常简单
socket();
bind();
send();
recv();
但是,如果内核空间想要使用这种连接的全部功能,驱动程序编写者必须创建特殊的套接字,必须了解 struct sk_buff
处理等等... 连接器驱动程序允许任何内核空间代理以更简单的方式使用基于 netlink 的网络进行进程间通信
int cn_add_callback(const struct cb_id *id, char *name, void (*callback) (struct cn_msg *, struct netlink_skb_parms *));
void cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group, int gfp_mask);
void cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, int gfp_mask);
struct cb_id
{
__u32 idx;
__u32 val;
};
idx 和 val 是必须在 connector.h 头文件中注册的唯一标识符,以便在内核中使用。void (*callback) (void *) 是一个回调函数,当连接器核心收到带有以上 idx.val 的消息时将调用该函数。该函数的参数必须被解引用为 struct cn_msg *
struct cn_msg
{
struct cb_id id;
__u32 seq;
__u32 ack;
__u16 len; /* Length of the following data */
__u16 flags;
__u8 data[0];
};
连接器接口¶
- int cn_add_callback(const struct cb_id *id, const char *name, void (*callback)(struct cn_msg*, struct netlink_skb_parms*))¶
向连接器核心注册新的回调。
参数
const struct cb_id *id
连接器的唯一用户标识符。它必须在 connector.h 中注册才能成为合法的内核用户。
const char *name
连接器的回调符号名称。
void (*callback)(struct cn_msg *, struct netlink_skb_parms *)
连接器的回调。参数是
cn_msg
和发送者的凭据
- void cn_del_callback(const struct cb_id *id)¶
从连接器核心取消注册新的回调。
参数
const struct cb_id *id
连接器的唯一用户标识符。
- int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 group, gfp_t gfp_mask, netlink_filter_fn filter, void *filter_data)¶
将消息发送到指定的组。
参数
struct cn_msg *msg
消息头(带有附加数据)。
u16 len
要发送的 msg 的大小。
u32 portid
目标端口。如果非零,消息将被发送到给定的端口,该端口应设置为原始发送者。
u32 group
目标组。如果 portid 和 group 都为零,那么将通过所有注册的连接器用户搜索相应的组,并且消息将传递到为与 msg 中具有相同 ID 的用户创建的组。如果 group 不为零,那么消息将传递到指定的组。
gfp_t gfp_mask
GFP 掩码。
netlink_filter_fn filter
要在 netlink 层使用的过滤器函数。
void *filter_data
要提供给过滤器函数的过滤器数据
描述
可以安全地从软中断上下文调用,但在强内存压力下可能会静默失败。
如果没有给定组的侦听器,则可能返回
-ESRCH
。
- int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 group, gfp_t gfp_mask)¶
将消息发送到指定的组。
参数
struct cn_msg *msg
消息头(带有附加数据)。
u32 portid
目标端口。如果非零,消息将被发送到给定的端口,该端口应设置为原始发送者。
u32 group
目标组。如果 portid 和 group 都为零,那么将通过所有注册的连接器用户搜索相应的组,并且消息将传递到为与 msg 中具有相同 ID 的用户创建的组。如果 group 不为零,那么消息将传递到指定的组。
gfp_t gfp_mask
GFP 掩码。
描述
可以安全地从软中断上下文调用,但在强内存压力下可能会静默失败。
如果没有给定组的侦听器,则可能返回
-ESRCH
。
- 注意
注册新的回调用户时,连接器核心会为用户分配一个等于其 id.idx 的 netlink 组。
协议描述¶
当前框架提供了一个带有固定头的传输层。推荐的协议使用如下头部
msg->seq 和 msg->ack 用于确定消息的谱系。当有人发送消息时,他们使用本地唯一的序列号和随机的确认号。序列号也可以复制到 nlmsghdr->nlmsg_seq 中。
序列号随着发送的每条消息递增。
如果您希望收到对消息的回复,那么接收到的消息中的序列号必须与原始消息中的序列号相同,并且确认号必须相同 + 1。
如果我们收到一条消息,并且其序列号不等于我们期望的序列号,那么它是一条新消息。如果我们收到一条消息,并且其序列号与我们期望的序列号相同,但其确认号不等于原始消息中的序列号 + 1,那么它是一条新消息。
显然,协议头包含以上 id。
连接器允许以下形式的事件通知:内核驱动程序或用户空间进程可以请求连接器在选定的 id 打开或关闭时(注册或取消注册其回调时)通知它。这是通过向连接器驱动程序发送特殊命令来完成的(它也使用 id={-1, -1} 注册自己)。
可以在 cn_test.c 模块中找到此用法的示例,该模块使用连接器来请求通知和发送消息。
可靠性¶
Netlink 本身不是可靠的协议。这意味着消息可能会由于内存压力或进程接收队列溢出而丢失,因此警告调用者必须做好准备。这就是为什么 struct cn_msg [主连接器的消息头] 包含 u32 seq 和 u32 ack 字段的原因。
用户空间的使用¶
2.6.14 有一个新的 netlink 套接字实现,默认情况下,它不允许人们向除 1 之外的 netlink 组发送数据。因此,如果您希望使用具有不同组号的 netlink 套接字(例如使用连接器),则用户空间应用程序必须首先订阅该组。这可以通过以下伪代码实现
s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
l_local.nl_family = AF_NETLINK;
l_local.nl_groups = 12345;
l_local.nl_pid = 0;
if (bind(s, (struct sockaddr *)&l_local, sizeof(struct sockaddr_nl)) == -1) {
perror("bind");
close(s);
return -1;
}
{
int on = l_local.nl_groups;
setsockopt(s, 270, 1, &on, sizeof(on));
}
其中,上面的 270 是 SOL_NETLINK,而 1 是 NETLINK_ADD_MEMBERSHIP 套接字选项。要删除多播订阅,应该使用定义为 0 的 NETLINK_DROP_MEMBERSHIP 参数调用上述套接字选项。
2.6.14 netlink 代码只允许选择小于或等于最大组号的组,该最大组号在 netlink_kernel_create() 时使用。在连接器的情况下,它是 CN_NETLINK_USERS + 0xf,因此如果您想使用组号 12345,则必须将 CN_NETLINK_USERS 递增到该数字。另外 0xf 个数字被分配给非内核用户使用。
由于此限制,组 0xffffffff 现在不起作用,因此不能使用添加/删除连接器的组通知,但据我所知,只有 cn_test.c 测试模块使用了它。
netlink 领域的一些工作仍在进行中,因此如果发生这种情况,事情可能会在 2.6.15 时间范围内发生变化,届时将更新该内核的文档。
代码示例¶
连接器测试模块和用户空间的代码示例可以在 samples/connector/ 中找到。要构建此代码,请启用 CONFIG_CONNECTOR 和 CONFIG_SAMPLES。