参数在scull的proc读取实现中意味着什么?

时间:2019-05-27 22:48:46

标签: c linux linux-kernel linux-device-driver kernel-module

在LDD3(Linux设备驱动程序第三版)scullpipe中,static int scull_read_p_mem(char *buf, char **start, off_t offset, int count, int *eof, void *data)中的参数是什么意思?具体来说,我不了解startpageoffset之间的区别。

我对实际的实现本身有很多疑问(可以在下面找到):

struct scull_pipe {
        wait_queue_head_t inq, outq;       /* read and write queues */
        char *buffer, *end;                /* begin of buf, end of buf */
        int buffersize;                    /* used in pointer arithmetic */
        char *rp, *wp;                     /* where to read, where to write */
        int nreaders, nwriters;            /* number of openings for r/w */
        struct fasync_struct *async_queue; /* asynchronous readers */
        struct semaphore sem;              /* mutual exclusion semaphore */
        struct cdev cdev;                  /* Char device structure */
};

int scull_p_nr_devs;            /* the number of scull_pipe devices */
scull_pipe *scull_p_devices;    /* scull_pipe devices to be malloc'ed */

/* ...... */

/* our proc read implementation */
static int scull_read_p_mem(char *buf, char **start, off_t offset, int count,
        int *eof, void *data)
{
    int i, len;
    struct scull_pipe *p;

#define LIMIT (PAGE_SIZE-200)   /* don't print any more after this size */
    *start = buf;
    len = sprintf(buf, "Default buffersize is %i\n", scull_p_buffer);
    for(i = 0; i < scull_p_nr_devs && len <= LIMIT; i++) {
        p = &scull_p_devices[i];
        if (down_interruptible(&p->sem))
            return -ERESTARTSYS;
        len += sprintf(buf+len, "\nDevice %i: %p\n", i, p);
        len += sprintf(buf+len, "   Buffer: %p to %p (%i bytes)\n", 
                                    p->buffer, p->end, p->buffersize);
        len += sprintf(buf+len, "   rp %p   wp %p\n", p->rp, p->wp);
        len += sprintf(buf+len, "   readers %i   writers %i\n", 
                                    p->nreaders, p->nwriters);
        up(&p->sem);
        scullp_proc_offset(buf, start, &offset, &len);
    }
    *eof = (len <= LIMIT);
    return len;
}


static void scullp_proc_offset(char *buf, char **start, off_t *offset, int *len)
{
    /* QUESTION: what does this function do? */
    if (*offset == 0)
        return;
    if (*offset >= *len) {
        *offset -= *len;    /* QUESTION: what is the purpose of this? */
        *len = 0;
    }
    else {
        *start = buf + *offset; /* QUESTION: why do you need to change "start"? */
        *offset = 0;
    }
}

2 个答案:

答案 0 :(得分:1)

函数scull_read_p_mem用于通过here函数创建proc条目create_proc_read_entry。 5分钟的Google搜索给出了this page,它解释了传递给create_proc_read_entry函数的函数指针中的参数。使用固定格式的sais:

  

参数:
  * buf:内核为尝试读取proc条目的任何进程分配一个内存页。页面指​​针指向该缓冲区   写入数据的内存大小。
  ** start:当proc文件的读取不应该从文件的开头而是一定的偏移量开始时,使用此指针。对于小读,通常将其设置为NULL。
  off:文件指针当前指向的从文件开头的偏移量
  count:要读取的数据字节数
  data:从create_read_proc_entry函数调用传递的数据。
  eof:设置为1表示文件结束

但是过了一会儿,我还在kenel fs/proc/generic.c中找到了一些文档。它有点长,但是我认为它是总结start参数的唯一来源:

        /*
         * How to be a proc read function
         * ------------------------------
         * Prototype:
         *    int f(char *buffer, char **start, off_t offset,
         *          int count, int *peof, void *dat)
         *
         * Assume that the buffer is "count" bytes in size.
         *
         * If you know you have supplied all the data you
         * have, set *peof.
         *
         * You have three ways to return data:
         * 0) Leave *start = NULL.  (This is the default.)
         *    Put the data of the requested offset at that
         *    offset within the buffer.  Return the number (n)
         *    of bytes there are from the beginning of the
         *    buffer up to the last byte of data.  If the
         *    number of supplied bytes (= n - offset) is 
         *    greater than zero and you didn't signal eof
         *    and the reader is prepared to take more data
         *    you will be called again with the requested
         *    offset advanced by the number of bytes 
         *    absorbed.  This interface is useful for files
         *    no larger than the buffer.
         * 1) Set *start = an unsigned long value less than
         *    the buffer address but greater than zero.
         *    Put the data of the requested offset at the
         *    beginning of the buffer.  Return the number of
         *    bytes of data placed there.  If this number is
         *    greater than zero and you didn't signal eof
         *    and the reader is prepared to take more data
         *    you will be called again with the requested
         *    offset advanced by *start.  This interface is
         *    useful when you have a large file consisting
         *    of a series of blocks which you want to count
         *    and return as wholes.
         *    (Hack by Paul.Russell@rustcorp.com.au)
         * 2) Set *start = an address within the buffer.
         *    Put the data of the requested offset at *start.
         *    Return the number of bytes of data placed there.
         *    If this number is greater than zero and you
         *    didn't signal eof and the reader is prepared to
         *    take more data you will be called again with the
         *    requested offset advanced by the number of bytes
         *    absorbed.
         */

我们可以see start之后在copy_to_user内使用-该参数用于优化biiiig文件上的proc条目读取。用户可以传递很小的count变量,但是您需要读取biig文件。因此,您可以使用*start参数从proc读取函数返回该文件的大小,该参数表示要读取多少字节。这样,内核甚至可以传递count=0,但是proc_read函数可以像5000一样返回带有有效*start地址的地址,稍后将在copy_to_user调用中使用它来加快处理速度。阅读。

所以:

  

静态int scull_read_p_mem(char * buf,char ** start,off_t偏移量,int count,int * eof,void * data)中的参数是什么意思?

  • buf-将结果复制到的目标缓冲区
  • start-上面的注释中解释的魔术指针用于加快proc读取速度。
  • offset-要读取的文件中的偏移量
  • count-要读取的字节数
  • eof-指向int的指针,需要将其设置为非零,以防读取整个文件
  • data-用户上下文,作为create_proc_entry函数中的最后一个参数传递。
  

我对实际的实现本身有很多疑问(可以在下面找到):

scullp_proc_offset操纵len缓冲区内的buf偏移量。如果为offset != 0,则scull_read_p_mem不需要从第一个字节读取,而需要从某个字节offset读取。由于它是延迟写入的,因此无论如何都会执行snprintf调用,因此您需要“有点移位”缓冲区。

what does this function do?-实际上,我发现这是一种很有趣的方法,它可以计算要/需要复制到用户的字节数。

what is the purpose of this?-不知道。看起来很麻烦,因为*offset会变成负数。函数/* FIXME this should use seq_file */上方的注释指出,尚有一些问题需要解决。我认为想法是在一次调用中准确返回有关一个scull_p_devices[i]的信息。

why do you need to change "start"?-涉及到这一点。如果*offset不等于0,并且还有一些字节要读取,则应该返回一个指向buf + offset的指针,以使内核知道从何处读取。请注意,*start = buf已被初始化,因此内核将执行copy_to_user(... *start, len)

答案 1 :(得分:0)

buf指向存储页面的底部,应该在其中存储读取结果。

offset建议从虚拟文件中开始读取。

start作为返回值可能意味着几件事:

  1. if start == null,我们已将结果写入buf + offset
  2. if start == some int < buf地址,我们已将结果写入buf
  3. if start == some地址在buf中,我们已经写出要开始的结果

该函数应解释如下。

// context:
// 1. We have set start to be the beginning of buf
//     *start = buf;
// 2. We have fill some string into the buf and increment len
static void scullp_proc_offset(char *buf, char **start, off_t *offset, int *len)
{
    // If offset is zero, we are filling a new buf.
    // (Or we have finished catching up as shown below)
    // We can continue to do so until we reach buf size
    if (*offset == 0)
        return;

    // Otherwise, we have some offset to catch up to.
    if (*offset >= *len) {
        // The offset might be still ahead given we have filled len.
        // So, we reduce offset(meaning that we have done some catch up)
        // and reset len(because we are still working on old data)
        *offset -= *len;
        *len = 0;
    }
    else {
        // Or, the offset might be already behind us
        // So, we set start to indicate that the write starts from this address
        // (case 2 in fs/proc/generic.c#L76)
        // and reset offset (meaning that we are done with catching up)
        *start = buf + *offset;
        *offset = 0;
        // this part might lack the handling of len
        // but it might be true if the structure is the same between two calls.
    }
}

最后,作者使用此函数来使写操作始终始于buf,但在追上之前的偏移量后才计数len。