初识 relay

详细的 GUIDE 参考 Documentation/filesystems/relay.txt

A 'relay channel' is a kernel->user data relay mechanism implemented as a set of per-cpu kernel buffers ('channel buffers'), each represented as a regular file ('relay file') in user space. Kernel clients write into the channel buffers using efficient write functions; these automatically log into the current cpu's channel buffer. User space applications mmap() or read() from the relay files and retrieve the data as it becomes available. The relay files themselves are files created in a host filesystem, e.g. debugfs, and are associated with the channel buffers using the API described

relay 可以这样看:内核中的 user 使用 relay 是把 message buffer 与 file 关联在一起,并进行 produce/consume track,用户态 client 通过文件名来 read message buffer 中的内容;具体 message 如何解析由 kernel user 和 userspace client 来决定,比如 blktrace 就自己定义了每一条 message 的 header 部分格式

Messages are never split across sub-buffers.

数据结构

一个 struct rchan - relay channel data structure 包含 percpu 数组 struct rchan_buf - per-cpu relay channel buffer,一个 buf 又管理 sub-bufs

    /*
     * Per-cpu relay channel buffer
     */
    struct rchan_buf
    {
        void                *start;             /* start of channel buffer */
        void                *data;              /* start of current sub-buffer */
        size_t              offset;             /* current offset into sub-buffer */
        size_t              subbufs_produced;   /* count of sub-buffers produced */
        size_t              subbufs_consumed;   /* count of sub-buffers consumed */
        struct rchan        *chan;              /* associated channel */
        wait_queue_head_t   read_wait;          /* reader wait queue */
        struct irq_work     wakeup_work;        /* reader wakeup */
        struct dentry       *dentry;            /* channel file dentry */
        struct kref         kref;               /* channel buffer refcount */
        struct page         **page_array;       /* array of current buffer pages */
        unsigned int        page_count;         /* number of current buffer pages */
        unsigned int        finalized;          /* buffer has been finalized */
        size_t              *padding;           /* padding counts per sub-buffer */
        size_t              prev_padding;       /* temporary variable */
        size_t              bytes_consumed;     /* bytes consumed in cur read subbuf */
        size_t              early_bytes;        /* bytes consumed before VFS inited */
        unsigned int        cpu;                /* this buf's cpu */
    } ____cacheline_aligned;

    /*
     * Relay channel data structure
     */
    struct rchan
    {
        u32                     version;        /* the version of this struct */
        size_t                  subbuf_size;    /* sub-buffer size */
        size_t                  n_subbufs;      /* number of sub-buffers per buffer */
        size_t                  alloc_size;     /* total buffer size allocated */
        struct rchan_callbacks  *cb;            /* client callbacks */
        struct kref             kref;           /* channel refcount */
        void                    *private_data;  /* for user-defined data */
        size_t                  last_toobig;    /* tried to log event > subbuf size */
        struct rchan_buf        ** __percpu buf;/* per-cpu channel buffers */
        int                     is_global;      /* One global buffer ? */
        struct list_head        list;           /* for channel list */
        struct dentry           *parent;        /* parent dentry passed to open */
        int                     has_base_filename;          /* has a filename associated? */
        char                    base_filename[NAME_MAX];    /* saved base filename */
    };

exported relay_file_operations

    const struct file_operations relay_file_operations = {
        .open           = relay_file_open,
        .poll           = relay_file_poll,
        .mmap           = relay_file_mmap,
        .read           = relay_file_read,
        .llseek         = no_llseek,
        .release        = relay_file_release,
        .splice_read    = relay_file_splice_read,
    };
    EXPORT_SYMBOL_GPL(relay_file_operations);

struct rchan_callbacks - relay channel client callbacks

    struct rchan_callbacks
    {
        /*
         * subbuf_start - called on buffer-switch to a new sub-buffer
         * @buf: the channel buffer containing the new sub-buffer
         * @subbuf: the start of the new sub-buffer
         * @prev_subbuf: the start of the previous sub-buffer
         * @prev_padding: unused space at the end of previous sub-buffer
         *
         * The client should return 1 to continue logging, 0 to stop
         * logging.
         *
         * NOTE: subbuf_start will also be invoked when the buffer is
         *       created, so that the first sub-buffer can be initialized
         *       if necessary.  In this case, prev_subbuf will be NULL.
         *
         * NOTE: the client can reserve bytes at the beginning of the new
         *       sub-buffer by calling subbuf_start_reserve() in this callback.
         */
        int (*subbuf_start) (struct rchan_buf *buf,
                     void *subbuf,
                     void *prev_subbuf,
                     size_t prev_padding);

        /*
         * buf_mapped - relay buffer mmap notification
         * @buf: the channel buffer
         * @filp: relay file pointer
         *
         * Called when a relay file is successfully mmapped
         */
            void (*buf_mapped)(struct rchan_buf *buf,
                   struct file *filp);

        /*
         * buf_unmapped - relay buffer unmap notification
         * @buf: the channel buffer
         * @filp: relay file pointer
         *
         * Called when a relay file is successfully unmapped
         */
            void (*buf_unmapped)(struct rchan_buf *buf,
                     struct file *filp);
        /*
         * create_buf_file - create file to represent a relay channel buffer
         * @filename: the name of the file to create
         * @parent: the parent of the file to create
         * @mode: the mode of the file to create
         * @buf: the channel buffer
         * @is_global: outparam - set non-zero if the buffer should be global
         *
         * Called during relay_open(), once for each per-cpu buffer,
         * to allow the client to create a file to be used to
         * represent the corresponding channel buffer.  If the file is
         * created outside of relay, the parent must also exist in
         * that filesystem.
         *
         * The callback should return the dentry of the file created
         * to represent the relay buffer.
         *
         * Setting the is_global outparam to a non-zero value will
         * cause relay_open() to create a single global buffer rather
         * than the default set of per-cpu buffers.
         *
         * See Documentation/filesystems/relay.txt for more info.
         */
        struct dentry *(*create_buf_file)(const char *filename,
                          struct dentry *parent,
                          umode_t mode,
                          struct rchan_buf *buf,
                          int *is_global);

        /*
         * remove_buf_file - remove file representing a relay channel buffer
         * @dentry: the dentry of the file to remove
         *
         * Called during relay_close(), once for each per-cpu buffer,
         * to allow the client to remove a file used to represent a
         * channel buffer.
         *
         * The callback should return 0 if successful, negative if not.
         */
        int (*remove_buf_file)(struct dentry *dentry);
    };

relay channel default callbacks

    static struct rchan_callbacks default_channel_callbacks = {
        .subbuf_start       = subbuf_start_default_callback,
        .buf_mapped         = buf_mapped_default_callback,
        .buf_unmapped       = buf_unmapped_default_callback,
        .create_buf_file    = create_buf_file_default_callback,
        .remove_buf_file    = remove_buf_file_default_callback,
    };

CONFIG_RELAY kernel API

    struct rchan *relay_open(const char *base_filename,
                             struct dentry *parent,
                             size_t subbuf_size,
                             size_t n_subbufs,
                             struct rchan_callbacks *cb,
                             void *private_data)
        create a new relay channel
            @base_filename: base name of files to create, %NULL for buffering only
            @parent: dentry of parent directory, %NULL for root directory or buffer
            @subbuf_size: size of sub-buffers
            @n_subbufs: number of sub-buffers
            @cb: client callback functions
            @private_data: user-defined data
        Returns channel pointer if successful, %NULL otherwise.
        Creates a channel buffer for each cpu using the sizes and attributes specified. 
        The created channel buffer files will be named
                base_filename0...base_filenameN-1. 
        File permissions will be 
                %S_IRUSR.
        If opening a buffer (@parent = NULL) that you later wish to register
        in a filesystem, call relay_late_setup_files() once the @parent dentry
        is available.

    void relay_close(struct rchan *chan)
        close the channel
            @chan: the channel
        Closes all channel buffers and frees the channel.

    void relay_reset(struct rchan *chan)
            @chan: the channel
        This has the effect of erasing all data from all channel buffers
        and restarting the channel in its initial state.  The buffers
        are not freed, so any mappings are still in effect.
        NOTE. 
            Care should be taken that the channel isn't actually
            being used by anything when this call is made.

    int relay_buf_full(struct rchan_buf *buf)   
        boolean, is the channel buffer full?
            @buf: channel buffer
        Returns 1 if the buffer is full, 0 otherwise.

    void relay_flush(struct rchan *chan)
        close the channel
            @chan: the channel
        Flushes all channel buffers, i.e. forces buffer switch

    size_t relay_switch_subbuf(struct rchan_buf *buf, size_t length)
        switch to a new sub-buffer
            @buf: channel buffer
            @length: size of current event
        Returns either the length passed in or 0 if full.
        Performs sub-buffer-switch tasks such as invoking callbacks,
        updating padding counts, waking up readers, etc.

    void relay_subbufs_consumed(struct rchan *chan,
                                unsigned int cpu,
                                size_t subbufs_consumed)
        update the buffer's sub-buffers-consumed count
            @chan: the channel
            @cpu: the cpu associated with the channel buffer to update
            @subbufs_consumed: number of sub-buffers to add to current buf's count
        Adds to the channel buffer's consumed sub-buffer count.
        subbufs_consumed should be the number of sub-buffers newly consumed,
        not the total consumed.
        NOTE. 
            Kernel clients don't need to call this function if the channel
            mode is 'overwrite'.

    int relay_late_setup_files(struct rchan *chan,
                               const char *base_filename,
                               struct dentry *parent)           
        triggers file creation
            @chan: channel to operate on
            @base_filename: base name of files to create
            @parent: dentry of parent directory, %NULL for root directory
        Returns 0 if successful, non-zero otherwise.
        Use to setup files for a previously buffer-only channel created
        by relay_open() with a NULL parent dentry.
        For example, this is useful for perfomring early tracing in kernel,
        before VFS is up and then exposing the early results once the dentry
        is available.