netlink 通讯模式
2026/5/12 13:31:01 网站建设 项目流程

Netlink 核心通信模型

Netlink = 全双工、面向消息、内核 / 用户双向通信支持:

  • 单播(1 对 1)
  • 多播(1 对多)
  • 双向主动发送(内核可主动发,用户也可主动发)
  • 多个协议隔离通道
  • 多个 socket 并行

5 种标准通信模式

1.用户 → 内核 单播(最常用)

用户主动发消息给内核。

  • 用户:目标 portid = 0
  • 内核:收到消息,知道是谁发的(portid)

适用:命令下发、配置、查询。

dest_addr.nl_pid = 0; // 发往内核 sendmsg(fd, &msg, 0);

2.内核 → 用户 单播(最常用)

内核主动发消息给某个用户 socket。

  • 内核必须知道用户 portid(从接收消息里拿)
  • 精准发给某一个 socket
u32 portid = NETLINK_CB(skb).portid; netlink_unicast(nl_sk, skb, portid, MSG_DONTWAIT);

3.双向请求应答模式(标准用法)

用户发请求 → 内核回响应这是 90% 项目的真实用法。

流程:

  1. 用户 send → 内核
  2. 内核 receive → 获取 portid
  3. 内核 unicast → 回复用户
  4. 用户 receive → 拿到结果

全双工,可靠通信。


4.内核 → 多用户 多播(事件 / 通知)

内核向一组用户广播消息(比如热插拔、状态变化)。

  • 内核:netlink_broadcast()
  • 用户:bind 时加入多播组nl_groups = 掩码
netlink_broadcast(sk, skb, 0, group, GFP_KERNEL);

适用:内核事件通知、uevent、状态同步。


5.用户 ↔ 用户 通信(少用)

Netlink 允许用户进程之间直接通信,不经过内核。

  • A 用户:portid=1000
  • B 用户:portid=2000
  • A 发消息目标 portid=2000

内核只做转发,不处理。

内核主动推送模式

✔ Netlink 支持内核主动发消息给用户

不需要用户先发送!

这是 Netlink 比 ioctl /proc 强 100 倍的地方。

内核可以:

  • 定时发消息
  • 中断里发消息
  • 硬件事件触发发消息
  • 多播给所有用户

唯一寻址规则 =协议号 + portid

  • 协议号:区分哪个内核模块 / 通道
  • portid:区分哪个用户 socket

只要协议号不同,哪怕用户态所有 socket 都用同一个 portid = getpid ()

内核会精准投递,完全不会乱!

两个内核模块 = 两个独立 Netlink 服务

  • 模块 A:使用协议号NETLINK_TEST1 = 17
  • 模块 B:使用协议号NETLINK_TEST2 = 18

协议号必须不同,内核才能区分两个 Netlink 通道。

同一个用户进程 = 两个独立客户端 Socket

  • Socket 1:连接模块 A(协议 17),portid =getpid()
  • Socket 2:连接模块 B(协议 18),portid =getpid() + 1

通信规则

  • 内核 A ↔ 用户 Socket1
  • 内核 B ↔ 用户 Socket2

完全隔离、互不干扰

不同 Netlink 协议号,为什么可以用同一个 pid?

Netlink 消息路由公式(内核内部就是这么干的):

匹配 = (协议号) + (目标portid)
  • 协议 17 → 只给内核模块 A
  • 协议 18 → 只给内核模块 B
  • portid 相同 = 同一个用户 socket 接收
  • 两条通道物理隔离

所以:同一个用户进程,N 个不同协议的 Netlink Socket,全部可以 bind 同一个 portid(getpid ())!

推荐用法

用户态:同一个进程 + 同一个 pid + 不同协议

// socket A:协议17,portid = getpid() int fd1 = socket(AF_NETLINK, SOCK_RAW, 17); bind(fd1, pid = getpid()); // socket B:协议18,portid = getpid() int fd2 = socket(AF_NETLINK, SOCK_RAW, 18); bind(fd2, pid = getpid()); // same pid!完全没问题

内核态:两个模块,不同协议

模块A:协议17 模块B:协议18

通信结果

  • 用户 fd1 <-> 内核 A(协议 17)
  • 用户 fd2 <-> 内核 B(协议 18)

互不干扰,portid 相同也完全安全!

关键要点

两个内核模块靠什么区分?

靠不同的 Netlink 协议号(17、18)

  • 内核创建 socket 时指定协议号
  • 用户创建 socket 时也指定相同协议
  • 协议不同 → 完全两条通道

同一个用户进程怎么开两个 Socket?

靠不同 portid(nl_pid)

  • Socket1:pid
  • Socket2:pid + 1

内核通过portid知道消息要发给哪个 Socket

内核回复消息时怎么精准回对 Socket?

内核从skb->portid拿到用户端 portid,直接用:

netlink_unicast(nl_sk, skb, portid, MSG_DONTWAIT);

最实用口诀

内核地址永远 0, 用户地址看 portid, 不同协议隔离走, 同pid也不冲突, 单播精准点对点, 多播广播一组人, 内核能主动推送, 双向全双工最稳。

完整代码案例

1. 内核模块 A(netlink_mod_a.c)

#include <linux/module.h> #include <linux/netlink.h> #include <linux/skbuff.h> #define NETLINK_TEST1 17 // 模块A专用协议 static struct sock *nl_sk; // 接收用户消息 static void nl_a_rcv(struct sk_buff *skb) { struct nlmsghdr *nlh = nlmsg_hdr(skb); u32 portid = NETLINK_CB(skb).portid; printk("模块A收到:portid=%u, 消息=%s\n", portid, (char*)NLMSG_DATA(nlh)); } static int __init nl_a_init(void) { struct netlink_kernel_cfg cfg = { .input = nl_a_rcv }; nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST1, &cfg); printk("模块A Netlink 加载成功\n"); return 0; } static void __exit nl_a_exit(void) { netlink_kernel_release(nl_sk); printk("模块A 卸载\n"); } module_init(nl_a_init); module_exit(nl_a_exit); MODULE_LICENSE("GPL");

2. 内核模块 B(netlink_mod_b.c)

#include <linux/module.h> #include <linux/netlink.h> #include <linux/skbuff.h> #define NETLINK_TEST2 18 // 模块B专用协议 static struct sock *nl_sk; static void nl_b_rcv(struct sk_buff *skb) { struct nlmsghdr *nlh = nlmsg_hdr(skb); u32 portid = NETLINK_CB(skb).portid; printk("模块B收到:portid=%u, 消息=%s\n", portid, (char*)NLMSG_DATA(nlh)); } static int __init nl_b_init(void) { struct netlink_kernel_cfg cfg = { .input = nl_b_rcv }; nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST2, &cfg); printk("模块B Netlink 加载成功\n"); return 0; } static void __exit nl_b_exit(void) { netlink_kernel_release(nl_sk); printk("模块B 卸载\n"); } module_init(nl_b_init); module_exit(nl_b_exit); MODULE_LICENSE("GPL");

3. 用户态测试程序(单进程双 Socket)

#include <stdio.h> #include <sys/socket.h> #include <linux/netlink.h> #include <string.h> #include <unistd.h> #include <getopt.h> #define NETLINK_TEST1 17 #define NETLINK_TEST2 18 // 创建一个连接指定内核协议的 netlink socket int create_socket(int proto, __u32 portid) { int fd = socket(AF_NETLINK, SOCK_RAW, proto); struct sockaddr_nl sa = {0}; sa.nl_family = AF_NETLINK; sa.nl_pid = portid; bind(fd, (struct sockaddr*)&sa, sizeof(sa)); return fd; } // 发送消息到内核 void send_msg(int fd, int proto, char *msg) { struct sockaddr_nl dest = {0}; dest.nl_family = AF_NETLINK; dest.nl_pid = 0; // 内核 struct nlmsghdr *nlh; struct iovec iov; struct msghdr msg_hdr = {0}; nlh = malloc(NLMSG_SPACE(strlen(msg)+1)); nlh->nlmsg_len = NLMSG_SPACE(strlen(msg)+1); nlh->nlmsg_pid = getpid(); memcpy(NLMSG_DATA(nlh), msg, strlen(msg)+1); iov.iov_base = nlh; iov.iov_len = nlh->nlmsg_len; msg_hdr.msg_name = &dest; msg_hdr.msg_namelen = sizeof(dest); msg_hdr.msg_iov = &iov; msg_hdr.msg_iovlen = 1; sendmsg(fd, &msg_hdr, 0); free(nlh); } int main() { pid_t pid = getpid(); int fd1 = create_socket(NETLINK_TEST1, pid); int fd2 = create_socket(NETLINK_TEST2, pid+1); send_msg(fd1, NETLINK_TEST1, "Hello 模块A"); send_msg(fd2, NETLINK_TEST2, "Hello 模块B"); close(fd1); close(fd2); return 0; }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询