Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,在一般情况下,用户态和内核态通信会使用传统的Ioctl、sysfs属性文件或者procfs属性文件,这3种通信方式都是同步通信方式,由用户态主动发起向内核态的通信,内核无法主动发起通信。Netlink是一种异步全双工的通信方式,它支持由内核态主动发起通信,内核为Netlink通信提供了一组特殊的API接口,用户态则基于socket API,内核发送的数据会保存在接收进程socket 的接收缓存中,由接收进程处理。

netlink 优点:

  • 可以由内核发起, 用户进程可以使用IO复用模型
  • 支持组播,即内核态可以将消息发送给多个接收进程

Netlink 子系统初始化流程

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 套接字用于和应用层通信;以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;
}

Netlink 内核实现分析 1的更多相关文章

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

    在前一篇博文<Netlink 内核实现分析(一):创建>中已经较为具体的分析了Linux内核netlink子系统的初始化流程.内核netlink套接字的创建.应用层netlink套接字的创 ...

  2. Netlink 内核实现分析 3

    Netlink IPC 数据结构 #define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_UNUSED 1 /* Unuse ...

  3. Netlink 内核实现分析 2

    netlink 应用层如何创建socket 应用层通过socket()系统调用创建Netlink套接字,socket系统调用的第一个参数可以是AF_NETLINK或PF_NETLINK(在Linux系 ...

  4. Netlink 内核实现分析 4

    netlink 库函数: http://www.infradead.org/~tgr/libnl/doc/core.html#core_netlink_fundamentals #define NET ...

  5. MINIX3 内核时钟分析

    MINIX3 内核时钟分析  4.1 内核时钟概要  先想想为什么 OS 需要时钟?时钟是异步的一个非常重要的标志,设想一下,如 果我们的应用程序需要在多少秒后将触发某个程序或者进程,我们该怎么做到? ...

  6. mkimage工具 加载地址和入口地址 内核启动分析

    第三章第二节 mkimage工具制作Linux内核的压缩镜像文件,需要使用到mkimage工具.mkimage这个工具位于u-boot-2013. 04中的tools目录下,它可以用来制作不压缩或者压 ...

  7. 第3阶段——内核启动分析之start_kernel初始化函数(5)

    内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只是浅尝辄止的描述一下函数的功能,很多函数 ...

  8. 几个常用内核函数(《Windows内核情景分析》)

    参考:<Windows内核情景分析> 0x01  ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...

  9. [1]windows 内核情景分析---说明

    本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...

随机推荐

  1. jsoncpp笔记

    Json Json是一种轻量级数据交换格式,可以表示数字,字符串,布尔值,null,数组,键值对: { "encoding" : "UTF-8", " ...

  2. centOS7 查看防火墙状态 开放端口

    一.防火墙的开启.关闭.禁用命令 (1)设置开机启用防火墙:systemctl enable firewalld.service (2)设置开机禁用防火墙:systemctl disable fire ...

  3. fedora30平台安装docker 19.03

    一,下载docker 1,说明:docker的打包对于fedora的支持很及时, 所以在fedora 30/31上都可以直接使用官方的rpm包 下载地址: https://download.docke ...

  4. doment ready事件和load事件的区别及实现

    从2017年9月至今工作中大部分使用react,前端渲染的一些基础性知识记忆不是很深刻了.面试**公司的时候,碰到"document ready和load的区别,以及document rea ...

  5. Jmeter入门(3)- Jmeter录制脚本

    一. 录制web端 1. Badboy的介绍和安装 1.1 使用第三方工具Badboy来录制. 免费的web自动化测试工具 一个浏览器模拟工具 主要进行脚本的录制和回访,和对录制脚本进行调试,可以将脚 ...

  6. 对于app监测root权限或者强制升级

    只做分享学习 以下方法对于大多数Root检测的App (不限于仅银行类App),均适用. 另,"主用户 + Island用户" 情况下: 如果App仅安装在 Island下,当绕不 ...

  7. 进程相关的API函数

    0x01. ID与句柄 如果我们成功创建一个进程之后,CreateProcess会返回四个数据,id和句柄 句柄的话就是 当前进程私有的句柄表索引(这是当前进程,给别进程也没用) 每个进程都有一张自己 ...

  8. SpringBoot+HikariCP+Dropwizard-Metrics统计连接池使用情况

    SpringBoot+HikariCP+Dropwizard-Metrics统计连接池使用情况 背景,HikariCP是Java目前使用最广的连接池工具类,SpringBoot默认也是用这个,现在想获 ...

  9. 解放双手,不写SQL!一个开源mybatis神器

    什么是通用 Mapper? 它是一个可以方便的使用 Mybatis 进行单表的增删改查优秀开源产品.它使用拦截器来实现具体的执行 Sql,完全使用原生的 Mybatis 进行操作.在 Github 上 ...

  10. 【源码】spring循环引用

    spring在单例,非构造方法注入的情况下允许循环依赖 1.循环依赖 a引用b,b引用a.a创建的时候需要b,但是b没有创建,需要先去创建b,b创建的时候又没有a,这就出现的循环依赖的问题 2.为什么 ...