什么是 Netlink

Netlink 是 linux 提供的用于内核和用户态进程之间的通信方式。
但是注意虽然 Netlink 主要用于用户空间和内核空间的通信,但是也能用于用户空间的两个进程通信。
只是进程间通信有其他很多方式,一般不用 Netlink。除非需要用到 Netlink 的广播特性时。
那么 Netlink 有什么优势呢?
一般来说用户空间和内核空间的通信方式有 4 种:系统调用、ioctl、虚拟文件系统(/proc、/sys)、Netlink。而前 3 种都是单向的(通信只能由用户空间主动发起),但是 Netlink 可以实现双向通信(内核和用户空间都可以主动发起)。
Netlink 协议基于 BSD socket 和 AF_NETLINK 地址簇(address family),使用 32 位的端口号寻址(以前称作 PID),每个 Netlink 协议(或称作总线,man 手册中则称之为 netlink family),通常与一个或一组内核服务/组件相关联,如 NETLINK_ROUTE 用于获取和设置路由与链路信息、NETLINK_KOBJECT_UEVENT 用于内核向用户空间的 udev 进程发送通知等。

Netlink 特点

支持全双工、异步通信(当然同步也支持)
用户空间可使用标准的 BSD socket 接口(但 Netlink 并没有屏蔽掉协议包的构造与解析过程,推荐使用 libnl 等第三方库)
在内核空间使用专用的内核 API 接口
支持多播(因此支持“总线”式通信,可实现消息订阅)
在内核端可用于进程上下文与中断上下文

Netlink 优点

Netlink 相对于其他的通信机制具有以下优点:

使用 Netlink 通过自定义一种新的协议并加入协议族即可通过 socket API 使用 Netlink 协议完成数据交换,而 ioctl 和 proc 文件系统均需要通过程序加入相应的设备或文件。
Netlink 使用 socket 缓存队列,是一种异步通信机制,而 ioctl 是同步通信机制,如果传输的数据量较大,会影响系统性能。
Netlink 支持多播,属于一个 Netlink 组的模块和进程都能获得该多播消息。
Netlink 允许内核发起会话,而 ioctl 和系统调用只能由用户空间进程发起。

实例

内核和用户空间通信

netlink_kernel.c

/****************************************
 * Author: zhangwj
 * Date: 2017-01-19
 * Filename: netlink_test.c
 * Descript: netlink of kernel
 * Kernel: 3.10.0-327.22.2.el7.x86_64
 * Warning:
 ******************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/types.h>
#include <net/sock.h>

#define NETLINK_TEST 30
#define MSG_LEN 125
#define USER_PORT 100

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhangwj");
MODULE_DESCRIPTION("netlink example");

struct sock *nlsk = NULL;
extern struct net init_net;

int send_usrmsg(char *pbuf, uint16_t len)
{
	struct sk_buff *nl_skb;
	struct nlmsghdr *nlh;

	int ret;

	/* 创建sk_buff 空间 */
	nl_skb = nlmsg_new(len, GFP_ATOMIC);
	if (!nl_skb) {
		printk("netlink alloc failure\n");
		return -1;
	}

	/* 设置netlink消息头部 */
	nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
	if (nlh == NULL) {
		printk("nlmsg_put failaure \n");
		nlmsg_free(nl_skb);
		return -1;
	}

	/* 拷贝数据发送 */
	memcpy(nlmsg_data(nlh), pbuf, len);
	ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);

	return ret;
}

static void netlink_rcv_msg(struct sk_buff *skb)
{
	struct nlmsghdr *nlh = NULL;
	char *umsg = NULL;
	char *kmsg = "hello users!!!";

	if (skb->len >= nlmsg_total_size(0)) {
		nlh = nlmsg_hdr(skb);
		umsg = NLMSG_DATA(nlh);
		if (umsg) {
			printk("kernel recv from user: %s\n", umsg);
			send_usrmsg(kmsg, strlen(kmsg));
		}
	}
}

struct netlink_kernel_cfg cfg = {
	.input = netlink_rcv_msg, /* set recv callback */
};

int test_netlink_init(void)
{
	/* create netlink socket */
	nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
	if (nlsk == NULL) {
		printk("netlink_kernel_create error !\n");
		return -1;
	}
	printk("test_netlink_init\n");

	return 0;
}

void test_netlink_exit(void)
{
	if (nlsk) {
		netlink_kernel_release(nlsk); /* release ..*/
		nlsk = NULL;
	}
	printk("test_netlink_exit!\n");
}

module_init(test_netlink_init);
module_exit(test_netlink_exit);

netlink_userspace.c

#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define NETLINK_TEST 30
#define MSG_LEN 125
#define MAX_PLOAD 125

typedef struct _user_msg_info {
	struct nlmsghdr hdr;
	char msg[MSG_LEN];
} user_msg_info;

int main(int argc, char **argv)
{
	int skfd;
	int ret;
	user_msg_info u_info;
	socklen_t len;
	struct nlmsghdr *nlh = NULL;
	struct sockaddr_nl saddr, daddr;
	char *umsg = "hello netlink!!";

	/* note: 使用前,应该要先在 kernel 中创建相应的 protocol ,不然好像这里会报 not support。所以,使用 netlink 貌似不支持单纯两个用户态进程通信,必须要牵扯到内核。两个进程进行通信,也要在内核先注册相应的 netlink */
	/* 创建NETLINK socket */
	skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
	if (skfd == -1) {
		perror("create socket error\n");
		return -1;
	}

	memset(&saddr, 0, sizeof(saddr));
	saddr.nl_family = AF_NETLINK;  // AF_NETLINK
	saddr.nl_pid = 100;			   //端口号(port ID)
	saddr.nl_groups = 0;
	if (bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) {
		perror("bind() error\n");
		close(skfd);
		return -1;
	}

	memset(&daddr, 0, sizeof(daddr));
	daddr.nl_family = AF_NETLINK;
	// daddr.nl_pid = 0;  // to kernel
	daddr.nl_groups = 0;

	nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
	memset(nlh, 0, sizeof(struct nlmsghdr));
	nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
	nlh->nlmsg_flags = 0;
	nlh->nlmsg_type = 0;
	nlh->nlmsg_seq = 0;
	// nlh->nlmsg_pid = saddr.nl_pid;	// self port

	memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
	ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
	if (!ret) {
		perror("sendto error\n");
		close(skfd);
		exit(-1);
	}
	printf("send kernel:%s\n", umsg);

	memset(&u_info, 0, sizeof(u_info));
	len = sizeof(struct sockaddr_nl);
	ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
	if (!ret) {
		perror("recv form kernel error\n");
		close(skfd);
		exit(-1);
	}

	printf("from kernel:%s\n", u_info.msg);
	close(skfd);

	free((void *)nlh);
	return 0;
}

Makefile

#
#Desgin of Netlink
#

MODULE_NAME :=netlink_kernel
obj-m :=$(MODULE_NAME).o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all: ko netlink_userspace nl_server nl_client

ko:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

netlink_userspace:
	gcc netlink_userspace.c -o netlink_userspace.out -Wall

nl_server:
	gcc nl_server.c -o nl_server.out -Wall

nl_client:
	gcc nl_client.c -o nl_client.out -Wall

安装

sudo insmod netlink_kernel.ko 

运行

$ ./netlink_userspace.out 
send kernel:hello netlink!!
from kernel:hello users!!!

dmesg

[19238.234434] test_netlink_init
[19252.924710] kernel recv from user: hello netlink!!

用户空间和用户空间通信

nl_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>

// 定义Netlink协议类型(使用用户态专用的NETLINK_USERSOCK)
#define NETLINK_TEST_PROTO NETLINK_USERSOCK
// 定义消息缓冲区大小
#define BUFFER_SIZE 1024
// 定义服务端端口ID(自定义,用于区分同一协议下的不同通信端点)
#define SERVER_PORT_ID 10001

int main() {
    int nl_sock;
    struct sockaddr_nl server_addr, client_addr;
    struct nlmsghdr *nl_msg_hdr;
    char *msg_payload;
    socklen_t client_addr_len = sizeof(client_addr);
    char recv_buffer[BUFFER_SIZE] = {0};

    // 1. 创建Netlink套接字
    nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST_PROTO);
    if (nl_sock < 0) {
        perror("socket create failed");
        exit(EXIT_FAILURE);
    }

    // 2. 初始化服务端Netlink地址结构
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.nl_family = AF_NETLINK;    // 地址族必须为AF_NETLINK
    server_addr.nl_pid = SERVER_PORT_ID;   // 进程ID(用户态通信时,自定义端口ID)
    server_addr.nl_groups = 0;             // 不加入多播组

    // 3. 绑定套接字到服务端地址
    if (bind(nl_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(nl_sock);
        exit(EXIT_FAILURE);
    }

    printf("Netlink服务端已启动,等待客户端消息...\n");

    // 4. 循环接收客户端消息
    while (1) {
        memset(recv_buffer, 0, BUFFER_SIZE);
        // 接收Netlink消息(包含消息头+有效载荷)
        ssize_t recv_len = recvfrom(nl_sock, recv_buffer, BUFFER_SIZE, 0,
                                    (struct sockaddr *)&client_addr, &client_addr_len);
        if (recv_len < 0) {
            perror("recvfrom failed");
            continue;
        }

        // 5. 解析Netlink消息头
        nl_msg_hdr = (struct nlmsghdr *)recv_buffer;
        printf("------------------------\n");
        printf("接收到客户端消息:\n");
        printf("消息长度(含头):%u 字节\n", nl_msg_hdr->nlmsg_len);
        printf("消息类型:%u\n", nl_msg_hdr->nlmsg_type);
        printf("消息标志:%u\n", nl_msg_hdr->nlmsg_flags);

        // 6. 提取并打印消息有效载荷(实际数据)
        msg_payload = NLMSG_DATA(nl_msg_hdr);  // NLMSG_DATA宏:获取有效载荷起始地址
        printf("消息内容:%s\n", msg_payload);
    }

    // 关闭套接字(实际循环中不会执行到)
    close(nl_sock);
    return 0;
}

nl_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>

// 与服务端保持一致的定义
#define NETLINK_TEST_PROTO NETLINK_USERSOCK
#define BUFFER_SIZE 1024
#define SERVER_PORT_ID 10001
// 客户端端口ID(自定义,与服务端区分)
#define CLIENT_PORT_ID 10002

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "使用方法:%s 要发送的消息\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int nl_sock;
    struct sockaddr_nl server_addr, client_addr;
    struct nlmsghdr *nl_msg_hdr;
    char send_buffer[BUFFER_SIZE] = {0};
    const char *send_data = argv[1];
    ssize_t send_len;

    // 1. 创建Netlink套接字
    nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST_PROTO);
    if (nl_sock < 0) {
        perror("socket create failed");
        exit(EXIT_FAILURE);
    }

    // 2. 初始化客户端Netlink地址结构
    memset(&client_addr, 0, sizeof(client_addr));
    client_addr.nl_family = AF_NETLINK;
    client_addr.nl_pid = CLIENT_PORT_ID;  // 客户端端口ID
    client_addr.nl_groups = 0;

    // 3. 绑定套接字到客户端地址(可选,但推荐,便于服务端回复)
    if (bind(nl_sock, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) {
        perror("bind failed");
        close(nl_sock);
        exit(EXIT_FAILURE);
    }

    // 4. 初始化服务端Netlink地址结构(用于指定发送目标)
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.nl_family = AF_NETLINK;
    server_addr.nl_pid = SERVER_PORT_ID;  // 目标服务端端口ID
    server_addr.nl_groups = 0;

    // 5. 构造Netlink消息(必须符合Netlink格式:消息头 + 有效载荷)
    nl_msg_hdr = (struct nlmsghdr *)send_buffer;
    // 填充Netlink消息头字段
    nl_msg_hdr->nlmsg_len = NLMSG_LENGTH(strlen(send_data));  // 消息总长度(头+有效载荷)
    nl_msg_hdr->nlmsg_type = 0;                              // 用户态通信可设为0
    nl_msg_hdr->nlmsg_flags = NLM_F_REQUEST;                 // 标记为请求消息
    nl_msg_hdr->nlmsg_seq = 0;                               // 消息序列号(可选,用于消息排序)
    nl_msg_hdr->nlmsg_pid = CLIENT_PORT_ID;                  // 发送方端口ID(客户端)

    // 6. 填充消息有效载荷(实际要发送的数据)
    strcpy(NLMSG_DATA(nl_msg_hdr), send_data);

    // 7. 发送消息到服务端
    send_len = sendto(nl_sock, send_buffer, nl_msg_hdr->nlmsg_len, 0,
                      (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (send_len < 0) {
        perror("sendto failed");
        close(nl_sock);
        exit(EXIT_FAILURE);
    }

    printf("消息发送成功,发送字节数:%zd\n", send_len);
    printf("发送的内容:%s\n", send_data);

    // 8. 关闭套接字
    close(nl_sock);
    return 0;
}
$ ./nl_server.out 
Netlink服务端已启动,等待客户端消息...
------------------------
接收到客户端消息:
消息长度(含头):22 字节
消息类型:0
消息标志:1
消息内容:abc123
$ ./nl_client.out abc123
消息发送成功,发送字节数:22
发送的内容:abc123
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐