日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

谈谈writev的问题

發(fā)布時間:2024/1/23 编程问答 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 谈谈writev的问题 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

轉(zhuǎn)載地址:http://blog.lucode.net/linux/talk-about-the-problem-of-writev.html

POSIX提供了一個比write函數(shù)更加高級的writev,在很多場景下,它相對于write有一定的優(yōu)勢。

APUE一書將writev的介紹放在了Advanced I/O部分,個人拙見,它和write應該是屬于同層次的IO,談不上Advanced。

最近,我在重寫HTTP解析器的時候用到了writev,發(fā)現(xiàn)它并不如想象中的方便,甚至相當坑,有必要記錄一下,或許對大家有所幫助。

使用場景

為什么說writev并不比write高級?拋開具體的實現(xiàn),它和write只不過是應用場景不同,嚴格而言,不應該用Advanced加以區(qū)分。APUE對于writev的描述也就2頁,老套路:函數(shù)原型、參數(shù)介紹、性能測試,實在看不出所謂的Advanced體現(xiàn)在哪里。

大體而言,write面向的是連續(xù)內(nèi)存塊,writev面向的是分散的數(shù)據(jù)塊,兩個函數(shù)的最終結(jié)果都是將內(nèi)容寫入連續(xù)的空間。

假設(shè)我們需要將N個key-value組合dump到文件中。

已知每個pair的空間是單獨分配的,那么在這個場景下,如果想要使用write完成任務,有如下2種做法:

  • 分配一塊大空間,將每個pair復制到其中,然后write
  • 分別write每個pair
  • 在數(shù)據(jù)量不是太大的情況下,方案1比方案2要高效,應為syscall的開銷是很可觀的,當每個pair的數(shù)據(jù)都比較大時,首選方案2。怎么判斷這里的臨界值?對于磁盤IO,考慮pagesize,這個臨界值很可能是N*pagesize(N>=1),當然我沒有具體考證。

    這個時候,我們就會期望能夠有一個函數(shù)可以做到:

  • 一次syscall
  • 無拷貝
  • 顯然,它就是writev了,其函數(shù)原型如下:

    writev(int fd, struct iovect *iov, int iovcnt);struct iovec {void *iov_base;size_t iov_len; };

    iov_base就是每個pair的基址,iov_len則是長度,不用包含“0”。

    APUE中指出writev的固有開銷比write大,因此對于小內(nèi)存的寫而言,很可能也沒有copy+write高效,具體參考APUE(en) P.522。另外,iovcnt不應超過IOV_MAX,Linux上的IOV_MAX=1024,且iov_len的總和不應溢出,雖然超過限制的情況很少出現(xiàn),但應該考慮到。

    除此之外,另一種比較高效的做法是,分配一大塊內(nèi)存,想辦法讓所有的pair連續(xù),這樣就不需要考慮write了,但是很多情況下你可能根本無法預先獲知總長度,例如從socket中讀取的數(shù)據(jù),為了能夠容納所有的數(shù)據(jù),就不得不分配適當大的數(shù)據(jù),當不夠用的時候再realloc。

    如此一來,會造成空間利用率稍低的問題,且realloc很可能帶來潛在的內(nèi)存拷貝開銷。

    writev的實現(xiàn)

    在Linux2.2之前,由于IOV_MAX過于小,glibc會提供一個wrapper function,代碼如下:

    /*Write data pointed by the buffers described by VECTOR, which is a vector of COUNT 'struct iovec's, to file descriptor FD.The data is written in the order specified.Operates just like 'write' (see <unistd.h>) except that the dataare taken from VECTOR instead of a contiguous buffer.*/ ssize_t __writev(int fd, const struct iovec *vector, int count) {/*Find the total number of bytes to be written.*/size_t bytes = 0;for (int i=0; i<count; ++i) {/*Check for ssize_t overflow. */if (SSIZE_MAX - bytes < vector[i].iov_len) {__set_errno(EINVAL);return -1;}bytes += vector[i].iov_len;}/*Allocate a temporary buffer to hold the data. We should normallyuse alloca since it's faster and does not require synchronizationwith other threads. But we cannot if the amount of memory required is too large.*/char *buffer;char *malloced_buffer __attribute__ ((__cleanup__ (ifree))) = NULL;if (__libc_use_alloca(bytes))buffer = (char *) __alloca(bytes);else {malloced_buffer = buffer = (char *)malloc (bytes);if (buffer == NULL) /*XXX I don't know whether it is acceptable to try writingthe data in chunks. Probably not so we just fail herr.*/return -1;}/*Copy the data into BUFFER. */size_t to_copy = bytes;char *bp = buffer;for (int i=0; i<count; ++i) {size_t copy = MIN(vector[i].iov_len, to_copy);bp = __mempcpy((void *) bp, (void *)vector[i].iov_base, copy);to_copy -= copy;if (to_copy == 0) break;}ssize_t bytes_written = __write(fd, buffer, bytes);return bytes_written; } weak_alias(__writev, writev)

    ? 大致流程就是:

    ? ? ? ?1.計算總長度

    ? ? ? ? 2.分配空間(棧/堆)

    ? ? ? ?3.拷貝數(shù)據(jù)

    ? ? ? ?4.使用write

    這么做完全是權(quán)宜之計,并沒有體現(xiàn)writev的優(yōu)點,如果沒有一次寫完,那么就需要多次復制。

    內(nèi)核中的實現(xiàn)則是有點類似分別對內(nèi)存塊write,只不過由于已經(jīng)位于內(nèi)核空間,自然沒有什么syscall的開銷了,也能使用更加直接的方式,比如直接寫buf,代碼在fs/read_write.c,感興趣的讀者可以挖掘下。

    writev的問題

    writev的出發(fā)點是好的,并且看起來似乎也比較美好,因此很受推崇。

    通過這兩天的使用情況來看,我個人認為writev在設(shè)計上可能存在一定的問題,產(chǎn)生這些問題的具體場景為socket IO,令人遺憾的是,盡管很多人推崇writev,但是google相關(guān)內(nèi)容,資料卻少得可憐。。。

    對于socket IO而言,write經(jīng)常不能夠一次寫完,好在它會返回已經(jīng)寫了多少字節(jié),如果繼續(xù)寫,此時就會阻塞;對于非阻塞socket而言,write會在buf不可寫時返回的EAGAIN,那么在下一次write時,便可通過之前返回的值重新確定基址和長度。

    manual中對于writev的相關(guān)描述為:和write類似。也就是說,它也會返回已經(jīng)寫入的長度或者EAGAIN(errno)。千萬不可天真地認為,每次傳同樣的iovec就能解決問題,writev并不會為你做任何事情,重新處理iovec是調(diào)用者的任務。

    問題是,這個返回值“實用性”并不高,因為參數(shù)傳入的是iovec數(shù)組,計量單位是iovcnt,而不是字節(jié)數(shù),用戶依舊需要通過遍歷iovec來計算新的基址,另外寫入數(shù)據(jù)的“結(jié)束點”可能位于一個iovec的中間某個位置,因此需要調(diào)整臨界iovec的io_base和io_len。

    可以通過如下代碼確認:

    while (iov_iter_count(iter)) {struct iovec iovec = iov_iter_iovec(iter);ssize_t nr;nr = fn(filp, iovec.iov_base, iovec.iov_len, ppos);if (nr < 0) {if (!ret) ret = nr;break;}ret += nr;if (nr != iovec.iov_len) break;iov_iter_advance(iter, nr); }

    個人認為這個設(shè)計和write并不是一個風格,write使用很方便,writev卻很繁瑣,難道僅從參數(shù)和返回的類型就能確定一套API的風格了?

    個人認為下述方案或許更好:

  • 返回已經(jīng)寫入了多少個iovec,并通過參數(shù)返回部分寫入的iovec的字節(jié)數(shù)
  • 提供offset參數(shù),供指定寫入的開始位置
  • 在do_loop_readv_writev中可以直接加入相關(guān)邏輯,對于do_iter_readv_writev或許會麻煩點,但是在回調(diào)中應該不難解決問題。好吧,說再多也只是紙上談兵。相信這并不是實現(xiàn)者的問題,而是POSIX在制定接口時就欠缺考慮。

    總結(jié)

    對于磁盤IO,可以放心使用writev,對于socket,尤其是非阻塞socket,還是盡可能避免的好,實現(xiàn)連續(xù)的內(nèi)存塊反而可以簡化實現(xiàn)。

    ?

    ?

    總結(jié)

    以上是生活随笔為你收集整理的谈谈writev的问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。