内核与用户空间通信有很多种通信方式,netlink是其中一种,其余的还有/proc、ioctl、sockopt、共享内存等等。netlink的特点是异步全双工
netlink使用32位端口寻址,称为pid(与进程号没有关系),其中内核的pid地址为0,。netlink主要特性如下:
- 支持全双工、异步通信(当然同步也支持)
- 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
- 在内核空间使用专用的内核API接口
- 支持多播(因此支持“总线”式通信,可实现消息订阅)
- 在内核端可用于进程上下文与中断上下文
基本数据结构:
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 (unused) */
};
struct sockaddr_nl
{
sa_family_t nl_family; /*该字段总是为AF_NETLINK */
unsigned short nl_pad; /* 目前未用到,填充为0*/
__u32 nl_pid; /* process pid */
__u32 nl_groups; /* multicast groups mask */
};
struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message including header */
__u16 nlmsg_type; /* Message content */
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process PID */
};
struct sockaddr_nl
是netlink通信地址,和通常socket编程中的sockaddr_in
作用一样。pid表示通信端口,groups表示组,注意这里为希望加入多播组号的掩码,也就是说最多只支持32个组。
Netlink报文的数据区由消息头和消息体构成,struct nlmsghdr
即为消息头,消息体接在消息头后。
操作实例:
用户层pid设置为100,应用层发送一条信息到内核,内核回复同样的信息;
内核层:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define NETLINK_TEST (25)
static dev_t devId;
static struct class *cls = NULL;
struct sock *nl_sk = NULL;
static void
hello_cleanup(void)
{
netlink_kernel_release(nl_sk);
device_destroy(cls, devId);
class_destroy(cls);
unregister_chrdev_region(devId, 1);
}
static void
netlink_send(int pid, uint8_t *message, int len)
{
struct sk_buff *skb_1;
struct nlmsghdr *nlh;
if(!message || !nl_sk) {
return;
}
skb_1 = alloc_skb(NLMSG_SPACE(len), GFP_KERNEL);
if( !skb_1 ) {
printk(KERN_ERR "alloc_skb error!\n");
}
nlh = nlmsg_put(skb_1, 0, 0, 0, len, 0);
NETLINK_CB(skb_1).portid = 0;
NETLINK_CB(skb_1).dst_group = 0;
memcpy(NLMSG_DATA(nlh), message, len);
netlink_unicast(nl_sk, skb_1, pid, MSG_DONTWAIT);
}
static void
netlink_input(struct sk_buff *__skb)
{
struct sk_buff *skb;
char str[100];
struct nlmsghdr *nlh;
if( !__skb ) {
return;
}
skb = skb_get(__skb);
if( skb->len < NLMSG_SPACE(0)) {
return;
}
nlh = nlmsg_hdr(skb);
memset(str, 0, sizeof(str));
memcpy(str, NLMSG_DATA(nlh), sizeof(str));
printk(KERN_INFO "receive message (pid:%d):%s\n", nlh->nlmsg_pid, str);
printk(KERN_INFO "space:%d\n", NLMSG_SPACE(0));
printk(KERN_INFO "size:%d\n", nlh->nlmsg_len);
netlink_send(nlh->nlmsg_pid, NLMSG_DATA(nlh), nlh->nlmsg_len - NLMSG_SPACE(0));
return;
}
static __init int netlink_init(void)
{
int result;
struct netlink_kernel_cfg nkc;
printk(KERN_WARNING "netlink init start!\n");
//动态注册设备号
if(( result = alloc_chrdev_region(&devId, 0, 1, "stone-alloc-dev") ) != 0) {
printk(KERN_WARNING "register dev id error:%d\n", result);
goto err;
} else {
printk(KERN_WARNING "register dev id success!\n");
}
//动态创建设备节点
cls = class_create(THIS_MODULE, "stone-class");
if(IS_ERR(cls)) {
printk(KERN_WARNING "create class error!\n");
goto err;
}
if(device_create(cls, NULL, devId, "", "hello%d", 0) == NULL) {
printk(KERN_WARNING "create device error!\n");
goto err;
}
//初始化netlink
nkc.groups = 0;
nkc.flags = 0;
nkc.input = netlink_input;
nkc.cb_mutex = NULL;
nkc.bind = NULL;
nkc.unbind = NULL;
nkc.compare = NULL;
nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &nkc);
if( !nl_sk ) {
printk(KERN_ERR "[netlink] create netlink socket error!\n");
goto err;
}
printk(KERN_ALERT "netlink init success!\n");
return 0;
err:
hello_cleanup();
return -1;
}
static __exit void netlink_exit(void)
{
hello_cleanup();
printk(KERN_WARNING "netlink exit!\n");
}
module_init(netlink_init);
module_exit(netlink_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stone");
应用层使用sendmsg、recvmsg :
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define NETLINK_TEST (25)
#define MAX_PAYLOAD (1024)
#define TEST_PID (100)
int netlink_create_socket(void)
{
//create a socket
return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
}
int netlink_bind(int sock_fd)
{
struct sockaddr_nl addr;
memset(&addr, 0, sizeof(struct sockaddr_nl));
addr.nl_family = AF_NETLINK;
addr.nl_pid = TEST_PID;
addr.nl_groups = 0;
return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl));
}
int
netlink_send_message(int sock_fd, const unsigned char *message, int len,
unsigned int pid, unsigned int group)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl dest_addr;
struct iovec iov;
struct msghdr msg;
if( !message ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(len));
if( !nlh ) {
perror("malloc");
return -2;
}
nlh->nlmsg_len = NLMSG_SPACE(len);
nlh->nlmsg_pid = TEST_PID;
nlh->nlmsg_flags = 0;
memcpy(NLMSG_DATA(nlh), message, len);
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
memset(&dest_addr, 0, sizeof(struct sockaddr_nl));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = pid;
dest_addr.nl_groups = group;
memset(&msg, 0, sizeof(struct msghdr));
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
//send message
if( sendmsg(sock_fd, &msg, 0) < 0 )
{
printf("send error!\n");
free(nlh);
return -3;
}
free(nlh);
return 0;
}
int
netlink_recv_message(int sock_fd, unsigned char *message, int *len)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl source_addr;
struct iovec iov;
struct msghdr msg;
if( !message || !len ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
if( !nlh ) {
perror("malloc");
return -2;
}
iov.iov_base = (void *)nlh;
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
memset(&source_addr, 0, sizeof(struct sockaddr_nl));
memset(&msg, 0, sizeof(struct msghdr));
msg.msg_name = (void *)&source_addr;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if ( recvmsg(sock_fd, &msg, 0) < 0 ) {
printf("recvmsg error!\n");
return -3;
}
*len = nlh->nlmsg_len - NLMSG_SPACE(0);
memcpy(message, (unsigned char *)NLMSG_DATA(nlh), *len);
free(nlh);
return 0;
}
int
main(int argc, char **argv)
{
int sock_fd;
char buf[MAX_PAYLOAD];
int len;
if( argc < 2) {
printf("enter message!\n");
exit(EXIT_FAILURE);
}
sock_fd = netlink_create_socket();
if(sock_fd == -1) {
printf("socket error!\n");
return -1;
}
if( netlink_bind(sock_fd) < 0 ) {
perror("bind");
close(sock_fd);
exit(EXIT_FAILURE);
}
netlink_send_message(sock_fd, argv[1], strlen(argv[1]) + 1, 0, 0);
if( netlink_recv_message(sock_fd, buf, &len) == 0 ) {
printf("recv:%s len:%d\n", buf, len);
}
close(sock_fd);
return 0;
}
应用层程序2 , 使用sendto、recvfrom发送接收数据:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define NETLINK_TEST (25)
#define MAX_PAYLOAD (1024)
#define TEST_PID (100)
int netlink_create_socket(void)
{
//create a socket
return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
}
int netlink_bind(int sock_fd)
{
struct sockaddr_nl addr;
memset(&addr, 0, sizeof(struct sockaddr_nl));
addr.nl_family = AF_NETLINK;
addr.nl_pid = TEST_PID;
addr.nl_groups = 0;
return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl));
}
int
netlink_send_message(int sock_fd, const unsigned char *message, int len,
unsigned int pid, unsigned int group)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl dest_addr;
if( !message ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(len));
if( !nlh ) {
perror("malloc");
return -2;
}
nlh->nlmsg_len = NLMSG_SPACE(len);
nlh->nlmsg_pid = TEST_PID;
nlh->nlmsg_flags = 0;
memcpy(NLMSG_DATA(nlh), message, len);
memset(&dest_addr, 0, sizeof(struct sockaddr_nl));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = pid;
dest_addr.nl_groups = group;
//send message
if( sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_nl)) != nlh->nlmsg_len ) {
printf("send error!\n");
free(nlh);
return -3;
}
free(nlh);
return 0;
}
int
netlink_recv_message(int sock_fd, unsigned char *message, int *len)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl src_addr;
socklen_t addrlen = sizeof(struct sockaddr_nl);
if( !message || !len ) {
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
if( !nlh ) {
perror("malloc");
return -2;
}
memset(&src_addr, 0, sizeof(struct sockaddr_nl));
if( recvfrom(sock_fd, nlh, NLMSG_SPACE(MAX_PAYLOAD), 0, (struct sockaddr *)&src_addr, (socklen_t *)&addrlen) < 0 ) {
printf("recvmsg error!\n");
return -3;
}
*len = nlh->nlmsg_len - NLMSG_SPACE(0);
memcpy(message, (unsigned char *)NLMSG_DATA(nlh), *len);
free(nlh);
return 0;
}
int
main(int argc, char **argv)
{
int sock_fd;
char buf[MAX_PAYLOAD];
int len;
if( argc < 2) {
printf("enter message!\n");
exit(EXIT_FAILURE);
}
sock_fd = netlink_create_socket();
if(sock_fd == -1) {
printf("socket error!\n");
return -1;
}
if( netlink_bind(sock_fd) < 0 ) {
perror("bind");
close(sock_fd);
exit(EXIT_FAILURE);
}
netlink_send_message(sock_fd, argv[1], strlen(argv[1]) + 1, 0, 0);
if( netlink_recv_message(sock_fd, buf, &len) == 0 ) {
printf("recv:%s len:%d\n", buf, len);
}
close(sock_fd);
return 0;
}
执行应用程序
$ ./netlink hello
查看内核信息: