驱动-热插拔-Netlink广播监听内核状态 - 实践
Netlink广播监听内核状态
文章目录
前言
前面了解过uevent 事件,内核发送事件到用户空间。 监听方式 使用 udevadm 命令 后台监听事件,查看打印内核信息。
那么 我们看看其它方式来监听 内核信息
参考资料
一、Netlink 知识点
Netlink 是 Linux 内核提供的一种用于内核与用户空间进程之间进行双向通信的 IPC(进程间通信)机制。它构建在标准的 Unix 套接字(socket)接口之上,专门设计用来传输各种类型的网络和系统信息。
你可以把它想象成一条专门用于“系统管理”的高速数据总线,用户空间的程序(如网络配置工具、监控工具)可以通过这条总线向内核发送指令或请求,内核也可以通过它向用户空间主动发送事件通知(如设备热插拔、网络链路状态变化)。
它本质上面我理解就是一个: socket 。
核心特点
- 全双工通信:通信是双向的,用户空间可以发消息给内核,内核也可以发消息给用户空间。
- 异步机制:通信通常是异步的,发送方不需要等待接收方的即时响应。
- 面向数据报:基于消息(
message)而非字节流,每个消息都是一个自包含的数据包,简化了协议解析。 - 协议族:
Netlink不是一个单一的协议,而是一个协议族。每个子系统(如路由、防火墙、设备通知)都有自己独特的Netlink协议号(NETLINK_ROUTE, NETLINK_NFLOG等),这使得通信井井有条,互不干扰。 - 支持多播:内核可以将一条消息多播给多个用户空间的监听者,这对于事件通知(如“有一个新
USB设备插入”)非常高效。
与其他机制的对比
| 机制 | 描述 | 缺点(与 Netlink 相比) |
|---|---|---|
| IOCTL | 通过设备文件进行控制,是传统的控制方式。 | 单向(只能用户空间发起),需要定义复杂的数据结构,扩展性差。 |
| /proc 或 /sys | 通过读写虚拟文件来获取信息或设置参数。 | 单向(通常为用户空间轮询),适用于输出信息,但不擅长复杂的控制和事件通知。 |
| 系统调用 | 用户空间调用内核函数。 | 需要预先定义,功能固定,不适合传输大量或动态变化的数据。 |
Netlink 克服了这些缺点,提供了更现代、灵活和强大的通信方式。
在 Linux 中的主要应用
网络配置 (Routing, Addressing, Links)
这是 Netlink 最经典的应用场景。用于配置网络接口、IP地址、路由表、邻居表(ARP/NDP)等。
工具:iproute2 工具集(如 ip addr, ip link, ip route, ip neigh)完全替代了古老的 ifconfig, route, arp 等net-tools工具集,其背后就是通过 Netlink 套接字(NETLINK_ROUTE 协议)与内核通信。
示例:
当你执行ip addr add 192.168.1.10/24 dev eth0时,ip 命令会通过 Netlink 发送一个 RTM_NEWADDR 消息给内核,内核收到后为 eth0 接口添加这个 IP 地址。
当你执行 ip link set eth0 up 时,会发送 RTM_NEWLINK 消息来启动接口。
网络防火墙与过滤 (Netfilter/IPTables)
Netlink 用于与内核的 Netfilter 子系统交互,管理防火墙规则。
工具:iptables, nftables(下一代防火墙工具)。
**机制:**虽然传统的 iptables 使用模块和内核系统调用,但 nftables 及其用户空间工具 nft 严重依赖 Netlink(NETLINK_NETFILTER)来传递规则集和计数器信息,效率更高。
策略路由和高级路由
对于复杂的网络环境,如多路由表、基于策略的路由,配置完全依赖于 Netlink。
设备与内核事件通知
内核可以通过 Netlink 多播组向用户空间主动发送事件消息。
协议:NETLINK_KOBJECT_UEVENT(最重要的应用之一)
工具:udev(设备管理器)
**机制:**当内核中发生设备事件(如U盘插入、硬盘热拔插)时,内核会向 NETLINK_KOBJECT_UEVENT 多播组发送一条消息(uevent)。udevd 守护进程一直在监听这个组,收到消息后,会根据 /lib/udev/rules.d/ 下的规则来创建设备节点、加载驱动、设置权限等。这是现代 Linux 桌面和服务器自动识别设备的基础。
进程管理与审计
协议:NETLINK_AUDIT, NETLINK_CONNECTOR(NETLINK_USERSOCK)
工具:auditd(审计守护进程)
机制: 内核的审计子系统可以通过 Netlink 将安全审计信息(如谁调用了某个系统调用、文件访问记录等)发送给用户空间的 auditd 进程进行记录和分析。
内核与用户空间自定义通信
开发者可以创建自己的 Netlink 协议号(NETLINK_USER 或更高)来编写内核模块和对应的用户空间程序,实现自定义的、高性能的内核-用户通信。这在某些特殊需求的驱动或内核模块开发中非常有用。
其他系统任务
IPC 命名空间: ipcrm, ipcs 等工具使用 Netlink 来管理 System V IPC 对象。
SELinux:SELinux 安全策略的某些通信也通过 Netlink 进行。
Nitlink 知识点小结
Netlink 是现代 Linux 系统中内核与用户空间进行“管理信息”交换的基石级机制。 它使得功能强大的用户空间工具(如 iproute2, udev, nftables, auditd)能够高效、灵活地控制和监控内核的各个方面,特别是在网络栈和设备管理领域。可以说,没有 Netlink,就没有现代 Linux 灵活而强大的网络配置和设备管理功能。
二、Nitlink 实现内核事件
上面介绍了nitlink 知识点,其实就是如何监听 内核事件
源码程序
#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
int main(int argc, char *argv[])
{
int ret;
struct sockaddr_nl *nl;
// 定义一个指向 struct sockaddr_nl 结构体的指针 nl
int len = 0;
char buf[4096] = {
0
};
// 数据接收缓冲区
int i = 0;
bzero(nl, sizeof(struct sockaddr_nl));
// 将 nl 指向的内存区域清零, 确保结构体的字段初始化为 0
nl->nl_family = AF_NETLINK;
// 设置 nl 结构体的 nl_family 字段为 AF_NETLINK, 指定地址族为 Netlink
nl->nl_pid = 0;
// 设置 nl 结构体的 nl_pid 字段为 0, 表示目标进程 ID 为 0, 即广播给所有进程
nl->nl_groups = 1;
// 设置 nl 结构体的 nl_groups 字段为 1, 表示只接收基本组的内核事件
int socket_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
// 创建一个 Netlink套接字
if (socket_fd <
0)
{
printf("socket error\n");
return -1;
}
ret = bind(socket_fd, (struct sockaddr *)nl, sizeof(struct sockaddr_nl));
// 使用 bind 函数将 socket_fd 套接字与 nl 地址结构体绑定在一起
if (ret <
0)
{
printf("bind error\n");
return -1;
}
while (1)
{
bzero(buf, 4096);
// 将缓冲区 buf 清零, 确保数据接收前的初始化
len = recv(socket_fd, &buf, 4096, 0);
// 从 socket_fd 套接字接收数据, 存储到缓冲区 buf 中,最大接收字节数为 4096
for (i = 0; i < len; i++)
{
if (*(buf + i) == '\0')
{
buf[i] = '\n';
// 如果接收到的数据中有 '\0' 字符, 将其替换为 '\n', 以便在打印时换行显示
}
}
printf("%s\n", buf);
// 打印接收到的数据
}
return 0;
}
配置交叉编译器
之前 HelloWorld驱动编写和加载驱动实验 篇有详细介绍,这里暂不再次说明:
测试实验
首先 交叉编译器 生成 可执行文件:
aarch64-linux-gnu-gcc -o netlink netlink.c


卸载驱动时候,也会有相关的 netlink 监听到的内核相关信息打印:
相关知识点小结
函数 bzero
功能: bzero(代表 “byte zero”)是一个传统的 C 库函数,用于将一段指定长度的内存块的所有字节设置为零(\0)。
参数:
- 第一个参数 void *s: 指向要清零的内存块的起始地址的指针。
- 第二个参数 size_t n: 要清零的字节数。
现状:
- 这个函数起源于 BSD 系统,目前已经被标记为废弃。在现代编程中,更推荐使用标准 C 库中的 memset 函数来实现相同的功能。
- 等价于: memset(nl, 0, sizeof(struct sockaddr_nl));
总结
Netlink 作用蛮多,这里举例用Netlink 来实现 内核监听的一个实例,实际开发当中使用很多,能够快速获取内核相关信息,调试、开发等。
浙公网安备 33010602011771号