使用 Relay 实现内核到用户空间的数据传输
  HvTJUzsxOBtS 2023年11月22日 22 0


Relay 是一种从Linux内核到用户空间的高效数据传输技术。通过用户定义的Relay通道,内核空间的程序能够高效、可靠、便捷地将数据传输到用户空间。Relay特别适用于内核空间有大量数据需要传输到用户空间的情形,目前已经广泛应用在内核调试工具(如SystemTap)中。

1、Relay 的原理

ReIay提供了一种机制,使得内核空间的程序能够通过用户定义的ReIay通道(ChanneI)将大
量数据高效地传输到用户空间。一个Relay通道由一组和CPU一一对应的内核缓冲区组成。这些缓
冲区又被称为ReIay缓冲区(Buffer),其中的每一个缓冲区在用户空间都用一个常规文件来表示,
这被叫做Relay文件(File)。内核空间的用户可以利用Relay提供的API接口来写入数据,这些数
据会被自动写入当前的CPUid对应的那个ReIay缓冲区;同时,这些缓冲区从用户空间看来,是
一组普通文件,可以直接使用read()进行读取,也可以使用mmap()进行映射。Relay并不关心数据
的格式和内容,这些完全依赖于使用ReIay的用户程序。ReIay的目的是提供一个足够简单的接口,从而使得基本操作尽可能的高效。
Relay对数据实现了读和写的分离,使得在突发性大量数据写入时,不需要受限于用户空间相
对较慢的读取速度,从而大大提高了效率。ReIay作为写入和读取的桥梁,也就是将内核用户写入
的数据缓存并转发给用户空间的程序。这种转发机制也正是Relay这个名称的由来。

2、Relay 的API

Relay中提供了许多API来支持用户程序完整地使用ReIayo这些API主要分为两大类,分别是
按照面向用户空间和面向内核空间,具体说明如下。
(1)  面向用户空间的API
      此类API编程接口向用户空间程序提供了访问ReIay通道缓冲区数据的基本操作的入口,主要
包括如下方法。

  • open() :允许用户打开一个己经存在的通道缓冲区。
  • mmap() :使通道缓冲区被映射到位于用户空间的调用者的地址空间。要特别注意的是,不能
    仅对局部区域进行映射。也就是说,必须映射整个缓冲区文件,其大小是CPU的个数和单
    个CPU缓冲区大小的乘积。
  • read();读取通道缓冲区的内容。这些数据一旦被读出,就意味着它们被用户空间的程序消费
    掉了,也就不能被之后的读操作看到.
  • sendfile():将数据从通道缓冲区传输到一个输出文件描述符。其中可能的填充字符会被自动
    去掉,不会被用户看到。
  • poll() : 支持POLLIN/POLLRDNORM/POLLERR信号。每次子缓冲区的边界被越过时,等
    待着的用户空间程序会得到通知。
  • close() : 将通道缓冲区的引用数减1。当引用数减为0时,表明没有进程或者内核用户需要打
    开它,从而这个通道缓冲区被释放。

(2)面向内核空间的API
    此类API接口向位于内核空间的用户提供了管理ReIay通道、数据写入等功能,其中最为常用
的如下所示。

  • relay_open():创建一个Relay通道,包括创建每个CPU对应的ReIay缓冲区。
  • relay-close():关闭一个Relay通道,包括释放所有的ReIay缓冲区,在此之前会调用
    relay-switch()来处理这些ReIay缓冲区以保证己读取但是未满的数据不会丢失。
  • relay_write():将数据写入到当前CPU对应的Relay缓冲区内。由于它使用了local_irqsave()
    保护,因此也可以在中断上下文中使用。
  • relayreserve():在Relay通道中保留一块连续的区域来留给未来的写入操作。这通常用于那
    些希望直接写入到ReIay缓冲区的用户。考虑到性能或其他因素,这些用户不希望先把数据
    写到一个临时缓冲区中,然后再通过relay一yrit到进行写入。

3、使用 Relay 

在下面的内容中,将通过一个最简单的例子来介绍使用ReIay的方法。本实例由如下两部分
组成:

  • 位于内核空间将数据写入ReIay文件的程序,使用时需要作为一个内核模块被加载。
  • 位于用户空间从文件中读取数据的程序,使用时作为普通用户态程序运行。

(1)实现内核空间
内核空间程序的主要操作如下所示。

  • 当加载模块时,打开一个ReIay通道,并且往打开的ReIay通道中写入消息。
  • 当卸载模块时,关闭relay通道。

实现文件hello-mod.c的具体实现代码如下:

#include <linux/module.h>
#include <linux/relayfs_fs.h>
static struct rchan *hello_rchan;
int init_module(void)
{
        const char *msg="Hello world\n";
        hello_rchan = relay_open("cpu", NULL, 8192, 2, NULL);
        if(!hello_rchan){
                printk("relay_open() failed.\n");
                return -ENOMEM;
        }
        relay_write(hello_rchan, msg, strlen(msg));
        return 0;
}
void cleanup_module(void)
{
        if(hello_rchan) {
                relay_close(hello_rchan);
                hello_rchan = NULL;
        }
        return;
}
MODULE_LICENSE ("GPL");
MODULE_DESCRIPTION ("Simple example of Relay");

(2) 实现用户空间

用户空间的函数主要操作过程如下所示。
如果relayfs文件系统还没有被 umount(是一个命令),则将其umount到 /mnt/relay 目录上。首
先遍历每一个CPU对应的缓冲文件,然后打开文件,接着读取所有文件内容,然后关闭文件,最后,
umount 掉 relay文件系统。
实现文件audience.c的具体实现代码如下:
 

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <fcntl.h>
#include <sched.h>
#include <errno.h>
#include <stdio.h>
#define MAX_BUFLEN 256
const char filename_base[]="/mnt/relay/cpu";
// implement your own get_cputotal() before compilation
static int get_cputotal(void);
int main(void)
{
        char filename[128]={0};
        char buf[MAX_BUFLEN];
        int fd, c, i, bytesread, cputotal = 0;
        if(mount("relayfs", "/mnt/relay", "relayfs", 0, NULL)
                        && (errno != EBUSY)) {
                printf("mount() failed: %s\n", strerror(errno));
                return 1;
        }
        cputotal = get_cputotal();
        if(cputotal <= 0) {
                printf("invalid cputotal value: %d\n", cputotal);
                return 1;
        }
        for(i=0; i<cputotal; i++) {
                // open per-cpu file
                sprintf(filename, "%s%d", filename_base, i);
                fd = open(filename, O_RDONLY);
                if (fd < 0) {
                        printf("fopen() failed: %s\n", strerror(errno));
                        return 1;
                }
                // read per-cpu file
                bytesread = read(fd, buf, MAX_BUFLEN);
                while(bytesread > 0) {
                        buf[bytesread] = '\0';
                        puts(buf);
                        bytesread = read(fd, buf, MAX_BUFLEN);
                };
                // close per-cpu file
                if(fd > 0) {
                        close(fd);
                        fd = 0;
                }
        }
        if(umount("/mnt/relay") && (errno != EINVAL)) {
                printf("umount() failed: %s\n", strerror(errno));
                return 1;
        }
        return 0;
}

 

 上述实例演示了使用Relay的过程,虽然上述代码并没有实际用处,但是形象地描述了从用户
窄间和内核空间两个方面使用ReIay的基本流程。实际应用中对ReIay的使用当然要比这复杂得多.
 

文章摘自:

《Android 底层接口与驱动开发技术详解》

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月22日 0

暂无评论

HvTJUzsxOBtS