内核连接器

内核连接器 - 一个基于 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

唯一的连接器用户标识符。

向指定的组发送消息。

参数

struct cn_msg *msg

消息头(附带数据)。

u16 len

要发送的 msg 数量。

u32 portid

目的端口。如果非零,消息将发送到给定端口,该端口应设置为原始发送方。

u32 group

目的组。如果 portidgroup 为零,则将在所有已注册的连接器用户中搜索适当的组,并将消息传递给为与 msg 中 ID 相同的用户创建的组。如果 group 不为零,则消息将传递到指定的组。

gfp_t gfp_mask

GFP 掩码。

netlink_filter_fn filter

在 netlink 层使用的过滤函数。

void *filter_data

提供给过滤函数的数据

描述

可以在软中断上下文安全调用,但在内存压力大时可能会静默失败。

如果给定组没有侦听器,可能会返回 -ESRCH

向指定的组发送消息。

参数

struct cn_msg *msg

消息头(附带数据)。

u32 portid

目的端口。如果非零,消息将发送到给定端口,该端口应设置为原始发送方。

u32 group

目的组。如果 portidgroup 为零,则将在所有已注册的连接器用户中搜索适当的组,并将消息传递给为与 msg 中 ID 相同的用户创建的组。如果 group 不为零,则消息将传递到指定的组。

gfp_t gfp_mask

GFP 掩码。

描述

可以在软中断上下文安全调用,但在内存压力大时可能会静默失败。

如果给定组没有侦听器,可能会返回 -ESRCH

注意

注册新的回调用户时,连接器核心会将一个 netlink 组分配给该用户,该组等于其 id.idx。

协议描述

当前框架提供了一个带有固定头部的传输层。使用此类头部的推荐协议如下

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。