大並發伺服器不得不說的技術–writev

來源:互聯網
上載者:User

前面我們說過,對於靜態檔案的傳輸,用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.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.