generic netlink 编程快速入门

目录

 

一、generic netlink 消息结构

二、接口搭建过程

kernel端程序的准备

1、定义想要传送的消息种类

2、定义一组命令

3、为每个命令定义一个响应函数

4、将命令与相应函数关联起来

5、创建一个命令族

6、向内核注册新建的命令族

7、将关联好的命令和响应函数绑定(注册)到命令族上

用户端程序的使用:

1、新建一个套接字,并绑定上 netlink address

2、向 GENL_ID_CRTL 询问想通信的命令族的 id

3、等待接收 GENL_ID_CRTL 的返回的查询结果

4、拆分读取接收到的信息

5、与想要通信的命令族通信

三、程序示例

用户态程序

内核态程序

 


一、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 的结构如下:

generic netlink 编程快速入门_第1张图片

二、接口搭建过程

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;
// 
 
//将填写好的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);
//

//申请内存空间用于存储此条 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;
//    
 
//从套接字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.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通信地址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);
    //
    
    //<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);
    // 
   
    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;
    // 
 
    //将填写好的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);
    //

    //<2>接收sockfd上收到的内核返回的信息,存储在buf中
    int len = 256;
    void *buf = genlmsg_alloc(&len); 
    len = genlmsg_recv(sockfd, buf, len);
    //
     
    //<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);
    //
 
    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;
    //
 
    //<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
    //
     
    //<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);
    //     

    //<4>填写消息发送的netlink socket address
    struct sockaddr_nl nladdr;
    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    //
 
    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);
    //
 
    //告知申请者封装之后的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;
    //    

    //从套接字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

 

你可能感兴趣的:(教程)