Netlink 内核实现分析 1

Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,在一般情况下,用户态和内核态通信会使用传统的Ioctl、sysfs属性文件或者procfs属性文件,这3种通信方式都是同步通信方式,由用户态主动发起向内核态的通信,内核无法主动发起通信。Netlink是一种异步全双工的通信方式,它支持由内核态主动发起通信,内核为Netlink通信提供了一组特殊的API接口,用户态则基于socket API,内核发送的数据会保存在接收进程socket 的接收缓存中,由接收进程处理。
netlink 优点:

  • 可以由内核发起, 用户进程可以使用IO复用模型
  • 支持组播,即内核态可以将消息发送给多个接收进程
netlink 内核子接口初始化实在 net/netlink/af_netlink.c中初始化完成

static const struct rhashtable_params netlink_rhashtable_params = {
   .head_offset = offsetof(struct netlink_sock, node),
   .key_len = netlink_compare_arg_len,
   .obj_hashfn = netlink_hash,
   .obj_cmpfn = netlink_compare,
   .automatic_shrinking = true,
};
/*
这里的hash(哈希表)用来索引同种协议类型的不同netlink套接字实例,mc_list为多播使用的sock散列表,listeners为监听者掩码,
groups为协议支持的最大多播组数量,
同时还定义了一些函数指针,它们会在内核首次创建netlink时被赋值,后续应用层创建和绑定socket时调用到。
回到初始化函数中,接下来初始化应用层使用的NETLINK_USERSOCK协议类型的netlink(用于应用层进程间通信);
然后调用sock_register向内核注册协议处理函数,即将netlink的socket创建处理函数注册到内核中,
如此以后应用层创建netlink类型的socket时将会调用该协议处理函数,其中
*/
static int __init netlink_proto_init(void)
{
   int i;
   int err = proto_register(&netlink_proto, 0);
   if (err != 0)
   	goto out;
   BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > FIELD_SIZEOF(struct sk_buff, cb));
//之开辟了MAX_LINKS个na_talbe指针空间
   nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);
   if (!nl_table)
   	goto panic;
//分配MAX_LINKS个netlink表结构, nl_table, 每个成员代表一种协议类型
   for (i = 0; i < MAX_LINKS; i++) {
   	if (rhashtable_init(&nl_table[i].hash,
   			    &netlink_rhashtable_params) < 0) {
   		while (--i > 0)
   			rhashtable_destroy(&nl_table[i].hash);
   		kfree(nl_table);
   		goto panic;
   	}
   }
   INIT_LIST_HEAD(&netlink_tap_all);
   netlink_add_usersock_entry();
   sock_register(&netlink_family_ops);//将netlink socket 创建 函数注册到内核中,即创建netlink socket 时 回调函数为 netlink_family_ops->create 回调
   register_pernet_subsys(&netlink_net_ops);
   /* The netlink device handler may be needed early. */
   rtnetlink_init();
out:
   return err;
panic:
   panic("netlink_init: Cannot allocate nl_table\n");
}
core_initcall(netlink_proto_init

本初始化函数首先向内核注册netlink协议;然后创建并初始化了nl_table表数组,这个表是整个netlink实现的最关键的一步,每种协议类型占数组中的一项,后续内核中创建的不同种协议类型的netlink都将保存在这个表中.

struct netlink_table {
	struct rhashtable	hash;// hash表控制块,内部的hash表记录了已经创建的同种协议类型的所有netlink套接字
	struct hlist_head	mc_list; // 这个hash表头节点用于记录同种协议类型下所有阅订了组播功能的套接字
	struct listeners __rcu	*listeners; // 记录了同种协议类型下所有被阅订了的组播消息集合 为监听者掩码
	unsigned int		flags;
	unsigned int		groups;// 记录了该协议类型支持的最大组播数量(通常就是32个)
	struct mutex		*cb_mutex;
	struct module		*module;
	//函数指针会在内核首次创建netlink时被赋值,后续应用层创建和绑定socket时调用到
	int			(*bind)(struct net *net, int group);
	void			(*unbind)(struct net *net, int group);
	bool			(*compare)(struct net *net, struct sock *sock);
	int			registered;
};
/*
应用层创建PF_NETLINK(AF_NETLINK)类型的socket()系统调用时将由netlink_create()函数负责处理
*/
static const struct net_proto_family netlink_family_ops = {
	.family = PF_NETLINK,
	.create = netlink_create,
	.owner	= THIS_MODULE,	/* for consistency 8) */
};

对于每个类型都会创建一个内核的netlink 套接字用于和应用层通信;以NETLINK_ROUTE为例

static int __net_init rtnetlink_net_init(struct net *net)
{
	struct sock *sk;
	struct netlink_kernel_cfg cfg = {
		.groups		= RTNLGRP_MAX,
		.input		= rtnetlink_rcv,
		.cb_mutex	= &rtnl_mutex,
		.flags		= NL_CFG_F_NONROOT_RECV,
	};

	sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);
	if (!sk)
		return -ENOMEM;
	net->rtnl = sk;
	return 0;
}
/*
定义了一个netlink_kernel_cfg结构体实例,设置groups为RTNLGRP_MAX后指定消息接收处理函数为rtnetlink_rcv,
并设置flag为NL_CFG_F_NONROOT_RECV,这表明非超级用户可以绑定到多播组,但是没有设置NL_CFG_F_NONROOT_SEND,
这表明非超级用户将不能发送组播消息。
随后init函数调用netlink_kernel_create()向当前的网络命名空间创建NETLINK_ROUTE类型的套接字,
并指定定义的那个配置结构cfg。进入netlink_kernel_create()函数内部:
*/
/*
 *	We export these functions to other modules. They provide a
 *	complete set of kernel non-blocking support for message
 *	queueing.
 */

struct sock *
__netlink_kernel_create(struct net *net, int unit, struct module *module,
			struct netlink_kernel_cfg *cfg)
{
	struct socket *sock;
	struct sock *sk;
	struct netlink_sock *nlk;
	struct listeners *listeners = NULL;
	struct mutex *cb_mutex = cfg ? cfg->cb_mutex : NULL;
	unsigned int groups;

	BUG_ON(!nl_table);

	if (unit < 0 || unit >= MAX_LINKS)
		return NULL;

	if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
		return NULL;
/*
首先进行简单的参数判断之后就调用sock_create_lite()函数创建了一个以PF_NETLINK为地址族的SOCK_DGRAM类型的socket套接字,
其协议类型就是作为参数传入的NETLINK_ROUTE。
然后该函数调用最核心的__netlink_create()函数向内核初始化netlink套接字
*/
	if (__netlink_create(net, sock, cb_mutex, unit, 1) < 0)
		goto out_sock_release_nosk;

	sk = sock->sk;

	if (!cfg || cfg->groups < 32)
		groups = 32;
	else
		groups = cfg->groups;

	listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL);
	if (!listeners)
		goto out_sock_release;

	sk->sk_data_ready = netlink_data_ready;
	if (cfg && cfg->input)
		nlk_sk(sk)->netlink_rcv = cfg->input; //设置netlink socket 的input 函数 即 rcv   //rtnetlink_rcv函数

	if (netlink_insert(sk, 0))
		goto out_sock_release;
/*
来校验groups,默认最小支持32个组播地址(用户层在绑定地址时最多绑定32个组播地址),但内核也有可能支持大于32个组播地址的情况(Genetlink就属于这种情况),
然后分配listeners内存空间,这里边保存了监听者(监听套接字)的信息;接下来继续初始化函数指针,这里将前文中定义的rtnetlink_rcv注册到了nlk_sk(sk)->netlink_rcv中,
这样就设置完了内核态的消息处理函数;然后调用netlink_insert()函数将本次创建的这个套接字添加到nl_table中去(其核心是调用__netlink_insert()),
注册的套接字是通过nl_table中的哈希表来管理的。
然后设置标识NETLINK_KERNEL_SOCKET表明这个netlink套接字是一个内核套接字
*/
	nlk = nlk_sk(sk);
	nlk->flags |= NETLINK_F_KERNEL_SOCKET;

	netlink_table_grab();
	if (!nl_table[unit].registered) {
		nl_table[unit].groups = groups;
		rcu_assign_pointer(nl_table[unit].listeners, listeners);
		nl_table[unit].cb_mutex = cb_mutex;
		nl_table[unit].module = module;
		if (cfg) {
			nl_table[unit].bind = cfg->bind;
			nl_table[unit].unbind = cfg->unbind;
			nl_table[unit].flags = cfg->flags;
			if (cfg->compare)
				nl_table[unit].compare = cfg->compare;
		}
		nl_table[unit].registered = 1;
	} else {
		kfree(listeners);
		nl_table[unit].registered++;
	}
	netlink_table_ungrab();
	return sk;
/*
nl_table[NETLINK_ROUTE].groups = RTNLGRP_MAX;
nl_table[NETLINK_ROUTE].cb_mutex = &rtnl_mutex;
nl_table[NETLINK_ROUTE].module = THIS_MODULE;
nl_table[NETLINK_ROUTE].bind = NULL;
nl_table[NETLINK_ROUTE].unbind = NULL;
nl_table[NETLINK_ROUTE].compare = NULL;
nl_table[NETLINK_ROUTE].flags= NL_CFG_F_NONROOT_RECV;
以 NETLINK_ROUTE 为参考的结果
*/
out_sock_release:
	kfree(listeners);
	netlink_kernel_release(sk);
	return NULL;

out_sock_release_nosk:
	sock_release(sock);
	return NULL;
}
static int __netlink_create(struct net *net, struct socket *sock,
			    struct mutex *cb_mutex, int protocol,
			    int kern)
{
	struct sock *sk;
	struct netlink_sock *nlk;
//将sock的操作函数集指针设置为netlink_ops
	sock->ops = &netlink_ops;// 设置socket 的bind 等回调函数

	sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto, kern);
	if (!sk)
		return -ENOMEM;
/*
分配sock结构并进行初始化,主要包括初始化发送接收消息队列、数据缓存、等待队列和互斥锁等等,
最后设置sk_destruct回调函数和协议类型。
*/
	sock_init_data(sock, sk);

	nlk = nlk_sk(sk);
	if (cb_mutex) {
		nlk->cb_mutex = cb_mutex;
	} else {
		nlk->cb_mutex = &nlk->cb_def_mutex;
		mutex_init(nlk->cb_mutex);
	}
	init_waitqueue_head(&nlk->wait);

	sk->sk_destruct = netlink_sock_destruct;
	sk->sk_protocol = protocol;
	return 0;
}

posted @ 2019-05-11 17:50  codestacklinuxer  阅读(986)  评论(0编辑  收藏  举报