高級I/O之函數readv和writev以及存儲映射I/O

本文來自個人博客:https://dunkwan.cn

函數readv和writev

readvwritev函數用於在一次函數調用中讀、寫多個非連續緩衝區。有時也將這兩個函數稱爲散佈讀聚集寫

#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
兩個函數的返回值:已讀或已寫的字節數;若出錯,返回-1

兩個函數的第二個參數是指向iovec結構數組的一個指針。

struct iovec{
    void *iov_base; /* starting address of buffer */
    size_t iov_len; /* size of buffer */
};

writev函數從緩衝區中聚集輸出數據的順序時:iov[0]iov[1]、直至iov[iovcnt-1]writev返回輸出的字節總數,通常應等於所有緩衝區長度之和。

readv函數則將讀入的數據按上述同樣的順序散佈到緩衝區中。readv總是先填滿一個緩衝區,然後在填寫下一個。readv返回讀到的字節總數。如果遇到文件尾端,已無數據可讀,則返回0。

函數readnwriten

考慮到管道、FIFO以及某些設備有下列性質:

  1. 一次read的操作可能少於所要求的的數據。
  2. 一次write的操作可能少於指定輸出的字節數。

readnwriten函數功能則是分別讀、寫指定的N字節數據,並處理返回值小於要求值的情況。這兩個函數只是按需多次調用readwrite直到讀、寫了N字節數據。

#include <apue.h>
ssize_t readn(int fd, void *buf, size_t nbytes);
ssize_t writen(int fd, void *buf, size_t nbytes);
兩個函數返回值:讀、寫的字節數;若出錯,返回-1

上面兩個函數並不是某個標準的組成部分,只是爲了方便定義的。

詳細定義如下:

#include "apue.h"
ssize_t             /* Read "n" bytes from a descriptor  */
readn(int fd, void *ptr, size_t n)
{
	size_t		nleft;
	ssize_t		nread;

	nleft = n;
	while (nleft > 0) {
		if ((nread = read(fd, ptr, nleft)) < 0) {
			if (nleft == n)
				return(-1); /* error, return -1 */
			else
				break;      /* error, return amount read so far */
		} else if (nread == 0) {
			break;          /* EOF */
		}
		nleft -= nread;
		ptr   += nread;
	}
	return(n - nleft);      /* return >= 0 */
}

ssize_t             /* Write "n" bytes to a descriptor  */
writen(int fd, const void *ptr, size_t n)
{
	size_t		nleft;
	ssize_t		nwritten;

	nleft = n;
	while (nleft > 0) {
		if ((nwritten = write(fd, ptr, nleft)) < 0) {
			if (nleft == n)
				return(-1); /* error, return -1 */
			else
				break;      /* error, return amount written so far */
		} else if (nwritten == 0) {
			break;
		}
		nleft -= nwritten;
		ptr   += nwritten;
	}
	return(n - nleft);      /* return >= 0 */
}

存儲映射I/O

存儲映射I/O能將一個磁盤文件映射到存儲空間中的一個緩衝區上,於是,當從緩衝區取數據時,就相當於讀文件的相應字節。與此類似,將數據存入緩衝區時,相應字節就自動寫入文件。

mmap函數告訴內核將一個給定的文件映射到一個存儲區域中。

#include <sys/mman.h>
void *mmap(void *addr, size_t len, int port, int flag, int fd, off_t off);
返回值:若成功,返回映射區的起始地址;若出錯,返回MAP_FAILED。
參數 說明
addr 用於指定映射存儲區的起始地址。通常設置爲0,表示由系統選擇該映射區的起始地址。
fd 指定要被映射文件的描述符。在文件映射到地址空間之前,必須打開該文件。
len 被映射的字節數。
off 要映射字節在文件中偏移量。
prot 指定了映射存儲區的保護要求。

prot參數可選值,可以是PROT_NONE,也可以是PROT_READPROT_WRITEPROT_EXEC的任意組合的按位或。對指定映射存儲區的保護要求不能超過文件open模式訪問權限。例如,若該文件是隻讀打開的,那麼對映射存儲區就不能指定PROT_WRITE

flag參數可選值如下:

  • MAP_FIXED

    返回值必須等於addr。因這不利於可移植性,所以不鼓勵使用此標誌。如果未指定此標誌

    ,而且addr非0,則內核只把addr視爲在何處設置映射區的一種建議,但是不保證會使用所要求的地址。將addr指定爲0可獲得的最大可移植性。

  • MAP_SHARED

    這一標誌描述了本進程對映射區所進行的存儲操作的配置。此標誌指定存儲操作修改映射文件,也就是,存儲操作相當於對該文件的write。必須指定本標誌或下一個標誌(MAP_PRIVATE),但不能同時指定兩者。

  • MAP_PRIVATE

    本標誌說明,對映射區的存儲操作導致創建該映射文件的一個私有副本。所有後來對該映射區的引用都是引用該副本。(此標誌的一種用途是用於調試程序,它將程序文件的正文部分映射至存儲區,但允許用戶修改其中的指令。任何修改隻影響程序文件的副本,而不影響原文件。)

下圖是存儲映射文件。

mprotect函數可以更改一個現有映射的權限。

#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
返回值:若成功,返回0;若出錯,返回-1

如果共享映射中的頁已修改,那麼可以調用msync將該頁沖洗到被映射的文件中。

#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);
返回值:若成功,返回0;若出錯,返回-1

flags參數

  • MS_ASYNC

    簡單調用要寫的頁。

  • MS_SYNC

    在返回前等待寫操作完成。

  • MS_INVALIDATE

    用於通知操作系統丟棄那些與底層存儲器沒有同步的頁。

當進程終止時,會自動解除存儲區的映射,或者直接調用munmap函數也可以解除映射區。關閉映射存儲區時使用的文件描述符並不解除映射區。

#include <sys/mman.h>
int munmap(void *addr, size_t len);
返回值:若成功,返回0;若出錯,返回-1

測試示例:
用存儲映射I/O實現複製文件(類似cp命令)。

#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h>

#define COPYINCR (1024*1024*1024)	/* 1 GB */

int
main(int argc, char *argv[])
{
	int			fdin, fdout;
	void		*src, *dst;
	size_t		copysz;
	struct stat	sbuf;
	off_t		fsz = 0;

	if (argc != 3)
		err_quit("usage: %s <fromfile> <tofile>", argv[0]);

	if ((fdin = open(argv[1], O_RDONLY)) < 0)
		err_sys("can't open %s for reading", argv[1]);

	if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC,
	  FILE_MODE)) < 0)
		err_sys("can't creat %s for writing", argv[2]);

	if (fstat(fdin, &sbuf) < 0)			/* need size of input file */
		err_sys("fstat error");

	if (ftruncate(fdout, sbuf.st_size) < 0)	/* set output file size */
		err_sys("ftruncate error");

	while (fsz < sbuf.st_size) {
		if ((sbuf.st_size - fsz) > COPYINCR)
			copysz = COPYINCR;
		else
			copysz = sbuf.st_size - fsz;

		if ((src = mmap(0, copysz, PROT_READ, MAP_SHARED,
		  fdin, fsz)) == MAP_FAILED)
			err_sys("mmap error for input");
		if ((dst = mmap(0, copysz, PROT_READ | PROT_WRITE,
		  MAP_SHARED, fdout, fsz)) == MAP_FAILED)
			err_sys("mmap error for output");

		memcpy(dst, src, copysz);	/* does the file copy */
		munmap(src, copysz);
		munmap(dst, copysz);
		fsz += copysz;
	}
	exit(0);
}

結果如下:

源代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章