一、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 messagechar* 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_nlstruct 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 attributeint nla_len = nlh->nlmsg_len - GENL_HDRLEN;           //len of attributes//一条nlmsg里面,可能会包含多个attrfor (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.csdn.net/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通信地址nladdrint sockfd = genlmsg_open();//<1>向kernel发送信息//这里必须先通过family的名字获取到family ID,名字需要与驱动里的一致int id = genlmsg_get_family_id(sockfd, "DOC_EXMPL");//获取自己的进程号pidpid_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_openstatic int genlmsg_open(void)
{    //申请一个generic netlink协议的socketint sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);//<1>填写netlink 通信地址 nladdrstruct 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_idstatic 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_sendstatic 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 headerstruct 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 的 idnlh->nlmsg_flags = NLM_F_REQUEST;nlh->nlmsg_seq = 0;nlh->nlmsg_pid = nlmsg_pid;//</1>//<2>填写generic netlink message headerstruct 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_ECHOglh->version = genl_version;//这里 generic netlink命令版本号是 1//</2>//<3>netlink attribute headerstruct nlattr *nla;//generic netlink message即是真实的消息部分,消息的格式是nlattr      nla = (struct nlattr *)GENLMSG_DATA(glh);//填写消息类型,即 DOC_EXMPL_A_MSGnla->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 addressstruct 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_allocstatic 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中接收消息,存储到msgrecvmsg(sockfd, &msg, 0);
}
genlmsg_dispatchstatic int genlmsg_dispatch(struct nlmsghdr *nlmsghdr, unsigned int nlh_len,int nlmsg_type, int nla_type, unsigned char *buf, int *len)
{//读取到的数据流里面,可能会包含多条nlmsgfor (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 attributeint nla_len = nlh->nlmsg_len - GENL_HDRLEN;           //len of attributes//一条nlmsg里面,可能会包含多个attrfor (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_policystruct 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_mcgrpstatic struct genl_multicast_group doc_exmpl_genl_mcgrp = {.name = "DOC_EXMPL_GRP",
};

小结
内核态程序

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

发送:genlmsg_unicast / genlmsg_multicast

用户态程序

接收:recvmsg

发送:sendto

netlink 011 -- generic netlink 编程入门相关推荐

  1. Generic Netlink机制

    文章目录 数据结构 genl_family genl_ops 多播组: genl_multicast_group Generic Netlink框架 Generic Netlink消息 消息头部 消息 ...

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

    目录 1 Generic Netlink 概述 2 Generic Netlink相关结构体 2.1 Generic Netlink消息头结构:struct genlmsghdr 2.2 Generi ...

  3. Generic Netlink内核实现分析(一):初始化

    Generic Netlink 是内核专门为了扩展netlink协议簇而设计的"通用netlink协议簇".由于netlink协议最多支持32个协议簇,目前Linux4.1的内核中 ...

  4. 转载博客:generic netlink 编程快速入门

    https://segmentfault.com/a/1190000016220770 generic netlink 编程快速入门

  5. linux4.4 内核 netlink,wpa_supplicant与内核nl80211通信之Generic Netlink

    代码位置: kernel/net/netlink/genetlink.c kernel/include/net/genetlink.h GENL简介 netlink仅支持32种协议类型,这在实际应用中 ...

  6. linux / Generic Netlink

    一.概述 Generic Netlink 是内核专门为了扩展 netlink 协议簇而设计的"通用netlink协议簇".由于 netlink 协议最多支持 32 个协议簇,目前 ...

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

    代码位置: kernel/net/netlink/genetlink.c kernel/include/net/genetlink.h GENL简介 netlink仅支持32种协议类型,这在实际应用中 ...

  8. .Net 3.5 Remoting编程入门三

    VS2008 .Net 3.5 Remoting编程入门三 信道 什么是信道?信道有哪些类型呢? 信道顾名思意就是通信的通道.就想那些宣传标语说的,"要想富,先修路!".同理,要学 ...

  9. COM编程入门---转发

    [ 原创文档 本文适合中级读者 已阅读68380次 ] 文档代码工具 COM编程入门 第一部分--什么是COM,如何使用COM 编译:赵湘宁 下载源代码 本文的目的是为刚刚接触COM的程序员提供编程指 ...

  10. Android传感器编程入门(三)

    上接<Android传感器编程入门(二)> 三.实例:窈窈录音器 通过上面的例子我们学会了如何获得某种类型的传感器,下面我通过一个实例来学会如何使用某一个类型的传感器.我们这里使用加速度传 ...

最新文章

  1. PPT 下载 | 神策数据孙超赟:多场景解读运营的价值、生存状态与解决方案
  2. 【问题记录】解决npm 报错This dependency was not found: A complete log of this run can be found in:
  3. OD-困难重重的追踪消息断点
  4. java重写6,java重写equals()方法和hashCode()方法
  5. iec104点号_QTouch之IEC60870-104通讯
  6. tensorflow python3.6_[教程]Tensorflow + win10 + CPU + Python3.6+ 安装教程
  7. idea怎么给项目改名_微软改名部惹祸了
  8. MacBook运行C++,不用安装任何软件
  9. 项目移植过程中报:“Project facet Java version 1.7 is not supported.” 错误
  10. 商业智能bi能带来什么价值
  11. zookeeper之理论基础
  12. DIV CSS设计时IE6、IE7、FF 与兼容性有关的特性
  13. 大一C语言和线性代数,线性代数:对于编程很重要
  14. Kubernetes网络flannel之host-gw模式分析
  15. linux snoop抓包命令,Snoop抓包工具用法简介.doc
  16. 将Firefox浏览器的Google工具栏拖动到浏览器底部
  17. 【常系数线性递推】51nod1538 一道难题
  18. ES-分片路由(routing)
  19. 您只能在Photoshop CS4中做的3酷技巧
  20. C语言编写 小企鹅表情包

热门文章

  1. 成为java高手的八大条件
  2. Unity Behaviors for Interception
  3. 用ODBC方式操作dbf文件
  4. 如何把握好 transition 和 animation 的时序,创作描边按钮特效...
  5. laravel Ajax请求 X-CSRF验证问题
  6. 【内购篇】5 分钟教你成为会赚钱的独立开发者
  7. 进程间通信之管道与有名管道
  8. java 访问Domino LOtus 数据库
  9. liferay开发小结, liferay瘦身一
  10. 35岁,程序员过不去的坎?