seq_file文件的内核读取过程

seq_file文件的内核读取过程

1 问题
seq_file只是在普通的文件read中加入了内核缓冲的功能,从而实现顺序多次遍历,读取大数据量的简单接口。seq_file一般只提供只读接口,在使用seq_file操作时,主要靠下述四个操作来完成内核自定义缓冲区的遍历的输出操作,其中pos作为遍历的iterator,在seq_read函数中被多次使用,用以定位当前从内核自定义链表中读取的当前位置,当多次读取时,pos非常重要,且pos总是遵循从0,1,2...end+1遍历的次序,其即必须作为遍历内核自定义链表的下标,也可以作为返回内容的标识。但是我在使用中仅仅将其作为返回内容的标示,并没有将其作为遍历链表的下标,从而导致返回数据量大时造成莫名奇妙的错误,注意:start返回的void*v如果非0,被show输出后,在作为参数传递给next函数,next可以对其修改,也可以忽略;当next或者start返回NULL时,在seq_open中控制路径到达seq_end。

复制代码
struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};
复制代码

2 seq_file操作细节
2.0 struct seq_file结构体描述

复制代码
struct seq_file {
    char *buf;       //在seq_open中分配,大小为4KB
    size_t size;     //4096
    size_t from;     //struct file从seq_file中向用户态缓冲区拷贝时相对于buf的偏移地址
    size_t count;    //可以拷贝到用户态的字符数目
    loff_t index;    //从内核态向seq_file的内核态缓冲区buf中拷贝时start、next的处理的下标pos数值,即用户自定义遍历iter
    loff_t read_pos; //当前已拷贝到用户态的数据量大小,即struct file中拷贝到用户态的数据量
    u64 version; 
    struct mutex lock; //保护该seq_file的互斥锁结构
    const struct seq_operations *op; //seq_start,seq_next,set_show,seq_stop函数结构体
    void *private;
};
*色为自定义内核相对于seq_file内核缓冲
*色为seq_file内核缓冲相对于用户态缓冲区
复制代码

2.1普通文件struct file的open函数建立seq_file于struct file即seq_file与struct seq_operation操作函数的连接关系

复制代码
/**
 *    seq_open -    initialize sequential file
 *    @file: file we initialize
 *    @op: method table describing the sequence
 *
 *    seq_open() sets @file, associating it with a sequence described
 *    by @op.  @op->start() sets the iterator up and returns the first
 *    element of sequence. @op->stop() shuts it down.  @op->next()
 *    returns the next element of sequence.  @op->show() prints element
 *    into the buffer.  In case of error ->start() and ->next() return
 *    ERR_PTR(error).  In the end of sequence they return %NULL. ->show()
 *    returns 0 in case of success and negative number in case of error.
 *    Returning SEQ_SKIP means "discard this element and move on".
如果初始化出现问题:start,next返回ERR_PTR
如果结束时出现问题:遍历结束时返回NULL
显示正确时,show返回0,如果显示错误则返回负值
*/
int seq_open(struct file *file, const struct seq_operations *op)
{
    struct seq_file *p = file->private_data;

    if (!p) {
        p = kmalloc(sizeof(*p), GFP_KERNEL);
        if (!p)
            return -ENOMEM;
        file->private_data = p;
    }
    memset(p, 0, sizeof(*p));
    mutex_init(&p->lock);
    p->op = op;

    /*
     * Wrappers around seq_open(e.g. swaps_open) need to be
     * aware of this. If they set f_version themselves, they
     * should call seq_open first and then set f_version.
     */
    file->f_version = 0;

    /*
     * seq_files support lseek() and pread().  They do not implement
     * write() at all, but we clear FMODE_PWRITE here for historical
     * reasons.
     *
     * If a client of seq_files a) implements file.write() and b) wishes to
     * support pwrite() then that client will need to implement its own
     * file.open() which calls seq_open() and then sets FMODE_PWRITE.
     */
    file->f_mode &= ~FMODE_PWRITE;
    return 0;
}
EXPORT_SYMBOL(seq_open);
复制代码

2.2 普通文件struct file的读取函数为seq_read,完成seq_file的读取过程
正常情况下分两次完成:第一次执行执行seq_read时:start->show->next->show...->next->show->next->stop,此时返回内核自定义缓冲区所有内容,即copied !=0,所以会有第二次读取操作
第二次执行seq_read时:由于此时内核自定义内容都返回,根据seq_file->index指示,所以执行start->stop,返回0,即copied=0,并退出seq_read操作
整体来看,用户态调用一次读操作,seq_file流程为:该函数调用struct seq_operations结构体顺序为:start->show->next->show...->next->show->next->stop->start->stop来读取顺序文件

复制代码
复制代码
  1 /**
  2 * seq_read - ->read() method for sequential files.
  3 * @file: the file to read from
  4 * @buf: the buffer to read to
  5 * @size: the maximum number of bytes to read
  6 * @ppos: the current position in the file
  7 *
  8 * Ready-made ->f_op->read()
  9 */
 10 ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
 11 {
 12     struct seq_file *m = (struct seq_file *)file->private_data;
 13     size_t copied = 0;
 14     loff_t pos;
 15     size_t n;
 16     void *p;
 17     int err = 0;
 18 
 19     mutex_lock(&m->lock);
 20 
 21     /* Don't assume *ppos is where we left it */
 22     if (unlikely(*ppos != m->read_pos))  //如果用户已经读取内容和seq_file中不一致,要将seq_file部分内容丢弃
 23     {
 24         m->read_pos = *ppos;
 25         while ((err = traverse(m, *ppos)) == -EAGAIN) //如果是这样,首先通过seq_start,seq_show,seq_next,seq_show...seq_next,seq_show,seq_stop读取*pos大小内容到seq_file的buf中
 26             ;
 27         if (err)
 28         {
 29             /* With prejudice... */
 30             m->read_pos = 0;
 31             m->version = 0;
 32             m->index = 0;
 33             m->count = 0;
 34             goto Done;
 35         }
 36     }
 37 
 38     /*
 39     * seq_file->op->..m_start/m_stop/m_next may do special actions
 40     * or optimisations based on the file->f_version, so we want to
 41     * pass the file->f_version to those methods.
 42     *
 43     * seq_file->version is just copy of f_version, and seq_file
 44     * methods can treat it simply as file version.
 45     * It is copied in first and copied out after all operations.
 46     * It is convenient to have it as part of structure to avoid the
 47     * need of passing another argument to all the seq_file methods.
 48     */
 49     m->version = file->f_version;
 50     /* grab buffer if we didn't have one */
 51     if (!m->buf)   如果第一次读取seq_file,申请4K大小空间
 52     {
 53         m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
 54         if (!m->buf)
 55             goto Enomem;
 56     }
 57     /* if not empty - flush it first */
 58     if (m->count)  //如果seq_file中已经有内容,可能在前面通过traverse考了部分内容
 59     {
 60         n = min(m->count, size);
 61         err = copy_to_user(buf, m->buf + m->from, n); //拷贝到用户态
 62         if (err)
 63             goto Efault;
 64         m->count -= n;
 65         m->from += n;
 66         size -= n;
 67         buf += n;
 68         copied += n;
 69         if (!m->count) //如果正好通过seq_序列操作拷贝了count个字节,从下个位置开始拷贝,不太清楚,traverse函数中,m->index已经增加过了,这里还要加?
 70             m->index++;
 71         if (!size)
 72             goto Done;
 73     }
 74     /* we need at least one record in buffer */
 75     pos = m->index;              //假设该函数从这里开始执行,pos=0,当第二次执行时,pos =上次遍历的最后下标 + 1 >0,所以在start中,需要对pos非0特殊处理
 76     p = m->op->start(m, &pos);   //p为seq_start返回的字符串指针,pos=0;
 77     while (1)
 78     {
 79         err = PTR_ERR(p);
 80         if (!p || IS_ERR(p))       //如果通过start或next遍历出错,即返回的p出错,则退出循环,一般情况下,在第二次seq_open时,通过start即出错[pos变化],退出循环
 81             break;
 82         err = m->op->show(m, p);   //将p所指的内容显示到seq_file结构的buf缓冲区中




 83         if (err < 0)               //如果通过show输出出错,退出循环,此时表明buf已经溢出
 84             break;
 85         if (unlikely(err))         //如果seq_show返回正常[即seq_file的buf未溢出,则返回0],此时将m->count设置为0,要将m->count设置为0
 86             m->count = 0;   
 87         if (unlikely(!m->count))   //一般情况下,m->count==0,所以该判定返回false,#define unlikely(x) __builtin_expect(!!(x), 0)用于分支预测,提高系统流水效率
 88         {
 89             p = m->op->next(m, p, &pos);
 90             m->index = pos;
 91             continue;
 92         }                              




 93         if (m->count < m->size)  //一般情况下,经过seq_start->seq_show到达这里[基本上是这一种情况],或者在err!=0 [即show出错] && m->count != 0时到达这里
 94             goto Fill;
 95         m->op->stop(m, p);
 96         kfree(m->buf);
 97         m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);
 98         if (!m->buf)
 99             goto Enomem;
100         m->count = 0;
101         m->version = 0;
102         pos = m->index;
103         p = m->op->start(m, &pos);
104     }
105     m->op->stop(m, p);   正常情况下,进入到这里,此时已经将所有的seq_file文件拷贝到buf中,且buf未溢出,这说明seq序列化操作返回的内容比较少,少于4KB
106     m->count = 0;
107     goto Done;
108 Fill:
109     /* they want more? let's try to get some more */
110     while (m->count < size)
111     {
112         size_t offs = m->count;          
113         loff_t next = pos;
114         p = m->op->next(m, p, &next);   //一般情况在上面的while循环中只经历了seq_start和seq_show函数,然后进入到这里,在这个循环里,执行下面循环:
115         if (!p || IS_ERR(p))            //如果seq_file的buf未满: seq_next,seq_show,....seq_next->跳出
116         {                               //如果seq_file的buf满了:则offs表示了未满前最大的读取量,此时p返回自定义结构内容的指针,但是后面show时候只能拷贝了该




117             err = PTR_ERR(p);           //内容的一部分,导致m->cont == m->size判断成立,从而m->count回滚到本次拷贝前,后面的pos++表示下次从下一个开始拷贝
118             break;
119         }
120         err = m->op->show(m, p);   //我遇到的实际问题是show后,直接到stop,所以从这里退出了,应该是seq_file的buf填满导致的问题,这里肯定是m->count == m->size
121         if (m->count == m->size || err)  //如果seq_file的buf满:   seq_next,seq_show,....seq_next,seq_show->跳出
122         {
123             m->count = offs;
124             if (likely(err <= 0))
125                 break;
126         }
127         pos = next;
128     }
129     m->op->stop(m, p);     最后执行seq_stop函数
130     n = min(m->count, size);
131     err = copy_to_user(buf, m->buf, n); //将最多size大小的内核缓冲区内容拷贝到用户态缓冲区buf中
132     if (err)
133         goto Efault;
134     copied += n;
135     m->count -= n;
136     if (m->count) 如果本次给用户态没拷贝完,比如seq_file中count=100,但是n=10,即拷贝了前10个,则下次从10位置开始拷贝,这种情况一般不会出现
137         m->from = n;
138     else //一般情况下,pos++,下次遍历时从next中的下一个开始,刚开始时,让seq_func遍历指针递减,但是每次以k退出后,下次继续从k递减,原来是这里++了,所以遍历最好让指针递增
139         pos++;
140     m->index = pos;
141 Done:
142     if (!copied)
143         copied = err;   //copied = 0
144     else
145     {
146         *ppos += copied;
147         m->read_pos += copied;
148     }
149     file->f_version = m->version;
150     mutex_unlock(&m->lock);
151     return copied;   //返回拷贝的字符数目,将copied个字符内容从seq_file的buf中拷贝到用户的buf中
152 Enomem:
153     err = -ENOMEM;
154     goto Done;
155 Efault:
156     err = -EFAULT;
157     goto Done;
158 }
我的情况是:
执行流程为:... next->show-> stop->start-> stop->start->stop,这种问题出现是因为,在某次调用show过程中发现seq_file的buf满了,此时m->count回退到调用前,然后调用stop函数,由于stop内容非常小,所以可以填入seq_file的buf,从而完成第一次fill_buf操作时,顺带有stop信息,操作完之后,此时pos=0,由于在seq_read末尾将其++,导致seq_file->index=1,然后第二次进入到seq_read中,此时可能出现err,导致该函数以非0返回,第3次时,才返回0.
复制代码

2.3 将数据从自定义核心中拷贝到seq_file结构体的buf缓冲中的操作函数

复制代码
int seq_printf(struct seq_file *m, const char *f, ...)
{
    va_list args;
    int len;

    if (m->count < m->size) {
        va_start(args, f);
        len = vsnprintf(m->buf + m->count, m->size - m->count, f, args);
        va_end(args);
        if (m->count + len < m->size) {
            m->count += len;
            return 0;     //成功返回0,此时buf未满
        }
    }
    m->count = m->size;
    return -1;   //如果buf缓冲已满,或者给buf输出后,导致buf溢出,返回-1
}
复制代码

附件:

1 错误代码

复制代码
 1 static void * seqStart (struct seq_file *m, loff_t *pos)
 2 {
 3     printk("--------int seqstart\n");
 4     spin_lock(&diskLog.lock);
 5     seq_printf(m,"the %d in seStart,pos =%lu\n",++countt,*pos);
 6     if (i >= LOG_RECORD_NUM || *pos != 0) 
 7         return NULL;
 8     else 
 9     {
10         *pos = (diskLog.currPos == 0) ? (LOG_RECORD_NUM - 1):(diskLog.currPos-1);
11         12       return diskLog.content[*pos];
13     }
14 }
15 static void seqStop (struct seq_file *m, void *v)
16 {
17     spin_unlock(&diskLog.lock);
18     printk("--------int seqstop\n");
19     seq_printf(m,"in seqStop\n");
20 }
21 static void * seqNext (struct seq_file *m, void *v, loff_t *pos)
22 {
23     i++;
24     printk("--------int seqnext\n");
25     seq_printf(m,"in seqNext,i=%d\n",i);
26     if(i >= LOG_RECORD_NUM)
27         return NULL;
28     if(*pos <= 0)
29          *pos = LOG_RECORD_NUM - 1;
30     else
31          *pos -=1;
32     poss = *pos; 
33     return diskLog.content[*pos];
34 }
35 static int seqShow (struct seq_file *m, void *v)
36 {
37     printk("--------int seqshow\n");
38     if(!i)
39         seq_printf(m,"\t\t--------The Recently Log Record--------\n");
40     if( *((char*)v) )
41     seq_printf(m,"i:%d,pos is:%d,currPos:%d,content:%s\n",i,poss,diskLog.currPos,(char*)v);
42     return 0;
43 }

可见,遍历的条件变为了由自己定义的静态变量i控制,而甩掉了pos,只用其做下标,而且并不是顺序递增操作,这样可能和seq_read主读取函数不一致,下面为跟踪结果

复制代码

2 linux kernel自带的源代码  seq_file.txt文件
 

复制代码
 1 static void *ct_seq_start(struct seq_file *s, loff_t *pos)
 2  { loff_t *spos = kmalloc(sizeof(loff_t), GFP_KERNEL);
            if(!*pos)

                return NULL;
 3          if (! spos) return NULL;
 4          *spos = *pos;   //刚开始时,*pos=0
 5          return spos; }  
 6 
 7  static void *ct_seq_next(struct seq_file *s, void *v, loff_t *pos)
 8  { loff_t *spos = v;
 9          *pos = ++*spos;
            if(*pos > 100)

               return NULL;
10          return spos; //返回1 }
11 
12  static void ct_seq_stop(struct seq_file *s, void *v)
13  { kfree(v); }
14 
15  static int ct_seq_show(struct seq_file *s, void *v)
16  { loff_t *spos = v;
17          seq_printf(s, "%lld\n", (long long)*spos);
18          return 0; }
19 
20  static const struct seq_operations ct_seq_ops = { .start = ct_seq_start,
21          .next  = ct_seq_next,
22          .stop  = ct_seq_stop,
23          .show  = ct_seq_show };
24 
25  static int ct_open(struct inode *inode, struct file *file)
26  { return seq_open(file, &ct_seq_ops); }
27 
28  static const struct file_operations ct_file_ops = { .owner   = THIS_MODULE,
29          .open    = ct_open,
30          .read    = seq_read,
31          .llseek  = seq_lseek,
32          .release = seq_release };
33 
34   static int ct_init(void)
35  { struct proc_dir_entry *entry;
36          entry = create_proc_entry("sequence", 0, NULL);
37          if (entry) entry->proc_fops = &ct_file_ops; return 0; }
38  module_init(ct_init);
复制代码

 

当用户添加上上述红色代码行后,该结果输出为0-100字符串

复制代码

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值