Netlink 0007 --- 创建实现分析

netlink原理及应用 netlink是一种基于网络的通信机制,允许内核内部、内核与用户态应用之间甚至用户态应用之间进行通信;netlink的主要作用是内核与用户态之间通信;它的思想是,基于BSD的socket使用网络框架在内核和用户态之间进行通信;如何在中新增netlink协议?参考nl80211模块初始化时,通过注册通用netlink协议族,将命令以及处理函数进行注册;int err; 阅读详情

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

1、双向全双工异步传输,支持由内核主动发起传输通信,而不需要用户空间出发(例如使用ioctl这类的单工方式)。如此用户空间在等待内核某种触发条件满足时就无需不断轮询,而异步接收内核消息即可。

2、支持组播传输,即内核态可以将消息发送给多个接收进程,这样就不用每个进程单独来查询了。

Netlink架构框图如下:

目前在Linux 4.1.x 的主线内核版本中,已经有许多内核模块使用netlink 机制,其中驱动模型中使用的uevent 就是基于netlink 实现。目前 netlink 协议族支持32种协议类型,它们定义在 include/uapi/linux/netlink.h 中:

#define NETLINK_ROUTE        0    /* Routing/device hook                */
#define NETLINK_UNUSED        1    /* Unused number                */
#define NETLINK_USERSOCK    2    /* Reserved for user mode socket protocols     */
#define NETLINK_FIREWALL    3    /* Unused number, formerly ip_queue        */
#define NETLINK_SOCK_DIAG    4    /* socket monitoring                */
#define NETLINK_NFLOG        5    /* netfilter/iptables ULOG */
#define NETLINK_XFRM        6    /* ipsec */
#define NETLINK_SELINUX        7    /* SELinux event notifications */
#define NETLINK_ISCSI        8    /* Open-iSCSI */
#define NETLINK_AUDIT        9    /* auditing */
#define NETLINK_FIB_LOOKUP    10    
#define NETLINK_CONNECTOR    11
#define NETLINK_NETFILTER    12    /* netfilter subsystem */
#define NETLINK_IP6_FW        13
#define NETLINK_DNRTMSG        14    /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT    15    /* Kernel messages to userspace */
#define NETLINK_GENERIC        16
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT    18    /* SCSI Transports */
#define NETLINK_ECRYPTFS    19
#define NETLINK_RDMA        20
#define NETLINK_CRYPTO        21    /* Crypto layer */
 
#define NETLINK_INET_DIAG    NETLINK_SOCK_DIAG
 
#define MAX_LINKS 32</span>

现在4.1.x 的内核版本中已经定义了22种协议类型,其中NETLINK_ROUTE是用于设置和查询路由表等网络核心模块的,NETLINK_KOBJECT_UEVENT是用于uevent消息通信的......
对于在实际的项目中,可能会有一些定制化的需求,以上这几种专用的协议类型无法满足,这时可以在不超过最大32种类型的基础之上自行添加。但是一般情况下这样做有些不妥,于是内核开发者就设计了一种通用netlink 协议类型(Generic Netlink)NETLINK_GENERIC,它就是一个Netlink复用器,便于用户自行扩展子协议类型(后面我会使用该Generic Netlink 编写一个示例程序用于演示内核和用户空间的通信)。

下面以linux 4.1.12版本的内核源码为例来分析Netlink的具体创建和通信流程。

一、Netlink子系统初始化
内核Netlink的初始化在系统启动阶段完成,初始化代码在af_netlink.c的netlink_proto_init()函数中,整个初始化流程如下:

图1 netlink子系统初始化

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));
 
    nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);
    if (!nl_table)
        goto panic;
 
    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);
    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;
    struct hlist_head    mc_list;
    struct listeners __rcu    *listeners;
    unsigned int        flags;
    unsigned int        groups;
    struct mutex        *cb_mutex;
    struct module        *module;
    int            (*bind)(struct net *net, int group);
    void            (*unbind)(struct net *net, int group);
    bool            (*compare)(struct net *net, struct sock *sock);
    int            registered;
};

这里的hash(哈希表)用来索引同种协议类型的不同netlink套接字实例,mc_list为多播使用的sock散列表,listeners为监听者掩码,groups为协议支持的最大多播组数量,同时还定义了一些函数指针,它们会在内核首次创建netlink时被赋值,后续应用层创建和绑定socket时调用到。
回到初始化函数中,接下来初始化应用层使用的NETLINK_USERSOCK协议类型的netlink(用于应用层进程间通信);然后调用sock_register向内核注册协议处理函数,即将netlink的socket创建处理函数注册到内核中,如此以后应用层创建netlink类型的socket时将会调用该协议处理函数,其中netlink_family_ops函数的定义如下:

static const struct net_proto_family netlink_family_ops = {
    .family = PF_NETLINK,
    .create = netlink_create,
    .owner    = THIS_MODULE,    /* for consistency 8) */
};

这样以后应用层创建PF_NETLINK(AF_NETLINK)类型的socket()系统调用时将由netlink_create()函数负责处理。
再次回到初始化函数中,接下来调用register_pernet_subsys向内核所有的网络命名空间注册”子系统“的初始化和去初始化函数,这里的"子系统”并非指的是netlink子系统,而是一种通用的处理方式,在网络命名空间创建和注销时会调用这里注册的初始化和去初始化函数(当然对于已经存在的网络命名空间,在注册的过程中也会调用其初始化函数),后文中创建各种协议类型的netlink也是通过这种方式实现的。这里netlink_net_ops定义如下:

static struct pernet_operations __net_initdata netlink_net_ops = {
    .init = netlink_net_init,
    .exit = netlink_net_exit,
};

其中netlink_net_init()会在文件系统中位每个网络命名空间创建一个proc入口,而netlink_net_exit()就是则销毁之。下面回来看netlink_proto_init()初始化函数的最后,调用rtnetlink_init()创建NETLINK_ROUTE协议类型的netlink,该种类型的netlink才是当初内核设计netlink的初衷,它用来传递网络路由子系统、邻居子系统、接口设置、防火墙等消息。至此整个netlink子系统初始化完成,还是比较直观易懂的,接下来就需要关注如何使用它进行通信了。

二、内核Netlink套接字
内核中各种协议类型的netlink分别在不同的模块中进行创建和初始化,我以前文中的NETLINK_ROUTE为例来分析一下内核中netlink套接字的创建流程。下面首先来看一下内核netlink使用到的几个关键数据结构:

1、内核netlink配置结构:struct netlink_kernel_cfg

/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
    unsigned int    groups;
    unsigned int    flags;
    void        (*input)(struct sk_buff *skb);
    struct mutex    *cb_mutex;
    int        (*bind)(struct net *net, int group);
    void        (*unbind)(struct net *net, int group);
    bool        (*compare)(struct net *net, struct sock *sk);
};


该结构包含了内核netlink的可选参数。其中groups用于指定最大的多播组;flags成员可以为NL_CFG_F_NONROOT_RECV或NL_CFG_F_NONROOT_SEND,这两个符号前者用来限定非超级用户是否可以绑定到多播组,后者用来限定非超级用户是否可以发送组播;input指针用于指定回调函数,该回调函数用于接收和处理来自用户空间的消息(若无需接收来自用户空间的消息可不指定),最后的三个函数指针实现sock的绑定和解绑定等操作,会添加到nl_table对应的项中去。

2、netlink属性头:struct nlattr

struct nlattr {
    __u16           nla_len;
    __u16           nla_type;
};

netlink的消息头后面跟着的是消息的有效载荷部分,它采用的是格式为“类型——长度——值”,简写TLV。其中类型和长度使用属性头nlattr来表示。其中nla_len表示属性长度;nla_type表示属性类型,它可以取值为以下几种类型(定义在include\net\netlink.h中):

enum {
    NLA_UNSPEC,
    NLA_U8,
    NLA_U16,
    NLA_U32,
    NLA_U64,
    NLA_STRING,
    NLA_FLAG,
    NLA_MSECS,
    NLA_NESTED,
    NLA_NESTED_COMPAT,
    NLA_NUL_STRING,
    NLA_BINARY,
    NLA_S8,
    NLA_S16,
    NLA_S32,
    NLA_S64,
    __NLA_TYPE_MAX,
};


其中比较常用的NLA_UNSPEC表示类型和长度未知、NLA_U32表示无符号32位整形数、NLA_STRING表示变长字符串、NLA_NESTED表示嵌套属性(即包含一层新的属性)。


3、netlink有效性策略:struct nla_policy

struct nla_policy {
    u16        type;
    u16        len;
};


netlink协议可以根据消息属性定义其特定的消息有效性策略,即对于某一种属性,该属性的期望类型是什么,内核将在收到消息以后对该消息的属性进行有效性判断(如果不设定len值,就不会执行有效性检查),只有判断一直的消息属性才算是合法的,否则只会默默的丢弃。这种有效性属性使用nla_policy来描述,一般定义为一个有效性对象数组(当前这种netlink协议中的每一种attr属性(指定不是属性类型,而是用户定义的属性)有一个对应的数组项),这里type值同struct nlattr中的nla_type,len字段表示本属性的有效载荷长度。


4、netlink套接字结构:netlink_sock

struct netlink_sock {
    /* struct sock has to be the first member of netlink_sock */
    struct sock        sk;
    u32            portid;
    u32            dst_portid;
    u32            dst_group;
    u32            flags;
    u32            subscriptions;
    u32            ngroups;
    unsigned long        *groups;
    unsigned long        state;
    size_t            max_recvmsg_len;
    wait_queue_head_t    wait;
    bool            bound;
    bool            cb_running;
    struct netlink_callback    cb;
    struct mutex        *cb_mutex;
    struct mutex        cb_def_mutex;
    void            (*netlink_rcv)(struct sk_buff *skb);
    int            (*netlink_bind)(struct net *net, int group);
    void            (*netlink_unbind)(struct net *net, int group);
    struct module        *module;
#ifdef CONFIG_NETLINK_MMAP
    struct mutex        pg_vec_lock;
    struct netlink_ring    rx_ring;
    struct netlink_ring    tx_ring;
    atomic_t        mapped;
#endif /* CONFIG_NETLINK_MMAP */
 
    struct rhash_head    node;
    struct rcu_head        rcu;
};

本结构用于描述一个netlink套接字,其中portid表示本套接字自己绑定的id号,对于内核来说它就是0,dst_portid表示目的id号,ngroups表示协议支持多播组数量,groups保存组位掩码,netlink_rcv保存接收到用户态数据后的处理函数,netlink_bind和netlink_unbind用于协议子协议自身特有的绑定和解绑定处理函数。

5、创建内核netlink套接字
现在查看rtnetlink_net_init()函数来分析NETLINK_ROUTE类型netlink套接字的创建流程:首先回到子系统初始化函数netlink_proto_init()的最后,来查看rtnetlink_init()函数的执行流程:

图2 内核netlink套接字创建流程

void __init rtnetlink_init(void)
{
    if (register_pernet_subsys(&rtnetlink_net_ops))
        panic("rtnetlink_init: cannot initialize rtnetlink\n");
 
    ...
}

这里的手法前文中已经见过了,这里将rtnetlink的init函数和exit函数注册到内核的每个网络命名空间中,对于已经存在的网络命名空间会调用其中个的init函数,这里就是rtnetlink_net_init()函数了。

static struct pernet_operations rtnetlink_net_ops = {
    .init = rtnetlink_net_init,
    .exit = rtnetlink_net_exit,
};
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()函数内部:

netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
{
    return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
}


它其实就是__netlink_kernel_create()的一个封装而已,__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;
 
    /*
     * We have to just have a reference on the net from sk, but don't
     * get_net it. Besides, we cannot get and then put the net here.
     * So we create one inside init_net and the move it to net.
     */
 
    if (__netlink_create(&init_net, sock, cb_mutex, unit) < 0)
        goto out_sock_release_nosk;


这里首先进行简单的参数判断之后就调用sock_create_lite()函数创建了一个以PF_NETLINK为地址族的SOCK_DGRAM类型的socket套接字,其协议类型就是作为参数传入的NETLINK_ROUTE。然后该函数调用最核心的__netlink_create()函数向内核初始化netlink套接字(其实在下文中将会看到用户态创建netlink套接字也是间接调用到该函数):

static int __netlink_create(struct net *net, struct socket *sock,
                struct mutex *cb_mutex, int protocol)
{
    struct sock *sk;
    struct netlink_sock *nlk;
 
    sock->ops = &netlink_ops;
 
    sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto);
    if (!sk)
        return -ENOMEM;
 
    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);
#ifdef CONFIG_NETLINK_MMAP
    mutex_init(&nlk->pg_vec_lock);
#endif
 
    sk->sk_destruct = netlink_sock_destruct;
    sk->sk_protocol = protocol;
    return 0;
}


首先将sock的操作函数集指针设置为netlink_ops,这在后面消息通讯时会详细分析,然后分配sock结构并进行初始化,主要包括初始化发送接收消息队列、数据缓存、等待队列和互斥锁等等,最后设置sk_destruct回调函数和协议类型。再回到__netlink_kernel_create()函数中继续分析:
    

sk = sock->sk;
    sk_change_net(sk, net);
 
    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;
 
    if (netlink_insert(sk, 0))
        goto out_sock_release;
 
    nlk = nlk_sk(sk);
    nlk->flags |= NETLINK_KERNEL_SOCKET;

这里有一点值得注意的就是前面在调用__netlink_create()时分配struct sock结构实例使用的是init_net命名空间,这里会调用sk_change_net将网络命名空间转移回到当前的net命名空间(至于为什么要这样做,注释中有说明,大概意思是当前的上下文中无法对net命名空间执行get_net操作,可能是防止内核还在初始化的过程中不支持这样的操作,具体原因还不是很理解)。
接下来校验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套接字是一个内核套接字。
    

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协议类型的数组项,首先会判断是否已经先有同样协议类型的已经注册过了,如果有就不再初始化该表项了,直接释放刚才申请的listeners内存空间然后递增注册个数并返回。这里假定是首次注册NETLINK_ROUTE协议类型的套接字,这里依次初始化了nl_table表项中的groups、listeners、cb_mutex、module、bind、unbind、flags和compare字段。通过前文中cfg的实例分析,这里的初始化的值分别如下:

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套接字中的sock指针,它会在最先前的rtnetlink_net_init()函数中被保存到net->rtnl中去,注意只有NETLINK_ROUTE协议类型的套接字才会执行这个步骤,因为网络命名空间中专门为其预留了一个sock指针。
至此内核NETLINK_ROUTE套接字创建完成,下面来看一下应用层是如何创建netlink套接字的。

三、应用层Netlink套接字
应用层通过标准的sock API即可使用Netlink完成通信功能(如socket()、sendto()、recv()、sendmsg()和recvmsg()等)。首先来看一些基本的数据结构及创建流程:

图3 用户层netlink套接字创建流程
1、套接字地址数据结构sockaddr_nl 

struct sockaddr_nl {
    __kernel_sa_family_t    nl_family;    /* AF_NETLINK    */
    unsigned short    nl_pad;        /* zero        */
    __u32        nl_pid;        /* port ID    */
           __u32        nl_groups;    /* multicast groups mask */
};


其中(1)nl_family始终为AF_NETLINK;(2)nl_pad始终为0;(3)nl_pid为netlink套接字的单播地址,在发送消息时用于表示目的套接字的地址,在用户空间绑定时可以指定为当前进程的PID号(对于内核来说这个值为0)或者干脆不设置(在绑定bind时由内核调用netlink_autobind()设置为当前进程的PID),但需要注意的是当用户同一个进程中需要创建多个netlink套接字时则必须保证这个值是唯一的(一般在多线程中可以使用”pthread_self() << 16 | getpid()“这样的方法进行设置);(4)nl_groups表示组播组。在发送消息时用于表示目的多播组,在绑定地址时用于表示加入的多播组。这里nl_groups为一个32位无符号数,其中的每一位表示一个   多播组,一个netlink套接字可以加入多个多播组用以接收多个多播组的多播消息(最多支持32个)。


2、创建Netlink套接字
应用层通过socket()系统调用创建Netlink套接字,socket系统调用的第一个参数可以是AF_NETLINK或PF_NETLINK(在Linux系统中它俩实际为同一种宏),第二个参数可以是SOCK_RAW或SOCK_DGRAM(原始套接字或无连接的数据报套接字),最后一个参为netlink.h中定义的协议类型,用户可以按需求自行创建上述不同种类的套接字。

例如调用 socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) 即创建了一个NETLINK_ROUTE类型的Netlink套接字。下面跟进这个系统调用,查看内核是如何为用户层创建这个套接字然后又做了哪些初始化动作:

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
    int retval;
    struct socket *sock;
    int flags;
 
    /* Check the SOCK_* constants for consistency.  */
    BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
    BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
    BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
    BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);
 
    flags = type & ~SOCK_TYPE_MASK;
    if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
        return -EINVAL;
    type &= SOCK_TYPE_MASK;
 
    if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
        flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
 
    retval = sock_create(family, type, protocol, &sock);
    if (retval < 0)
        goto out;
 
    retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
    if (retval < 0)
        goto out_release;
 
out:
    /* It may be already another descriptor 8) Not kernel problem. */
    return retval;
 
out_release:
    sock_release(sock);
    return retval;
}


该函数首先做了一些参数检查之后就调用sock_create()函数创建套接字,在创建完成后向内核申请描述符并返回该描述符。进入sock_create()函数内部,它是__sock_create()的一层封装(内核中往往前面带两个下划线的函数才是做事实的,嘿嘿),这里要注意的是调用时又多了两个个参数,一是当前进程绑定的网络命名空间,而是最后一个kern参数,这里传入0表明是从应用层创建的套接字。__sock_create()函数比较长,来分段分析之:

int sock_create(int family, int type, int protocol, struct socket **res)
{
    return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}
int __sock_create(struct net *net, int family, int type, int protocol,
             struct socket **res, int kern)
{
    int err;
    struct socket *sock;
    const struct net_proto_family *pf;
 
    /*
     *      Check protocol is in range
     */
    if (family < 0 || family >= NPROTO)
        return -EAFNOSUPPORT;
    if (type < 0 || type >= SOCK_MAX)
        return -EINVAL;
 
    /* Compatibility.
       This uglymoron is moved from INET layer to here to avoid
       deadlock in module load.
     */
    if (family == PF_INET && type == SOCK_PACKET) {
        static int warned;
        if (!warned) {
            warned = 1;
            pr_info("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
                current->comm);
        }
        family = PF_PACKET;
    }
这里依然是一些入参判断,非常直观,无需分析,继续往下:
    err = security_socket_create(family, type, protocol, kern);
    if (err)
        return err;
 
    /*
     *    Allocate the socket and allow the family to set things up. if
     *    the protocol is 0, the family is instructed to select an appropriate
     *    default.
     */
    sock = sock_alloc();
    if (!sock) {
        net_warn_ratelimited("socket: no more sockets\n");
        return -ENFILE;    /* Not exactly a match, but its the
                   closest posix thing */
    }
 
    sock->type = type;
首先对创建socket执行安全性检查,security_socket_create这个函数在内核没有启用CONFIG_SECURITY_NETWORK配置时是一个空函数直接返回0,这里先不考虑。接下来调用sock_alloc()分配socket实例,它会为其创建和初始化索引节点(inode)。然后将sock->type赋值为传入的SOCK_RAW。
#ifdef CONFIG_MODULES
    /* Attempt to load a protocol module if the find failed.
     *
     * 12/09/1996 Marcin: But! this makes REALLY only sense, if the user
     * requested real, full-featured networking support upon configuration.
     * Otherwise module support will break!
     */
    if (rcu_access_pointer(net_families[family]) == NULL)
        request_module("net-pf-%d", family);
#endif
 
    rcu_read_lock();
    pf = rcu_dereference(net_families[family]);
    err = -EAFNOSUPPORT;
    if (!pf)
        goto out_release;
在启用内核模块的情况下,这里会到内核net_families数组中查找该family(AF_NETLINK)是否已经注册,如果没有注册就会尝试加载网络子系统模块。其实在内核的netlink初始化函数中已经调用sock_register()完成注册了(见前文)。接下来从net_families数组中获取已经注册的struct net_proto_family结构实例,这里就是第一节中描述过的netlink_family_ops了。继续往下分析:
    /*
     * We will call the ->create function, that possibly is in a loadable
     * module, so we have to bump that loadable module refcnt first.
     */
    if (!try_module_get(pf->owner))
        goto out_release;
 
    /* Now protected by module ref count */
    rcu_read_unlock();
 
    err = pf->create(net, sock, protocol, kern);
    if (err < 0)
        goto out_module_put;
 
    /*
     * Now to bump the refcnt of the [loadable] module that owns this
     * socket at sock_release time we decrement its refcnt.
     */
    if (!try_module_get(sock->ops->owner))
        goto out_module_busy;
 
    /*
     * Now that we're done with the ->create function, the [loadable]
     * module can have its refcnt decremented
     */
    module_put(pf->owner);
    err = security_socket_post_create(sock, family, type, protocol, kern);
    if (err)
        goto out_sock_release;
    *res = sock;
 
    return 0;


这里先获取当前模块的引用计数并上锁,然后调用netlink协议的creat()钩子函数执行进一步的创建和初始化操作(这里就是netlink_family_ops中定义的netlink_create()了),完成之后就释放锁同时释放当前模块的引用计数并返回创建成功的socket。下面进入netlink_create()内部继续分析:

static int netlink_create(struct net *net, struct socket *sock, int protocol,
              int kern)
{
    struct module *module = NULL;
    struct mutex *cb_mutex;
    struct netlink_sock *nlk;
    int (*bind)(struct net *net, int group);
    void (*unbind)(struct net *net, int group);
    int err = 0;
 
    sock->state = SS_UNCONNECTED;
 
    if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
        return -ESOCKTNOSUPPORT;
 
    if (protocol < 0 || protocol >= MAX_LINKS)
        return -EPROTONOSUPPORT;
 
    netlink_lock_table();
#ifdef CONFIG_MODULES
    if (!nl_table[protocol].registered) {
        netlink_unlock_table();
        request_module("net-pf-%d-proto-%d", PF_NETLINK, protocol);
        netlink_lock_table();
    }
#endif
    if (nl_table[protocol].registered &&
        try_module_get(nl_table[protocol].module))
        module = nl_table[protocol].module;
    else
        err = -EPROTONOSUPPORT;
    cb_mutex = nl_table[protocol].cb_mutex;
    bind = nl_table[protocol].bind;
    unbind = nl_table[protocol].unbind;
    netlink_unlock_table();
 
    if (err < 0)
        goto out;
 
    err = __netlink_create(net, sock, cb_mutex, protocol);
    if (err < 0)
        goto out_module;
 
    local_bh_disable();
    sock_prot_inuse_add(net, &netlink_proto, 1);
    local_bh_enable();
 
    nlk = nlk_sk(sock->sk);
    nlk->module = module;
    nlk->netlink_bind = bind;
    nlk->netlink_unbind = unbind;
out:
    return err;
 
out_module:
    module_put(module);
    goto out;
}


首先将socket的状态标记为未连接,判断套接字的类型是否是SOCK_RAW或SOCK_DGRAM类型的,若不是就不能继续创建;接着判断该协议类型的netlink是否已经注册了,由于前文中内核在初始化netlink子系统时已经初始化了NETLINK_ROUTE内核套接字并向nl_table注册,所以这里的几个赋值结果如下:

cb_mutex = nl_table[NETLINK_ROUTE].cb_mutex = &rtnl_mutex; 
module = nl_table[NETLINK_ROUTE].module = THIS_MODULE;
bind = nl_table[NETLINK_ROUTE].bind = NULL;
unbind = nl_table[NETLINK_ROUTE].unbind = NULL;

接下来将调用__netlink_create()完成核心的创建初始化,这个函数在前面已经分析过了,就不进入继续分析了。再往下调用sock_prot_inuse_add添加协议的引用计数,最后完成赋值:

nlk->module = module = THIS_MODULE ;
nlk->netlink_bind = bind = NULL;
nlk->netlink_unbind = unbind = NULL;

至此用户态NETLINK_ROUTE类型的套接字就创建完成了。

3、绑定套接字
在创建完成套接字后需要调用bind()函数进行绑定,将该套接字绑定到一个特定的地址或者加入一个多播组中,以后内核或其他应用层套接字向该地址单播或向该多播组发送组播消息时即可通过recv()或recvmsg()函数接收消息了。绑定地址时需要使用到sockaddr_nl地址结构,如果使用使用单播则需要将地址本地地址信息填入nl_pid变量并设置nl_groups为0,如果使用多播则将nl_pid设置为0并填充nl_groups为多播地址,如下可将当前进程的PID号作为单播地址进行绑定:

struct sockaddr_nl local;
 
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
 
bind(fd, (struct sockaddr *) &local, sizeof(local));

其中bind()的第一个参数为刚创建的Netlink套接字描述符,第二个参数就是需要绑定的套接字地址,最后一个参数是地址的长度。这个绑定操作同创建TCP套接字类似,需要制定绑定的端口(或者由内核给指定一个亦可)。下面进入bind()系统调用分析整个绑定的过程:

图3 用户层netlink套接字绑定流程

/*
 *    Bind a name to a socket. Nothing much to do here since it's
 *    the protocol's responsibility to handle the local address.
 *
 *    We move the socket address to kernel space before we call
 *    the protocol layer (having also checked the address is ok).
 */
 
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err, fput_needed;
 
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        err = move_addr_to_kernel(umyaddr, addrlen, &address);
        if (err >= 0) {
            err = security_socket_bind(sock,
                           (struct sockaddr *)&address,
                           addrlen);
            if (!err)
                err = sock->ops->bind(sock,
                              (struct sockaddr *)
                              &address, addrlen);
        }
        fput_light(sock->file, fput_needed);
    }
    return err;
}
首先根据用户传入的fd文件描述符向内核查找对应的socket结构,然后将用户空间传入的地址struct sockaddr拷贝到内核中(会使用到copy_from_user()),接下来继续跳过安全检查函数security_socket_bind(),剩下的主要工作就交给了sock->ops->bind()注册函数了。在创建套接字时调用的__netlink_create()函数中已经将sock->ops赋值为netlink_ops了,来看一下这个结构实例:
static const struct proto_ops netlink_ops = {
    .family =    PF_NETLINK,
    .owner =    THIS_MODULE,
    .release =    netlink_release,
    .bind =        netlink_bind,
    .connect =    netlink_connect,
    .socketpair =    sock_no_socketpair,
    .accept =    sock_no_accept,
    .getname =    netlink_getname,
    .poll =        netlink_poll,
    .ioctl =    sock_no_ioctl,
    .listen =    sock_no_listen,
    .shutdown =    sock_no_shutdown,
    .setsockopt =    netlink_setsockopt,
    .getsockopt =    netlink_getsockopt,
    .sendmsg =    netlink_sendmsg,
    .recvmsg =    netlink_recvmsg,
    .mmap =        netlink_mmap,
    .sendpage =    sock_no_sendpage,
};
这个结构中的各个函数指针都会由系统调用根据套接字的协议类型间接调用到,此时就会调用到这里的netlink_bind()函数,这个函数较长,分段分析:
static int netlink_bind(struct socket *sock, struct sockaddr *addr,
            int addr_len)
{
    struct sock *sk = sock->sk;
    struct net *net = sock_net(sk);
    struct netlink_sock *nlk = nlk_sk(sk);
    struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
    int err;
    long unsigned int groups = nladdr->nl_groups;
    bool bound;
 
    if (addr_len < sizeof(struct sockaddr_nl))
        return -EINVAL;
 
    if (nladdr->nl_family != AF_NETLINK)
        return -EINVAL;
 
    /* Only superuser is allowed to listen multicasts */
    if (groups) {
        if (!netlink_allowed(sock, NL_CFG_F_NONROOT_RECV))
            return -EPERM;
        err = netlink_realloc_groups(sk);
        if (err)
            return err;
    }

可以看到,这里又将用户传入的地址类型强制转换成了sockaddr_nl类型的地址结构,然后做了一些参数的判断,接着如果用户设定了需要绑定的多播地址,这里会去检擦nl_table中注册的套接字是否已经设置了NL_CFG_F_NONROOT_RECV标识,如果没有设置将拒绝用户绑定到组播组,显然在前文中已经看到了NETLINK_ROUTE类型的套接字是设置了这个标识的,所以这里会调用netlink_realloc_groups分配组播空间,进入看一下:

static int netlink_realloc_groups(struct sock *sk)
{
    struct netlink_sock *nlk = nlk_sk(sk);
    unsigned int groups;
    unsigned long *new_groups;
    int err = 0;
 
    netlink_table_grab();
 
    groups = nl_table[sk->sk_protocol].groups;
    if (!nl_table[sk->sk_protocol].registered) {
        err = -ENOENT;
        goto out_unlock;
    }
 
    if (nlk->ngroups >= groups)
        goto out_unlock;
 
    new_groups = krealloc(nlk->groups, NLGRPSZ(groups), GFP_ATOMIC);
    if (new_groups == NULL) {
        err = -ENOMEM;
        goto out_unlock;
    }
    memset((char *)new_groups + NLGRPSZ(nlk->ngroups), 0,
           NLGRPSZ(groups) - NLGRPSZ(nlk->ngroups));
 
    nlk->groups = new_groups;
    nlk->ngroups = groups;
 out_unlock:
    netlink_table_ungrab();
    return err;
}


这里会比较验证一下当前套接字中指定的组播地址上限是否大于NETLINK_ROUTE套接字支持的最大地址(这里为RTNLGRP_MAX),由于这个套接字是前面刚刚创建的,所以nlk->ngroups = 0。
然后为其分配内存空间,分配的空间大小为NLGRPSZ(groups)(这是一个取整对齐的宏),分配完成后将新分配的空间清零,首地址保存在nlk->groups中,最后更新nlk->ngroups变量。回到netlink_bind()函数中继续往下分析:

  

 bound = nlk->bound;
    if (bound) {
        /* Ensure nlk->portid is up-to-date. */
        smp_rmb();
 
        if (nladdr->nl_pid != nlk->portid)
            return -EINVAL;
    }
 
    if (nlk->netlink_bind && groups) {
        int group;
 
        for (group = 0; group < nlk->ngroups; group++) {
            if (!test_bit(group, &groups))
                continue;
            err = nlk->netlink_bind(net, group + 1);
            if (!err)
                continue;
            netlink_undo_bind(group, groups, sk);
            return err;
        }
    }


接下来如果已经绑定过了,会检查新需要绑定的id号是否等于已经绑定的id号,若不相等则返回失败。接着如果netlink套接字子协议存在特有的bind函数且用户指定了需要绑定的组播地址,则调用之为其绑定到特定的组播组中去。现由于NETLINK_ROUTE套接字并不存在nlk->netlink_bind()函数实现,所以这里并不会调用。

    /* No need for barriers here as we return to user-space without
     * using any of the bound attributes.
     */
    if (!bound) {
        err = nladdr->nl_pid ?
            netlink_insert(sk, nladdr->nl_pid) :
            netlink_autobind(sock);
        if (err) {
            netlink_undo_bind(nlk->ngroups, groups, sk);
            return err;
        }
    }


如果本套接字并没有被绑定过(目前就是这种情况),这里会根据用户是否指定了单播的绑定地址来调用不同的函数。首先假定用户空间指定了单播的绑定地址,这里会调用netlink_insert()函数将这个套接字插入到nl_table[NETLINK_ROUTE]数组项的哈希表中去,同时设置nlk_sk(sk)->bound = nlk_sk(sk)->portid = nladdr->nl_pid。我们再假定用户空间没有设置单播的绑定地址,这里会调用netlink_autobind()动态的绑定一个地址,进入该函数简单的看一下:

static int netlink_autobind(struct socket *sock)
{
    struct sock *sk = sock->sk;
    struct net *net = sock_net(sk);
    struct netlink_table *table = &nl_table[sk->sk_protocol];
    s32 portid = task_tgid_vnr(current);
    int err;
    static s32 rover = -4097;
 
retry:
    cond_resched();
    rcu_read_lock();
    if (__netlink_lookup(table, portid, net)) {
        /* Bind collision, search negative portid values. */
        portid = rover--;
        if (rover > -4097)
            rover = -4097;
        rcu_read_unlock();
        goto retry;
    }
    rcu_read_unlock();
 
    err = netlink_insert(sk, portid);
    if (err == -EADDRINUSE)
        goto retry;
 
    /* If 2 threads race to autobind, that is fine.  */
    if (err == -EBUSY)
        err = 0;
 
    return err;
}


这里会首先尝试选用当前的进程ID作为端口地址,如果当前进程ID已经绑定过其他的相同protocol套接字则会选用一个负数作为ID号(查找直到存在可用的),最后同样调用netlink_insert()函数。回到netlink_bind()函数中:
  

 if (!groups && (nlk->groups == NULL || !(u32)nlk->groups[0]))
        return 0;
 
    netlink_table_grab();
    netlink_update_subscriptions(sk, nlk->subscriptions +
                     hweight32(groups) -
                     hweight32(nlk->groups[0]));
    nlk->groups[0] = (nlk->groups[0] & ~0xffffffffUL) | groups;
    netlink_update_listeners(sk);
    netlink_table_ungrab();
 
    return 0;


如果没有指定组播地址且没有分配组播的内存,绑定工作到这里就已经结束了,可以直接返回了。现假定用户指定了需要绑定的组播地址,这里首先调用netlink_update_subscriptions绑定sk->sk_bind_node到nl_table[sk->sk_protocol].mc_list中,同时将加入的组播组数目记录到nlk->subscriptions中,并将组播地址保存到nlk->groups[0]中,最后更新netlink监听位掩码。至此绑定操作结束。
分析完成netlink子系统的创建、内核netlink套接字的创建、应用层netlink套接字的创建和绑定后,下一篇来分析一下内核和应用层之间是如何发送消息的。

参考文献:《Linux Kernel Networking Implementation and Theory》
 

【OVS2.5.0源码分析】nlattr数据结构分析 从nlattr类型定义来看,是非常简单,但是OVS使用该数据结构来定义action,nlattr自身是不存储数据,而使用来定义数据格式的,OVS也会使用skb来存储真实数据。 nlattr数据结构定义 struct nlattr { uint16_t nla_len; //数据长度 uint16_t nla_type; //数据类型 };使用nlattr构造的数据 阅读详情

相关推荐

linux netlink实现机制:注册、创建

目录 1 netlink 概述 2 Netlink子系统初始化 3 内核Netlink套接字 3.1 内核netlink配置结构:struct netlink_kernel_cfg 3.2 netlink属性头:struct nlattr 3.3 netlink有效性策略:struct nla_policy 3.4 netlink套接字结构:netlink_sock 3.5 创建内核netlink套接字 4 应用层 Netlink 套接字 4.1 套接字地址数据结构 sockaddr_n

3414

Netlink+内核实现分析(一):创建

Netlink+内核实现分析(一):创建 Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,同时它也以用于进程间通信(Netlink 更多用于内核通信,进程之间通信更多使用Unix域套接字)。在一般情况下,用户态和内核态通信会使用传统的Ioctl、sysfs属性文件或者procfs属性文件,这3种通信方式都是同步通信

sinat_32343037的博客 1177

VBA一键汇总多个工作簿-名称相同的工作表-的指定区域数据

VBA一键汇总多个工作簿-名称相同的工作表-的指定区域数据 日常工作,我们经常需要汇总相同格式的工作簿的某个工作表的数据 如1月业绩、2月业绩。。。。12月业绩等 姓名 数量 数据22 22 数据23 23 数据24 24 数据25 25 数据26 26 数据27 27 数据28 28 数据29 29 数据30 30 数据31 31 数据32 32 数据33 33 数据34

陈表达的博客 5982

Netlink机制详解

更多文章请多关注个人网站:http://www.readbk.net,谢谢浏览! 机制原理: Netlink 是一种特殊的 socket,它是 Linux 所特有的,由于传送的消息是暂存在socket接收缓存中,并不被接收者立即处理,所以netlink是一种异步通信机制。 系统调用和 ioctl 则是同步通信机制。 用户空间进程可以通过标准socket API来实现消息的发送、接收,在L

xinyuan510214的专栏 7714

【Unity-UGUI控件全面解析】| Layout自动布局组件详解

UGUI中有个自动布局组件Auto Layout,主要包含下面几个模块(点击可进入官方文档下查看描述): 自动布局 布局元素 (Layout Element) 水平布局组 (Horizontal Layout Group) 垂直布局组 (Vertical Layout Group) 网格布局组 (Grid Layout Group) 内容大小适配器 (Content Size Fitter) 自动布局系统 基于布局元素和布局控制器的概念。一个布局元素是具有矩形变换以及其他可选组件的游戏对象。布局元素对自身

努力前行,总会成为自己心中的那道光 2万+

SCTPPortScan SCTP协议端口扫描、关联地址识别、传输状态检测

Windows:Winsock2+SCTP扩展)。- **SCTP API封装**:Linux下直接调用`socket()`、`connect()`、`getsockopt()`等系统函数,解析`struct sctp_status`和`struct sctp_paddrinfo`获取状态和关联地址。qDebug() << "开始SCTP扫描:" << ipList.size() << "个IP," << ports.size() << "个端口,共" << m_totalTasks << "个任务";

寒寒豆豆软件公司的博客 93

linux查看USB运行状态,Linux下获取USB设备插拔状态的通知

最近遇到一个需求,要求浏览器的内嵌keyboard与USB keyboard状态同步。主要就是几个特殊的功能键,shift、alt、ctrl、caps、tab等等,借鉴了一个博客:http://www.voidcn.com/article/p-sjjkjzze-cy.html,其中提供了一段获取USB设备的代码,自己运行过后,有一定的帮助,具体的还需要深究。代码如下:#include #inclu...

weixin_29038155的博客 1573

使用linuxdeployqt打包Qt程序问题及解决方法

安装过程中提示缺少依赖multiarch-support: 下载对应依赖包。安装过程中提示缺少libpq.so.5: 下载对应依赖包。下载libmysqlclient18/5.6.25。

weixin_74239923的博客 1059

Linux下获取USB设备插拔状态的通知

最近遇到一个需求,要求浏览器的内嵌keyboard与USB keyboard状态同步。主要就是几个特殊的功能键,shift、alt、ctrl、caps、tab等等,借鉴了一个博客:http://www.voidcn.com/article/p-sjjkjzze-cy.html,其中提供了一段获取USB设备的代码,自己运行过后,有一定的帮助,具体的还需要深究。代码如下:#include &lt;st...

蜗牛想飞翔 6320

化工仿真软件:gPROMS二次开发_(1).gPROMS基础知识

在gPROMS中,模型是通过定义变量、参数和方程来创建的。模型可以是简单的单元操作,如换热器、反应器,也可以是复杂的系统,如整个化工流程。变量是模型中表示状态或输出的量。在gPROMS中,变量需要显式定义其类型和单位。# 定义变量VAR# 温度变量,单位为K# 压力变量,单位为Pa# 组分摩尔分数,数组变量参数是模型中表示常量或初始条件的量。参数同样需要定义其类型和单位。# 定义参数PAR# 理想气体常数,单位为J/(mol·K)# 反应器体积,单位为m^3# 初始摩尔数。

kkchenjj的博客 617

AI幻觉缓解的成本效益分析:投入产出比优化

在当今AI技术飞速发展的时代,AI幻觉成为了一个不容忽视的问题。AI幻觉就像是AI在“做梦”,会给出一些不符合实际或者错误的答案。我们这篇文章的目的就是要分析缓解AI幻觉所需要投入的成本和能够获得的效益,看看怎么能让投入和产出的比例达到最优。范围涵盖了常见的AI应用场景,比如智能客服、自动驾驶等领域。接下来,我们会先介绍一些和AI幻觉、成本效益分析相关的术语。然后用一个有趣的故事引出核心概念,解释什么是AI幻觉、成本效益分析等,以及它们之间的关系。

AI 原生应用开发的博客 935

Android7 U盘插拔链路源码全解析(三)Native层Vold与NetlinkManage

本文是Android存储系统系列分析的第三篇,聚焦Native层的vold与NetlinkManager。vold作为用户态首个感知U盘插入的守护进程,负责接收内核uevent事件、解析设备信息、管理存储卷,并桥接Java层。文章详细剖析了vold的五大核心模块架构,包括NetlinkManager(监听内核事件)、VolumeManager(管理存储对象)、CommandListener(处理Java层命令)等,并深入源码分析了vold启动流程和事件处理机制,揭示了从内核事件到用户态存储管理的完整链路。

AfinalStone的专栏 253

反应动力学仿真软件:Kintecus_(11).数据输入与输出解析

在反应动力学仿真软件的二次开发中,数据输入与输出解析是至关重要的步骤。正确地解析输入数据和生成输出结果不仅能够确保仿真过程的顺利进行,还可以提高仿真的准确性和效率。本节将详细介绍如何在仿真软件中实现数据输入与输出的解析,包括文件格式、数据结构、解析方法以及实际操作示例。

kkchenjj的博客 757

【Linux】SD卡调试

1)hotplugEventsocket = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);socket 返回Protocol not supported,跟踪libc库中的文件socketcalls.c,socket的系统调用:int socket(int family, int type, int protocol){     u...

vickytong1018的博客 2856

C++和OpenGL实现3D游戏编程【连载6】——不规则图形的纹理贴图(附源码)

上一节我们讨论了纹理贴图的相关基础操作,但上一节的纹理贴图操作基本上都是规则图形,包括圆形和球形虽然复杂一点,但是它也是规则的。这一节课我们要讨论一下,怎么在不规则图形上纹理贴图,就比如文章下图的心形纹理贴图,以及纹理的平移、旋转、放大缩小功能。

zhooyu的博客 1308

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Android12 U盘挂载流程学习(一)

这是开机流程的一个重要步骤,标志着user正式启动,而系统启动的第一个用户的id就是0,会走到这里。// /data/media/0/Android/data挂载到/mnt/user/0/emulated/0/Android/data。// /data/media/0/Android/obb挂载到/mnt/user/0/emulated/0/Android/obb。// 挂载/dev/fuse到/mnt/user/0/usb0,访问/mnt/user/0/usb0就会指向/dev/fuse。

c025200的博客 1659

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Linux下如何获取网卡信息

有时候,写程序的时候需要获取计算机的网络信息,比如IP地址、电脑名称、DNS等信息。IP地址和电脑名称是比较容易获取到的,而要想获取地址掩码、DNS、网关等信息就有些麻烦了。 在Windows下我们一般都是通过从注册表读取这些信息。在Linux怎么做呢?其实,Linux下更加容易一些。因为我们可以拿现成的程序看它的源代码。通过阅读其源代码找到解决该问题的方法。那么,看哪个程序的源代码呢?如果你使

Alan0521的专栏 1726

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

制作RootFS

本文介绍了Linux系统中RootFS(根文件系统)的概念及其制作方法。RootFS是内核默认创建的顶层挂载实例,通过initramfs加载最小工具和驱动后,再切换到真正的业务根文件系统。文章详细演示了使用debootstrap工具在Ubuntu/Debian系统中制作ARM架构嵌入式设备的RootFS步骤,包括环境准备、QEMU模拟支持、chroot配置、基础软件安装和打包过程。同时解释了内核启动所需的三个核心文件(内核镜像、initrd和业务RootFS)的作用,并探讨了initrd与initramfs

fanged的专栏 1820

Ubuntu 18.04 下 Mosquitto 四层安全加固实战

MQTT 是物联网通信的核心协议,其安全性依赖于传输加密、身份认证、访问控制与系统隔离的协同保障。TLS/SSL 加密确保消息机密性与完整性,而 ACL(访问控制列表)和密码认证共同实现细粒度权限管理;在 Ubuntu 18.04 这类经典 LTS 环境中,OpenSSL 1.1.1 与 systemd 深度集成,为服务沙箱、证书自动续签和日志审计提供了底层支撑。本文聚焦真实生产场景,解析如何通过网络隔离、协议级鉴权、TLS 双向认证及可观测性闭环,构建纵深防御体系——尤其适用于树莓派集群、工业网关与需通过

weixin_34008784的博客 366

linux / Generic Netlink

Genetlink 消息是命令驱动式的,即每一条消息的 genlmsghdr 中都指明了当前消息的 cmd 消息命令,这些消息 cmd 命令由用户自行定义。由于 netlink 协议最多支持 32 个协议簇,目前 Linux4.1 的内核中已经使用其中 21 个,对于用户需要定制特殊的协议类型略显不够,而且用户还需自行在 include/linux/netlink.h 中添加簇定义,但有时不方便,为此 Linux 设计了这种通用 Netlink 协议簇,用户可在此之上定义更多类型的子协议。

布袋和尚 1650

linux的netlink机制

linux下从内核空间到用户空间的异步通信可以使用到netLink,像设备的路由等信息都是通过该机制实现。 以下是一个netlink实例的链接: https://blog.youkuaiyun.com/qq_29214249/article/details/74909160

lailaiquququ11的博客 1150

Netlink 内核实现分析(一):创建

Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,同时它也以用于进程间通信(Netlink更多用于内核通信,进程之间通信更多使用Unix域套接字)。在一般情况下,用户态和内核态通信会使用传统的Ioctl、sysfs属性文件或者procfs属性文件,这3种通信方式都是同步通信方式,由用户态主动发起向内核态的通信,内核无法主动发起通信。而Netlink是一种异步全双工的通信方式,它支持由内核态主动发起通信,内核为Netlink通信提供.

~~ LINUX ~~ 1380

Linux Netlink全面解析:从原理到实践

架构统一性:Netlink提供了一套统一的通信框架,多个内核子系统可以在同一套机制上构建自己的用户空间接口。这减少了代码重复,提高了系统一致性。协议扩展性:通过定义不同的Netlink协议类型,系统可以支持多种通信语义和数据类型。从最初的NETLINK_ROUTE到后来的NETLINK_GENERIC,Netlink协议家族不断扩展,满足了不同子系统的需求。高效性:相比于字符设备或proc文件系统,Netlink避免了频繁的系统调用上下文切换和数据拷贝,特别是对于大量数据的传输,效率更高。异步通信。

X 1752

使用netlink实时获取网络信息原理分析

最近在做主机侧网络连接相关的调研,发现通过 linux 自带的netlink能实时获取网络连接五元组信息,可以用于网络活动可视化、异常连接检测、安全策略优化以及审计等功能,但网络上找到的相关文章不多,因此在此分析下netlink实时获取网络连接信息的原理。

2401_85023633的博客 1609

linux内核与用户空间通信机制——Netlink通信

可以参考这个链接的文章了解:https://blog.youkuaiyun.com/zhao_h/article/details/80943226 netlink是一种用于内核态和用户态进程之间进行数据传输的特殊的IPC机制。它的特点是: (1) 用户态采用socket风格的API (2)除了预定义的协议类型之外,支持自定义协议类型 (3)异步通讯 (4)支持消息组播 (5)全双工(特别是支持内核...

weixin_39094034的博客 4556
上一篇: netlink 0006 --- genetlink代码例子
下一篇: Netlink 0008 --- 通信
废言Pro
博客等级 码龄16年 1093粉丝 142原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值