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字符串