Chinaunix首页 | 论坛 | 博客
  • 博客访问: 972667
  • 博文数量: 403
  • 博客积分: 27
  • 博客等级: 民兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-25 22:20
文章分类

全部博文(403)

文章存档

2016年(3)

2015年(16)

2014年(163)

2013年(222)

分类: LINUX

2014-01-29 16:31:34

若干星期前分析proc的时候提到了seqfile,今天就来从源码角度分析一下seqfile的使用。网上关于seqfile的源码分析很少,直接讲 start next show stop如何写,这让人觉得莫名其妙。在分析之前可能需要了解一些关于vfs的知识,推荐http://www.ibm.com/developerworks/cn/linux/l-vfs/,写得实在是非常非常好。

以下分析基于2.6.35内核

先看主要我们自己要实现的4个函数:

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);
};  

都在一个结构体里,而这个结构体最后要存在哪里(文中所说的存放都是指地址的存放)?看seq_open的实现:

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;
    file->f_version = 0;
    file->f_mode &= ~FMODE_PWRITE;
    return 0;
}
从seq_open看出seq_operations最后要存放在一个struct seq_file中,而这个seq_file结构体存放在file->private_data中。那最后是哪个函数调用start   next    show  stop 这4个函数呢?以read为例,其过程为:

(syscall)read->vfs_read->seq_read,  最后在seq_read中调用start   next    show  stop,  这里注意seq_read是内核已经实现的,所以我们只需把struct file_operations中的read赋值为seq_read即可。当然,如果你觉的内核中的seq_read写的不符合你的要求,你也可自己实现seq_read,把struct file_operations中的read赋为你自己的seq_read。一般情况下,我们用内核提供的seq_read,  seq_lseek, seq_release。。注意内核中的seq_open不能直接赋给struct file_operations中的open,我们需要写一个类似这样的:

static int my_seq_open(struct inode *ind, struct file *fp)
{
    return seq_open(fp, &seq_ops);
}
(seq_ops中的start,next,show,stop的实现最后讲, 现在讲整体框架)

同样seq_write也不能直接用,这个seq_write是向buf写数据用的,也就是输出用的,这里不详解,我们只分析主要的seq_read。最后我们的struct file_operations应该是这样子:

static struct file_operations seq_fops = {
    .owner = THIS_MODULE,
    .open = my_seq_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = seq_release,
};

有了操作集之后我们需要建个文件把操作集关联进去,于是我们的模块初始化就可以这么写:
static int __init seqfile_init(void)
{
    struct proc_dir_entry *ent;

    ent = create_proc_entry("xzd_seq", 0644, NULL);
    /*if error*/
    ent->proc_fops = &seq_fops;
    return 0;
}
创建一个proc文件,然后把这个文件和上面的操作集关联。有了以上的整体构建之后, 我们现在根据seq_read源码去实现start,next, show,stiop。




132 ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
133 {
134     struct seq_file *m = (struct seq_file *)file->private_data;
135     size_t copied = 0;
136     loff_t pos;
137     size_t n;
138     void *p;
139     int err = 0;
140
141     mutex_lock(&m->lock);
142
143     /* Don't assume *ppos is where we left it */
144     if (unlikely(*ppos != m->read_pos)) {
145         m->read_pos = *ppos;
146         while ((err = traverse(m, *ppos)) == -EAGAIN)
147             ;
148         if (err) {
149             /* With prejudice... */
150             m->read_pos = 0;
151             m->version = 0;
152             m->index = 0;
153             m->count = 0;
154             goto Done;
155         }
156     }
169     m->version = file->f_version;
170     /* grab buffer if we didn't have one */
171     if (!m->buf) {
172         m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
173         if (!m->buf)
174             goto Enomem;
175     }
176     /* if not empty - flush it first */
177     if (m->count) {
178         n = min(m->count, size);
179         err = copy_to_user(buf, m->buf + m->from, n);
180         if (err)
181             goto Efault;
182         m->count -= n;
183         m->from += n;
184         size -= n;
185         buf += n;
186         copied += n;
187         if (!m->count)
188             m->index++;
189         if (!size)
190             goto Done;
191     }
192     /* we need at least one record in buffer */
193     pos = m->index;
194     p = m->op->start(m, &pos);
195     while (1) {
196         err = PTR_ERR(p);
197         if (!p || IS_ERR(p))
198             break;
199         err = m->op->show(m, p);
200         if (err < 0)
201             break;
202         if (unlikely(err))
203             m->count = 0;
204         if (unlikely(!m->count)) {
205             p = m->op->next(m, p, &pos);
206             m->index = pos;
207             continue;
208         }
209         if (m->count < m->size)
210             goto Fill;
211         m->op->stop(m, p);
212         kfree(m->buf);
213         m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);
214         if (!m->buf)
215             goto Enomem;
216         m->count = 0;
217         m->version = 0;
218         pos = m->index;
219         p = m->op->start(m, &pos);
220     }
221     m->op->stop(m, p);
222     m->count = 0;
223     goto Done;

224 Fill:
225     /* they want more? let's try to get some more */
226     while (m->count < size) {
227         size_t offs = m->count;
228         loff_t next = pos;
229         p = m->op->next(m, p, &next);
230         if (!p || IS_ERR(p)) {
231             err = PTR_ERR(p);
232             break;
233         }
234         err = m->op->show(m, p);
235         if (m->count == m->size || err) {
236             m->count = offs;
237             if (likely(err <= 0))
238                 break;
239         }
240         pos = next;
241     }
242     m->op->stop(m, p);
243     n = min(m->count, size);
244     err = copy_to_user(buf, m->buf, n);
245     if (err)
246         goto Efault;
247     copied += n;
248     m->count -= n;
249     if (m->count)
250         m->from = n;
251     else
252         pos++;
253     m->index = pos;
254 Done:
255     if (!copied)
256         copied = err;
257     else {
258         *ppos += copied;
259         m->read_pos += copied;
260     }
261     file->f_version = m->version;
262     mutex_unlock(&m->lock);
263     return copied;
264 Enomem:
265     err = -ENOMEM;
266     goto Done;
267 Efault:
268     err = -EFAULT;
269     goto Done;
270 }

一开始mutex_lock,保证在多核处理器中也能正常工作。144~156的if判断防止由于某种情况(例如没有使用seq_printf.....等这组操作来输出数据)造成的*ppos != m->read_pos的情况。一般情况下144~156的if判断成立,则177~191的if判断也会成立,其效果就是会输出indx=0时*ppos字节以后的数据。具体看traverse函数,m->from代表m->buf的读指针,也就是从buf的什么位置开始读,m->count表示读多少字节,具体不再赘述,有兴趣可以自己看。

正常情况下,在171~175先kmalloc一个页大小buf。在194第一次执行p = m->op->start(m, &pos);  接下来在while(1)中err = m->op->show(m, p);  由此看出p应当是数据的首地址,所以start可以这样写:

假设有数据:static char *buf[] = {"123\n", "456\n", "789\n", NULL};

static void * seq_start (struct seq_file *m, loff_t *pos)
{
    return buf[*pos];
}
其中*pos是作为index,也就是指第几笔数据,在show中我们要输出数据:

static int seq_show (struct seq_file *m, void *v)
{
    seq_printf(m, "%s", (char *)v);
    return 0;
}
注意写数据要用seq_printf seq_write  seq_puts等 这组操作,在这些函数内部会更新,m->count m->size等值。
值得一题的是在show中,正常情况应该返回0,至于为什么,继续看源码。在show之后有3个if:

200         if (err < 0)
201             break;
202         if (unlikely(err))
203             m->count = 0;
204         if (unlikely(!m->count)) {
205             p = m->op->next(m, p, &pos);
206             m->index = pos;
207             continue;
208         }
第一个if 很明显出错情况,第二、三个if条件判断中都有一个unlikely宏,这个宏告诉编译器这个条件为0可能性比较大,这个宏对乱序执行的处理器有效,例如cortex-a8、a9、a15、a7,能提升流水线效率。从这能看出show期望返回0作为正常返回。如果show返回大于0的值,则m->count置0,第三个if成立,这就意味着这次向m->buf写数据无效,因为用seq_printf seq_write这类操作写数据,是往m->buf + m->count的地方开始写,也就是count为0则第二笔数据会覆盖前一笔数据。
继续执行,程序会跳转到Fill,顾名思义,填充数据,但是不用理会它什么填补填充的,改结束就结束。从226开始while (m->count < size), p = m->op->next(m, p, &next);  这之前
next = pos,pos和m->index同步的,也就是索引值,在next函数中只要把它++就好,同时返回下一笔数据首地址,如果返回NULL则结束。

static void * seq_next (struct seq_file *m, void *v, loff_t *pos)
{
    ++*pos;
    return buf[*pos];
}
跳出循环之后执行m->op->stop(m, p); 由于简单写的不需要什么反操作,所以stop里什么也不做。

static void seq_stop (struct seq_file *m, void *v)
{

}
最后用err = copy_to_user(buf, m->buf, n);拷贝数据到用户空间中,同时更新m->count, m->index值,解锁。

最后一点值得注意的是,在210 goto Fill 那里,  m->count < m->size不成立条件是,第一笔数据大于4K这时在seq_printf一系列操作中,m->count=m->size,这种情况下,会先kfree,再kmalloc 2倍m->size大小。如果是在Fill中数据超了,则只拷贝这笔数据前的数据到用户空间,所以用户态想要确保读到一定数量的数据,需要做循环读取。

总结:
seq_read代码写得很繁琐复杂,阅读可能稍显费力,但这恰恰也是它的优点,严谨。内核代码考虑到了很多错误情况,甚至有些不太可能发生的情况也包含在里面了。虽然,自己实现
seq_read会显得比较清晰,但很可能发生预料之外的事情,而用内核代码就没这种担忧,所以首选还是用内核提供的seq_read。

转载注明:
出自xiezhendong1992    chinaunix






阅读(1147) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~