第14章 高級I/O

14.2 非阻塞I/O (沒法進行I/O操做時直接出錯返回)數據庫

(1)可將系統調用分紅兩類:數組

1)「低速」系統調用:可能會使進程永遠阻塞的一類系統調用。<388>網絡

2)其餘。多線程


(2)對一個給定的描述符,指定非阻塞I/O的方法。(兩種)異步

1)調用open函數時,指定O_NONBLOCK標誌。函數

2)調用fcntl函數,打開描述符的O_NONBLOCK標誌。測試


(3)實例:長的非阻塞writeui

#include "apue.h"
#include <errno.h>
#include <fcntl.h>

char	buf[500000];

int
main(void)
{
	int		ntowrite, nwrite;
	char	*ptr;

	ntowrite = read(STDIN_FILENO, buf, sizeof(buf));
	fprintf(stderr, "read %d bytes\n", ntowrite);

	set_fl(STDOUT_FILENO, O_NONBLOCK);	/* set nonblocking */

	ptr = buf;
	while (ntowrite > 0) {
		errno = 0;
		nwrite = write(STDOUT_FILENO, ptr, ntowrite);
		fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno);

		if (nwrite > 0) {
			ptr += nwrite;
			ntowrite -= nwrite;
		}
	}

	clr_fl(STDOUT_FILENO, O_NONBLOCK);	/* clear nonblocking */

	exit(0);
}
void
set_fl(int fd, int flags) /* flags are file status flags to turn on */
{
	int		val;

	if ((val = fcntl(fd, F_GETFL, 0)) < 0)    //得到當前文件狀態標誌
		err_sys("fcntl F_GETFL error");

	val |= flags;		/* turn on flags */ //修改標誌

	if (fcntl(fd, F_SETFL, val) < 0)    //設置標誌
		err_sys("fcntl F_SETFL error");
}

1)set_fl函數:對一個文件描述符設置一個或多個文件狀態標誌的函數。spa

      clr_fl函數:清除。操作系統

2)標準輸出是普通文件和終端時的區別。

    普通文件:能夠指望write只執行一次。

    終端:write執行屢次,有時返回錯誤。

緣由:終端驅動程序一次能接受的數據量隨系統而變。

3)輪詢:這種形式的循環稱做輪詢,在多用戶系統上會浪費CPU時間。

4)多線程可避免使用非阻塞I/O。能夠容許單個線程在I/O調用中阻塞,其餘線程中執行其它任務。


14.3 記錄鎖

(1)記錄鎖的功能:

當第一個進程正在讀或修改文件的某個部分時,使用記錄鎖能夠阻止其餘進程修改同一文件區。

(一個更適合的術語:字節範圍鎖。由於它鎖定的只是文件中的一個區域(也多是整個文件))


fcntl記錄鎖:

(2)請求鎖的一些規則。

1)不一樣進程提出的鎖請求,不一樣類型鎖彼此之間的間的兼容性。(共享讀鎖和獨佔性寫鎖)

2)單個進程提出的多個鎖請求,新鎖將替換已有鎖。

3)請求一把鎖時對描述符的要求:

加讀鎖時,該描述符必須是讀打開。加寫鎖時,該描述符必須是寫打開。


(3)fcntl函數3個關於記錄鎖的命令:

F_GETLK:測試可否創建一把鎖。

F_SETLK:企圖建議一把鎖。

F_SETLKW:企圖建議一把鎖。(阻塞版本)


(4)餓死:頻繁的加讀鎖的請求,使一個文件區間始終存在一把或幾把讀鎖,致使欲加寫鎖的進程等待很長時間。

    

(5)組合,分裂

在設置或釋放文件上的一把鎖時,系統按要求組合或分裂相鄰區。


(6)實例1

1)定義一個lock_reg函數,此函數加鎖或解鎖一個文件區域,避免每次分配flock結構,而後填入各項信息。

2)將特定的加鎖或解鎖一個文件區域定義成宏。


(7)實例2:測試一把鎖

1)定義了一個lock_test函數用於測試一把鎖。(基於fcntl函數,並用宏來調用)

2)不能用此函數測試本身是否在文件的某一部分持有一把鎖。(單個進程提出多個鎖請求的替換規則)


(8)實例3:死鎖

1)何時兩個進程處於死鎖狀態:兩個進程互相等待對方持有而且不釋放(鎖定)的資源時。

2)何時有發生死鎖的可能性:一個進程已經控制了文件的一個加鎖區域,而後它又試圖對另外一個進程控制的區域加鎖,那麼它就會休眠。

3)檢測到死鎖時,內核必須選擇一個進程接收出錯返回。

#include "apue.h"
#include <fcntl.h>

static void
lockabyte(const char *name, int fd, off_t offset)
{
	if (writew_lock(fd, offset, SEEK_SET, 1) < 0)
		err_sys("%s: writew_lock error", name);
	printf("%s: got the lock, byte %lld\n", name, (long long)offset);
}

int
main(void)
{
	int		fd;
	pid_t	pid;

	/*
	 * Create a file and write two bytes to it.
	 */
	if ((fd = creat("templock", FILE_MODE)) < 0)
		err_sys("creat error");
	if (write(fd, "ab", 2) != 2)
		err_sys("write error");

	TELL_WAIT();
	if ((pid = fork()) < 0) {
		err_sys("fork error");
	} else if (pid == 0) {			/* child */
		lockabyte("child", fd, 0);
		TELL_PARENT(getppid());
		WAIT_PARENT();
		lockabyte("child", fd, 1);
	} else {						/* parent */
		lockabyte("parent", fd, 1);
		TELL_CHILD(pid);
		WAIT_CHILD();
		lockabyte("parent", fd, 0);
	}
	exit(0);
}


鎖的隱含繼承和釋放

(9)記錄鎖的自動繼承和釋放的3條規則。

1)鎖與進程和文件二者相關聯。(進程終止鎖釋放,描述符關閉鎖釋放)

2)fork產生的子進程不繼承父進程所設置的鎖。(鎖是阻止多個進程同時寫一個文件)

3)執行exec後,新程序能夠繼承原執行程序的鎖。


456沒看


14.4 I/O多路轉接(先看一下那些描述符可使用,再調用I/O函數,確保I/O操做必定能進行)

(1)

1)當從一個描述符讀,而後又寫到另外一個描述符時,能夠在循環中使用阻塞I/O。

2)當從兩個描述符讀,不能在任一個描述符上進行阻塞讀,不然致使另外一個描述符即便有數據也沒法處理。


(2)5中解決此問題的技術。

1)使用兩個進程。

2)使用兩個線程。

3)使用非阻塞I/O,輪詢。(浪費CPU時間,在多任務系統中應當避免使用)

4)異步I/O。進程告訴內核:當描述符準備好能夠進行I/O時,用一個信號通知它。

5)I/O多路轉接。(比較好)


14.4.1 函數select和pselect

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);


(1)使用select函數的返回信息,調用相應的I/O函數(write或read),可確知該函數不會阻塞。


(2)select函數的參數

1)timeout:指定願意等待的時間長度。

2)readfds,writefds,exceptfds:指向描述符集的指針。(咱們關心的可讀,可寫或處於異常條件的描述符集)

3)fd_set類型可進行的操做:

    1.分配一個這種類型的變量,將這種類型的一個變量值賦值給同類型的另外一個變量。

    2.

           void FD_CLR(int fd, fd_set *set);

       int  FD_ISSET(int fd, fd_set *set);

       void FD_SET(int fd, fd_set *set);

       void FD_ZERO(fd_set *set);

4)nfds:「最大文件描述符編號值加1」 或 要檢查的描述符數。(描述符編號從0開始)

經過指定咱們所關注的最大描述符,內核就只需在此範圍內尋找打開的位。


(3)select函數的返回值

-1:出錯。

0:沒有描述符準備好。

一個正值:已準備好的描述符數之和。

(描述符集中仍舊打開的位對應於已準備好的描述符)(經過修改參數指示哪個描述符已準備好)


(4)什麼是描述符」準備好「的概念。<407>


(5)影響select是否阻塞的因素。

1)設定的願意等待的時間長度。

2)是否捕捉到一個信號。

(描述符阻塞與否並不影響select是否阻塞)


(6)描述符到達了文件尾端,則select會認爲該描述符是可讀的。


(7)pselect與select的區別。

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);

1)timespec比timeval提供更精準的超時時間。

2)timeout聲明爲const。

3)以原子方式安裝信號屏蔽字。


14.4.2 poll函數

(1)int poll(struct pollfd *fds, nfds_t nfds, int timeout);

1)poll函數構造一個pollfd結構的數組。每一個數組元素指定一個描述符編號以及咱們對該描述符感興趣的條件。

2)nfds:指定數組中的元素數。

3)經過設置結構中的成員


(2)文件尾端,掛斷之間的區別。


(3)select和poll中斷後不重啓。


14.5 異步I/O(提出I/O請求就能夠了,程序能夠去作其餘事情了,I/O操做在某個時刻會完成)

(1)POSIX異步I/O接口使用AIO控制塊來描述I/O操做。

aiocb結構定義了AIO控制塊。


(2)異步I/O接口不影響操做系統維護的文件偏移量。

不要在同一個進程裏把異步I/O函數和傳統I/O函數混在一塊兒用在同一個文件上。


(3)aio_read函數進行異步讀操做,aio_write函數進行異步寫操做。

它們返回成功時,異步I/O請求便已經被操做系統放入等待處理的隊列。


(4)I/O操做在等待時,必須確保AIO控制塊和數據庫緩衝區保持穩定;它們下面對應的內存必須始終是合法的,除非I/O操做完成,不然不能被複用。


實例:將一個文件從一種格式翻譯成另外一種格式。

(1)非異步實現:循環調用read,write。

#include "apue.h"
#include <ctype.h>
#include <fcntl.h>

#define BSZ 4096

unsigned char buf[BSZ];

unsigned char
translate(unsigned char c)
{
	if (isalpha(c)) {
		if (c >= 'n')
			c -= 13;
		else if (c >= 'a')
			c += 13;
		else if (c >= 'N')
			c -= 13;
		else
			c += 13;
	}
	return(c);
}

int
main(int argc, char* argv[])
{
	int	ifd, ofd, i, n, nw;

	if (argc != 3)
		err_quit("usage: rot13 infile outfile");
	if ((ifd = open(argv[1], O_RDONLY)) < 0)
		err_sys("can't open %s", argv[1]);
	if ((ofd = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, FILE_MODE)) < 0)
		err_sys("can't create %s", argv[2]);

	while ((n = read(ifd, buf, BSZ)) > 0) {
		for (i = 0; i < n; i++)
			buf[i] = translate(buf[i]);
		if ((nw = write(ofd, buf, n)) != n) {
			if (nw < 0)
				err_sys("write failed");
			else
				err_quit("short write (%d/%d)", nw, n);
		}
	}

	fsync(ofd);
	exit(0);
}

1)int fsync(int fd);

此函數保證磁盤上實際文件系統與緩衝區中內容的一致性。(把延遲寫數據塊寫入磁盤)


(2)使用異步I/O實現。

#include "apue.h"
#include <ctype.h>
#include <fcntl.h>
#include <aio.h>
#include <errno.h>

#define BSZ 4096
#define NBUF 8

enum rwop {
	UNUSED = 0,
	READ_PENDING = 1,
	WRITE_PENDING = 2
};

struct buf {
	enum rwop     op;
	int           last;
	struct aiocb  aiocb;
	unsigned char data[BSZ];
};

struct buf bufs[NBUF];

unsigned char
translate(unsigned char c)
{
	/* same as before */
	if (isalpha(c)) {
		if (c >= 'n')
			c -= 13;
		else if (c >= 'a')
			c += 13;
		else if (c >= 'N')
			c -= 13;
		else
			c += 13;
	}
	return(c);
}

int
main(int argc, char* argv[])
{
	int					ifd, ofd, i, j, n, err, numop;
	struct stat			sbuf;
	const struct aiocb	*aiolist[NBUF];    //用於aio_suspend
	off_t				off = 0;    //所讀文件的偏移,寫入輸出文件時使用相同的偏移

	if (argc != 3)
		err_quit("usage: rot13 infile outfile");
	if ((ifd = open(argv[1], O_RDONLY)) < 0)
		err_sys("can't open %s", argv[1]);
	if ((ofd = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, FILE_MODE)) < 0)
		err_sys("can't create %s", argv[2]);
	if (fstat(ifd, &sbuf) < 0)
		err_sys("fstat failed");

	/* initialize the buffers */
	for (i = 0; i < NBUF; i++) {
		bufs[i].op = UNUSED;
		bufs[i].aiocb.aio_buf = bufs[i].data;
		bufs[i].aiocb.aio_sigevent.sigev_notify = SIGEV_NONE;
		aiolist[i] = NULL;
	}

	numop = 0;    //    用於記錄當前還未執行完的異步操做數
	for (;;) {
		for (i = 0; i < NBUF; i++) {
			switch (bufs[i].op) {
			case UNUSED:
				/*
				 * Read from the input file if more data
				 * remains unread.
				 */
				if (off < sbuf.st_size) {
					bufs[i].op = READ_PENDING;
					bufs[i].aiocb.aio_fildes = ifd;
					bufs[i].aiocb.aio_offset = off;
					off += BSZ;
					if (off >= sbuf.st_size)
						bufs[i].last = 1;
					bufs[i].aiocb.aio_nbytes = BSZ;
					if (aio_read(&bufs[i].aiocb) < 0)
						err_sys("aio_read failed");
					aiolist[i] = &bufs[i].aiocb;
					numop++;
				}
				break;

			case READ_PENDING:
				if ((err = aio_error(&bufs[i].aiocb)) == EINPROGRESS)
					continue;
				if (err != 0) {
					if (err == -1)
						err_sys("aio_error failed");
					else
						err_exit(err, "read failed");
				}

				/*
				 * A read is complete; translate the buffer
				 * and write it.
				 */
				if ((n = aio_return(&bufs[i].aiocb)) < 0)
					err_sys("aio_return failed");
				if (n != BSZ && !bufs[i].last)
					err_quit("short read (%d/%d)", n, BSZ);
				for (j = 0; j < n; j++)
					bufs[i].data[j] = translate(bufs[i].data[j]);
				bufs[i].op = WRITE_PENDING;
				bufs[i].aiocb.aio_fildes = ofd;
				bufs[i].aiocb.aio_nbytes = n;
				if (aio_write(&bufs[i].aiocb) < 0)
					err_sys("aio_write failed");
				/* retain our spot in aiolist */
				break;

			case WRITE_PENDING:
				if ((err = aio_error(&bufs[i].aiocb)) == EINPROGRESS)
					continue;
				if (err != 0) {
					if (err == -1)
						err_sys("aio_error failed");
					else
						err_exit(err, "write failed");
				}

				/*
				 * A write is complete; mark the buffer as unused.
				 */
				if ((n = aio_return(&bufs[i].aiocb)) < 0)
					err_sys("aio_return failed");
				if (n != bufs[i].aiocb.aio_nbytes)
					err_quit("short write (%d/%d)", n, BSZ);
				aiolist[i] = NULL;
				bufs[i].op = UNUSED;
				numop--;
				break;
			}
		}
		if (numop == 0) {
			if (off >= sbuf.st_size)
				break;
		} else {
			if (aio_suspend(aiolist, NBUF, NULL) < 0)
				err_sys("aio_suspend failed");
		}
	}

	bufs[0].aiocb.aio_fildes = ofd;
	if (aio_fsync(O_SYNC, &bufs[0].aiocb) < 0)
		err_sys("aio_fsync failed");
	exit(0);
}

1)定義的struct buf包含一下信息。

struct buf {
	enum rwop     op;    //此結構體正在用於數目異步操做
	int           last;    //輸入文件是否讀完
	struct aiocb  aiocb;    //AIO控制塊
	unsigned char data[BSZ];    //數據緩衝區
};


2)int fstat(int fd, struct stat *buf);

此函數得到已在描述符fd上打開文件的有關信息。struct stat用於存儲這些信息。

這裏主要爲了獲得輸入文件的大小。


3)int aio_error(const struct aiocb *aiocbp);

獲知一個異步讀,寫或者同步操做的完成狀態。


4)ssize_t aio_return(struct aiocb *aiocbp);

異步操做成功,則可調用此函數來獲取異步操做的返回值(read,write或fsync在被成功調用時可能返回的結果)。


5)int aio_suspend(const struct aiocb * const aiocb_list[],  int nitems, const struct timespec *timeout);

阻塞進程,直到異步操做完成。


6) int aio_fsync(int op, struct aiocb *aiocbp);

強制全部等待中的異步操做不等待而寫入持久化的存儲中。


14.6 函數readv和writev(散步讀,彙集寫)

(1)功能:在一次函數調用中讀,寫多個非連續緩衝區。


(2)實例1:將兩個緩衝區的內容連續地寫到一個文件中。

3種方法比較:

1)調用兩次write

2)分配一個新緩衝區,複製,調用一次write

3)調用writev


(3)應當用盡可能少的系統調用次數來完成任務。


14.7 函數readn和writen(由做者定義)

(1)功能:分別讀,寫指定的N字節數據,並處理返回值可能小於要求值的狀況。


(2)爲何要定義這兩個函數:

對管道,FIFO,以及某些設備(特別是終端和網絡),read,write可能返回少於所要求的數據。(讀寫磁盤文件可能不會有這種狀況)

相關文章
相關標籤/搜索