linux的机制和策略通信—seqfile

晨曦之光 发布于 2012/04/10 15:02
阅读 204
收藏 0

seqfile不是一个独立的文件系统,它在某种意义上就是一个数据格式化系统,用它的意义就是可以平滑地从内核得到数据。因为原始的procfs的read例程只能读取最大一个页面的数据,大于一个页面的数据就要在用户空间重复读,因此需要一个机制,在内核空间可以连续不断的将数据取出,而不管数据有多大。
struct seq_file {
         char *buf;
         size_t size;
         size_t from;
         size_t count;
         loff_t index;
         struct semaphore sem;
         struct seq_operations *op;
         void *private;
};
和很多linux的特性一样,seqfile也有相关的操作,linux善于将操作封装成一个XX_operations的结构体,那么对于seqfile机制,这个操作例程集就是seq_operations了,策略正是在这些回调例程要提供的,seqfile提供了一套将数据格式化的机制,数据不再像传统的那样,靠独有的read例程最终内部调用copy_to_user拷贝给用户,如此一来,拷贝数据的大小和类型就受到了此独有的read例程的限制,比如procfs的file_operations实现的read函数就限制了数据最大为1个页面,seqfile机制不是这样的,它做到具体如何拷贝由内核模块编写实现,写得很糟糕的可能最后一字节也没有拷贝,写得好的可以一次拷贝好几G的数据,就是这样,用户告知内核怎么拷贝。
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_operations里面的回调函数就是定位/格式化的作用了,start返回第一次的位置,相当于拿到了事情,next返回下一步的位置,相当于一步一步的做事,而show就是格式化操作了,它按照用户的实现格式化了数据。
在seqfile机制中,因为它并不是一个独立的文件系统,故而没有必要提供一个完整的file_operations结构体,而是仅仅提供一个seq_operations就可以了,但是为了使得seqfile机制更加完整,linux内核还是提供了一些松散的read,llseek等函数,这些函数并不像别的read,llseek等函数属于一个file_operations,它们是游离的,因此你可以将它们用到任何file_operations中,也就是替换掉任何file_operations的read等函数,那么原先的read函数的逻辑怎么实现呢?如果你真的这么做了,我承认你是高手,首先第一步就是进行抽象,将原先的read逻辑抽象成一步一步的事情,然后把每一步如何定位偏移实现在next回调函数里面,注意next返回的是个指针,它可以是任何类型,不一定是文件偏移位置,只要你可以合理并相同地解释next的返回值和next的第二个参数就可以了,最后在每一步的show中,将数据按照原先的意思进行格式化,格式化成的数据放进一个buff里面,最后拷贝给用户。我觉得这么做挺好,并且希望将来有一天,所有的file_operations中的函数全部用类似的机制实现,这样抽象程度更高,编程的门槛更低,十分不错,我指的仅仅是用类似的机制而不是特定就是seqfile。下面就看一下最常被用到的seq_read吧:
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
         struct seq_file *m = (struct seq_file *)file->private_data;
         size_t copied = 0;
         loff_t pos;
         size_t n;
         void *p;
         int err = 0;
         down(&m->sem);
         /* grab buffer if we didn't have one */
         if (!m->buf) {  //读的时候还没有分配空间,这可以吗?注意,seqfile和linux中的别的特性一样只提供机制,那么这里的m->buf根本就是一个二传手,为了格式化数据而存在的临时缓存,具体过程就是必须有相关措施将内核中的真正数据拷贝到m->buf中,然后再把m->buf的内容拷贝到用户空间,具体如何将真正的内核数据拷贝给m->buf和江m->buf拷贝给用户空间就是seqfile机制的事情了,其实就是seq_read的逻辑.
                 m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);
                 if (!m->buf)
                         goto Enomem;
         }
         if (m->count) {  //首先读出剩余的数据
                 n = min(m->count, size);
                 err = copy_to_user(buf, m->buf + m->from, n);
                 if (err)
                         goto Efault;
                 m->count -= n;
                 m->from += n;
                 size -= n;
                 buf += n;
                 copied += n;
                 if (!m->count)
                         m->index++;
                 if (!size)
                         goto Done;
         }
         while (1) {   //这个循环并不是真正的拷贝数据,而是“试探”需要空间的大小,注意每次增加一个页面的大小的整数倍
                 pos = m->index;
                 p = m->op->start(m, &pos);  
                 err = PTR_ERR(p);
                 if (!p || IS_ERR(p))
                         break;
                  err = m->op->show(m, p);
                 if (err)
                         break;
                 if (m->count < m->size)  //如果当前buf大小够用的话就直接跳转到填充。
                         goto Fill;
                 m->op->stop(m, p);
                 kfree(m->buf);
                 m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);  //增加buf的大小,每次一个页面的整数倍,并且二倍增长
                 if (!m->buf)
                         goto Enomem;
                 m->count = 0;
         }
         m->op->stop(m, p);
         m->count = 0;
         goto Done;
Fill:
         while (m->count < size) {  //循环读取数据,这个循环怎么没有看到更新m->count的,注意,这个值是在show中被更改的,因为一切逻辑都在start/next/show/stop中,那么外界也就没有必要干预其“内政”了
                 size_t offs = m->count;
                 loff_t next = pos;
                 p = m->op->next(m, p, ≠xt);  //得到下一次要做的事情的抽象实体。
...//太长了,故而错误判断省略
                 err = m->op->show(m, p); //格式化
...//推出条件省略
                 pos = next;
         }
         m->op->stop(m, p);  //结束,善后。
         n = min(m->count, size);
         err = copy_to_user(buf, m->buf, n); //拷贝给用户空间。
...//省略一些必要的处理,对于讲述seqfile逻辑思想没有什么用。
}
show回调函数主要负责格式化数据,怎么格式化当然是用户的策略了,seqfile机制并不干预,但是当你知道了怎么格式化以后,真正格式化的还是内核,seqfile的机制可以帮你格式化数据,同时数据当前位置值也随之更新。可以看出,linux内核十分清楚机制和策略的边界在那里,一点也不为用户做不该做的事,当然该做的事,内核一件也不少做。
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;
                 }
         }
         m->count = m->size;
         return -1;
}
看到最后,我想总结一下了,linux中有很多的规范,这都不算什么,最要紧的是,linux允许人们动态注册根据该规范机制实现的策略模块,这就是linux中著名的“确定规范--注册使用”的特性,比如文件系统,netfilter,sysctl,驱动等等好多好多都是。


原文链接:http://blog.csdn.net/dog250/article/details/5303437
加载中
返回顶部
顶部