三、无线信息传递——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(通信)
相关文章推荐
- 四、无线信息传递——Generic netlink(二、通信)
- Generic+Netlink内核实现分析(一):初始化
- 五、无线信息传递——通过ssid传递对hostapd传输方式的具体说明
- 二、无线信息传递——user space下的hostapd至kernel space的信息传递
- Generic Netlink内核实现分析(一):初始化
- 一、无线信息传递——user space下的hostapd
- 在与WCF服务交互过程中传递用户信息等附加信息
- //实现中等难度通讯录。需求: //1、定义联系人类Contact。实例变量:姓名(拼音,首字母大写)、性别、电话号码、住址、分组名称、年龄。方法:自定义初始化方法(姓名、电话号码)、显示联系人信息
- 获取地址栏传递信息方法汇总
- JSON Web Token-在Web应用间安全地传递信息
- 科学家首次远程传递“心灵感应”信息
- MFC copydata实现两个进程间信息传递。
- GetAdaptersInof获取本地连接的网卡信息,排除无线网卡和虚拟网卡
- 在jsp页面中使用list列表来传递信息到action
- c++中巧用函数对象传递附加信息
- JSON Web Token - 在Web应用间安全地传递信息
- 信息的传递 认识自身5
- 关于Android如何给传递线程初始化数据
- Qt获取IP地址、MAC地址等网卡信息,区分本地网卡、无线网卡和虚拟网卡
- Liferay 6.2 改造系列之六:修改系统初始化信息