内核连接器

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

注意

注册新的回调用户时,连接器核心会为用户分配一个等于其 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。