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

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签