瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第十期_热插拔_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
第116章netlink监听广播信息实验
在上一章的实验中,我们填充了三个回调函数,分别为.filter = myfilter, .uevent = myevent, .name = myname,但在最后的实验中只验证filter过滤了kset中的kobject1,那另外两个回调函数要如何验证呢?
内核通过kobject uevent接口发送广播事件之后,用户空间可以通过netlink来监听这些广播信息。通过监听广播信息,就可以获取到携带环境变量的事件,在本章节将会对netlink进行详细的讲解。
116.1 netlink机制介绍
Netlink是Linux内核中用于内核和用户空间之间进行双工通信的机制。它基于socket通信机制,并提供了一种可靠的、异步的、多播的、有序的通信方式。
Netlink机制的主要特点包括:
(1)双工通信:Netlink允许内核和用户空间之间进行双向通信,使得内核可以向用户空间发送消息,同时也可以接收来自用户空间的消息。
(2)可靠性:Netlink提供了可靠的消息传递机制,保证消息的完整性和可靠性。它使用了确认和重传机制,以确保消息的可靠传输。
(3)异步通信:Netlink支持异步通信,即内核和用户空间可以独立地发送和接收消息,无需同步等待对方的响应。
(4)多播支持:Netlink允许向多个进程或套接字广播消息,以实现一对多的通信。
(5)有序传输:Netlink保证消息的有序传输,即发送的消息按照发送的顺序在接收端按序接收。
Netlink的应用广泛,常见的应用包括:
(1)系统管理工具:如ifconfig、ip等工具使用Netlink与内核通信来获取和配置网络接口的信息。
(2)进程间通信:进程可以使用Netlink进行跨进程通信,实现进程间的数据交换和协调。
(3)内核模块和用户空间应用程序的通信:内核模块可以通过Netlink向用户空间应用程序发送通知或接收用户空间应用程序的指令。
116.2 netlink的使用
116.2.1 创建socket
在Linux socket编程中,创建套接字是构建网络应用程序的第一步。套接字可以理解为应用程序和网络之间的桥梁,用于在网络上进行数据的收发和处理。该系统调用的原型和所需头文件如下所示:
所需头文件 | 函数原型 |
#include <sys/types.h> #include <sys/socket.h> | int socket(int domain, int type, int protocol); |
其中,domain参数指定了套接字的协议族,type参数指定了套接字的类型,protocol参数指定了套接字所使用的具体协议。下面分别介绍这三个参数的含义:
(1)协议族
协议族指定了套接字所使用的协议类型,常用的协议族包括AF_INET、AF_INET6、AF_UNIX等。其中,AF_INET表示IPv4协议族,AF_INET6表示IPv6协议族,AF_UNIX表示Unix域协议族,这里的协议族为netlink,所以该参数要在程序中设置为AF_ NETLINK。
(2)套接字类型
套接字类型指定了套接字的数据传输方式,常用的套接字类型包括SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等。其中,SOCK_STREAM表示面向连接的流套接字,主要用于可靠传输数据,例如TCP协议。SOCK_DGRAM表示无连接的数据报套接字,主要用于不可靠传输数据,例如UDP协议。在本实验中该参数要设置为SOCK_RAW表示原始套接字,可以直接访问底层网络协议。
(3)协议类型
协议类型指定了套接字所使用的具体协议类型,常用的协议类型包括IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP等。其中,IPPROTO_TCP表示TCP协议,IPPROTO_UDP表示UDP协议,IPPROTO_ICMP表示ICMP协议,在本实验中,我们要设置为NETLINK_ _KOBJECT_ UEVENT。
在本小节中将使用以下代码创建一个新的套接字:
1 | int socket_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); |
AF_NETLINK:指定了使用Netlink协议族。Netlink协议族是一种Linux特定的协议族,用于内核和用户空间之间的通信。
SOCK_RAW:指定了创建原始套接字,这种套接字类型可以直接访问底层协议,而不需要进行协议栈处理。在这种情况下,我们可以直接使用Netlink协议进行通信。
NETLINK_KOBJECT_UEVENT:指定了Netlink协议的一种类型,即kobject uevent类型。kobject uevent用于内核对象相关的事件通知,当内核中的kobject对象发生变化时,会通过此类型的Netlink消息通知用户空间。
116.2.2绑定套接字
创建套接字后,需要将其与一个网络地址绑定,以便其他计算机可以访问该套接字。在Linux系统下,可以使用bind()系统调用绑定套接字和地址。该系统调用的原型和所需头文件如下所示:
所需头文件 | 函数原型 | |
1 2 | #include <sys/types.h> #include <sys/socket.h> | int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
- sockfd参数指定了需要绑定的套接字描述符,
- addr参数指定了需要绑定的地址信息,这里使用sockaddr_nl结构体,sockaddr_nl结构体的定义如下:
struct sockaddr_nl {
sa_family_t nl_family; // AF_NETLINK
unsigned short nl_pad; // zero
uint32_t nl_pid; // port ID
uint32_t nl_groups; // multicast groups mask
};
nl_family:表示地址族,此处固定为AF_NETLINK,指示使用Netlink协议族。
nl_pad:填充字段,设置为0。在结构体中进行字节对齐时使用。
nl_pid:端口ID,表示进程的标识符。可以将其设置为当前进程的PID,也可以设为0,表示不加入任何多播组。
nl_groups:多播组掩码,用于指定感兴趣的多播组。当设置为1时,表示用户空间进程只会接收内核事件的基本组的内核事件。这意味着,用户空间进程将只接收到属于基本组的内核事件,而不会接收其他多播组的事件。
- addrlen参数:addrlen参数是一个整数,指定了addr所指向的结构体对应的字节长度。它用于确保正确解析传递给addr参数的结构体的大小。
具体编程示例如下所示:
struct sockaddr_nl *nl; // 定义一个指向 struct sockaddr_nl 结构体的指针 nl
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,表示只接收基本组的内核事件
ret = bind(socket_fd, (struct sockaddr *)nl, sizeof(struct sockaddr_nl)); // 使用 bind 函数将 socket_fd 套接字与 nl 地址结构体绑定在一起
if (ret < 0) {
printf("bind error\n");
return -1;
}
116.2.3接收数据
Netlink套接字在接收数据时不需要调用listen函数,而是可以直接使用recv函数进行接收。下面是recv函数的相关说明:
头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
函数参数:
sockfd:指定套接字描述符,即要接收数据的Netlink套接字。
buf:指向数据接收缓冲区的指针,用于存储接收到的数据。
len:指定要读取的数据的字节大小。
flags:指定一些标志,用于控制数据的接收方式。通常情况下,可以将其设置为0。
返回值:
成功情况下,返回实际读取到的字节数。
如果返回值为0,表示对方已经关闭了连接。
如果返回值为-1,表示发生了错误,可以通过查看errno变量来获取具体的错误代码。
使用recv函数可以从指定的Netlink套接字中接收数据,并将其存储在提供的缓冲区中。函数的返回值表示实际读取到的字节数,可以根据返回值来判断是否成功接收到数据。
接收数据的具体代码示例如下所示:
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') { // 如果接收到的数据中有 '\0' 字符,将其替换为 '\n',以便在打印时换行显示
buf[i] = '\n';
}
}
printf("%s\n", buf); // 打印接收到的数据
}
116.3 实验程序的编写
本应用程序对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\80_netlink。
根据上一小节所讲解的内容,使用netlink监听广播信息的应用程序netlink.c.c代码如下所示:
#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') { // 如果接收到的数据中有 '\0' 字符,将其替换为 '\n',以便在打印时换行显示
buf[i] = '\n';
}
}
printf("%s\n", buf); // 打印接收到的数据
}
return 0;
}
116.4 运行测试
116.4.1 编译应用程序
下面进行应用程序编译,因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译,输入以下命令,编译完成以后会生成一个netlink的可执行程序,如下图(图116-1)所示:
aarch64-linux-gnu-gcc -o netlink netlink.c
图 116-1
下面进行程序的测试。
116.4.2 运行测试
本小节测试所使用的驱动文件为上一章编译生成的uevent_ops.ko,应用程序为上一小节编译出来的netlink。
开发板启动之后,首先使用以下命令让应用程序在后台运行,如下图(图116-2)所示:
./netlink &
图 116-2
然后继续使用以下命令加载uevent_ops.ko驱动,打印如下图(116-3)所示:
insmod uevent_ops.ko
图 116-3
SUBSYSTEM=my_kset,表示设备或对象所属的子系统。在这里,子系统是 "my_kset"。MYDEVICE表示设备的名称或标识。在这里,设备的名称是 "TOPEET",正是我们在回调函数中所设置的。
最后可以使用以下命令进行驱动的卸载,如下图(图116-4)所示:
rmmod uevent_ops.ko
图 116-4
至此,使用netlink监听广播信息实验就完成了。