若干星期前分析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) |