seq_file文件的内核读取过程_seq_read_iter-程序员宅基地

技术标签: start_kernel  内核  

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_read中分配,大小为4KB

    size_t size;     //seq_read赋值为4096

    size_t from;     //struct file从seq_file中向用户态缓冲区拷贝时相对于buf的偏移地址

    size_t count;   //可以拷贝到用户态的字符数目

    loff_t index;    //从内核态向seq_file的内核态缓冲区buf中拷贝时startnext的处理的下标pos数值,即用户自定义遍历iter

    loff_t read_pos; //当前已拷贝到用户态的数据量大小,即struct file中拷贝到用户态的数据量

    u64 version;

    struct mutexlock; //保护该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".
如果初始化出现问题:startnext返回ERR_PTR
如果结束时出现问题:遍历结束时返回NULL
显示正确时,show返回0,如果显示错误则返回负值
*/

int seq_open(struct file *file,conststruct 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;

    return0;

}

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_filebuf

 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中,需要对pos0特殊处理

 76     p = m->op->start(m, &pos);   //pseq_start返回的字符串指针,pos=0

 77     while (1)

 78     {

 79         err = PTR_ERR(p);

 80         if (!p || IS_ERR(p))       //如果通过startnext遍历出错,即返回的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_filebuf未溢出,则返回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_startseq_show函数,然后进入到这里,在这个循环里,执行下面循环:

115         if (!p || IS_ERR(p))            //如果seq_filebuf未满: seq_next,seq_show,....seq_next->跳出

116         {                               //如果seq_filebuf满了:则offs表示了未满前最大的读取量,此时p返回自定义结构内容的指针,但是后面show时候只能拷贝了该

117             err = PTR_ERR(p);           //内容的一部分,导致m->count == m->size判断成立,从而m->count回滚到本次拷贝前,后面的pos++表示下次从下一个开始拷贝

118             break;

119         }

120         err = m->op->show(m, p);   //我遇到的实际问题是show后,直接到stop,所以从这里退出了,应该是seq_filebuf填满导致的问题,这里肯定是m->count == m->size

121         if (m->count == m->size || err)  //如果seq_filebuf:  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_filecount=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_filebuf中拷贝到用户的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_filebuf满了,此时m->count回退到调用前,然后调用stop函数,由于stop内容非常小,所以可以填入seq_filebuf,从而完成第一次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,constchar *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 错误代码

 1staticvoid * 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 }

15staticvoid 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 }

21staticvoid * 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 }

35staticint 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     return0;

43 }

 

可见,遍历的条件变为了由自己定义的静态变量i控制,而甩掉了pos,只用其做下标,而且并不是顺序递增操作,这样可能和seq_read主读取函数不一致,下面为跟踪结果

start->show->next->show->next->show->next->show->next->show->next->show->next->show->stop->start->stop->start->stop

2 linux kernel自带的源代码 seq_file.txt文件

 1staticvoid *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  staticvoid *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  staticvoid ct_seq_stop(struct seq_file *s, void *v)

13  { kfree(v); }

14

15  staticint ct_seq_show(struct seq_file *s, void *v)

16  { loff_t *spos = v;

17          seq_printf(s,"%lld\n", (longlong)*spos);

18          return0; }

19

20  staticconststruct 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  staticint ct_open(struct inode *inode, struct file *file)

26  { return seq_open(file, &ct_seq_ops); }

27

28  staticconststruct 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   staticint 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;return0; }

38  module_init(ct_init);

当用户添加上上述红色代码行后,该结果输出为0-100字符串

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/coldsnow33/article/details/38874919

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签