linux的机制和策略通信—seqfile

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 size) //如果当前buf大小够用的话就直接跳转到填充。
goto Fill;
m->op->stop(m, p);
kfree(m->buf);
m->buf = kmalloc(m->size if (!m->buf)
goto Enomem;
m->count = 0;
}
m->op->stop(m, p);
m->count = 0;
goto Done;
Fill:
while (m->count count的,注意,这个值是在show中被更改的,因为一切逻辑都在start/next/show/stop中,那么外界也就没有必要干预其“内政”了
size_t offs = m->count;
loff_t next = pos;
p = m->op->next(m, p, &next); //得到下一次要做的事情的抽象实体。
...//太长了,故而错误判断省略
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 size) {
va_start(args, f);
len = vsnprintf(m->buf + m->count, m->size - m->count, f, args);
va_end(args);
if (m->count + len size) {
m->count += len;
return 0;
}
}
m->count = m->size;
return -1;
}
看到最后,我想总结一下了,linux中有很多的规范,这都不算什么,最要紧的是,linux允许人们动态注册根据该规范机制实现的策略模块,这就是linux中著名的“确定规范--注册使用”的特性,比如文件系统,netfilter,sysctl,驱动等等好多好多都是。

你可能感兴趣的:(linux)