relayfs介绍

147 篇文章 1 订阅
50 篇文章 2 订阅

relayfs介绍

Relay 是一种从 Linux 内核到用户空间的高效数据传输技术。通过用户定义的 relay 信道,内核空间的程序能够高效、可靠、便捷地将数据传输到用户空间。通过灵活运用改造也可双向传输。Relay 特别适用于内核空间有大量数据需要传输到用户空间的情形。Relay 的前身是 RelayFS,即作为 Linux 的一个新型文件系统,即用虚拟文件系统的方式(内核创建,用户空间去读)进行通讯。现在(后)Relay 已是内核的一部分,它的源码也从fs/目录下转移到kernel/relay.c,头文件在include/linux/下,说明文件在 docunment/filesystem中。RelayFS目前已经被越来越多的内核工具使用,包括内核调试工具SystemTap、LTT,以及一些 特殊的文件系统例如DebugFS等。
Relay的基本原理:一个relay通道由一组和CPU一一对应的内核缓冲区组成。这些缓冲区又被称为relay缓冲区(buffer),其中的每一个 在用户空间都用一个常规档来表示,这被叫做relay檔(file)。内核空间的用户可以利用relay提供的API接口来写入数据,这些数据会被自动的 写入当前的CPU id对应的那个relay缓冲区;同时,这些缓冲区从用户空间看来,是一组普通档,可以直接使用read()进行读取,也可以使用mmap()进行映射。 Relay并不关心数据的格式和内容,这些完全依赖于使用relay的用户程序。Relay的目的是提供一个足够简单的接口,从而使得基本操作尽可能的高 效。Relay将数据的读和写分离,使得突发性大量数据写入的时候,不需要受限于用户空间相对较慢的读取速度,从而大大提高了效率。Relay作为写入和 读取的桥梁,也就是将内核用户写入的数据缓存并转发给用户空间的程序。这种转发机制也正是Relay这个名称的由来。

可以看到,这里的relay通道由四个relay缓冲区(kbuf0到kbuf3)组成,分别对应于系统中的cpu0到cpu1。每个CPU上的代码调用 relay_write()的时候将数据写入自己对应的relay缓冲区内。每个relay缓冲区称一个relay檔,即/cpu0到/cpu3。当档系 统被mount到/mnt/以后,这个relay档就被映像成映像到用户空间的地址空间。一旦数据可用,用户程序就可以把它的数据读出来写入到硬盘上的文件 中,即cpu0.out到cpu3.out。
relayfs内核API:
relayfs提供给内核的API包括四类:channel管理、写函数、回调函数和辅助函数。
Channel管理函数包括:
• relay_open(base_filename, parent, subbuf_size, n_subbufs, overwrite, callbacks) 
• relay_close(chan) 
• relay_flush(chan) 
• relay_reset(chan) 
• relayfs_create_dir(name, parent) 
• relayfs_remove_dir(dentry) 
• relay_commit(buf, reserved, count) 
• relay_subbufs_consumed(chan, cpu, subbufs_consumed) 
写函数包括:
• relay_write(chan, data, length) 
• __relay_write(chan, data, length) 
• relay_reserve(chan, length) 
回调函数包括:
• subbuf_start(buf, subbuf, prev_subbuf_idx, prev_subbuf) 
• buf_mapped(buf, filp) 
• buf_unmapped(buf, filp) 
辅助函数包括:
• relay_buf_full(buf) 
• subbuf_start_reserve(buf, length)
relayfs的用户空间API:
relayfs实现了四个标准的文件I/O函数,open、mmap、poll和close
• open(),打开一个channel在某一个CPU上的缓存对应的档。 
• mmap(),把打开的channel缓存映像到调用者进程的内存空间。 
• read(),读取channel缓存,随后的读操作将看不到被该函数消耗的字节,如果channel的操作模式为非覆盖写,那么用户空间应用在有内核 模块写时仍可以读取,但是如果channel的操作模式为覆盖式,那么在读操作期间如果有内核模块进行写,结果将无法预知,因此对于覆盖式写的 channel,用户应当在确认在channel的写完全结束后再进行读。 
• poll(),用于通知用户空间应用转发数据跨越了子缓存的边界,支持的轮询标志有POLLIN、POLLRDNORM和POLLERR。 
• close(),关闭open函数返回的文件描述符,如果没有进程或内核模块打开该channel缓存,close函数将释放该channel缓存。 
需要注意的是上面这五种方法,在用户空间使用时都是用系统提供的标准系统调用接口,参数可以和relay的API不同,因为进入系统调用后会自动完成一些 工作,构造一些参数后再去调用相应文件系统提供的API,这是Linux的机制。其中POLL函数是阻塞操作,类似的阻塞操作还有select,EPOLL 可以选择非阻塞操作。POLL函数原型是int poll(struct pollfd fds[ ], nfds_t nfds, int timeout),参数含义如下: struct pollfd fds[ ] - pollfd结构数组, nfds_t nfds - fds[ ]中档描述符集合的数目,int timeout - poll()等待事件发生的时间长度(单位是毫秒)。
前面已经讲过,每一个channel由一组channel缓存组成,每个CPU对应一个该channel的缓存,每一个缓存又由一个或多个子缓存组成, (多少和大小用户可自己设定),每一个缓存是子缓存组成的一个环型缓存。函数relay_open用于创建一个channel并分配对应于每一个CPU的 缓存,用户空间应用通过在relayfs文件系统中对应的文件可以访问channel缓存,参数base_filename用于指定channel的文件名, relay_open函数将在relayfs文件系统中创建base_filename0..base_filenameN-1,即每一个CPU对应一个 channel檔,其中N为CPU数,缺省情况下,这些档将建立在relayfs文件系统的根目录下,但如果参数parent非空,该函数将把 channel文件创建于parent目录下,parent目录使用函数relay_create_dir创建,函数relay_remove_dir用于 删除由函数relay_create_dir创建的目录,谁创建的目录,谁就负责在不用时负责删除。参数subbuf_size用于指定channel缓 存中每一个子缓存的大小,参数n_subbufs用于指定channel缓存包含的子缓存数,因此实际的channel缓存大小为 (subbuf_size x n_subbufs),参数overwrite用于指定该channel的操作模式,relayfs提供了两种写模式,一种是覆盖式写,另一种是非覆盖式 写。使用哪一种模式完全取决于函数subbuf_start的实现(更换到新的子缓存时的策略,用relay_buf_full函数探测buf是否满,然 后决定是否继续写,系统提供默认的subbuf_start,但用户可改写,默认支持非覆盖写),覆盖写将在缓存已满的情况下无条件地继续从缓存的开始写 资料,而不管这些数据是否已经被用户应用读取,因此写操作决不失败。在非覆盖写模式下,如果缓存满了,写将失败,但内核将在用户空间应用读取缓存数据时通 过函数relay_subbufs_consumed()通知relayfs(此函数将“消耗”读掉有效数据,用改写rchan中参数方法通知 relayfs)。如果用户空间应用没来得及消耗缓存中的数据或缓存已满,两种模式都将导致数据丢失,唯一的区别是,前者丢失资料在缓存开头,而后者丢失 数据在缓存末尾。一旦再次调用函数relay_subbufs_consumed(),已满的缓存将不再满,因而可以继续写该缓存。当缓存满了以后, relayfs将调用回调函数buf_full()来通知内核模块或子系统。当新的数据太大无法写入当前子缓存剩余的空间时, relay_switch_subbuf函数将调用回调函数subbuf_start()来通知内核模块或子系统将需要使用新的子缓存。可以通过在回调函 数subbuf_start()中调用辅助函数subbuf_start_reserve()在子缓存中预留头空间,预留空间可以保存任何需要的信息,如 预留空间用于保存子缓存填充字节数,subbuf_start()也被在channel创建时分配每一个channel缓存的第一个子缓存时调用,以便 预留头空间。注意在relay实现中,用subbuf_start_reserve来预留空间保存填充字节数不是必做的,因为relay本身提供把填充 字节数记录在rchan结构中的功能,可以被用户空间调用读函数时所见。
内核模块使用函数relay_write()或__relay_write()往channel缓存中写需要转发的数据,它们的区别是前者失效了本地中 断,而后者只抢占失效,因此前者可以在任何内核上下文安全使用,而后者应当在没有任何中断上下文将写channel缓存的情况下使用。这两个函数没有返回 值,因此用户不能直接确定写操作是否失败,在缓存满且写模式为非覆盖模式时,relayfs将通过回调函数buf_full来通知内核模块。函数 relay_reserve()用于在channel缓存中预留一段空间以便以后写入,在那些没有临时缓存而直接写入channel缓存的内核模块可能需 要该函数,使用该函数的内核模块在实际写这段预留的空间时可以通过调用relay_commit()来通知relayfs。当所有预留的空间全部写完并通 过relay_commit通知relayfs后,relayfs将调用回调函数deliver()通知内核模块一个完整的子缓存已经填满。由于预留空间 的操作并不在写channel的内核模块完全控制之下,因此relay_reserve()不能很好地保护缓存,因此当内核模块调用 relay_reserve()时必须采取恰当的同步机制。当内核模块结束对channel的使用后需要调用relay_close() 来关闭channel,如果没有任何用户在引用该channel,它将和对应的缓存全部被释放。函数relay_flush()强制在所有的 channel缓存上做一个子缓存切换,它在channel被关闭前使用来终止和处理最后的子缓存。函数relay_reset()用于将一个 channel恢复到初始状态,因而不必释放现存的内存映像并重新分配新的channel缓存就可以使用channel,但是该调用只有在该 channel没有任何用户在写的情况下才可以安全使用。回调函数buf_mapped() 在channel缓存被映像到用户空间时被调用。回调函数buf_unmapped()在释放该映射时被调用。内核模块可以通过它们触发一些内核操作,如 开始或结束channel写操作。当然为了使用relayfs,用户必须让内核支持relayfs,并且要mount它。当使用 relay_file_mmap对relay文件映像到用户空间时没有采用relay_file_read时内部可以调用的类似跨越子缓存时通知这样的同步 机制,这样就必须自己设计同步机制。
Relay流程介绍
内核空间:首先调用relay_open()创建并打开一个约定好名字的relay档,设定好子缓存大小和个数,cb参数为NULL的话将使用默认 callback,此调用将调用relay API的relay_open,并将私有数据传递好。执行成功的话返回一个rchan信道结构的指针。在relay_open中用 for_each_online_cpu宏检测所有在线CPU数(SMP系统应检测出来有多个CPU需应用程序支持),为每一个CPU创建相应buf和 buf_file(在文件系统相应路径下),当一个通道因为只定义一个CPU而用单一档代表时(chan->is_global置1),调用 creat_buf_file一次,这时创建统一的一个buf,如果有多CPU的话这样要注意同步。
当有数据需要写入时可以调用relay_write往信道中写数据,先用local_irq_save做临界区保护,然后CPU向相应buf中写入数据, 当当前子缓存末尾小于需要写入的数据大小时,调用relay_switch_subbuf更换子缓存,在其中记录上一个子缓存的填充字节数到buf- >padding(在读时要读取这个buf->padding),然后找到新的写入位置,调用subbuf_start更换到新的子缓存,唤 醒在buf->read_wait上等待的进程。最后调用memcpy写到通道中。
最后关闭通道和缓存。

用户空间:首先调用mount挂载相应relay文件系统。
然后对每一个CPU打开其相对应file档,调用标准文件open操作接口,O_RDONLY方式。之后调用poll标准接口,调用poll_wait使进 程在相应buf的buf->read_wait上等待POLLIN和POLLRDNORM事件发生。这个过程也不是必需的,因为下面将介绍的 read操作会自动进行循环查询读。
当进程被唤醒后,调用标准文件read接口(其中系统会自动调用relay_file _read),读取相应buf数据到用户空间缓冲区中,其中有关buf信息记录在filp->private_data中(在打开文件时从inode ->private_data中拷贝过来)。在read的输入参数中,由用户缓冲区地址,和准备读取的字节数,如果没有读够的话,将会循环等待读 够为止。在此过程中,首先调用relay_file_read_avail查找是否有可读数据,在其中会调用 relay_file_read_consume消耗掉相应子缓存。然后调用relay_file_read_start_pos,查找读取开始位置。再 调用relay_file_read_subbuf_avail,确定可读字节数,其中会减去buf->padding中的填充字节数。调用 subbuf_actor进行实际读取,用的是copy_to_user机制。最后进行已读取字节数和当前buf和用户缓冲区偏移位置指针调整,回到回 圈开始处。值得注意,可以在read的外层再加一曾循环(因为每次read的字节数只是用户设定好的缓冲区字节数),用判断每次read(内促就是一 个循环机制)出来的字节数不为0来继续下次循环,如果循环被打破说明在read内部的第一次读时copy_to_user出现错误,应该报错。
当采用mmap进行映像方式读取时,可以避免数据拷贝,但就没有了read中可以调用的一系列保护和同步机制,要自行设计。调用时采用标准系统mmap API。但可以设想采用一种方式即:改写read函数试其把copy_to_user时要用到的起始地址和长度等信息传递回用户空间,先用mmap映射 好,然后调用改写后的read,得到必要信息后开始直接访问数据。或者采用poll轮询(这种方式最适合mmap)
最后关掉打开的文件,卸载文件系统。

代码范例:
内核空间:
/*
* hello-mod.c
* a kernel-space client example of relayfs filesystem
*/
#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");

用户空间:
/*
* audience.c
* a user-space client example of relayfs filesystem
*/
#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;
}

http://bbs.linux-cn.com/redirect.php?tid=15731&goto=lastpost

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值