公告

Gentoo群:87709706,OS群:838664909

#1 2024-04-02 21:02:20

batsom
管理团队
注册时间: 2022-08-03
帖子: 594
个人网站

Gentoo 之 user space relay support

为了使得用户空间的程序可以使用relayfs文件,relayfs必须被mount,格式跟proc差不多:
         mount -t relayfs relayfs /mnt/relay/       

=========================================================================

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

    relay 要解决的问题

        对于大量数据需要在内核中缓存并传输到用户空间需求,很多传统的方法都已到达了极限,例如内核程序员很熟悉的printk() 调用。此外,如果不同的内核子都开发自己的缓存和传输,造成很大的冗余,而且也带来维护上的困难。

        这些,都要求开发一套能够高效可靠地将数据从内核空间转发到用户空间的,而且这个应该独立于各个调试子。这样就诞生了 relayFS。

    relay的发展历史

        relay 的前身是 relayFS,即作为 Linux 的一个新型文件。2003年3月,relayFS的第一个版本的被开发出来,在7月14日,第一个针对2.6内核的版本也开始提供。经过广泛的试用和改进,直到2005年9月,relayFS才被加入mainline内核(2.6.14)。同时,relayFS也被移植到2.4内核中。在 2006年2月,从2.6.17开始,relayFS不再作为单独的文件存在,而是成为内核的一部分。它的源码也 从fs/目录下转移到 kernel/relay.c中,名称中也从relayFS改成了relay。

        relayFS目前已经被越来越多的内核工具使用,包括内核调试工具SystemTap、LTT,以及一些特殊的文件,例如DebugFS。

    relay的基本原理

        relay提供了一种机制,使得内核空间的程序能够通过用户定义的relay通道(channel)将大量数据高效的传输到用户空间。

        一个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。

    relay的主要API

1、 面向用户空间的API:

        这些 relay 编程接口向用户空间程序提供了访问 relay 通道缓冲区数据基本操作入口,包括:

        open() - 允许用户打开一个已经存在的通道缓冲区。

        mmap() - 使通道缓冲区被映射到位于用户空间的调用者的地址空间。要特别注意的是,我们不能仅对局部区域进行映射。也就是说,必须映射整个缓冲区文件,其大小是CPU的个数和单个CPU 缓冲区大小的乘积。

        read() - 读取通道缓冲区的内容。这些数据一旦被读出,就意味着他们被用户空间的程序消费掉了,也就不能被之后的读操作看到。

        sendfile() - 将数据从通道缓冲区传输到一个输出文件描述符。其中可能的填充字符会被自动去掉,不会被用户看到。

        poll() - 支持 POLLIN/POLLRDNORM/POLLERR 信号。每次子缓冲区的边界被越过时,等待着的用户空间程序会得到通知。

        close() - 将通道缓冲区的引用数减1。当引用数减为0时,表明没有进程或者内核用户需要打开它,从而这个通道缓冲区被释放。

2、 面向内核空间的API:

        这些API接口向位于内核空间的用户提供了管理relay通道、数据写入等功能。包括:

        relay_open() - 创建一个relay通道,包括创建每个CPU对应的relay缓冲区。

        relay_close() - 关闭一个relay通道,包括释放所有的relay缓冲区,在此之前会调用relay_switch()来处理这些relay缓冲区以保证已读取但是未满的数据不会丢失。

        relay_write() - 将数据写入到当前CPU对应的relay缓冲区内。由于它使用了local_irqsave()保护,因此也可以在中断上下文中使用。

        relay_reserve() - 在relay通道中保留一块连续的区域来留给未来的写入操作。这通常用于那些希望直接写入到relay缓冲区的用户。考虑到性能或者其它因素,这些用户不希望先把数据写到一个临时缓冲区中,然后再通过relay_write()进行写入。

    Linux relayfs的介绍以及使用

        从Linux-2.6.14内核(2.6.12需要打补丁)开始,relayfs开始作为内核中File System选项中伪文件系统(Pseudo File System)来出现,这是一个新特性。
    File System--->
        Pseudo filesystems---->
            <>Relayfs File System Support
    我们知道,Pseduo File System 另外一个很有名的东西是Proc File System,几乎每个学习Linux的都知道使用这个文件系统来查看cpu型号、内存容量等其它很多的runtime information。Proc FS为users提供了一个方便的接口来查询很多只有内核才能查看的信息,比如:cpuinfo,meminfo,interrupts等,这些都只是 kernel管理的对象,但是我们可以以一个普通users的身份也可以查看。proc FS将内核信息可以动态地传递出来,供普通的process随时查看,某些情况下,用户也可以将信息传递到内核空间,比如:echo 1>/proc/sys/net/ipv4/ip_forward。同样地,relayfs也是可以一种内核和用户空间交换数据的工具,不同的是,它支持大容量的数据交换。

        relayfs中有一个很重要的概念叫做“channel”,具体来说,一个channel就是由很多个内核的buffer组成的一个集合,这些内核的buffer在relayfs中就体现为一个个的文件。 当kernel中的程序把数据写入某个channel时,这些数据实际上自动填入这些channel的buffer。 用户空间的应用程序mmap()将relayfs中的这些文件做个映射,然后在适当的时候把数据提取出来。

        写入channel的数据格式完全取决于最终从channel中提取数据的程序,relayfs可以提取一些hook程序,这些hook程序允许relayfs的数据提取程序(relayfs的客户端)为buffer中的数据增加一些数据结构。这个过程,就像解码跟编码的关系一样,你使用的编码程序和解码程序只有对应就可以,与传输程序无关,当然,你在传输的同时也可以对它进行一些编码,但是这些取决于你最终的解码。 但是,relayfs不提供任何形式的数据过滤,这些任务留给relayfs客户端去完成。 relayfs的设计目标就是尽可能地简单。

        每一个relayfs channel都有一个buffer(单CPU情况),每一个buffer又有一个或者多个二级buffer。 消息是从第一个二级buffer开始写入的,直到这个buffer满为止。然后如果第二个二级buffer可用,就写入第二个二级buffer,依次类推。 所以,如果第一个二级buffer被填满,那么就会通知用户空间;同时,kernel就会去写第二个二级buffer。

        如果kernel发出通知说一个二级buffer被填满了,那么kernel肯定知道填了多少字节。userspace根据这个数字就可以仅仅拷贝合法的数据。拷贝完毕,userpsace通知kernel说一个二级buffer已经被使用了。

        relayfs采用这么一种模式,它会直接去覆盖数据,即使这些数据还没有被userspace所收集。

    relayfs的user space API:

        relayfs为了使得空间程序可以访问channel里面的buffer数据,实现了基本的文件操作。文件操作函数如下:
    open   打开一个存在的buffer;
    mmap  可以使得channel的buffer被映射到调用函数的内存空间,注意,不能部分映射,而是要映射整个文件;
    read   读取channel buffer的内容;
    poll     通知用户空间程序二级buffer空间已满;
    close   关闭。

        为了使得用户空间的程序可以使用relayfs文件,relayfs必须被mount,格式跟proc差不多:
            mount -t relayfs relayfs /mnt/relay/

       kernel空间的一些API:

      relay_open(base_filename, parent, subbuf_size, n_subbufs, callbacks)
        relay_close(chan)
        relay_flush(chan)
        relay_reset(chan)
        relayfs_create_dir(name, parent)
        relayfs_remove_dir(dentry)
        relayfs_create_file(name, parent, mode, fops, data)
        relayfs_remove_file(dentry)
        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, prev_padding)
      buf_mapped(buf, filp)
      buf_unmapped(buf, filp)
      create_buf_file(filename, parent, mode, buf, is_global)
      remove_buf_file(dentry)

离线

页脚