Linux seq_printf输出内容不完整的问题

写在前面的话:这是多年前在项目中遇到的问题,作为博客的开篇之作,有不足之处,请各位大侠斧正!谢谢!

seq_file接口介绍

  有许多种方法能够实现设备驱动(或其它内核组件)提供信息给用户或系统管理员。一个有用的技术是在debugfs,/proc或其他地方创建虚拟文件。虚拟文件能够提供容易获取的人类可读的输出,而且并不需要任何特殊的工具软件,他们能够减轻脚本作者的工作。

  seq_file接口就是其中一个能够为内核模块提供信息给用户或管理员的接口,它通过在/proc目录下创建虚拟文件,提供相关内核模块的用户接口。

struct seq_operations 结构体

  struct seq_operations提供了seq_file文件的迭代操作接口。其中:

  start用于起始访问时文件的初始化工作,并返回一个链接(迭代)对象,或者SEQ_START_TOKEN(表示所有循环的开始);

  stop用于文件访问结束时的清理工作,这里文件访问结束表示所有链接对象遍历完毕;

  next用于在遍历中寻找下一个链接对象;

  show用于对遍历对象的操作,主要调用seq_printf和seq_puts等函数,打印这个对象节点的信息。

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);

};

内核模块struct seq_operations结构体定义

在自研内核模块中,skb_status_seq_ops实现当前skb使用状态的打印,其定义如下:

static const struct seq_operations skb_status_seq_ops = {

  .start = xxxdriver_seq_start,

  .next  = xxxdriver_seq_next,

  .stop  = xxxdriver_seq_stop,

  .show  = skb_status_seq_show,

};

xxxdriver_seq_start:开始遍历xxxdriver_dev_list的初始化工作;

xxxdriver_seq_next:遍历xxxdriver_dev_list时返回一个xxxdriver链接对象;

xxxdriver_seq_stop:遍历xxxdriver_dev_list完成时,做清理工作;

skb_status_seq_show:输出遍历对象的一些信息。

在skb_status_seq_show函数中,通过seq_printf函数实现信息的打印,其实现如下:

static int skb_status_seq_show(struct seq_file *seq, void *v)

{

  if (v == SEQ_START_TOKEN)

  {

   seq_printf(seq, "%s", "skb status info start:\n");

   skbstatusflag = 1;

  }

  else

  {

    skb_status_seq_printf_stats(seq, v);

  }

  return 0;

}

  在调用xxxdriver_seq_start函数后,返回SEQ_START_TOKEN,首先输出一行信息:“skb status info start:”。在遍历xxxdriver_dev_list链接对象过程中,调用skb_status_seq_printf_stats函数,输出实际的skb状态信息。

  skb_status_seq_printf_stats函数打印的信息:xxxdriver在每个cpu上使用预分配skb的情况,包括预分配skb总数,空闲skb数目,已占用skb数目,skb为空的数目等等。

故障说明

  为了调试xxxdriver驱动,新增了skb_status_seq_show输出内容,不幸的是,该功能不能显示全部信息了,只显示了“skb status info start:”信息。

  查看seq_printf函数的实现:

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;

  }

}

m->count = m->size;

return -1;

}

  发现seq_printf函数能否继续填充数据,需要判断当前seq_file缓冲区是否还有空间可供使用。于是在xxxdriver skb_status_seq_printf_stats函数中打印seq->count和seq->size的值,发现size大小为4096,而count值在填充完所有信息之前,已经到达4096字节。想当然认为是由于seq_file缓冲区限制导致不能输出全部信息。

seq_file文件输出流程:

在xxxdriver中,seq_flle文件指定的文件操作接口如下:

static const struct file_operations skb_status_seq_fops = {

  .owner  = THIS_MODULE,

  .open    = skb_status_seq_open,

  .read    = seq_read,

  .llseek  = seq_lseek,

  .release = seq_release,

};

seq_read函数定义如下:

ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)

{

  struct seq_file *m = file->private_data;

  size_t copied = 0;

  loff_t pos;

  size_t n;

  void *p;

  int err = 0;

  mutex_lock(&m->lock);

  /* Don't assume *ppos is where we left it */

  if (unlikely(*ppos != m->read_pos)) {

    m->read_pos = *ppos;

    while ((err = traverse(m, *ppos)) == -EAGAIN);

    if (err) {

      /* With prejudice... */

      m->read_pos = 0;

      m->version = 0;

      m->index = 0;

      m->count = 0;

      goto Done;

    }

  }

  /*

  * seq_file->op->..m_start/m_stop/m_next may do special actions

   * or optimisations based on the file->f_version, so we want to

   * pass the file->f_version to those methods.

   *

   * seq_file->version is just copy of f_version, and seq_file

   * methods can treat it simply as file version.

   * It is copied in first and copied out after all operations.

   * It is convenient to have it as  part of structure to avoid the

   * need of passing another argument to all the seq_file methods.

   */

  m->version = file->f_version;

  /* grab buffer if we didn't have one */

    字节 */

  if (!m->buf) {

    m->buf = kmalloc(m->size = PAGE_SIZE, GFP_KERNEL);

    if (!m->buf)

      goto Enomem;

  }

  /* if not empty - flush it first */

     /* 如果缓冲区有数据,先输出 */

  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;

  }

  /* we need at least one record in buffer */

  pos = m->index;

 /* 对应于xxxdriver_seq_start函数,开始扫描驱动链接的对象 */

  p = m->op->start(m, &pos);

  while (1) {

    err = PTR_ERR(p);

    if (!p || IS_ERR(p))

      break;

    err = m->op->show(m, p); /*对应于xxxdriver_seq_show函数*/

     /* xxxdriver 会存放字符串:“skb status info start:” */

    if (err < 0)

      break;

    if (unlikely(err))

      m->count = 0;

    /* 如果没有输出,则查找下一个链接对象 */

    if (unlikely(!m->count)) {

      /*对应于xxxdriver_seq_next函数*/

      p = m->op->next(m, p, &pos);

      m->index = pos;

      continue;

    }

    /* 如果输出内容小于当前buf大小,则走填充数据流程 */

字节 */

    if (m->count < m->size)

      goto Fill;

    /* 如果我们一次性输出内容大于当前seq_file->size,还有机会扩大缓冲区大小,重新输出 */

    m->op->stop(m, p); /*对应于xxxdriver_seq_stop函数 */

    kfree(m->buf);

    /* 如果输出内容大于当前buf大小,则扩展当前buf大小 */

    m->buf = kmalloc(m->size <<= 1, GFP_KERNEL);

    if (!m->buf)

      goto Enomem;

    m->count = 0;

    m->version = 0;

    pos = m->index;

    p = m->op->start(m, &pos); /* 重新开始扫描链接对象 */

  }

  m->op->stop(m, p); /* 结束扫描链接对象 */

  m->count = 0;

  goto Done;

Fill:

  /* they want more? let's try to get some more */

    /* 如果当前缓冲区数据小于要读取的数据,继续获取新数据 */

  while (m->count < size) {

    size_t offs = m->count;

    loff_t next = pos;

    p = m->op->next(m, p, &next); /* 获取下一个链接对象 */

    /* 链接对象为空(链接末尾),或者有错 */

    if (!p || IS_ERR(p)) {

      err = PTR_ERR(p);

      break;

    }

    /* 在m->size为4096字节情况下,显示相关信息

    * 由于函数skb_status_seq_printf_stats里输出其余数据大于4096字节,* 则数据被截断。offs的值为输出第一行“skb status info start:”的长度 */

    err = m->op->show(m, p); /* 显示相关数据 */

    if (m->count == m->size || err) { /* 已经填满缓冲区或出错 */

      m->count = offs;

      if (likely(err <= 0))

        break;

    }

    pos = next;

  }

  m->op->stop(m, p);

  /* 对于我们的问题,只copy第一行输出 */

  n = min(m->count, size);

  err = copy_to_user(buf, m->buf, n); /* 拷贝数据到用户层 */

  if (err)

    goto Efault;

  copied += n;

  m->count -= n;

  if (m->count)

    m->from = n;

  else

    pos++;

  m->index = pos;

Done:

  if (!copied)

    copied = err;

  else {

    *ppos += copied;

    m->read_pos += copied;

  }

  file->f_version = m->version;

  mutex_unlock(&m->lock);

  return copied;

Enomem:

  err = -ENOMEM;

  goto Done;

Efault:

  err = -EFAULT;

  goto Done;

}

PS:您的支持是对博主最大的鼓励

最新文章

  1. mysql 优化(一)
  2. 判断整数是否能被n整除
  3. Java Hour 64 JVM 最大内存设置
  4. 敏捷开发中高质量 Java 代码开发实践
  5. 前端开发中的SEO
  6. ant 安装过程中问题记录
  7. the second assignment of software testing
  8. HDU 4513 哥几个系列故事——形成完善II manacher求最长回文
  9. 关于Mongo的一些坑
  10. 收集—— css实现垂直居中
  11. 关于使用Xcode9.0使用[UIImage imageNamed:]返回null的问题
  12. 用Python满足满足自己的“小虚荣”
  13. SSO 基于Cookie+fliter实现单点登录 实例解析(一)
  14. ES2015 中的函数式Mixin
  15. 2018JAVA面试题附答案
  16. BugkuCTF 矛盾
  17. Mac 下的 C++ 开发环境
  18. Mysql相关技术细节整理
  19. (4opencv)OpenCV PR 成功的收获和感悟
  20. 深挖android low memory killer

热门文章

  1. VirtualApk 插件入门小试
  2. USB协议学习
  3. Web前端数据存储
  4. DOM知识点总结
  5. Linux下如何查看进程准确启动时间
  6. Axure原型设计工具介绍
  7. WCF服务could not be activated
  8. Go 1.11 Module 介绍
  9. 淘宝App直播宝贝数据采集
  10. eclipse注解模板,实实在在的