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 底层接口与驱动开发技术详解》