Netlink 内核实现分析 1
阅读原文时间:2023年07月09日阅读: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;
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章