消息传递Netlink介绍

509 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情 

一、Netlink

netlinkLinux中被广泛用于用户空间和内核之间的消息传递,netlink在本质上是一种基于socket的通信方式,但相对于传统的socketnetlink通信地址不是IP和端口号,而是以进程PID作为通信地址,这为进程间的消息传递提供了很大的便利。当用户空间进程和内核通信时。内核被看作一个大的进程,PID0。当用户空间进程互相通信时,就为各自的进程,通过getpid()函数可以获得进程自身的PID在这里插入图片描述

1、用户空间Netlink套接字

在用户空间,Netlink支持标准的套接字接口,与普通套接字一样使用socket方法来对其进行初始化:

/* 创建netlink socket连接 */
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ATNLPROXY);

初始化函数中NETLINK_ATNLPROXY表示了通信协议,对于内核空间驱动和应用程序之间的消息传递,通信双方必须有相同的协议,其实质上是一个整数型:

#define NETLINK_ATNLPROXY             27

在套接字初始化后,必须使用Netlink特有的地址结构体对当前进程进行标识。Netlink地址结构如下所示:

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 */
};
  • nl_family定义了消息的地址域,一般情况下赋值为AF_NETLINK
  • nl_pad作为Netlink的填充字段,一般默认值0
  • 在单播情况下,nl_pid代表进程的唯一标识符PID,而在多播情况下,需将nl_pid设置为0,同时使用nl_groups记录组播地址掩码。 当程序执行时,sockaddr_nl结构会被转化为标准的socket地址,通过bind()函数将其与Netlink套接字进行绑定后作为参数传递给用于发送和接受消息的接口sendmsg()recvmsg()

二、Netlink IPC 数据结构

1、netlink消息的开头

每个 netlink消息的开头是固定长度的netlink报头,报头后才是实际的载荷。netlink报头一共占16个字节。

struct nlmsghdr
{
	__u32 nlmsg_len;   /* Length of message */
	__u16 nlmsg_type; /* Message type*/
	__u16 nlmsg_flags; /* Additional flags */
	__u32 nlmsg_seq;   /* Sequence number */
	__u32 nlmsg_pid;   /* Sending process PID */
};
  • nlmsg_len 指定消息的总长度
  • nlmsg_type 用于应用内部定义消息的类型
  • nlmsg_flags 用于设置消息标志
  • nlmsg_seq 用于应用追踪消息,表示顺序号
  • nlmsg_pid 用于应用追踪消息,后者为消息来源进程ID

实例:

/* 填充消息头 */
    (void)osa_memset_s((char*)nlmsg, size, 0, size);
    nlmsg->nlmsg_len  = NLMSG_LENGTH(final_len + sizeof(*device_event));
    nlmsg->nlmsg_type = NLMSG_DONE;
    nlmsg->nlmsg_pid = getpid();
    nlmsg->nlmsg_seq = seq++;      /* seq暂时没有用到,后期可能会进行扩展使用 */

2、设置结构 iovec

struct iovec定义了一个向量元素。通常,这个结构用作一个多元素的数组。对于每一个传输的元素,指针成员iov_base指向一个缓冲区,这个缓冲区是存放的是readv所接收的数据或是writev将要发送的数据。成员iov_len在各种情况下分别确定了接收的最大长度以及实际写入的长度。

struct iovec {
    ptr_t iov_base; /* Starting address */
    size_t iov_len; /* Length in bytes */
};

实现:

/* 设置结构 iovec */
iov.iov_base = nlmsg;
iov.iov_len = nlmsg->nlmsg_len;

3、结构 msghdr

struct msghdr
{
     void *msg_name;
     socklen_t msg_namelen;

     struct iovec *msg_iov;
     size_t msg_iovlen;
 
     void *msg_control;
     size_t msg_controllen;

     int msg_flags;
};