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

smith9527 2018-12-24 原文


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

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_printfseq_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->countseq->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 */

     /* 初始化seq_file buf大小为一个page,即4096字节 */

  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大小,则走填充数据流程 */

    /* xxxdriver skb_status_seq_show 输出第一行数据后,会跳转到Fill,去填充数据到用户层,此时m->size4096字节 */

    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->size4096字节情况下,显示相关信息

    * 由于函数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:您的支持是对博主最大的鼓励

版权声明:本文为smith9527原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/smith9527/p/10170710.html

Linux seq_printf输出内容不完整的问题的更多相关文章

  1. Linux 中的零拷贝技术

    第 1 部分 https://www.ibm.com/developerworks/cn/linux/l-cn […]...

  2. Linux

    Linux Linux第一章 起步第二章 常用命令第一节 背景知识1、Linux系统中一切皆文件2、Linux文件系统3、Linux系统中的路径4、用户的家目录5、文件扩展名不敏感6、一级目录简介第二节 文件和目录相关命令Part01 ho...

  3. k8s Kubernetes v1.10 最简易安装 shell

    k8s Kubernetes v1.10 最简易安装 shell # Master 单节点快速安装 # 最简单 […]...

  4. Nginx实现404页面的几种方法

    一个网站项目,肯定是避免不了404页面的,通常使用Nginx作为Web服务器时,有以下集中配置方式,一起来看看 […]...

  5. Linux三剑客grep、awk和sed

    grep,sed 和 awk是Linux/Unix 系统中常用的三个文本处理的命令行工具,称为文本处理三剑客。 […]...

  6. Linux USB ECM Gadget 驱动介绍

    USB ECM,属于USB-IF定义的CDC(Communication Device Class)下的一个子 […]...

  7. linux 二级域名设置

    首先,你的拥有一个有泛域名解析的顶级域名,例如: domain.com  其次,在 httpd.conf 中打 […]...

  8. Linux CentOS7 搭建ntp时间同步服务器

    前提条件:ntp服务器需要与集群在同一网段内,也就是同一VMent中,并且关闭防火墙!! 关闭selinux1 […]...

随机推荐

  1. 你真的会Excel排序吗?这四种高级排序不容错过

    排序功能在Excel中算是比较基础的功能了,但是你真的精通了吗?自定义排序,按笔画排序,多条件排序,按行排序等 […]...

  2. 关于阿里云服务器安装 wordpress(linux环境)的教程

    阿里云服务器环境(PHP+Nginx+MySQL) 1. 打开终端(Xshell / SecureCRT / […]...

  3. 解决端口号冲突(两种解决方案)

    第一种: 1、查看所有的端口 1 netstat -ano 8080(冲突端口号)      2、打开任务管理 […]...

  4. 【数学建模】美赛冲刺课笔记01

    冲刺课     数值处理问题 插值拟合 主要用于对数据的补全和基本的趋势分析 小波分析、聚类分析(高斯混合聚合 […]...

  5. 使用FluentScheduler和IIS预加载在asp.net中实现定时任务管理

    FluentScheduler介绍 github地址:https://github.com/fluentsch […]...

  6. 什么是GP、LP、PE、VC、FOF?

            GP GP是General Partner的缩写,意思是普通合伙人。投资者经常听到的一些基金、 […]...

  7. 分享一个免费开源的后台管理系统

          关于 Tao Admin Tao Admin 是一套企业级的通用型中后台前端解决方案,它基于 vu […]...

  8. 如何在ASP.NET Core中上传超大文件

    默认情况下,ASP.NET Core只允许您上载最大大小约为28 MB的文件。但是,有时您希望突破此限制,想要 […]...

展开目录

目录导航