netlink
摘要:Netlink是Linux内核与用户空间进程间通信的一种机制,支持双向异步通信,相比系统调用、ioctl等单向通信方式更具优势。它基于BSD socket和AF_NETLINK地址簇,通过32位端口号寻址,支持多播和全双工通信。Netlink允许内核主动发起会话,适用于需要内核通知用户空间的场景。文中提供了内核模块和用户空间程序的代码示例,展示了Netlink的基本使用方法:内核模块通过ne
什么是 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
更多推荐


所有评论(0)