四、无线信息传递——Generic netlink(二、通信)

本文深入探讨Generic netlink在内核与应用层间的通信,包括内核代码中Generic netlink的创建、应用层代码创建及通信流程。通过示例详细解释了数据包的封装、用户查询Family ID、发送与接收消息的过程。
摘要由CSDN通过智能技术生成

系列说明

  仍旧是系列二:

  • 1、无线驱动信息传递框架:说明无线信息传递的步骤流程以及各程序块之间的联系;
  • 2、generic Netlink信号传递机制:hostapd与无线驱动之间的信息传递机制
  • 3、以ssid为例说明用户将user space中的ssid配置内容传递至kernel space的流程:以此系统地了解整个无线信息传递流程。

  上一篇文章是对generic netlink的初始化和主要结构体,接口的认识。这一篇文章将主要以一个例子来系统地说明Generic netlink代码实现和原理,以便后续实际工作中的应用。
!!!这一篇文章同样也是参考前一篇文章中的链接内容加上一些自己的理解,需要真正了解的可以点击上一章的链接。

这一章的主要说明内容:

  • 1、实现例子功能说明;
  • 2、内核代码中Generic netlink的创建;
  • 3、应用层中Generic netlink的代码创建;
  • 4、应用层与内核层的通信代码流程说明。

例子代码附件:
http://download.csdn.net/detail/xpbob/9619686

一、例子功能说明

  应用层程序接收用户的输入“字符串”和“数据”向内核发送,内核接收后回发应用层,应用层通过终端打印输出。

二、内核代码中Generic netlink的创建示例

  generic netlink在内核代码中的实现分为以下几步:

  • 1、定义Genetlink——主要定义上文中的两个结构体,分别为genl_family和genl_ops(这里先对内核为发送端,封包进行说明;接收后续说明);
  • 2、内核注册Genetlink——主要对内核中Genetlink的接口调用流程说明;(同样也仅对内核为发送端,封包的接口说明)

1.2、定义Genetlink结构体

  在前文中已经对主要的4个Genetlink结构体进行了说明,genlmsghdr,genl_family,genl_ops和genl_info。
  因为这里只说明内核端作为发送端的结构体定义,所以用于解析genetlink消息头的genlmsghdr和用于处理接收消息的这里在先不进行说明。主要是对genl_family和genl_ops两个接口说明。

定义例子使用的family:

enum {
    DEMO_CMD_ATTR_UNSPEC = 0,
    DEMO_CMD_ATTR_MESG,  /* demo message  */        //msg属性
    DEMO_CMD_ATTR_DATA,  /* demo data */            //data属性
    __DEMO_CMD_ATTR_MAX,
};
#define DEMO_CMD_ATTR_MAX (__DEMO_CMD_ATTR_MAX - 1) 

static const struct nla_policy demo_cmd_policy[DEMO_CMD_ATTR_MAX+1] = {
    [DEMO_CMD_ATTR_MESG] = { .type = NLA_STRING },      //定义msg类型为NLA_STRING(字符串类型)
    [DEMO_CMD_ATTR_DATA] = { .type = NLA_S32 },         //定义data类型为NLA_S32(有符号32位数)
};

static struct genl_family demo_family = {
    .id   = GENL_ID_GENERATE,               //表示由内核统一分配
    .name  = DEMO_GENL_NAME,                //内核的唯一genetlink标志名
    .version = DEMO_GENL_VERSION,               //版本号(可以为1,2)
    .maxattr = DEMO_CMD_ATTR_MAX,               //内核由此决定为其分配缓存空间大小
};

定义例子使用的genl_ops:

enum {
    DEMO_CMD_UNSPEC = 0, /* Reserved */
    DEMO_CMD_ECHO,   /* user->kernel request/get-response */    //定义显示cmd的id号
    DEMO_CMD_REPLY,   /* kernel->user event */          //定义返回cmd的id号
    __DEMO_CMD_MAX,
};
#define DEMO_CMD_MAX (__DEMO_CMD_MAX - 1)

static const struct genl_ops demo_ops[] = {
    {
        .cmd  = DEMO_CMD_ECHO,                  //接收到的判断id(DEMO_CMD_ECHO)
        .doit  = demo_echo_cmd,                 //内核对应命令的执行回调函数
        .policy  = demo_cmd_policy,             //执行策略,上一模块的demo_cmd_policy中有说明
        .flags  = GENL_ADMIN_PERM,              //定义所需权限为admin
    },
};

  内核注册中genl_register_family_with_ops为主要入口。

static int __init demo_genetlink_init(void) 
{  
    int ret;  
    pr_info("demo generic netlink module %d init...\n", DEMO_GENL_VERSION);  

    ret = genl_register_family_with_ops(&demo_family, demo_ops);            //注册family和ops(由于genl_register_family_with_ops在前一文中已经说明,相同的不再赘述,所以这里主要说明不是组播发送的操作)
    if (ret != 0) {  
        pr_info("failed to init demo generic netlink example module\n");  
        return ret;
    }  
    pr_info("demo generic netlink module init success\n");  
    return 0; 
}  
//其后genl_register_family_with_ops调用genl_register_family和genl_register_ops
//而genl_register_family和genl_register_ops中调用genl_ctrl_event,这个接口对是不是组播发送会有所区别,在下面的代码中进行说明
/*
 * 在发送组播时:
 *        _genl_register_family_with_ops_grps调用genl_ctrl_event的方式是
 * 
 *           genl_ctrl_event(CTRL_CMD_NEWFAMILY, family, NULL, 0);
 *           for (i = 0; i < family->n_mcgrps; i++)
 *                 genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, family, &family->mcgrps[i], family->mcgrp_offset + i);
 * 发送指定程序时只调用用一次的genl_ctrl_event()
*/
static int genl_ctrl_event(int event, struct genl_family *family,
      const struct genl_multicast_group *grp,
      int grp_id)
{
    struct sk_buff *msg;
    /* genl is still initialising */
    if (!init_net.genl_sock)                    //判断是否已经注册了控制器CTRL簇
        return 0;
    switch (event) {
    case CTRL_CMD_NEWFAMILY:
    case CTRL_CMD_DELFAMILY:
        WARN_ON(grp);
        msg = ctrl_build_family_msg(family, 0, 0, event);   //创建单播控制器family
        break;
    case CTRL_CMD_NEWMCAST_GRP:
    case CTRL_CMD_DELMCAST_GRP:
        BUG_ON(!grp);
        msg = ctrl_build_mcgrp_msg(family, grp, grp_id, 0, 0, event);   //创建组播控制器family
        break;
    default:
        return -EINVAL;

    //下面的genlmsg_multicast_netns和genlmsg_multicast_allns接口,不论哪一种情况,最后都是调用nlmsg_multicast()函数。不过这里有一点需要注意的就是这里的第三个入参portid为0,这是为了防止向发送端发送报文,这也就表明内核控制器簇套接字是不会接受该广播报文的(内核也不应该接收,否则会让内核“恐慌”)。
    if (IS_ERR(msg))
        return PTR_ERR(msg);
    if (!family->netnsok) {                     //根据是否支持net命名空间来选择发送的流程
        genlmsg_multicast_netns(&genl_ctrl, &init_net, msg, 0, 0, GFP_KERNEL);  //指定了向init_net发送
    } else {
        rcu_read_lock();
        genlmsg_multicast_allns(&genl_ctrl, msg, 0, 0, GFP_ATOMIC); //会向所有命名空间的控制器簇发送消息
        rcu_read_unlock();
    }
}
static struct sk_buff *ctrl_build_family_msg(struct genl_family *family,
          u32 portid, int seq, u8 cmd)
{
    struct sk_buff *skb; 
    int err;
    skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); //创建netlink类型的skb,参数分别为消息长度和内存空间分配类型
    if (skb == NULL)
        return ERR_PTR(-ENOBUFS);
    err = ctrl_fill_info(family, portid, seq, 0, skb, cmd); //填充数据包数据
    if (err < 0) {
        nlmsg_free(skb);
        return ERR_PTR(err);
    }
    return skb;
}

//从下面的下层函数调用中可以看出:总共预留的空间为NLMSG_ALIGN(NLMSG_HDRLEN+NLMSG_DEFAULT_SIZE)
static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
{
    return alloc_skb(nlmsg_total_size(payload), flags);
}

static inline int nlmsg_total_size(int payload)
{
    return NLMSG_ALIGN(nlmsg_msg_size(payload));
}

static inline int nlmsg_msg_size(int payload)
{
    return NLMSG_HDRLEN + payload;
}

  下面进行说明主要的接口ctrl_fill_info进行数据包的填充数据的代码分段说明,程序说明时中间会穿插说明调用到的接口。最后结合数据包框图。

/*
 * 参数讲解:
 *       family:为新注册的demo_family;
 *       portid: 表示消息的发送端为内核;
 *       seq:  发送消息序号为0;
 *       cmd:   命令id号,当前调用到的命令号为CTRL_CMD_NEWFAMILY
*/
static int ctrl_fill_info(struct genl_family *family, u32 portid, u32 seq,
     u32 flags, struct sk_buff *skb, u8 cmd)
{
    void *hdr;
    hdr = genlmsg_put(skb, portid, seq, &genl_ctrl, flags, cmd);        //初始化netlink消息头和genetlink消息头(下部分代码说明)
    if (hdr == NULL)
        return -1;

  穿插说明genlmsg_put接口


void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
    struct genl_family *family, int flags, u8 cmd)
{
    struct nlmsghdr *nlh;
    struct genlmsghdr *hdr;
    nlh = nlmsg_put(skb, portid, seq, family->id, GENL_HDRLEN + family->hdrsize, flags); //向skb缓冲区中获取消息头空间并且初始化netlink消息头,第5个参数为genetlink消息头和用户私有消息头(这里并未使用)的总空间,,实际调用的函数为__nlmsg_put()
    if (nlh == NULL)
        return NULL;

    hdr = nlmsg_data(nlh); //通过宏nlmsg_data获取genetlink消息头的地址,前文说过genetlink消息头中包含genl_family内容
    hdr->cmd = cmd; //消息的cmd命令,例子中为CTRL_CMD_NEWFAMILY;
    hdr->version = family->version; //genl_ctrl family簇的version
    hdr->reserved = 0; //0
    return (char *) hdr + GENL_HDRLEN; //返回消息用户私有头(若有)或实际载荷的首地址

}

struct nlmsghdr * __nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int type, int len, int flags)
{
    struct nlmsghdr *nlh;
    int size = nlmsg_msg_size(len);
    nlh = (struct nlmsghdr *)skb_put(skb, NLMSG_ALIGN(size)); //分配的空间大小为size = 传入的len长度 + netlink消息头的长度
    nlh->nlmsg_type = type; //例子中:内核genl_ctrl family簇的ID 号GENL_ID_CTRL
    nlh->nlmsg_len = size; //例子中:消息长度,即genetlink头+用户私有头+netlink头的长度总和
    nlh->nlmsg_flags = flags; //例子中:0
    nlh->nlmsg_pid = portid; //例子中:发送端的ID号为0,表示由内核发送
    nlh->nlmsg_seq = seq; //例子中:0
    if (!__builtin_constant_p(size) || NLMSG_ALIGN(size) - size != 0) //将内存对齐用的空白区刷为0
        memset(nlmsg_data(nlh) + len, 0, NLMSG_ALIGN(size) - size);
    return nlh;
}

  至此完成数据包的netlink头和genetlink头填充操作,如下图所示:
这里写图片描述
  接下来进行填充实际数据,仍然是ctrl_fill_info接口接下来的代码。


//将新注册的family结构中的几个字段都填充到了消息中,包括name、id号、版本号、私有头长度以及maxattr(注意属性需要一一对应)
if (nla_put_string(skb, CTRL_ATTR_FAMILY_NAME, family->name) || //调用的函数nla_put_string、nla_put_u16和nla_put_u32都是nla_put()的封装
     nla_put_u16(skb, CTRL_ATTR_FAMILY_ID, family->id) || //nla_put实际调用的是__nla_put(),下面进行说明
     nla_put_u32(skb, CTRL_ATTR_VERSION, family->version) ||
     nla_put_u32(skb, CTRL_ATTR_HDRSIZE, family->hdrsize) ||
     nla_put_u32(skb, CTRL_ATTR_MAXATTR, fa
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值