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

系列说明

  上一篇说明了无线信息的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(一、初始化)_第1张图片
  从上图中可以看出Generic Netlink也还是需要借助Netlink来完成用户环境和内核环境的信息传递。用户层发信息先至Netlink,然后再转给Generic netlink分支,再至内核;同样的内核发信息先发给Generic netlink分支,然后再通过netlink传给用户层。
  框图中Ctrl控制器是一种特殊类型的Genetlink协议族,它用于用户空间通过Genetlink簇名查找对应的ID号。
  以下为netlink类型的消息结构:
三、无线信息传递——Generic Netlink(一、初始化)_第2张图片
三、无线信息传递——Generic Netlink(一、初始化)_第3张图片
三、无线信息传递——Generic Netlink(一、初始化)_第4张图片
  具象化地来说,上面的三幅图内容如下图说明所示:
三、无线信息传递——Generic Netlink(一、初始化)_第5张图片

二、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解析的图。
三、无线信息传递——Generic Netlink(一、初始化)_第6张图片
  从接收到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的基本几段程序来进行初始化说明。
三、无线信息传递——Generic Netlink(一、初始化)_第7张图片

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(通信)

你可能感兴趣的:(beacon快速配置,kernel,框架)