RK3568驱动指南|第十篇 热插拔-第116章netlink监听广播信息实验

发布时间:2024年01月11日

瑞芯微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);

  1. sockfd参数指定了需要绑定的套接字描述符,
  2. 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时,表示用户空间进程只会接收内核事件的基本组的内核事件。这意味着,用户空间进程将只接收到属于基本组的内核事件,而不会接收其他多播组的事件。

  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监听广播信息实验就完成了。


文章来源:https://blog.csdn.net/BeiJingXunWei/article/details/135525790
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。