netlink 011 -- generic netlink 编程入门

三、无线信息传递——Generic Netlink(一、初始化) 系列说明  上一篇说明了无线信息的user space至kernel space的大致传递流程,这一主要针对以下3点进行一个顺序的描述: 1、无线驱动信息传递框架:说明无线信息传递的步骤流程以及各程序块之间的联系; 2、generic Netlink信号传递机制:hostapd与无线驱动之间的信息传递机制; 3、以ssid为例说明用户将user space中的ssid配置内容传递至kernel sp 阅读详情

一、generic netlink 消息结构
消息以流的形式在程序之间进行传递,一个流中可能包含多个消息。

对于每个消息消息来说,为了便于维护和方便使用,还需要一些有关记录消息的信息。一个 netlink message 结构如下:

其中 nlmsghdr 结构里记录了该条 netlink message 的如下信息:

struct nlmsghdr {
    __u32        nlmsg_len;    //该 netlink message 的长度(包括 nlmsghdr 部分本身)
    __u16        nlmsg_type;    //该 netlink message 的种类
    __u16        nlmsg_flags; //一个附加的标记
    __u32        nlmsg_seq;    //该 netlink message 在消息流中的序列号
    __u32        nlmsg_pid;    //发送该 netlink message 的进程的进程号 pid
};


为了使数据对齐,在 payload 两端可能会塞入了一些填充字段。netlink message 的 payload 结构如下:

这个结构称作 family。进程间通信的目的通常是希望信息接收方能够执行某些动作,而很多命令都是围绕同一个事件的(如对某个表的增、删、改、查)。因此为了便于管理,netlink 通信协议将这些相关的一套命令和其对应的操作组织起来,归于一个family管辖,每个family都有个独有的名字,该名字需公示出来,以便其他程序与该 family 通信,当 family 在内核完成注册后,内核会给其分配一个独有的 id。如果用户需要结构化的信息,可以在 netlink message 的 payload 中添加可自选的信息头 user specific header。剩下的 attributes 结构如下:

每个 attribute 即是一个不可再分的最基本信息单元,结构如下:

其中 nlattr 是管理 payload 的信息头,记录了 payload 的以下信息:

struct nlattr {
    __u16           nla_len;//attribute 长度,包括 nlattr 本身
    __u16           nla_type;//attribute 的类型
};


payload 部分即是想要传递的信息了,如传递一句话 “ hello world ”。为了对齐,payload 两端可能会塞入 pad。

netlink message 协议使用的信息结构即是以上这些,但对着使用越来越多,内核可以用来分配给 family 的 id 资源开始紧张了。因此 generic netlink 协议在 netlink 协议上又做了一些拓展,即在 netlink message 的 payload 上又做了一层封装:

该结构即为 generic netlink message。该信息头 genlmsghdr 中记录如下内容。

struct genlmsghdr {
    __u8    cmd;        //命令号
    __u8    version;    //命令版本号
    __u16    reserved;   //保留字段
};


故最终 generic netlink message 的结构如下:

 

二、接口搭建过程
kernel端程序的准备
1、定义想要传送的消息种类

/* generic netlink message attributes */
enum {
      MY_ATTR_UNSPEC,
      MY_ATTR_MSG,
      __MY_ATTR_MAX,
};
#define MY_ATTR_MAX (__MY_ATTR_MAX - 1)

你可能有多种消息需要传递,使用枚举类型来管理消息种类是个不错的办法。这里实际上只定义了一个消息种类,即 MY_ATTR_MSG。 根据枚举类型的特点,在枚举体最后定义的 __MY_ATTR_MAX 实际上表示了该属性集的大小,而 MY_ATTR_MAX 则表示了该属性集中最后一类属性(即 MY_MTTR_MSG)的编号 1。 

(在 linux 内核中大量的枚举体的首个元素名均是以 UNSPEC 结尾,在这里 MY_ATTR_UNSPEC 作为该属性集的“一般属性”,可能只是用来对应一些“空操作”,而并不特指哪一消息类型。——作者猜测)

2、定义一组命令

/* commands 定义命令类型,用户空间以此来表明需要执行的命令 */
enum {
    MY_CMD_UNSPEC, 
    MY_CMD_1,      //命令
    __MY_CMD_MAX,  //表示命令集的大小,并不是命令
};
#define MY_CMD_MAX (__MY_CMD_MAX - 1)

与消息属性的定义类似,这里同样使用了枚举类型来定义命令集,但其实这里只定义了一个命令,即 MY_CMD_1。根据枚举类型的特点,在枚举体最后定义的 __MY_CMD_MAX 实际上表示了该命令集的大小,而 MY_CMD_MAX 则表示了该命令集中最后一个命令(即 MY_CMD_1)的编号 1。 

3、为每个命令定义一个响应函数
generic netlink协议要求命令相应函数的类型是:

static int my_callback_function(struct sk_buff *skb, struct genl_info *info);
4、将命令与相应函数关联起来

static struct genl_ops my_cmd_ops = {
        .cmd = MY_CMD_1, //命令
        .flags = 0,
        .policy = my_cmd_policy,
        .doit = my_callback_function, //响应函数
        .dumpit = NULL,
};

通过定义一个 genl_ops, 我们可以将一个命令和其对应的响应函数关联起来。成员 policy 指明了该响应函数对其能够处理的信息的要求。具体如下:

static struct nla_policy my_cmd_policy[MY_ATTRIBUTE_MAX + 1] = {
      [MY_ATTRIBUTE_MSG] = { .type = NLA_NUL_STRING },
};

这里使用了比较难懂的语法,先来看一下 struct nla_policy 的定义:

struct nla_policy {
    u16        type;
    u16        len;
};

my_cmd_policy 是一个 nal_policy 结构体数组,数组长度为2(即 MY_ATTRIBUTE_MAX+1) ,即前文定义的 generic netlink message 属性集的长度(my_cmd_policy[0] 对应 MY_ATTRIBUTE_UNSPEC;my_cmd_policy[1] 对应 MY_ATTRIBUTE_MSG)。在大括号中将 my_cmd_policy[1] 的 type 属性赋值为 NLA_NUL_STRING,该宏的意思是除去NULL外的最大字符串长度。

5、创建一个命令族
 

/* family definition */
static struct genl_family my_cmd_family = {
      .id = GENL_ID_GENERATE,   //这里不指定family ID,由内核进行分配
      .hdrsize = 0,             //自定义的头部长度,参考genl数据包结构
      .name = "MY_CMDS",        //这里定义family的名称,user program需要根据这个名字来找到对应的family ID。
      .version = 1,             //版本号1
      .maxattr = ATTRIBUTES_MAX,//最大属性数
};

该命令族的名为 MY_CMD_FAMILY。这个名字需要事先告知用户态程序的编写者,用户态程序便通过这个名字来表明想跟内核中的哪个命令族进行通信。

在具体的用户态向内核命令族发送消息的 API 函数中,用户需要使用 id 来指定想要与之进行通信的内核命令族,而不能直接使用命令族的名字。解决这个问题的办法是,当一个命令族在内核中注册完成时,内核会为其分配一个id。而内核中还存在一个默认的命令族 GENL_ID_CTRL,用户态程序便可以先向该命令族发送 CTRL_CMD_GETFAMILY 命令来获取某个命令族的 id(这里即是内核分配给 MY_CMDS 命令族的 id)。

6、向内核注册新建的命令族
genl_register_family(&my_cmd_family);


7、将关联好的命令和响应函数绑定(注册)到命令族上
genl_register_ops(&my_cmd_family, &my_cmd_ops);

用户端程序的使用:
1、新建一个套接字,并绑定上 netlink address

//申请一个generic netlink协议的socket
int sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
 
//<1>填写netlink 通信地址 nladdr
struct sockaddr_nl nladdr;
nladdr.nl_family = AF_NETLINK;//填写要使用的协议
nladdr.nl_pid = getpid();//填写当前进程的进程号
/*这个是mask值,如果family ID & nl_groups为0,
 *则这个family的广播就接收不到,
 *所以这里设为0xffffffff就可以接收所有的family消息*/
nladdr.nl_groups = 0xffffffff;
//</1> 
 
//将填写好的nladdr绑定到申请的sock上
 bind(sockfd, (struct sockaddr *)&nladdr, sizeof(nladdr));

    
2、向 GENL_ID_CRTL 询问想通信的命令族的 id
此时,这里用户态程序要先与命令族GENL_ID_CRTL 进行通信,使用命令族 id 查询命令 CTRL_CMD_GETFAMILY,发送的 netlink message 的类型是 CTRL_ATTR_FAMILY_NAME,信息的内容即使上文定义的命令族名 "MY_CMDS",信息的长度为 strlen("MY_CMDS")+1。 具体过程如下

1)创建一个 netlink message

char* my_family_name = "MY_CMDS";
int len = strlen(my_family_name) + 1;
 
//<1>计算长度为 lne 的消息封装成 netlink message 后的长度
//加上 netlink message attributes header 的长度
len = nla_total_size(len);
//加上自定义消息体头的长度
len += 0;
//加上 generic netlink message header 的长度  
len += GENL_HDRLEN;
//加上 netlink message header 的长度,netlink message 是消息的最后一层封装
len = NLMSG_SPACE(len);
//</1>
 
//申请内存空间用于存储此条 netlink message
unsigned char *buf = genlmsg_alloc(&len);

2)填写 netlink message header 
struct nlmsghdr *nlh;    
nlh = (struct nlmsghdr *)buf;//获取buf开头的 netlink message header 部分
nlh->nlmsg_len = len;//填写此netlink message的长度
nlh->nlmsg_type = GENL_ID_CTRL;//填写此netlink message的类型,即命令族 DOC_EXMPL 的 id
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = 0;//这是第0个 netlink message
nlh->nlmsg_pid = 0;//发送这条 netlink message 的进程的进程号


3)填写 generic netlink message header
struct genlmsghdr *glh;
/*generic netlink message是netlink message的负载,
 *在该负载中获取generic netlink message header*/  
glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
glh->cmd = CTRL_CMD_GETFAMILY;//填写 generic netlink 命令
glh->version = 1;//填写 generic netlink命令版本号


4)填写 netlink message attribute header,和想要发送的消息内容
struct nlattr *nla;
//generic netlink message 即是真实的消息部分,消息的格式是 nlattr      
nla = (struct nlattr *)GENLMSG_DATA(glh);
//填写消息类型,即 DOC_EXMPL_A_MSG
nla->nla_type = CTRL_ATTR_FAMILY_NAME;
//填写消息长度(包括消息头和负载)
nla->nla_len = nla_attr_size(strlen(my_family_name)+1);
//将想要传递的数据 nla_data = “hello from user space!”填入消息体 nalttr 的负载部分
memcpy(NLA_DATA(nla), my_family_name, nla_len);


5) 填写要使用的 netlink socket address
struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;//使用 netlink 通信协议

6)发送填写完毕的 netlink message
int ret;
int count = 0;
do {
    ret = sendto(sockfd, &buf[count], len - count, 0,
                        (struct sockaddr *)&nladdr, sizeof(nladdr));
    count += ret;
}while (count < len);


使用套接字sockfd(地址为nladdr),尝试传递存储在以 buf[count] 为起始位置,长度为 len 的消息。但在实际传送过过程中可能无法一次性传递完存储在 buf 区的所有内容,sendto 函数会返回此次调用实际传送的消息长度,我们可以使用上面这个循环直至将 buf 区的消息全部送出。3、等待接收 GENL_ID_CRTL 的返回的查询结果

1)新建一个 netlink message,用于存储 GENL_ID_CRTL 返回的信息

这里一般给待接收的信息预留 len = 256 字节的空间,创建 netlink message 的过程见上文

2)填写“收件地址” sockaddr_nl

struct sockaddr_nl nladdr; 
nladdr.nl_family = AF_NETLINK;//使用 netlink 通信协议
nladdr.nl_pid = getpid();     //填写当前进程的进程号
nladdr.nl_groups = 0xffffffff;//此广播号意味着可以接收所有内核命令族发来的信息
3)设置数据接收区,绑定“收件地址”,等待接收内核命令族发出的信息

struct iovec iov;
iov.iov_base = buf;
iov.iov_len = len;
 
struct msghdr msg;
msg.msg_name = (void *)&nladdr;
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;
//</1>    
 
//从套接字sockfd中接收消息,存储到msg
recvmsg(sockfd, &msg, 0);
4、拆分读取接收到的信息
//读取到的数据流里面,可能会包含多条nlmsg
for (struct nlmsghdr *nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len))
{
    /* The end of multipart message. */
    struct genlmsghdr *glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
    struct nlattr *nla = (struct nlattr *)GENLMSG_DATA(glh);   //the first attribute
    int nla_len = nlh->nlmsg_len - GENL_HDRLEN;           //len of attributes
    //一条nlmsg里面,可能会包含多个attr
    for (int i = 0; NLA_OK(nla, nla_len); nla = NLA_NEXT(nla, nla_len), ++i)
    {
        //如果消息里的nla_type与调用者指定的nla_type相同
        if (nla_type == nla->nla_type)
        {
            int l = nla->nla_len - NLA_HDRLEN;  
            *len = *len > l ? l : *len;
            //将该 nlattr 对应的数据拷贝给到调用者指定的 buf 中
            memcpy(buf, NLA_DATA(nla), *len);
            break;
        }
    }
}
 
此时 buf 中存储的就是内核分配给命令族 MY_CMDS 的 id 号

5、与想要通信的命令族通信
该通信过程与上文所述的用户态程序与命令族 GENL_ID_CRTL 的通信过程相同,不再赘述。不同的是:

填写 netlink message header 的 nlmsg_type 成员时,使用 MY_CMDS,而不是 GENL_ID_CTRL
填写 generic netlink message header 的 cmd 成员时,使用 MY_CMD_1,而不是 CTRL_CMD_FAMILY
填写 netlink message attribute header 的 nla_type 成员时,使用 MY_ATTR_MSG,而不是 CTRL_ATTR_FAMILY_NAME

三、程序示例
注:为了程序易读好懂,这里改变了函数定义的顺序,删去了差错校验代码。完整程序可参考:

https://blog.youkuaiyun.com/ty3219/article/details/63683698用户态程序

int main(int argc, char *argv[])
{
    test_netlink_unicast(); 
    return 0;
}
test_netlink_unicast

#define BUF_SIZE    256
void test_netlink_unicast(void)
{
    //获取用于与内核通信的socket,并绑定上自己的netlink通信地址nladdr
    int sockfd = genlmsg_open();
    
    //<1>向kernel发送信息
    //这里必须先通过family的名字获取到family ID,名字需要与驱动里的一致
    int id = genlmsg_get_family_id(sockfd, "DOC_EXMPL");
    //获取自己的进程号pid
    pid_t pid = getpid();
    //向内核发送genl消息
    /*通过sockfd套接字,向 DOC_EXMPL 命令族,
     *本进程号是 pid,该命令是 DOC_EXMPL_C_ECHO(命令版本号为1),
     *命令负载的信息种类是 DOC_EXMPL_A_MSG,
     *信息内容为MESSAGE_TO_KERNEL,长度为strlen(MESSAGE_TO_KERNEL) + 1*/
    genlmsg_send(sockfd, id, pid, DOC_EXMPL_C_ECHO, 1,
                        DOC_EXMPL_A_MSG, MESSAGE_TO_KERNEL, strlen(MESSAGE_TO_KERNEL) + 1);
    //</1>
    
    //<2>接收从kernel传来的信息
    /*申请一个长度为len的内存空间,用于存储netlink message,
     *但以netlink header类型指针指示这段空间的起始位置*/
    int len = BUF_SIZE;
    struct nlmsghdr *nlh = genlmsg_alloc(&len);
    //使用sockfd进行通信,将接收到的内核消息存储在刚申请的内存空间中
    nlh_len = genlmsg_recv(sockfd, (unsigned char *)nlh, len);
    //</2> 
   
    unsigned char buf[BUF_SIZE];
    genlmsg_dispatch(nlh, nlh_len, DOC_EXMPL_A_MSG, buf, &len);
}
genlmsg_open

static int genlmsg_open(void)
{    
    //申请一个generic netlink协议的socket
    int sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
 
    //<1>填写netlink 通信地址 nladdr
    struct sockaddr_nl nladdr;
    nladdr.nl_family = AF_NETLINK;//填写要使用的协议
    nladdr.nl_pid = getpid();//填写当前进程的进程号
    /*这个是mask值,如果family ID & nl_groups为0,
     *则这个family的广播就接收不到,
     *所以这里设为0xffffffff就可以接收所有的family消息*/
    nladdr.nl_groups = 0xffffffff;
    //</1> 
 
    //将填写好的nladdr绑定到申请的sock上
    bind(sockfd, (struct sockaddr *)&nladdr, sizeof(nladdr));
 
    return sockfd;
}
genlmsg_get_family_id

static int genlmsg_get_family_id(int sockfd, const char *family_name)
{
    //<1>向GENL_ID_CTRL命令族查询内核给family_name分配的id
    /*通过sockfd套接字,向GENL_ID_CTRL命令族,
     *进程号是 0,该命令是CTRL_CMD_GETFAMILY(命令版本号为1),
     *命令负载的信息种类是CTRL_ATTR_FAMILY_NAME,
     *信息内容为family_name,长度为strlen(family_name) + 1*/
    genlmsg_send(sockfd, GENL_ID_CTRL, 0, CTRL_CMD_GETFAMILY, 1,
            CTRL_ATTR_FAMILY_NAME, family_name, strlen(family_name) + 1);
    //</1>
 
    //<2>接收sockfd上收到的内核返回的信息,存储在buf中
    int len = 256;
    void *buf = genlmsg_alloc(&len); 
    len = genlmsg_recv(sockfd, buf, len);
    //</2>
     
    //<3>拆解从内核传来的信息,得到family_name对应的id
    __u16 id = 0;
    int l = sizeof(id);
    genlmsg_dispatch((struct nlmsghdr *)buf, len, 0, CTRL_ATTR_FAMILY_ID, (unsigned char *)&id, &l);
    //</3>
 
    return id;
}
genlmsg_send

static int genlmsg_send(int sockfd, unsigned short nlmsg_type, unsigned int nlmsg_pid,
        unsigned char genl_cmd, unsigned char genl_version,
        unsigned short nla_type, const void *nla_data, unsigned int nla_len)
{
    /*申请一个长度为nla_len的内存空间,用于存储netlink message,
     *但以最简单的unsigned char类型指针指示这段空间的起始位置*/
    int len = nla_len;
    unsigned char *buf = genlmsg_alloc(&len);
    
    //<1>填写netlink message header
    struct nlmsghdr *nlh;    
    nlh = (struct nlmsghdr *)buf;//获取buf开头的 netlink message header 部分
    nlh->nlmsg_len = len;//填写此netlink message的长度
    nlh->nlmsg_type = nlmsg_type;//填写此netlink message的类型,即命令族 DOC_EXMPL 的 id
    nlh->nlmsg_flags = NLM_F_REQUEST;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = nlmsg_pid;
    //</1>
 
    //<2>填写generic netlink message header
    struct genlmsghdr *glh;
    /*generic netlink message是netlink message的负载,
     *在该负载中获取generic netlink message header*/  
    glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
    glh->cmd = genl_cmd;//填写 generic netlink 命令,即 DOC_EXMPL_C_ECHO
    glh->version = genl_version;//这里 generic netlink命令版本号是 1
    //</2>
     
    //<3>netlink attribute header
    struct nlattr *nla;
    //generic netlink message即是真实的消息部分,消息的格式是nlattr      
    nla = (struct nlattr *)GENLMSG_DATA(glh);
    //填写消息类型,即 DOC_EXMPL_A_MSG
    nla->nla_type = nla_type;
    //填写消息长度(包括消息头和负载)
    nla->nla_len = nla_attr_size(nla_len);
    //将想要传递的数据 nla_data = “hello from user space!”填入消息体 nalttr 的负载部分
    memcpy(NLA_DATA(nla), nla_data, nla_len);
    //</3>     
 
    //<4>填写消息发送的netlink socket address
    struct sockaddr_nl nladdr;
    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    //</4>
 
    int ret;
    int count = 0;
    do {
        /*使用套接字sockfd(地址为nladdr),尝试传递存储在buf区,长度为len的消息,
         *sendto会返回实际传送的消息长度,使用循环直至将buf区的消息全部送出*/
        ret = sendto(sockfd, &buf[count], len - count, 0,
                        (struct sockaddr *)&nladdr, sizeof(nladdr));
        count += ret;
    }while (count < len);
 
    return count;
}
genlmsg_alloc

static void *genlmsg_alloc(int *size)
{
    //<1>计算长度为size的消息封装成netlink message后的长度
    int len;
    //加上nlattr的长度(nalttr是最基本的消息体头,其后跟的负载部分再无任何封装)
    len = nla_total_size(*size);
    //加上自定义消息体头的长度
    len += 0;
    //加上generic netlink message header 的长度  
    len += GENL_HDRLEN;
    //加上netlink message header 的长度,netlink message 是消息的最后一层封装
    len = NLMSG_SPACE(len);
    //</1>
 
    //告知申请者封装之后的netlink message的长度
    *size = len;
    //申请netlink message所需的内存空间
    unsigned char *buf = malloc(len);
    return buf;
}
genlmsg_recv

//从套接字sockfd接收从内核传来的消息,存储在buf(长度为len)中
static int genlmsg_recv(int sockfd, unsigned char *buf, unsigned int len)
{
    //<1>设置好message header,准备接收消息
    //填写“收件地址”
    struct sockaddr_nl nladdr; 
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = getpid();
    nladdr.nl_groups = 0xffffffff;
     
    //设置好数据接收区
    struct iovec iov;
    iov.iov_base = buf;
    iov.iov_len = len;
 
    struct msghdr msg;
    msg.msg_name = (void *)&nladdr;
    msg.msg_namelen = sizeof(nladdr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_flags = 0;
    //</1>    
 
    //从套接字sockfd中接收消息,存储到msg
    recvmsg(sockfd, &msg, 0);
}
genlmsg_dispatch

static int genlmsg_dispatch(struct nlmsghdr *nlmsghdr, unsigned int nlh_len,
                            int nlmsg_type, int nla_type, unsigned char *buf, int *len)
{
    //读取到的数据流里面,可能会包含多条nlmsg
    for (struct nlmsghdr *nlh = nlmsghdr; NLMSG_OK(nlh, nlh_len); nlh = NLMSG_NEXT(nlh, nlh_len))
    {
        /* The end of multipart message. */
        struct genlmsghdr *glh = (struct genlmsghdr *)NLMSG_DATA(nlh);
        struct nlattr *nla = (struct nlattr *)GENLMSG_DATA(glh);   //the first attribute
        int nla_len = nlh->nlmsg_len - GENL_HDRLEN;           //len of attributes
        //一条nlmsg里面,可能会包含多个attr
        for (int i = 0; NLA_OK(nla, nla_len); nla = NLA_NEXT(nla, nla_len), ++i)
        {
            //如果消息里的nla_type与调用者指定的nla_type相同
            if (nla_type == nla->nla_type)
            {
                int l = nla->nla_len - NLA_HDRLEN;  
                *len = *len > l ? l : *len;
                //将该nlattr对应的数据拷贝给到调用者指定的buf中
                memcpy(buf, NLA_DATA(nla), *len);
                break;
            }
        }
    }
}
内核态程序
static int genetlink_init(void)
{
    //注册一个命令族,linux 3.12才开始引入generic netlink机制
    genl_register_family(&doc_exmpl_genl_family);   
    //给注册的命令族绑定上对应的操作集
    genl_register_ops(&doc_exmpl_genl_family, &doc_exmpl_genl_ops_echo);   
    /*
     * for multicast
     */
    genl_register_mc_group(&doc_exmpl_genl_family, &doc_exmpl_genl_mcgrp);
 
}
doc_exmpl_genl_family

/* family definition */
static struct genl_family doc_exmpl_genl_family = {
      .id = GENL_ID_GENERATE,   //这里不指定family ID,由内核进行分配
      .hdrsize = 0,             //自定义的头部长度,参考genl数据包结构
      .name = "DOC_EXMPL",      //这里定义family的名称,user program需要根据这个名字来找到对应的family ID。
      .version = 1,
      .maxattr = DOC_EXMPL_A_MAX,
};
doc_exmpl_genl_ops_echo

 
/* operation definition 将命令command echo和具体的handler对应起来 */
static struct genl_ops doc_exmpl_genl_ops_echo = {
        .cmd = DOC_EXMPL_C_ECHO,
        .flags = 0,
        .policy = doc_exmpl_genl_policy,
        .doit = doc_exmpl_echo,
        .dumpit = NULL,
};
doc_exmpl_genl_policy

/* netlink attributes */
enum {
      DOC_EXMPL_A_UNSPEC,
      DOC_EXMPL_A_MSG,
      __DOC_EXMPL_A_MAX,
};
#define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1)
 
/* attribute policy */
static struct nla_policy doc_exmpl_genl_policy[DOC_EXMPL_A_MAX + 1] = {
      [DOC_EXMPL_A_MSG] = { .type = NLA_NUL_STRING },
};
nla_policy

struct nla_policy {
    u16        type;
    u16        len;
};
doc_exmpl_echo

//echo command handler, 命令处理函数,当接收到user program发出的命令后,这个函数会被内核调用
#define MY_TEST_MSG   "Hello from kernel space!!!"
#define MSG_LEN       (strlen(MY_TEST_MSG) + 1)
static int doc_exmpl_echo(struct sk_buff *skb, struct genl_info *info)
{
    //新建一个 netlink message
    /* total length of attribute including padding */
    size_t size = nla_total_size(MSG_LEN);    
    struct sk_buff *skbp = genlmsg_new(size, GFP_KERNEL);
 
    /* Add a new netlink message to an skb */
    struct nlmsghdr *nlhdr = nlmsg_hdr(skb);
    pid_t pid = nlhdr->nlmsg_pid;
    
    genlmsg_put(skbp, pid, 0, &doc_exmpl_genl_family, 0, DOC_EXMPL_C_ECHO);
    
    nla_put(skbp, DOC_EXMPL_A_MSG, MSG_LEN, MY_TEST_MSG);
 
    void *head = genlmsg_data(nlmsg_data(nlmsg_hdr(skbp)));
    //
    genlmsg_end(skbp, head);
 
    genlmsg_unicast(&init_net, skbp, pid);
 
}
doc_exmpl_genl_genl_mcgrp

 
static struct genl_multicast_group doc_exmpl_genl_mcgrp = {
        .name = "DOC_EXMPL_GRP",
};


小结
内核态程序

接收:注册 genl_ops,填写命令和相关的响应函数。等待用户态程序发送该命令后,由内核调用相关的响应函数

发送:genlmsg_unicast / genlmsg_multicast

用户态程序

接收:recvmsg

发送:sendto
 

generic netlink 编程快速入门 目录   一、generic netlink 消息结构 二、接口搭建过程 kernel端程序的准备 1、定义想要传送的消息种类 2、定义一组命令 3、为每个命令定义一个响应函数 4、将命令与相应函数关联起来 5、创建一个命令族 6、向内核注册新建的命令族 7、将关联好的命令和响应函数绑定(注册)到命令族上 用户端程序的使用: 1、新建一个套接字,并绑定上 netlink ... 阅读详情

相关推荐

通用netlink的基本使用(1) NETLINK_GENERIC

内核部分 #include &lt;net/genetlink.h&gt; #include &lt;linux/module.h&gt; #include &lt;linux/kernel.h&gt; #include &lt;linux/init.h&gt; int ctl_test_function(struct sk_buff *skb, struct genl_info *info...

yldfree的博客 2663

GENERIC NETLINK 介绍及使用

之前一篇博文讲述了Netlink如何使用,当时我们是自己定义了一种协议NETLINK_TEST进行用户进程和内核的通信,这篇文章就讲一下如何使用内核为我们提供的NETLINK_GENERIC进行通信。 如果把Netlink协议比作IP层,那么NETLINK_GENERIC可以比作UDP这一层。它们的具体关系如图1。 图中NETLINK_GENERIC协议作为Netlink层的payload,当然NETLINK_GENERIC作为一个上层协议,也拥有自己的头部(图中的Family头部)和pa...

weixin_39094034的博客 5119

通用netlink NETLINK_GENERIC 基本函数

消息格式   int genl_register_family(struct genl_family *family)功能:通用link协议由family来管理,此函数注册一个通用netlink family,其实就是将family表项加入到family_ht表中 family:通用netlink family 返回值:成功返回0 失败返回负数错误码 头文件: #include &lt;n...

yldfree的博客 4328

linux / Generic Netlink

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

布袋和尚 1650

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

目录 1Generic Netlink 概述 2 Generic Netlink相关结构体 2.1 Generic Netlink消息头结构:struct genlmsghdr 2.2 Generic Netlink Family结构:struct genl_family(内核中完成注册) 2.3 Generic Netlink Family命令处理结构:struct genl_ops(内核中完成注册) 2.4 Generic Netlink Family内核接收消息结构:struct gen.

3484

Flocking for Multi-Agent Dynamic Systems: Algorithms and Theory的理论简述和python仿真实现(附源码)

经典论文Flocking for Multi-Agent Dynamic Systems: Algorithms and Theory的学习与编程仿真,提供源码

m0_59701926的博客 1235

制作RootFS

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

fanged的专栏 1820

生信实战:用bcftools玩转VCF文件处理(从压缩到注释全流程)

本文详细介绍了使用bcftools处理VCF文件的全流程实战指南。从数据准备、压缩索引、信息提取、质量统计到核心的变异过滤与功能注释,手把手教你高效管理基因组变异数据。重点阐述了如何利用bcftools进行VCF文件注释,整合外部数据库信息,为变异位点添加关键的生物学标签,是生物信息学数据分析的必备技能。

rgv23456789的博客 340

optee介绍(一)代码下载编译以及使用qemu运行

本文主要介绍了optee代码的下载编译以及使用qemu运行,以及此过程中可能遇到的问题及解决方案。本文着重介绍手动下载代码方式,主要针对repo下载方式因为万络原因容易失败的情况,若是网络好,可使用repo下载会更方便。

Fifty的博客 2126

YOLOv11垃圾检测实战:从零搭建带GUI的智能分类系统(附完整数据集)

本文详细介绍了如何从零开始构建一个基于YOLOv11的智能垃圾检测系统,并集成图形化界面。内容涵盖数据集构建与增强、模型选型与轻量化改进(如引入注意力机制),以及使用PyQt5开发稳定GUI的完整流程,旨在解决实际部署中精度与速度的平衡问题,为环保科技等应用场景提供可落地的解决方案。

646

Shopee滑块验证码太刁钻?手把手教你用Python+Playwright绕过弧形验证(附影刀RPA接入代码)

本文详细介绍了如何使用Python和Playwright结合SadCaptcha第三方服务高效破解Shopee平台的弧形滑块验证码,并集成到影刀RPA工作流中。该方案开发周期短、准确率高(>90%),特别适合跨境电商自动化操作,已在实际项目中验证成功。

weixin_29284657的博客 565

手撕ARM64启动栈(一):QEMU + TF-A → OP-TEE → U-Boot → Linux 全链路总览

ARM 官方提供的参考固件实现,本文用作 BL1/BL2/BL31:开源 TEE 实现,本仓库用作 BL32,运行在 S-EL1,通过 opteed(SPD)与 BL31 交互,提供 TA 运行环境U-Boot:本仓库用作 BL33/Normal World bootloader,通过 ARM semihosting(,非网络/非fw_cfg)从宿主机文件系统读取内核与 initrd,bootm跳转内核。

Fifty的博客 493

手把手教你用U盘给海信IP501H刷机:GK6323V100C芯片的4.4.2和9.0固件实战

本文详细介绍了海信IP501H电视盒子基于GK6323V100C芯片的刷机实战指南,涵盖Android 4.4.2强刷和Android 9.0免拆刷机两种方案。从工具准备、短接操作到ADB调试,提供全流程解析和常见问题解决方案,帮助用户安全高效地完成刷机操作。

lemon的博客 1454

Generic Netlink(genl)介绍与例子

一、Generic Netlink介绍 介绍Generic Netlink之前,不得不说Netlink。Netlink是一种灵活的并且健壮的通讯方式,可以用于kernel to user、user to kernel、kernel to kernel甚至user to user的通讯。Netlink的通道是通过Family来组织的,但随着使用越来越多,Family ID已经不够分配了,所以才有了Generic N...

weixin_42017225的博客 1694

103、模拟增益vs数字增益:信噪比、动态范围与噪声放大的权衡艺术及调试陷阱

摘要 本文深入探讨了模拟增益与数字增益在图像处理中的差异及其对信噪比、动态范围和噪声放大的影响。作者通过实际案例揭示了数字增益可能导致的噪点放大问题,分析了模拟增益在物理层面对信号和噪声的放大机制,以及数字增益对量化噪声的影响。文章详细比较了两种增益方式下信噪比的变化曲线,指出数字增益会平方放大量化噪声的致命缺陷。此外,作者总结了常见的调试陷阱,并提供了针对不同光照场景的增益分配策略建议。最后强调调试时应优先考虑模拟增益,严格控制数字增益幅度,并配合降噪算法优化以获得最佳图像质量。

zzfmlxs的博客 48

PCL 逐点插入法构建Delaunay三角网(C++详细过程版)

点云构建Delaunay三角网的逐点插入法基于PCL点云库的C++详细过程版实现

点云侠的博客 2991

Generic Netlink详解

netlink socket是一种用于用户态进程和内核态进程之间的通信机制。它通过为内核模块提供一组特殊的API,并为用户程序提供了一组标准的socket接口的方式,实现了全双工的通讯连接。 Netlink的特点: 双向传输,异步通信用户空间中使用标准socket API内核空间中使用专门的API支持多播可由内核端发起通信支持32种协议类型 netlink仅支持32种协议类型

工匠重生 3912

dolphinscheduler集群服务一键安装启动实现流程剖析

通过以上分析我们基本梳理清楚了整个dolphinscheduler项目的一键安装启动逻辑, 我们现在在通过一张图正好理一下整个实现流程。关于一键停止集群服务及一件查询集群各个角色服务实例运行状态的实现也都在stop-all.sh/status-all.sh及dolphinscheduler-daemon.sh这三个脚本中, 有兴趣自行研究。写这篇文章的核心目的不仅在于给大家分享如何优秀的开源项目的一键安装部署的实现过程。

最爱嫣夜来 1207

Linux基础 -- Generic Netlink 框架详解与开发实践

在 Linux 内核开发中,内核模块往往需要提供接口供用户空间配置或查询内部状态。ioctl:接口扩展性差、维护成本高;procfssysfs:适合只读状态/参数设置,但不适合复杂命令;Netlink:适合复杂的、结构化的内核通信,但原始 Netlink 使用繁琐,需要自定义协议号与消息调度。u8 cmd;// 命令编号u8 flags;// 权限控制,如 GENL_ADMIN_PERM// 参数校验策略// 处理函数// 列表输出函数doit():处理用户空间发送的一次性命令;

sz66cm 学习随笔 1280

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Generic Netlink机制

文章目录数据结构genl_familygenl_ops多播组: genl_multicast_groupGeneric Netlink框架Generic Netlink消息消息头部消息API消息的封装消息的解析消息的发送协议初始化family的管理family的注册&去注册family操作集的注册&去注册控制familyfamily查询命令: ctrl_getfamily()消息接收: genl_rcv()genl_rcv_msg()Generic Netlink组播多播组的注册&去

九天小哥的专栏 2211

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

二 generic netlink

一 基础 由于netlink协议最多支持32个协议簇,目前内核中已经使用其中21个,对于用户需要定制特殊的协议类型略显不够,为此Linux设计了这种通用Netlink协议簇,用户可在此之上定义更多类型的子协议。 Generic Netlink是基于客户端-服务端模型的通信机制。服务端(内核)注册family(family是对genl服务的各项定义的集合)。控制器和客户端都通过已注册的信息与服务

clytiejoe的专栏 1997

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

genetlink

转自: http://www.tuicool.com/articles/jE7nim netlink socket是一种用于用户态进程和内核态进程之间的通信机制。它通过为内核模块提供一组特殊的API,并为用户程序提供了一组标准的socket接口的方式,实现了全双工的通讯连接。 Netlink的特点:  双向传输,异步通信 用户空间中使用标准socket API 内核空间中使用专门的API

caoshunxin01的专栏 1169

[Linux Kernel] generic_netlink 网络连接

文章目录1. Generic Netlink By Example参考    本文档简要介绍了Generic Netlink,提供了一些有关如何使用它的简单示例,以及一些有关如何充分利用Generic Netlink通信接口的建议。 尽管本文档不需要读者对Netlink是什么以及它如何工作有详细的了解,但是假定读者具有一些Netlink基本知识。 和往常一样,内核源代码是您最好的朋友。    尽管本文档从用户空间的角度简要讨论了Generic Netlink,但其主要重点是内核的Generic Netlin

u012849539的博客 1093

Android wpa_supplicant源码分析---nl80211内核通信Generic Netlink

代码位置: kernel/net/netlink/genetlink.c kernel/include/net/genetlink.hGENL简介netlink仅支持32种协议类型,这在实际应用中可能并不足够。因此产生了generic netlink(以下简称为genl)。 generic netlink支持1023(前10个保留不用)个子协议号,弥补了netlink协议类型较少的缺陷。1 架

cuijiyue的专栏 1万+
上一篇: netlink 0010 -- Generic Netlink 实现通信
下一篇: 路由重发布Route Redistribution
废言Pro
博客等级 码龄16年 1092粉丝 142原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值