您的位置:首页 > 其它

三、无线信息传递——Generic Netlink(一、初始化)

2016-09-02 16:50 691 查看

系列说明

  上一篇说明了无线信息的user space至kernel space的大致传递流程,这一主要针对以下3点进行一个顺序的描述:

1、无线驱动信息传递框架:说明无线信息传递的步骤流程以及各程序块之间的联系;

2、generic Netlink信号传递机制:hostapd与无线驱动之间的信息传递机制

3、以ssid为例说明用户将user space中的ssid配置内容传递至kernel space的流程:以此系统地了解整个无线信息传递流程。

  上篇中,多次提到了genetlink,他的全名为generic netlink。而generic netlink在user space和kernel space之间起到了关键性的作用。由于内容较多,这一篇就对generic netlink进行第一阶段的分析。下一篇再通过一个具体的代码例子系统地说明generic netlink是怎么让user space和kernel space双向通信的。

  关于generic netlink的两篇说明均参考下面的两篇链接,这里由链接的文章作出自己的总结:

Generic Netlink内核实现分析(一):初始化

Generic Netlink内核实现分析(二):通信

一、generic netlink

为什么会出现generic netlink?

  因为netlink最多只支持32个协议簇,所以generic netlink的出现是为扩展netlink协议簇而设计,即是netlink协议簇的子集。

  博文中是这样说明的:Generic Netlink 是内核专门为了扩展netlink协议簇而设计的“通用netlink协议簇”。由于netlink协议最多支持32个协议簇,目前Linux4.1的内核中已经使用其中21个,对于用户需要定制特殊的协议类型略显不够,而且用户还需自行在include/linux/netlink.h中添加簇定义(略显不妥),为此Linux设计了这种通用Netlink协议簇,用户可在此之上定义更多类型的子协议。

generic netlink在程序中的代码实现框架:



  从上图中可以看出Generic Netlink也还是需要借助Netlink来完成用户环境和内核环境的信息传递。用户层发信息先至Netlink,然后再转给Generic netlink分支,再至内核;同样的内核发信息先发给Generic netlink分支,然后再通过netlink传给用户层。

  框图中Ctrl控制器是一种特殊类型的Genetlink协议族,它用于用户空间通过Genetlink簇名查找对应的ID号。

  以下为netlink类型的消息结构:







  具象化地来说,上面的三幅图内容如下图说明所示:



二、generic netlink主要结构体

/*
* 为上图中的family header
*/
struct genlmsghdr {
__u8    cmd;                     //表示消息命令,对于用户自己定义的每个子协议类型都需要定义特定的消息命令集,这里该字段表示当前消息的消息命令
__u8    version;                 //version字段表示版本控制(可以在在不破坏向后兼容性的情况下修改消息的格式),可以不使用该字段
__u16   reserved;                //保留字段
};


/*
* Generic Netlink按照family进行管理,用户需注册自己定义的genl_family结构,同时内核使用一个哈希表family_ht对已经注册的genl family进行管理
*/
struct genl_family {
struct list_head list;      //链表结构,用于将当前当前簇链入全局family_ht散列表中
unsigned int id;        //genl family的ID号,一般由内核进行分配,取值范围为GENL_MIN_ID~GENL_MAX_ID(16~1023),其中GENL_ID_CTRL为控制器的family ID,不可另行分配,该familyID全局唯一并且在family_ht中的位置也由该值确定
unsigned int hdrsize;       //用户私有报头的长度,即上图中可选的user msg header长度,若没有则为0
unsigned int version;       //版本号
unsigned int maxattr;       //消息属性attr最大的类型数(即该genl family所支持的最大attr属性类型的种类个数)
const char *name;       //genl family的名称,必须是独一无二且用户层已知的(用户通过它来向控制查找family id)
bool netnsok;           //指示当前簇是否能够处理网络命名空间
struct nlattr **attrbuf;    //保存拷贝的attr属性缓存
int (*pre_doit)(struct genl_ops *ops, struct sk_buff *skb, struct genl_info *info); //调用genl_ops结构中处理消息函数doit()前调用的钩子函数,一般用于执行一些前置的当前簇通用化处理,例如对临界区加锁等
void (*post_doit)(struct genl_ops *ops, struct sk_buff *skb, struct genl_info *info);   //调用genl_ops结构中处理消息函数doit()后调用的钩子函数,一般执行pre_doit函数相反的操作
void  (*mcast_unbind)(struct net *net, int group);  //在解绑定socket到一个特定的genl netlink组播组中调用(目前内核中没有相关使用)
int (*mcast_bind)(struct net *net, int group);      //在绑定socket到一个特定的genl netlink组播组中调用(目前内核中没有相关使用)
const struct genl_multicast_group *mcgrps;      //保存当前簇使用的组播组及组播地址的个数
unsigned int n_mcgrps;
const struct genl_ops * ops;                //保存genl family命令处理结构即命令的个数,后面详细描述
unsigned int n_ops;

};


/*
* 该结构用于注册genl family的用户命令cmd处理函数(对于只向应用层发送消息的簇可以不用实现和注册该结构)
*/
struct genl_ops {
u8 cmd; //簇命令类型,由用户自行根据需要定义
u8 internal_flags; //簇私有标识,用于进行一些分支处理,可以不使用
unsigned int flags; //操作标识,有下面四中类型
const struct nla_policy *policy; //属性attr有效性策略,若该字段不为空,在genl执行消息处理函数前会对消息中的attr属性进行校验,否则则不做校验
int (*doit)(struct sk_buff *skb, struct genl_info *info); //标准命令回调函数,在当前族中收到数据时触发调用,函数的第一个入参skb中保存了用户下发的消息内容
int (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb); //转储回调函数,当genl_ops的flag标志被添加了NLM_F_DUMP以后会调用该回调函数,这里的第一个入参skb中不再有用户下发消息内容,而是要求函数能够在传入的skb中填入消息载荷并返回填入数据长度
int (*done)(struct netlink_callback *cb); //转储结束后执行的回调函数
};

#define GENL_ADMIN_PERM     0x01    /* 当设置该标识时表示本命令操作需要具有CAP_NET_ADMIN权限 */
#define GENL_CMD_CAP_DO     0x02    /* 当genl_ops结构中实现了doit()回调函数则设置该标识 */
#define GENL_CMD_CAP_DUMP   0x04    /* 当genl_ops结构中实现了dumpit()回调函数则设置该标识 */
#define GENL_CMD_CAP_HASPOL 0x08    /* 当genl_ops结构中定义了属性有效性策略(nla_policy)则设置该标识 */


/*
* 内核在接收到用户的genetlink消息后,会对消息解析并封装成genl_info结构,便于命令回调函数进行处理
*/
struct genl_info {
struct sock* dst_sk; //目的socket
u32 snd_seq; //消息的发送序号(不强制使用)
u32 snd_pid; //消息发送端socket所绑定的ID
struct genlmsghdr *genlhdr; //generic netlink消息头
struct nlattr **attrs; //netlink属性,包含了消息的实际载荷
void *user_ptr[2]; //用户私有报头
struct nlmsghdr* nlhdr; //netlink消息头
};


那么这几个结构体有什么联系呢?

  回到原来netlink解析的图。



  从接收到user space的消息顺序来看:

genlmsghdr(第一个结构体)在第二层中的family header中使用,用于获取接收到的消息的cmd命令;

  然后是genl_info(第四个结构体),该结构体中的attrs包含了消息的实际载荷,以此可以获取到用户传下来的id,程序中从全局family_ht[](这里的family_ht链表在程序运行时通过调用genl_register_family接口向该链表添加family协议簇)表示的哈希表中找到相同对应id的genl_family(第二个结构体)

  genl_family一般是控制器协议簇,结构体中有两个重要内容。一个为name,user space以此向内核获取family id来建立与内核通信的连接,所以user space想要与内核通信时,这里的name与user space的name必须一致,同时name也需要确保他的一致性。另外一个为ops列表,其中包含genl_ops(第三个结构体)

genl_ops结构体用于调用上层用户发下来的命令的对应的回调处理函数。

三、Generic netlink 初始化

  在了解了generic netlink的主要几个结构体之后,接下来继续对genetlink初始化的了解会方便很多。下面我们以一个程序流程图和generic netlink的基本几段程序来进行初始化说明。



static int __init genl_init(void)
{
int i, err;
for (i = 0; i < GENL_FAM_TAB_SIZE; i++)
INIT_LIST_HEAD(&family_ht[i]);              //初始化用于保存和维护Generic netlink family的散列表family_ht数组,其中family_ht为一个全局变量
err = genl_register_family_with_ops_groups(&genl_ctrl, genl_ctrl_ops, genl_ctrl_groups);    //向内核Generic netlink子系统注册控制器簇类型的Genetlink Family(genl_ctrl_groups, genl_ctrl和genl_ctrl_ops, genl_register_family_with_ops_groups如下面代码所示)
if (err < 0)
goto problem;
err = register_pernet_subsys(&genl_pernet_ops);         //为当前系统中的网络命名空间创建Generic Netlink套接字(在下面代码中说明)
if (err)
goto problem;
return 0;
problem:
panic("GENL: Cannot register controller: %d\n", err);
}


static struct genl_family genl_ctrl = {
.id = GENL_ID_CTRL, //GENL_ID_CTRL(16),分配区间的最小值
.name = "nlctrl",
.version = 0x2,
.maxattr = CTRL_ATTR_MAX, //maxattr定义为支持的attr属性最大个数CTRL_ATTR_MAX(表示该genl family所支持的最大attr属性类型的种类个数),如下枚举所示
.netnsok = true, //为true表示支持net命名空间
};

enum {
CTRL_ATTR_UNSPEC,
CTRL_ATTR_FAMILY_ID,
CTRL_ATTR_FAMILY_NAME,
CTRL_ATTR_VERSION,
CTRL_ATTR_HDRSIZE,
CTRL_ATTR_MAXATTR,
CTRL_ATTR_OPS,
CTRL_ATTR_MCAST_GROUPS,
__CTRL_ATTR_MAX,
};
#define CTRL_ATTR_MAX(__CTRL_ATTR_MAX - 1)


static struct genl_ops genl_ctrl_ops[] = {
{
.cmd  = CTRL_CMD_GETFAMILY, //用于应用空间从内核中获取指定family名称的ID号的命令(因为该ID号在内核注册family时由内核进行分配,应用空间一般只知道需要通信的family name,但是要发起通信就必须知道该ID号,所以内核设计了控制器类型的family并定义了CTRL_CMD_GETFAMILY命令的处理接口用于应用程序查找ID号)
.doit  = ctrl_getfamily, //回调执行函数
.dumpit  = ctrl_dumpfamily,
.policy  = ctrl_policy, //有效性策略为ctrl_policy(如下代码所示)
},
};

//attr有效性策略为ctrl_policy,这里获取应用的值笔者理解为attr[CTRL_ATTR_FAMILY_ID]获取到cmd的id(u16类型),attr[CTRL_ATTR_FAMILY_NAME]获取到的为cmd的msg(string类型)
static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
[CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 }, //属性限定类型为16位无符号数
[CTRL_ATTR_FAMILY_NAME] = { .type = NLA_NUL_STRING, //属性限定为空结尾的字符串类型并限定了长度
.len = GENL_NAMSIZ - 1 },
};

//注册的组播组
static struct genl_multicast_group genl_ctrl_groups[] = {
{
.name = "notify",   //添加了name为”notify“的组播组
},
};


#define genl_register_family_with_ops_groups(family, ops, grps) \
_genl_register_family_with_ops_grps((family),   \
(ops), ARRAY_SIZE(ops), \
(grps), ARRAY_SIZE(grps))

static inline int
_genl_register_family_with_ops_grps(struct genl_family *family,
const struct genl_ops *ops, size_t n_ops,
const struct genl_multicast_group *mcgrps,
size_t n_mcgrps)
{
family->module = THIS_MODULE;
family->ops = ops;             //genl_family中赋值genl_ops类型的genl_ctrl_ops指针
family->n_ops = n_ops;
family->mcgrps = mcgrps;           //genl_family赋值genl_multicast_group类型的genl_ctrl_groups指针
family->n_mcgrps = n_mcgrps;
return __genl_register_family(family);     //继续注册
}

int __genl_register_family(struct genl_family *family)
{
int err = -EINVAL, i;
if (family->id && family->id < GENL_MIN_ID) //对入参的ID号进行判断,一般来说,为了保证ID号的全局唯一性,程序中一般都设置为GENL_ID_GENERATE,由内核统一分配(当然这里注册控制器family除外了)
goto errout;
if (family->id > GENL_MAX_ID)
goto errout;

err = genl_validate_ops(family);        //对ops函数集做校验
//校验内容为:
//  1、每一个注册的genl_ops结构,其中doit和dumpit回调函数必须至少实现一个
//  2、针对的cmd命令不可以出现重复
if (err)
return err;
genl_lock_all();                //上锁开始启动链表操作
if (genl_family_find_byname(family->name)) {    //前面已经说明family中的name需要确保其唯一性,所以这里进行判断是否有name重复,有则不符合,不进行注册
err = -EEXIST;
goto errout_locked;
}

if (family->id == GENL_ID_GENERATE) {       //判断传入的ID号是否为GENL_ID_GENERATE,若是则由内核分配一个空闲的ID号
u16 newid = genl_generate_id();
if (!newid) {
err = -ENOMEM;
goto errout_locked;
}
family->id = newid;
} else if (genl_family_find_byid(family->id)) { //不是内核分配时,判断是否已经存在该id号,有则不注册
err = -EEXIST;
goto errout_locked;
}

if (family->maxattr && !family->parallel_ops) { //根据注册的最大attr参数maxattr(注意仅仅是保存属性的地址而非内容)
family->attrbuf = kmalloc((family->maxattr+1) * sizeof(struct nlattr *), GFP_KERNEL);   //分配内核空间(GFP_KERNEL)
if (family->attrbuf == NULL) {
err = -ENOMEM;
goto errout_locked;
}
} else
family->attrbuf = NULL;

err = genl_validate_assign_mc_groups(family);   //调用genl_validate_assign_mc_groups()函数判断新增组播地址空间
/*
* 这里的genl_validate_assign_mc_groups接口一共做3件事:
* 1、判断注册family的group组播名的有效性
* 2、为该family分配组播地址比特位并将bit偏移保存到family->mcgrp_offset变量中(由于generic netlink中不同类型的family簇共用NETLINK_GENERIC协议类型的group组播地址空间,因此内核特别维护了几个全局变量mc_groups_longs、mc_groups和mc_group_start用以维护组播地址的比特位,另外对于几种特殊的family是已经分配了的。无需再行分配,例如这里的crtl控制器)
* 3、更新全局nl_table对应的NETLINK_GENERIC协议类型netlink的groups标识
*/
if (err)
goto errout_locked;

list_add_tail(&family->family_list, genl_family_chain(family->id)); //将family注册到链表中
genl_unlock_all();
/* send all events */
genl_ctrl_event(CTRL_CMD_NEWFAMILY, family, NULL, 0);           //调用genl_ctrl_event()函数向内核的控制器family发送CTRL_CMD_NEWFAMILY和CTRL_CMD_NEWMCAST_GRP命令消息
for (i = 0; i < family->n_mcgrps; i++)
genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, family, &family->mcgrps[i], family->mcgrp_offset + i);
return 0;
errout_locked:
genl_unlock_all();
errout:
return err;
}
EXPORT_SYMBOL(__genl_register_family);


static int __net_init genl_pernet_init(struct net *net)
{
//定义了genetlink内核套接字的配置
struct netlink_kernel_cfg cfg = {
.input  = genl_rcv, //消息处理函数
.flags  = NL_CFG_F_NONROOT_RECV,
.bind  = genl_bind, //套接字绑定函数
.unbind  = genl_unbind, //套接字解绑定函数
};
/* we'll bump the group number right afterwards */
net->genl_sock = netlink_kernel_create(net, NETLINK_GENERIC, &cfg); //内核套接字的注册,并将生成的套接字赋值到网络命名空间net的genl_sock中,以后就可以通过net->genl_sock来找到genetlink内核套接字了(这里netlink的说明可以参考文章末尾链接)
if (!net->genl_sock && net_eq(net, &init_net))
panic("GENL: Cannot initialize generic netlink\n");
if (!net->genl_sock)
return -ENOMEM;
return 0;
}


netlink说明参考博文:

http://blog.csdn.net/luckyapple1028/article/details/50839395(创建)

http://blog.csdn.net/luckyapple1028/article/details/50936563(通信)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  kernel 框架