您的位置:首页 > 其它

大并发服务器不得不说的技术--writev

2013-03-20 11:26 232 查看
前面我们说过,对于静态文件的传输,用sendfile可以减少系统调用,现在我们看看动态的数据应该如何处理。

首先,如果数据足够小(小于1024)且只有唯一的一个buffer,我们直接用 send/write 就可以了。

通常的情况下,程序可能会在多个地方产生不同的buffer,如 nginx,第一个phase里都可能会产生buffer,放进一个chain里,

如果对每个buffer调用一次send,系统调用的个数将直接等于buffer的个数,对于多buffer的情况会很糟。

可能大家会想到重新分配一个大的buffer, 再把数据全部填充进去,这样其实只用了一次系统调用了。

又或者在一开始就预先分配一块足够大的内存。

这两种情况是能满足要求,不过都不足取,前一种会浪费内存,后一种方法对phase的独立性有影响。

linux 有一个writev可以支持这种情况,先看下函数声明:

ssize_t writev (int fd, __const struct iovec * iov, int count)


相关的结构:

struct iovec {
char *iov_base;               /*缓冲区起始地址*/
size_t iov_len;                 /*缓冲区长度*/
};


函数声明很明显的告诉我们可以同时发送多个buffer。

不妨看一下nginx的用法:

ngx_chain_t   *cl;
struct iovec  *iov, headers[NGX_HEADERS];
for (cl = in; cl && send < limit; cl = cl->next) {
		iov = ngx_array_push(&header);
                iov->iov_base = (void *) cl->buf->pos;
                iov->iov_len = (size_t) size;
}
writev(c->fd, header.elts, header.nelts);


一开始创建一下 struct iovec 数组,将每个元素的 iov_base指向 单个要发送的buffer,iov_len 则是等于长度。

最后调用 writev一齐发送。

接着我们看一下函数posix定义:

ssize_t writev(int fd, const struct iovec *vecs, size_t count){
	ssize_t bytes = sys_writev(fd, vecs, count);

	RETURN_AND_SET_ERRNO(bytes);
}


内核函数sys_writev:

ssize_t sys_writev(unsigned long fd, const struct iovec __user *vec, unsigned long vlen)
{
	struct file *file;
	ssize_t ret = -EBADF;
	int fput_needed;

	file = fget_light(fd, &fput_needed);
	if (file) {
		ret = vfs_writev(file, vec, vlen, &file->f_pos);
		fput_light(file, fput_needed);
	}

	return ret;
}



我们看到实际上是调用 vfs_writev:

ssize_t  vfs_writev(struct file *file, const struct iovec __user *vec,  unsigned long vlen, loff_t *pos )
{
if (!(file->f_mode & FMODE_WRITE))
return -EBADF;
if (!file->f_op || (!file->f_op->writev && !file->f_op->write))
return -EINVAL;

return do_readv_writev(WRITE, file, vec, vlen, pos);
}


发现 readv, writev 其实都是用 do_readv_writev 来do的:

static ssize_t do_readv_writev(int type, struct file *file, const struct iovec __user * uvector,  unsigned long nr_segs, loff_t *pos)


这个函数比较比较长,我们拣重点分析:

struct iovec iovstack[UIO_FASTIOV];  
struct iovec *iov=iovstack, *vector;


内核 创建数据结构。

copy_from_user(iov, uvector, nr_segs*sizeof(*uvector));


将用户空间的数据考贝到内核空间,注意只是拷贝了 struct iovec 结构,里面的 iov_base 指定的内容没有拷贝。

fnv = file->f_op->writev;
if (fnv) {
ret = fnv(file, iov, nr_segs, pos);
goto out;
}


如果fs 实现了 file_operation 结构体中的 writev 函数,就直接调用它,否则才会调用下面:

while (nr_segs > 0) {
void __user * base;
size_t len;
ssize_t nr;

base = vector->iov_base;
len = vector->iov_len;
vector++;
nr_segs--;

nr = file->f_op->write(file, base, len, pos);
  //报错处理代码 省略
}


不过可惜的是,目前主流文件系统的驱动层fs_operation都不支持 writev 函数,以 ext4为例:

const struct file_operations ext4_file_operations = {
.llseek = generic_file_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = ext4_file_write,
.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext4_compat_ioctl,
#endif
.mmap = ext4_file_mmap,
.open = ext4_file_open,
.release = ext4_release_file,
.fsync = ext4_sync_file,
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};


的确没有对 writev 进行实现。

所以对于writev目前的做法是内核是循环write的,但是比用户层的循环节省了切换的开销,因此效率上还是会好一些,但也好不了多少,不过有理由相

信未来的文件系统会实现 file_operation 的writev.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: