Netlink 核心通信模型Netlink 全双工、面向消息、内核 / 用户双向通信支持单播1 对 1多播1 对多双向主动发送内核可主动发用户也可主动发多个协议隔离通道多个 socket 并行5 种标准通信模式1.用户 → 内核 单播最常用用户主动发消息给内核。用户目标 portid 0内核收到消息知道是谁发的portid适用命令下发、配置、查询。dest_addr.nl_pid 0; // 发往内核 sendmsg(fd, msg, 0);2.内核 → 用户 单播最常用内核主动发消息给某个用户 socket。内核必须知道用户 portid从接收消息里拿精准发给某一个 socketu32 portid NETLINK_CB(skb).portid; netlink_unicast(nl_sk, skb, portid, MSG_DONTWAIT);3.双向请求应答模式标准用法用户发请求 → 内核回响应这是 90% 项目的真实用法。流程用户 send → 内核内核 receive → 获取 portid内核 unicast → 回复用户用户 receive → 拿到结果全双工可靠通信。4.内核 → 多用户 多播事件 / 通知内核向一组用户广播消息比如热插拔、状态变化。内核netlink_broadcast()用户bind 时加入多播组nl_groups 掩码netlink_broadcast(sk, skb, 0, group, GFP_KERNEL);适用内核事件通知、uevent、状态同步。5.用户 ↔ 用户 通信少用Netlink 允许用户进程之间直接通信不经过内核。A 用户portid1000B 用户portid2000A 发消息目标 portid2000内核只做转发不处理。内核主动推送模式✔ Netlink 支持内核主动发消息给用户不需要用户先发送这是 Netlink 比 ioctl /proc 强 100 倍的地方。内核可以定时发消息中断里发消息硬件事件触发发消息多播给所有用户唯一寻址规则 协议号 portid协议号区分哪个内核模块 / 通道portid区分哪个用户 socket只要协议号不同哪怕用户态所有 socket 都用同一个 portid getpid ()内核会精准投递完全不会乱两个内核模块 两个独立 Netlink 服务模块 A使用协议号NETLINK_TEST1 17模块 B使用协议号NETLINK_TEST2 18协议号必须不同内核才能区分两个 Netlink 通道。同一个用户进程 两个独立客户端 SocketSocket 1连接模块 A协议 17portid getpid()Socket 2连接模块 B协议 18portid getpid() 1通信规则内核 A ↔ 用户 Socket1内核 B ↔ 用户 Socket2完全隔离、互不干扰不同 Netlink 协议号为什么可以用同一个 pidNetlink 消息路由公式内核内部就是这么干的匹配 (协议号) (目标portid)协议 17 → 只给内核模块 A协议 18 → 只给内核模块 Bportid 相同 同一个用户 socket 接收两条通道物理隔离所以同一个用户进程N 个不同协议的 Netlink Socket全部可以 bind 同一个 portidgetpid ()推荐用法用户态同一个进程 同一个 pid 不同协议// socket A协议17portid getpid() int fd1 socket(AF_NETLINK, SOCK_RAW, 17); bind(fd1, pid getpid()); // socket B协议18portid 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靠不同 portidnl_pidSocket1pidSocket2pid 1内核通过portid知道消息要发给哪个 Socket内核回复消息时怎么精准回对 Socket内核从skb-portid拿到用户端 portid直接用netlink_unicast(nl_sk, skb, portid, MSG_DONTWAIT);最实用口诀内核地址永远 0 用户地址看 portid 不同协议隔离走 同pid也不冲突 单播精准点对点 多播广播一组人 内核能主动推送 双向全双工最稳。完整代码案例1. 内核模块 Anetlink_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. 内核模块 Bnetlink_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, pid1); send_msg(fd1, NETLINK_TEST1, Hello 模块A); send_msg(fd2, NETLINK_TEST2, Hello 模块B); close(fd1); close(fd2); return 0; }