Generic Netlink机制

本文详细介绍了Linux内核的Generic Netlink机制,包括genl_family、genl_ops和多播组的数据结构,以及Generic Netlink框架、消息处理、协议初始化、家庭管理和控制命令等方面。内容涵盖了从消息的封装、解析、发送到接收的全过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


系统中有已经有很多具体的Netlink协议了,不过它们都是针对某个子系统的,功能已经比较固定,如果我们需要扩展新的Netlink协议,那么就需要对内核进行侵入式修改(最起码要扩展MAX_LINKS),为此,Netlink框架还提供了一个通用的Netlink协议: Generic Netlink。通过它,内核模块可以在无需扩充协议的基础上使用Netlink机制。

这篇笔记分析了内核Generic Netlink核心部分的实现,官方参考资料见这里

文件说明
net/netlink/genetlink.cGeneric Netlink协议框架实现文件
include/net/genetlink.h仅内核态可见的Generic Netlink协议头文件
include/linux/genetlink.h用户态也可见的Generic Netlink协议头文件

数据结构

genl_family

/**
 * struct genl_family - generic netlink family
 * @id: protocol family idenfitier
 * @hdrsize: length of user specific header in bytes
 * @name: name of family
 * @version: protocol version
 * @maxattr: maximum number of attributes supported
 * @attrbuf: buffer to store parsed attributes
 * @ops_list: list of all assigned operations
 * @family_list: family list
 * @mcast_groups: multicast groups list
 */
struct genl_family
{
	unsigned int id;
	unsigned int hdrsize;
	char name[GENL_NAMSIZ]; // 每个family也拥有一个全局唯一的名字
	unsigned int version;
	unsigned int maxattr;
	struct nlattr ** attrbuf;	/* private */
	struct list_head ops_list;	/* private */ // 该family的命令操作集链表
	struct list_head family_list;	/* private */ // 将family对象保存在全局哈希表中
	struct list_head mcast_groups;	/* private */
};
  • id

每个family拥有一个全局唯一的ID,该ID可以由faimily的实现指定(比较难保证唯一性),也可由内核自动分配(推荐做法),可用范围为(GENL_MIN_ID(0x10), GENL_MAX_ID(1023)];

  • hdrsize

每个Generic Netlink消息首部除了标准的genlmsghdr外,family还可以追加自己的首部,只需要通过该字段指定自己追加的首部大小即可;

  • maxattr/attrbuf

maxattr字段记录了该faimily支持的最大属性个数,如果指定了属性个数,Generic Netlink框架会负责分配attrbuf,该区域用于消息接收过程中的属性解析,具体见genl_rcv_msg()。

genl_ops

genl_ops定义一个family支持的命令。如genl_family的定义,每个family可以支持多个命令,这些命令被组织到genl_family.ops_list中。

/**
 * struct genl_ops - generic netlink operations
 * @cmd: command identifier
 * @flags: flags
 * @policy: attribute validation policy
 * @doit: standard command callback
 * @dumpit: callback for dumpers
 * @done: completion callback for dumps
 * @ops_list: operations list
 */
struct genl_ops
{
	u8 cmd; // 命令ID,在family内部唯一标识命令
	unsigned int flags;
	const struct nla_policy	*policy; // 属性策略,nlmsg_parse()会根据该策略对属性格式做基本的校验
	int	(*doit)(struct sk_buff *skb, struct genl_info *info);
	int	(*dumpit)(struct sk_buff *skb, struct netlink_callback *cb);
	int	(*done)(struct netlink_callback *cb);
	struct list_head ops_list; // 将ops对象放入family->ops_list链表中
};
  • flags

取值来自标准的Netlink消息中的NLM_F_XXX,不过Generic Netlink只用了其中部分标记,这些标记会影响消息的接收流程。

flags含义
GENL_ADMIN_PERM0x1表示该消息的发送需要管理员权限
GENL_CMD_CAP_DO0x2表示该操作集指定了doit()回调
GENL_CMD_CAP_DUMP0x4表示该操作集指定了dumpit()回调
GENL_CMD_CAP_HASPOL0x8表示该操作集指定了属性策略

多播组: genl_multicast_group

/**
 * struct genl_multicast_group - generic netlink multicast group
 * @name: name of the multicast group, names are per-family
 * @id: multicast group ID, assigned by the core, to use with
 *      genlmsg_multicast().
 * @list: list entry for linking
 * @family: pointer to family, need not be set before registering
 */
struct genl_multicast_group
{
	struct genl_family	*family;	/* private */
	struct list_head	list;		/* private */
	char name[GENL_NAMSIZ]; // 多播组名字
	u32	id; // 多播组ID,在整个Generic Netlink层面唯一
};

/*
 * Bitmap of multicast groups that are currently in use.
 *
 * To avoid an allocation at boot of just one unsigned long,
 * declare it global instead.
 * Bit 0 is marked as already used since group 0 is invalid.
 */
static unsigned long mc_group_start = 0x1;
static unsigned long *mc_groups = &mc_group_start;
// 按位表示所有多播组需要的unsigned long整数个数
static unsigned long mc_groups_longs = 1;

Generic Netlink框架

     +---------------------+      +---------------------+
     | (3) application "A" |      | (3) application "B" |
     +------+--------------+      +--------------+------+
            |                                    |
            \                                    /
             \                                  /
              |                                |
      +-------+--------------------------------+-------+
      |        :                               :       |   user-space
 =====+        :   (5)  kernel socket API      :       +================
      |        :                               :       |   kernel-space
      +--------+-------------------------------+-------+
               |                               |
         +-----+-------------------------------+----+
         |        (1)  Netlink subsystem            |
         +---------------------+--------------------+
                               |
         +---------------------+--------------------+
         |       (2) Generic Netlink bus            |
         +--+--------------------------+-------+----+
            |                          |       |
    +-------+---------+                |       |
    |  (4) controller |               /         \
    +-----------------+              /           \
                                     |           |
                  +------------------+--+     +--+------------------+
                  | (3) kernel user "X" |     | (3) kernel user "Y" |
                  +---------------------+     +---------------------+

需要解释几个核心关键点:

  1. Generic Netlink是基于Netlink框架的一种协议;
  2. 基于Generic Netlink的模块在Generic Netlink来看是一个个的family,对应到代码就是一个个的genl_family对象,特殊的,Generic Netlink框架实现了一个特殊的控制family,它用来辅助管理所有的普通family。

Generic Netlink消息

Generic Netlink消息就是在通用的Netlink消息基础上扩展的,其基本格式如下:

  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                Netlink message header (nlmsghdr)              |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           Generic Netlink message header (genlmsghdr)         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |             Optional user specific message header             |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |           Optional Generic Netlink message payload            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

消息头部

如上,除了通用的Netlink消息首部外,Generic Netlink消息有固定首部genlmsghdr,其格式如下:

struct nlmsghdr {
...
    __u16 nlmsg_type; // Generic Netlink消息时,该字段必须设置为Generic Netlink的famlyId
...
}
struct genlmsghdr {
	__u8 cmd; // family内部唯一标识命令的ID
	__u8 version;
	__u16 reserved;
};

此外,如果具体的family在实现时需要有自己的标准首部,那么可以通过genl_family.hdrsize指定其大小,这样框架在需要时就可以做出正确的处理了。

消息API

为了方便编程,Generic Netlink框架在Netlink协议消息API的基础上,实现了一组包装接口,核心API介绍如下。

消息的封装

使用genlmsg_new()分配一个Generic Netlink消息的skb。

/**
 * genlmsg_new - Allocate a new generic netlink message
 * @payload: size of the message payload
 * @flags: the type of memory to allocate.
 */
static inline struct sk_buff *genlmsg_new(size_t payload, gfp_t flags);

然后使用genlmsg_put()填充消息首部,包括Netlink消息首部和Generic Netlink消息首部。

/**
 * genlmsg_put - Add generic netlink header to netlink message
 * @skb: socket buffer holding the message
 * @pid: netlink pid the message is addressed to(发送方pid)
 * @seq: sequence number (usually the one of the sender)
 * @family: generic netlink family
 * @flags netlink message flags
 * @cmd: generic netlink command
 *
 * Returns pointer to user specific header
 */
static inline void *genlmsg_put(struct sk_buff *skb, u32 pid, u32 seq,
				struct genl_family *family, int flags, u8 cmd);

之后便可以使用Netlink消息属性相关的API填充消息的payload,在填充完毕后可以使用genlmsg_end()结束消息的封装。

/**
 * genlmsg_end - Finalize a generic netlink message
 * @skb: socket buffer the message is stored in
 * @hdr: user specific header,来自genlmsg_put()的返回值
 */
static inline int genlmsg_end(struct sk_buff *skb, void *hdr);

消息的解析

可以使用genlmsg_data()获得消息得payload指针(如果family有自己的首部,返回的是指向该首部的指针)。

/**
 * gennlmsg_data - head of message payload
 * @gnlh: genetlink messsage header
 */
static inline void *genlmsg_data(const struct genlmsghdr *gnlh)
{
	return ((unsigned char *) gnlh + GENL_HDRLEN);
}

可以使用genlmsg_len()获取到消息payload得长度。

/**
 * genlmsg_len - length of message payload
 * @gnlh: genetlink message header
 */
static inline int genlmsg_len(const struct genlmsghdr *gnlh)

可以使用下面两个api计算出长度为payload的Generic Netlink消息得长度。

/**
 * genlmsg_msg_size - length of genetlink message not including padding
 * @payload: length of message payload
 */
static inline int genlmsg_msg_size(int payload);

/**
 * genlmsg_total_size - length of genetlink message including padding
 * @payload: length of message payload
 */
static inline int genlmsg_total_size(int payload);

消息的发送

内核态可以使用如下接口进行单播和组播消息发送。

/**
 * genlmsg_unicast - unicast a netlink message
 * @skb: netlink message as socket buffer
 * @pid: netlink pid of the destination socket
 */
static inline int genlmsg_unicast(struct sk_buff *skb, u32 pid);

/**
 * genlmsg_multicast - multicast a netlink message
 * @skb: netlink message as socket buffer
 * @pid: own netlink pid to avoid sending to yourself
 * @group: multicast group id
 * @flags: allocation flags
 */
static inline int genlmsg_multicast(struct sk_buff *skb, u32 pid,
				    unsigned int group, gfp_t flags);

协议初始化

在Generic Netlink初始化时,向Netlink注册了NETLINK_GENERIC协议类型。

static int __init genl_init(void)
{
	int i, err;

    // 初始化全局family对象哈希表
	for (i = 0; i < GENL_FAM_TAB_SIZE; i++)
		INIT_LIST_HEAD(&family_ht[i]);
    // 注册控制family
	err = genl_register_family(&genl_ctrl);
	if (err < 0)
		goto errout;
    // 为控制family注册命令操作集
	err = genl_register_ops(&genl_ctrl, &genl_ctrl_ops);
	if (err < 0)
		goto errout_register;

    // 设置非root用户也可以接收GeNetlink组播消息
	netlink_set_nonroot(NETLINK_GENERIC, NL_NONROOT_RECV);
	// 向Netlink注册了NETLINK_GENERIC协议类型,对应的数据接收函数为genl_rcv()
	genl_sock = netlink_kernel_create(&init_net, NETLINK_GENERIC, 0,
					  genl_rcv, &genl_mutex, THIS_MODULE);
	if (genl_sock == NULL)
		panic("GENL: Cannot initialize generic netlink\n");

	err = genl_register_mc_group(&genl_ctrl, &notify_grp);
	if (err < 0)
		goto errout_register;

	return 0;

errout_register:
	genl_unregister_family(&genl_ctrl);
errout:
	panic("GENL: Cannot register controller: %d\n", err);
}
subsys_initcall(genl_init);

family的管理

Generic Netlink将内部更具体的协议称为family,每个family对应一个genl_family对象,所有的family对象被保存在全局哈希表中。

#define GENL_FAM_TAB_SIZE	16
#define GENL_FAM_TAB_MASK	(GENL_FAM_TAB_SIZE - 1)

static struct list_head family_ht[GENL_FAM_TAB_SIZE];
static DEFINE_MUTEX(genl_mutex); /* serialization of message processing */

family的注册&去注册

#define GENL_MIN_ID	NLMSG_MIN_TYPE
#define GENL_MAX_ID	1023

int genl_register_family(struct genl_family *family)
{
	int err = -EINVAL;

    // 检查指定的family ID是否在有效范围内
	if (family->id && family->id < GENL_MIN_ID)
		goto errout;
	if (family->id > GENL_MAX_ID)
		goto errout;

    // 初始化操作链表和多播链表
	INIT_LIST_HEAD(&family->ops_list);
	INIT_LIST_HEAD(&family->mcast_groups);

	genl_lock();
    // 确保family的名字全局唯一
	if (genl_family_find_byname(family->name)) {
		err = -EEXIST;
		goto errout_locked;
	}
    // 确保family的ID全局唯一
	if (genl_family_find_byid(family->id)) {
		err = -EEXIST;
		goto errout_locked;
	}

    // 当指定的id为GENL_ID_GENERATE时,内核会自动分配一个(很实用的功能)
	if (family->id == GENL_ID_GENERATE) {
		u16 newid = genl_generate_id();
		if (!newid) {
			err = -ENOMEM;
			goto errout_locked;
		}
		family->id = newid;
	}

    // 如果指定了支持的属性个数,则分配属性buffer,方便后面接收到消息后的属性解析
	if (family->maxattr) {
		family->attrbuf = kmalloc((family->maxattr+1) * sizeof(struct nlattr *), GFP_KERNEL);
		if (family->attrbuf == NULL) {
			err = -ENOMEM;
			goto errout_locked;
		}
	} else
		family->attrbuf = NULL;

    // 将新的family保存到全局哈希表中
	list_add_tail(&family->family_list, genl_family_chain(family->id));
	genl_unlock();

    // 发布一个CTRL_CMD_NEWFAMILY组播事件
	genl_ctrl_event(CTRL_CMD_NEWFAMILY, family);
	return 0;

errout_locked:
	genl_unlock();
errout:
	return err;
}

对应的去注册函数为genl_unregister_family(),这里不再展开。

family操作集的注册&去注册

每个family能够支持多个命令,每个命令对应一个genl_ops对象,family将其支持的命令组织到family->ops_list链表中。对应的注册函数为genl_register_ops(),去注册函数为genl_unregister_ops(),这里不再展开去注册函数。

int genl_register_ops(struct genl_family *family, struct genl_ops *ops)
{
	int err = -EINVAL;

    // 命令至少指定dumpit()和doit()回调中的一个
	if (ops->dumpit == NULL && ops->doit == NULL)
		goto errout;

    // 命令ID在family内要保持唯一
	if (genl_get_cmd(ops->cmd, family)) {
		err = -EEXIST;
		goto errout;
	}

    // 设置回调函数标记
	if (ops->dumpit)
		ops->flags |= GENL_CMD_CAP_DUMP;
	if (ops->doit)
		ops->flags |= GENL_CMD_CAP_DO;
	if (ops->policy)
		ops->flags |= GENL_CMD_CAP_HASPOL;

    // 将ops对象放入family->ops_list链表中
	genl_lock();
	list_add_tail(&ops->ops_list, &family->ops_list);
	genl_unlock();

    // 向外发送CTRL_CMD_NEWOPS组播事件
	genl_ctrl_event(CTRL_CMD_NEWOPS, ops);
	err = 0;
errout:
	return err;
}

控制family

Generic Netlink框架的核心是其控制family,其family对象实例化如下:

#define GENL_ID_CTRL NLMSG_MIN_TYPE // 0x10,控制family的familyID是固定的

static struct genl_family genl_ctrl = {
	.id = GENL_ID_CTRL,
	.name = "nlctrl",
	.version = 0x2,
	.maxattr = CTRL_ATTR_MAX,
};

其命令操作集如下。可见控制family只支持接收一个CTRL_CMD_GETFAMILY命令(其它命令是向外发送的),用户态程序可以通过该命令查询已注册family的信息。

static struct genl_ops genl_ctrl_ops = {
	.cmd		= CTRL_CMD_GETFAMILY,
	.doit		= ctrl_getfamily,
	.dumpit		= ctrl_dumpfamily,
	.policy		= ctrl_policy,
};

family查询命令: ctrl_getfamily()

static int ctrl_getfamily(struct sk_buff *skb, struct genl_info *info)
{
	struct sk_buff *msg;
	struct genl_family *res = NULL;
	int err = -EINVAL;

    // 通过family ID查询
	if (info->attrs[CTRL_ATTR_FAMILY_ID]) {
		u16 id = nla_get_u16(info->attrs[CTRL_ATTR_FAMILY_ID]);
		res = genl_family_find_byid(id);
	}
    // 通过family名字查询,显然名字的优先级更高
	if (info->attrs[CTRL_ATTR_FAMILY_NAME]) {
		char *name = nla_data(info->attrs[CTRL_ATTR_FAMILY_NAME]);
		res = genl_family_find_byname(name);
	}
    // 没有找到
	if (res == NULL) {
		err = -ENOENT;
		goto errout;
	}
    // 填充该family的信息到skb
	msg = ctrl_build_family_msg(res, info->snd_pid, info->snd_seq, CTRL_CMD_NEWFAMILY);
	if (IS_ERR(msg)) {
		err = PTR_ERR(msg);
		goto errout;
	}
	// 将封装好的skb返回给用户态
	err = genlmsg_reply(msg, info);
errout:
	return err;
}

可见,内核支持通过名字和ID来查询family的信息,不过大多数的使用场景是通过名字查询。ctrl_build_family_msg()函数不再展开,从中可以看到通过该命令可以查询到一个family的所有信息,包括名字、ID、版本、支持的命令列表等等。

此外,控制family还支持一个组播,通过该组播,控制family向外广播family的变化以及family组播的变化情况。

消息接收: genl_rcv()

如协议初始化部分分析,当Netlink框架收到发送往Generic Netlink的消息时,会调用Generic Netlink提供的回调函数: genl_rcv()。

static void genl_rcv(struct sk_buff *skb)
{
	genl_lock();
	netlink_rcv_skb(skb, &genl_rcv_msg);
	genl_unlock();
}

持锁后,调用Netlink框架提供的netlink_rcv_skb()函数对Netlink消息进行接收,该函数的核心思想就是循环处理skb中的Netlink消息,对于合法的消息,将其交给回调函数处理。

int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, struct nlmsghdr *))
{
	struct nlmsghdr *nlh;
	int err;

    // 循环解析Netlink消息
	while (skb->len >= nlmsg_total_size(0)) { // 消息长度满足一个Netlink消息头部
		int msglen;

		nlh = nlmsg_hdr(skb); // Netlink消息首部
		err = 0;
        // 检查skb中是否够一个完整的Netlink消息
		if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
			return 0;

		// 发往内核的Netlink消息必须设置REQUEST标记
		if (!(nlh->nlmsg_flags & NLM_F_REQUEST))
			goto ack;

		// 跳过控制类消息:NLMSG_NOOP等属于控制消息
		if (nlh->nlmsg_type < NLMSG_MIN_TYPE)
			goto ack;
        // 将消息传给回调函数处理
		err = cb(skb, nlh);
		if (err == -EINTR)
			goto skip;

ack:
        // 可见,对于错误消息,内核总是会ACK的
		if (nlh->nlmsg_flags & NLM_F_ACK || err)
			netlink_ack(skb, nlh, err);

skip:
        // skb指针向前偏移指定长度,继续处理下一个Netlink消息
		msglen = NLMSG_ALIGN(nlh->nlmsg_len);
		if (msglen > skb->len)
			msglen = skb->len;
		skb_pull(skb, msglen);
	}
	return 0;
}

genl_rcv_msg()

该函数用于接收一个Generic Netlink消息,消息数据已经在参数nlh中了。

static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
	struct genl_ops *ops;
	struct genl_family *family;
	struct genl_info info;
	struct genlmsghdr *hdr = nlmsg_data(nlh);
	int hdrlen, err;

    // 根据family ID找到注册的family对象
	family = genl_family_find_byid(nlh->nlmsg_type);
	if (family == NULL)
		return -ENOENT;
    // 头部长度检查:消息的长度要至少包含完整的Generic Netlink消息首部
	hdrlen = GENL_HDRLEN + family->hdrsize;
	if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
		return -EINVAL;

    // 从family中查找该命令对应的genl_ops对象
	ops = genl_get_cmd(hdr->cmd, family);
	if (ops == NULL)
		return -EOPNOTSUPP;

    // 如果该操作有权限限制,则进行权限检查
	if ((ops->flags & GENL_ADMIN_PERM) && security_netlink_recv(skb, CAP_NET_ADMIN))
		return -EPERM;

    // 消息如果指定了NLM_F_DUMP标记,则调用的是ops中的dumpit()和done()回调
	if (nlh->nlmsg_flags & NLM_F_DUMP) {
		if (ops->dumpit == NULL)
			return -EOPNOTSUPP;
		genl_unlock();
		err = netlink_dump_start(genl_sock, skb, nlh, ops->dumpit, ops->done);
		genl_lock();
		return err;
	}
    // 命令必须提供doit()回调
	if (ops->doit == NULL)
		return -EOPNOTSUPP;

    // 如果family提供了属性buf,那么用该buffer空间存储解析后的消息属性
	if (family->attrbuf) {
		err = nlmsg_parse(nlh, hdrlen, family->attrbuf, family->maxattr, ops->policy);
		if (err < 0)
			return err;
	}

    // 将信息组织到info中,然后调用命令操作集的doit()回调
	info.snd_seq = nlh->nlmsg_seq;
	info.snd_pid = NETLINK_CB(skb).pid;
	info.nlhdr = nlh;
	info.genlhdr = nlmsg_data(nlh);
	info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN;
	info.attrs = family->attrbuf;
	return ops->doit(skb, &info);
}

Generic Netlink组播

Generic Netlink在Netlink组播的基础上,也可支持具体family实现自己的组播。

多播组的注册&去注册

具体family要支持多播组,那么需要首先向Generic Netlink框架注册,对应的函数为genl_register_mc_group(),类似的,去注册接口为genl_unregister_mc_group(),这里不再展开。

/**
 * genl_register_mc_group - register a multicast group
 *
 * Registers the specified multicast group and notifies userspace
 * about the new group.
 *
 * Returns 0 on success or a negative error code.
 *
 * @family: The generic netlink family the group shall be registered for.
 * @grp: The group to register, must have a name.
 */
int genl_register_mc_group(struct genl_family *family,
			   struct genl_multicast_group *grp)
{
	int id;
	unsigned long *new_groups;
	int err;

	BUG_ON(grp->name[0] == '\0');

	genl_lock();

	/* special-case our own group */
	if (grp == &notify_grp) // 控制family的多播组ID是固定的
		id = GENL_ID_CTRL;
	else
	    // 分配一个组ID
		id = find_first_zero_bit(mc_groups,
					 mc_groups_longs * BITS_PER_LONG);

    // 多播组掩码已经不够用了,重新分配
	if (id >= mc_groups_longs * BITS_PER_LONG) {
		size_t nlen = (mc_groups_longs + 1) * sizeof(unsigned long);

		if (mc_groups == &mc_group_start) {
			new_groups = kzalloc(nlen, GFP_KERNEL);
			if (!new_groups) {
				err = -ENOMEM;
				goto out;
			}
			mc_groups = new_groups;
			*mc_groups = mc_group_start;
		} else {
			new_groups = krealloc(mc_groups, nlen, GFP_KERNEL);
			if (!new_groups) {
				err = -ENOMEM;
				goto out;
			}
			mc_groups = new_groups;
			mc_groups[mc_groups_longs] = 0;
		}
		mc_groups_longs++;
	}
    // 让Netlink框架变更协议对象nl_table中的组播字段
	err = netlink_change_ngroups(genl_sock, mc_groups_longs * BITS_PER_LONG);
	if (err)
		goto out;
    // 设置多播组对象中的各字段
	grp->id = id;
	set_bit(id, mc_groups);
	list_add_tail(&grp->list, &family->mcast_groups);
	grp->family = family;

    // 控制family对外广播NEWMCAST_GRP事件
	genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, grp);
 out:
	genl_unlock();
	return err;
}
<think>好的,我需要回答用户关于generic netlink的doit回调函数是同步还是异步执行的问题。首先,我得回忆一下NetlinkGeneric Netlink的基本知识。NetlinkLinux内核提供的用户空间和内核空间通信的机制,而Generic Netlink是对传统Netlink的扩展,更方便使用。 用户的问题核心在于发送消息到generic netlink时,doit函数是同步还是异步执行。记得在Linux内核中,当用户空间发送消息到内核,处理通常是同步的。也就是说,当用户程序通过socket发送一个消息,内核会在接收到这个消息后立即处理,处理完成后再返回结果。对于Generic Netlink的doit回调函数,这个函数应该是在发送消息的上下文里被调用的,也就是同步执行。 接下来需要验证这个理解是否正确。可以查阅内核源码中的相关部分,比如net/netlink/genetlink.c中的处理流程。当内核收到一个Netlink消息时,会调用相应的处理函数,比如doit,这个过程应该是同步的,即发送消息的进程会等待doit执行完毕后才继续执行。如果是异步的话,消息会被放入队列,由内核线程处理,但这种情况通常用于需要延后处理的情况,而Generic Netlink的标准操作应该是同步的。 另外,可以参考内核文档或者权威资料,确认doit回调的执行模式。例如,内核文档可能提到,每个Netlink消息的处理都是在接收的软中断上下文中处理的,或者是通过工作队列异步处理。但根据经验,Netlink消息处理通常是同步的,以保证操作的顺序性和及时性。 还需要考虑用户空间发送消息的方式,比如使用sendmsg发送消息后,是否会阻塞直到内核处理完毕。如果用户空间调用发送后立即可以收到回复,这说明处理是同步的。而如果是异步的,用户可能需要等待某个事件或回调才能获取结果,但Generic Netlink通常使用同步通信模型。 总结起来,用户空间的请求会触发内核同步执行doit回调,处理完成后返回结果。因此,doit是同步执行的。需要确保在回答时结构清晰,逐步解释,并引用相关机制来支持结论。</think>在Linux内核中,用户空间向Generic Netlink发送消息时,`doit`回调函数是**同步执行**的。以下是分步说明: --- ### 1. **Generic Netlink通信机制基础** - **Generic Netlink**是传统Netlink的扩展,采用客户端-服务端模型。内核模块作为服务端注册Family,用户空间作为客户端通过套接字通信。 - 每个Family会定义一操作(operations),对应`doit`回调函数。 --- ### 2. **消息处理流程** 1. **用户空间发送消息**:通过`sendmsg()`系统调用向内核发送Netlink消息。 2. **内核接收消息**:内核在**进程上下文**(通常是发送进程的上下文)中接收并解析消息。 3. **调用`doit`回调**:内核根据消息中的操作类型(如`cmd`字段),直接调用对应的`doit`函数。 4. **同步执行**:`doit`函数会**阻塞当前用户进程**,直到执行完成并返回响应。 --- ### 3. **关键证据** - **内核源码逻辑**: 在`genl_family_rcv_msg`函数(位于`net/netlink/genetlink.c`)中,`doit`回调直接通过函数指针调用,无异步队列或工作队列机制。 ```c if (ops->doit) return ops->doit(skb, info); ``` - **用户空间行为**: 用户调用`sendmsg()`后,通常需要同步调用`recvmsg()`等待内核响应。若`doit`异步执行,用户需通过事件驱动机制获取结果,但Generic Netlink未采用此设计。 --- ### 4. **同步执行的影响** - **优点**:保证操作顺序性,简化用户空间编程(直接通过请求-响应模型交互)。 - **缺点**:若`doit`函数执行耗时较长,会阻塞用户进程,甚至导致超时。 --- ### 5. **异步场景的例外** 若需异步处理,需自行实现机制: - 在`doit`中触发异步任务(如内核工作队列)。 - 通过其他通道(如另一个Netlink会话)通知用户空间结果。 --- ### 总结 Generic Netlink的`doit`回调是同步执行的,用户空间发送消息后会阻塞等待内核处理完成。这种设计符合大多数内核操作的实时性要求,但需注意避免在`doit`中执行耗时操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值