内核态与用户态进行数据交互的方法很多,分为用户程序主动发起的消息交交互和内核主动发起的信息交互。通常我们所熟知的系统调用、对/proc进行读写操作、mmap和编写驱动程序等都属于前者,而由内核主动发起的信息交互方法比较少,最典型的方法为内核发送信号给当前进程。上述这些方法都是单向通信的,也就是说要么是用户态进程主动发起数据交互会话,要么是内核主动发起会话。有没有一种可以在用户态和内核态进行双向数据交互的方法呢?
Netlink是一种在内核态和用户态可以进行双向数据交互的通信机制。在用户态,我们可以将netlink看作是一种特殊的socket,因此通过socket接口来就可以直接使用netlink;在内核态,则需要通过一组特殊的内核接口编写内核模块。
如果初次编写netlink程序,可能会对它所涉及的数据结构感到困惑,本文将简单介绍一下netlink编程中遇到的基本数据结构。
struct msghdr
如果使用sendmsg()发送数据,那么必须使用msghdr结构,该结构内部封装了本次发送数据的一些参数:
struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ };
netlink提供的是一种基于数据报的通信服务,因此与UDP通信协议类似,必须通过msg_name指明目的套接字地址结构,而msg_namelen则指明该地址结构的长度。msg_iov指定数据缓冲区数组,而msg_iovlen指明了该数组的元素个数。
struct iovec
iovec结构表示一个向量元素,它定义了一个标准的数据缓冲区格式。其包含两个字段:指向数据的指针和数据长度。
struct iovec { void *iov_base; /* Pointer to data. */ size_t iov_len; /* Length of data. */ };
当使用iovec结构类型的数组传递数据时,可以将多个消息通过一次系统调用进行发送。
struct nlmsghdr
如果使用sendmsg函数来发送netlink数据报,那么iovec结构中iov_base字段应该根据具体协议指向一个自定义的数据结构。一般这个自定义的数据结构如下所示:
struct req { struct nlmsghdr *nlh; struct special_struct *r; };
其中nlmsghdr结构为必须所有的,它用来描述netlink消息头。
struct nlmsghdr { __u32 nlmsg_len; /* Length of message including header. */ __u16 nlmsg_type; /* Type of message content. */ __u16 nlmsg_flags; /* Additional flags. */ __u32 nlmsg_seq; /* Sequence number. */ __u32 nlmsg_pid; /* PID of the sending process. */ };
对于special_struct结构则要根据具体需求而定。如果使用netlink现有的协议,比如NETLINK_INET_DIAG,那么special_struct结构在用户程序向内核发送消息时应该为inet_diag_req结构,如果当内核发送消息给用户进程那么special_struct结构又必须定义为inet_diag_msg。这些结构体都是事先定义好的,我们直接拿来用,按需获取这些结构中的某些字段的值即可。
如果是自己定义的通信协议,那么则根据具体需求自定义数据结构即可。比如用户进程想通过pid在内核中获取该进程亲属的pid,那么用户进程在发送消息和接受消息时的special_struct分别可以按照如下定义:
struct get_pid_req { int pid; }; struct get_pid_msg { int parent_pid; int sibling_pid; int child_pid; }
为netlink消息确定好了结构以后,则将该结构赋值给iovec结构的iov_base字段。
struct sockaddr_nl
netlink是一种特殊的无连接套接字,因此必须在msghdr结构的msg_name中指明目的netlink套接字的地址。套接字地址的通用结构为struct sockaddr,但对于具体类型的套接字则有不同的地址结构,比如TCP/IP协议族为sockaddr_in,对于netlink则为sockaddr_nl结构:
struct sockaddr_nl { sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* Zero. */ pid_t nl_pid; /* Process ID. */ __u32 nl_groups; /* Multicast groups mask. */ };
其中该结构中的nl_family字段填充为AF_NETLINK。
以上这些结构在netlink编程中都会涉及到,但是通过上面描述的关系来看,它们最终都是与msghdr结构有关的。而msghdr结构作为参数通过sendmsg函数就可以netlink数据包发送至内核。