seq文件传输

seq文件传输

一、PROC读写

在进行proc接口创建的时候,我们需要制定对应的文件操作函数,即创建的proc文件被读、写的时候要调用的函数,也就是我们的回调函数。其结构体定义如下:

struct file_operations {
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	int (*open) (struct inode *, struct file *);
	......
} __randomize_layout;

这个结构体也是VFS通用的文件操作函数设置的结构体,由于procfs相对比较简单,因此里面大部分的字段都是用不到的,这里只列出常用的几个。

  • open:打开文件时会被调用
  • read:读取文件时调用,其中参数包括要读取到的用户态buf、要读取的长度和开始的位置
  • write:对于某些proc接口,可以支持写操作
  • llseek:定位当前读取文件到指定位置。大部分proc接口都是顺序读的,但是有些接口也支持随机访问,比如/proc/<pid>/mem

从这里我们可以看出来,当用户顺序读取proc接口时,我们的实现是很不方便的。例如,用户要读取/proc/<pid>/status,read方法会被调用。此时我们会把进程的状态信息写到用户态传递下来的buf,但是由于该buf可能不够大,因此我们只能写一部分进去。之后,用户会再次调用read,想从刚才的地方pos继续往下读,这时我们就不好实现了。

二、SEQ文件

为了方便这种序列化的读取,内核封装了struct seq_file,即序列化读取数据,增强了proc对于大数据的支持。下面简单说一下其实现,其过程可以大致分为以下几步:

  • 定义open函数,在open函数中创建并初始化struct seq_file,一般直接调用seq_open(file, op)即可,该函数会创建个seq_file实例,并添加到file->private_data。创建的时候,还需要指定seq_file的操作函数,这个下面分析;
  • read函数设置为seq_read,这是一个内核已经封装好的基于seq机制的读接口;
  • write函数设置为seq_write,这是一个内核已经封装好的基于seq机制的写接口;
  • release设置为seq_release,该函数用于完事后释放资源。

经过上述设置,在用户读取proc的时候,seq_read就会被调用。我们简单来看一下其具体实现。这个函数的参数我们上面已经分析过了,值得注意的是*ppos为要读取的位置。

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;
	size_t n;
	void *p;
	int err = 0;
   
	/* 常规的加锁保护,防止并发read */
	mutex_lock(&m->lock);

	m->version = file->f_version;

	/*
	 * 如果是从开头读取的话,那么将数据进行初始化设置
	 */
	if (*ppos == 0) {
		m->index = 0;
		m->version = 0;
		m->count = 0;
	}

	/* 一般上一次读到的位置会保存在m->read_pos里,但是也有可能用户不进行顺序读取。
	 * 这个比较少见,这里还是要预防一下。 
	 */
	if (unlikely(*ppos != m->read_pos)) {
		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;
		} else {
			m->read_pos = *ppos;
		}
	}

	/* seq_file有个自带的buf缓冲区,其大小为一个页。因此使用seq_file,我们的数据并不是存储到用户态buf,
	 * 而是先存储到seq_file的缓冲区 
	 */
	if (!m->buf) {
		m->buf = seq_buf_alloc(m->size = PAGE_SIZE);
		if (!m->buf)
			goto Enomem;
	}
	/* count里存储的是当前seq_file缓冲区中数据的长度。如果里面还有数据,那就先把这部分数据给用户。 */
	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 (!size)
			goto Done;
	}
	/* 走到这里,说明缓冲区是空的,需要读取数据了 */
	m->from = 0;

    /* 下面就开始调用seq_file上面的操作函数了,注意调用顺序,分别是:start、show、next和stop */
    
    /* start主要完成一次读取前的初始化,包括根据当前index计算出对应的元素,这个取决于具体的实现。
     * 当返回值为NULL或者ERR的时候,会停止读取,即认为已经读取完毕。
     * 注意:一次读取只会分配一次buf,调用一次start,但是可能会调用多次show和next,直到把buf填满
     */
	p = m->op->start(m, &m->index);
	while (1) {
		err = PTR_ERR(p);
		if (!p || IS_ERR(p))
			break;
        /* 这个是核心函数,用于进行数据的输出,即将被读取的数据使用对应的seq帮助函数输出到缓冲区,
         * 帮助函数有seq_printf、seq_putc等,很方便。
         */
		err = m->op->show(m, p);
		if (err < 0)
			break;
		if (unlikely(err))
			m->count = 0;
        /* 如果没有读取到数据的话,那么调用next,将index移动到下一个元素,这个也是根据具体实现的。 */
		if (unlikely(!m->count)) {
			p = m->op->next(m, p, &m->index);
			continue;
		}
        /* 如果读取到的数据没有把缓冲区填满,那么跳转到Fill */
		if (m->count < m->size)
			goto Fill;
        /* 走到这里,说明缓冲区溢出了,那么把缓冲区释放,并分配一个更大的缓冲区,重新读取一次 */
		m->op->stop(m, p);
		kvfree(m->buf);
		m->count = 0;
		m->buf = seq_buf_alloc(m->size <<= 1);
		if (!m->buf)
			goto Enomem;
		m->version = 0;
		p = m->op->start(m, &m->index);
	}
	m->op->stop(m, p);
	m->count = 0;
	goto Done;
Fill:
	/* they want more? let's try to get some more */
	while (1) {
		size_t offs = m->count;
		loff_t pos = m->index;

        /* 前面调用过了show,这里要调用一次next,移动到下一个元素 */
		p = m->op->next(m, p, &m->index);
		if (pos == m->index)
			/* Buggy ->next function */
			m->index++;
		if (!p || IS_ERR(p)) {
			err = PTR_ERR(p);
			break;
		}
		if (m->count >= size)
			break;
        /* 继续读取 */
		err = m->op->show(m, p);
        /* 如果这次读取溢出了,那么这次的读取就不算数,并且结束读取过程。 */
		if (seq_has_overflowed(m) || err) {
			m->count = offs;
			if (likely(err <= 0))
				break;
		}
	}
	m->op->stop(m, p);
	n = min(m->count, size);
    /* 拷贝到用户态 */
	err = copy_to_user(buf, m->buf, n);
	if (err)
		goto Efault;
	copied += n;
	m->count -= n;
	m->from = n;
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;
}

整体的流程如下图所示:

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值