?? 12.htm
字號:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 3.0">
<title>C:\WINDOWS\Desktop\UnixProg\7.htm</title>
</head>
<body>
<font SIZE="2">
<h1 align="center">第十二章 高級I/O </h1>
<p>12.1 引言 </p>
<p>本章內容包括:非阻塞I/O、記錄鎖、系統V流機制、I/O多路轉接(select和poll
</p>
<p>函數)、readv和writev函數,以及存儲映照I/O(mmap)。在第十四章、十五章中
</p>
<p>說明的進程間通信,以及以后各章中的很多實例都要使用本章所述的概念和函數。
</p>
<p>12.2 非阻塞I/O </p>
<p>在10.5節中曾將系統調用分成兩類:低速系統調用和其它。低速系統調用是可能會
</p>
<p>使進程永遠阻塞的一類系統調用: </p>
<p>l
如果數據并不存在,則讀文件可能會使調用者永遠阻塞(例如讀管道,終端設備
</p>
<p>和網絡設備)。 </p>
<p>l
如果數據不能立即被接受,則寫這些同樣的文件也會使調用者永遠阻塞。
</p>
<p>l
在某些條件發生之前,打開文件會被阻塞(例如打開一個終端設備可能需等待到
</p>
<p>一個連接的調制解調器應答;又例如若以只寫方式打開FIFO,那么在沒有其它進程
</p>
<p>已用讀方式打開該FIFO時也要等待)。 </p>
<p>l 對已經加上了強制性記錄鎖的文件進行讀、寫。 </p>
<p>l 某些ioctl操作。 </p>
<p>l 某些進程間通信函數(第十四章)。 </p>
<p>雖然讀、寫磁盤文件會使調用在短暫時間內阻塞,但并不能將它們視為"低速"。
</p>
<p>非阻塞I/O使我們可以調用不會永遠阻塞的I/O操作,例如open,read和write。如果
</p>
<p>這種操作不能完成,則立即出錯返回,表示該操作如繼續執行將繼續阻塞下去。
</p>
<p>對于一個給定的描述符有兩種方法對其指定非阻塞I/O: </p>
<p>1. 如果是調用open以獲得該描述符,則可指定O_NONBLOCK標志(3.3節)。
</p>
<p>2. 對于已經打開的一個描述符,則可調用fcntl,對其打開O_NONBOCK文件狀態標
</p>
<p>志(3.13節)。在程序3.5中的函數可用來為一個描述符打開任一文件狀態標志。
</p>
<p>早期的系統V版本使用標志O_NDELAY指定非阻塞方式。在這些版本中,如果無數據
</p>
<p>可讀,則read返回值0。而Unix又常將read的返回值0解釋為文件結束,兩者有所混
</p>
<p>淆。PISIX.1則提供了一個非阻塞標志,它的名字和語義都與O_NDELAY不同。PISI
</p>
<p>X.1要求,對于一個非阻塞的描述符如果無數據可讀,則read返回-1,并且errno被
</p>
<p>設置為EAGAIN。SVR4支持較老的O_NDELAY和POSIX.1的O_NONBLOCK,但在本書的實
</p>
<p>例中只使用POSIX.1規定的特征。O_NDELAY是為了向后兼容性,不應在新應用程序
</p>
<p>中使用。 </p>
<p>4.3BSD為fcntl提供FNDELAY標志,其語義也稍有區別。它不只影響該描述符的文件
</p>
<p>狀態標志,它將終端設備或套接口的標志更改成非阻塞的,因此影響了終端或套接
</p>
<p>口的所有用戶,不只是影響共享同一文件表項的用戶(4.3BSD非阻塞I/O只對終端
</p>
<p>和套接口起作用)。如果對一個非阻塞描述符的操作不能無阻塞地完成,那么4.3
</p>
<p>BSD返回EWOULDBLOCK。4.3+BSD提供POSIX.1的O_NONBLOCK標志,但其語義卻類似于
</p>
<p>4.3BSD的FNDELAY。非阻塞I/O通常用來處理終端設備或網絡連接,而這些設備通常
</p>
<p>一次由一個進程使用。這就意味著BSD語義的更改通常不會影響我們。出錯返回EW
</p>
<p>OULDBLOCK而不是POSIX.1的EAGAIN,這造成了可移植性問題,我們必須處理這一問
</p>
<p>題。4.3+BSD也支持FIFO,非阻塞I/O也對FIFO起作用。 </p>
<p>實例 </p>
<p>程序12.1是一個非阻塞I/O的實例,它從標準輸入讀100,000字節,并試圖將它們
</p>
<p>寫到標準輸出上。該程序先將標準輸出設置為非阻塞的,然后用for循環進行輸出
</p>
<p>,每次寫的結果都在標準出錯上打印。函數ctl-f1類似于程序3.5中的set-f1,但
</p>
<p>與set-f1的功能相反,它清除1個或多個標志位。 </p>
<p>#include <sys/types.h> </p>
<p>#include <errno.h> </p>
<p>#include <fcntl.h> </p>
<p>#include "ourhdr.h" </p>
<p>char buf[100000]; </p>
<p>int </p>
<p>main(void) </p>
<p>{ </p>
<p>int ntowrite, nwrite; </p>
<p>char *ptr; </p>
<p>ntowrite = read(STDIN_FILENO, buf, sizeof(buf)); </p>
<p>fprintf(stderr, "read %d bytes\n", ntowrite); </p>
<p>set_fl(STDOUT_FILENO, O_NONBLOCK); /* set nonblocking */ </p>
<p>for (ptr = buf; ntowrite > 0; ) { </p>
<p>errno = 0; </p>
<p>nwrite = write(STDOUT_FILENO, ptr, ntowrite); </p>
<p>fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno); </p>
<p>if (nwrite > 0) { </p>
<p>ptr += nwrite; </p>
<p>ntowrite -= nwrite; </p>
<p>} </p>
<p>} </p>
<p>clr_fl(STDOUT_FILENO, O_NONBLOCK); /* clear nonblocking */ </p>
<p>exit(0); </p>
<p>} </p>
<p>程序12.1 長的非阻塞寫 </p>
<p>若標準輸出是普通文件,則可以期望write只執行一次。 </p>
<p>$ ls -l /etc/termcap </p>
<p>印文件長度 </p>
<p>-rw-rw-r-1 root 133439 Oct 11 1990 /etc/termcap </p>
<p>$a.out < /etc/termcap >temp.file 先試一普 </p>
<p>文件 </p>
<p>read 100000 bytes </p>
<p>nwrite-100000, errno=0 一次寫 </p>
<p>$ls -l temp.file </p>
<p>驗輸出文件長度 </p>
<p>-rw-rw-r-1 stevens 100000 Nev 21 16:27 temp.file </p>
<p>但是,若標準輸出是終端,則可期望write有時會返回一個數字,有時則出錯返回
</p>
<p>。下面是在一個系統上運行程序12.1的結果: </p>
<p>$ a.out < /etc/termcap 2>stderr.out 向終端輸出 </p>
<p>大量輸出至終端 </p>
<p>$ cat stderr.out </p>
<p>read 100000 bytes </p>
<p>nwrite=8192, errno=0 </p>
<p>nwrite=8192, errno=0 </p>
<p>nwrite=-1, errno=11 </p>
<p>庵執?11次 </p>
<p>… </p>
<p>nwrite=4096,errno=0 </p>
<p>nwrite=-1, errno=11 </p>
<p>庵執?98次 </p>
<p>… </p>
<p>nwrite=4096,errno=0 </p>
<p>nwrite=-1, errno=11 </p>
<p>庵執?04次 </p>
<p>… </p>
<p>nwrite=4096,errno=0 </p>
<p>nwrite=-1, errno=11 </p>
<p>庵執?047次 </p>
<p>… </p>
<p>nwrite=-1, errno=11 </p>
<p>庵執?046次 </p>
<p>… </p>
<p>nwrite=4096,errno=0 …等等 </p>
<p>在該系統上,errno11是EAGAIN。此系統上的終端驅動程序總是一次接收4096或81
</p>
<p>92字節。在另一個系統上,前三個write返回2005,1822和1811,然后是96次出錯
</p>
<p>返回,接著是返回1864等等。 </p>
<p>每個write能寫多少字節是依賴于系統的。 </p>
<p>此程序在SVR中運行,則其結果完全不同于前面的情況。當輸出到終端上時,輸出
</p>
<p>該整個輸入文件只需要一個write。顯然,非阻塞方式并不構成區別。創建了一個
</p>
<p>較大的輸入文件,并且系統為運行該程序增加了程序緩存。程序的這種運行方式(
</p>
<p>即輸出一整個文件,只調用一次write)一直繼續到輸入文件長度到達約700,000
</p>
<p>字節。到達此長度后,每一個write都返回出錯EAGAIN。(輸入文件則決不會再輸
</p>
<p>出到終端上-該程序只是連續地產生出錯消息流。) </p>
<p>所以發生這種情況是因為:在SVR4中終端驅動程序通過流I/O系統連接到程序。(
</p>
<p>12.4節將詳細說明流。)流系統有它自己的緩存,它一次能從程序接收更多的數據
</p>
<p>。SVR4的行為也依賴于終端類型-硬連線終端、控制臺設備或偽終端。
</p>
<p>在此實例中,程序發出了數千個write調用,但是只有20個左右是真正輸出數據的
</p>
<p>,其余的則出錯返回。這種形式的循環稱為輪詢,在多用戶系統上它浪費了CPU時
</p>
<p>間。在12.5節中,我們將會看到對于非阻塞描述符的I/O,多路轉接是進行這種操
</p>
<p>作的更加有效的方法。 </p>
<p>在第十七章,我們將會用到非阻塞I/O,那時我們要輸出到終端設備(PostScript
</p>
<p>打印機)而且不希望在write上阻塞。 </p>
<p>12.3 記錄鎖(Record Locking) </p>
<p>當兩個人同時編輯一個文件時,其后果將如何呢?在很多Unix系統中,該文件的最
</p>
<p>后狀態取決于寫該文件的最后一個進程。但是對于有些應用程序,例如數據庫,有
</p>
<p>時進程需要確保它正在單獨寫一個文件。為了向進程提供這種能力,較新的Unix系
</p>
<p>統提供了記錄鎖機制。(在第十六章中包含了使用記錄鎖的數據庫子程序庫。)
</p>
<p>記錄鎖機制的功能是:一個進程正在讀或修改文件的某個部分時,可以阻止其它進
</p>
<p>程修改同一文件區。對于Unix,"記錄"這個定語也是誤用,因為Unix系統核根本沒
</p>
<p>有使用文件記錄這種概念。一個更適合的術語可能是"區域鎖",因為它鎖定的只是
</p>
<p>文件的一個區域(也可能是整個文件)。 </p>
<p>歷史 </p>
<p>圖12.1 示出了各種Unix系統提供的不同形式的記錄鎖。 </p>
<p>圖12.1 各種Unix系統支持的記錄鎖形式 </p>
<p>在本節的最后部分將說明建議性鎖和強制性鎖之間的區別。POSIX.1選擇了以fcnt
</p>
<p>l函數為基礎的系統V風格的記錄鎖。這種風格也得到4.3BSD Reno版本的支持
</p>
<p>。 </p>
<p>早期的貝克萊版只支持BSD flock函數。此函數只鎖整個文件,而不鎖文件中的一
</p>
<p>個區域。但是POSIX.1的fcntl函數可以鎖文件中的任一區域,大至整個文件,小至
</p>
<p>單個字節。 </p>
<p>在本書中只說明POSIX.1的fcntl鎖。系統V的lockf函數只是fcntl函數的一個界面
</p>
<p>。 </p>
<p>記錄鎖是1980年由John Bass最早加到Version7上的。系統核中相應系統 </p>
<p>調用入 </p>
<p>口表項是名為locking的函數。此函數提供了強制性記錄鎖功能,它傳到
</p>
<p>了很多制 </p>
<p>造商的系統III版本。Xenix系統采用了此函數,SVR4在Xenix兼容庫中仍 </p>
<p>舊支 </p>
<p>持該函數。 </p>
<p>SVR2是系統V中第一個支持fcntl風格記錄鎖的版本(1984)。 </p>
<p>fcntl記錄鎖 </p>
<p>3.13節中已經給出了fcntl函數的原型,為了敘說方便,這里再重復一次。
</p>
<p>_______________________________________________________________________ </p>
<p>________ </p>
<p>#include <sys/types.h> </p>
<p>#include <unistd.h> </p>
<p>#include <fcnt1.h> </p>
<p>int fcnt1(int filedes,int cmd,…/* struct flock *flockptr */); </p>
<p>返回:若成功依賴于cmd(見下),? </p>
<p>錯為-1 </p>
<p>_______________________________________________________________________ </p>
<p>________ </p>
<p>對于記錄鎖,cmd是F_GETLK、F_SETLK或F_SETLKW。第三個參數(我們將其稱為fl
</p>
<p>ockptr)是一個指向flock結構的指針。 </p>
<p>Struct flock{ </p>
<p>short l_type; /* F_RDLCK,F_WRLCK, 或 F_UNLCK */ </p>
<p>off_t l_start; /*相對于l_whence的字節位移量*/ </p>
<p>short l_whence /SEEK_SET,SEEK_CUR,或SEEK_END */ </p>
<p>off_t l_len; /*長度(字節),O表示鎖至EOF */ </p>
<p>pid_t l_pid; /*隨F--FETLK命令返回 </p>
<p>} </p>
<p>flock結構說明: </p>
<p>l 所希望的鎖類型:F_RDLCK(共享讀鎖)、F_WRLCK(獨占性寫鎖)、或F_UNLCK
</p>
<p>(解鎖一個區域) </p>
<p>l 要加鎖或解鎖的區域的起始地址,它由l_stant和l__whence兩者決定。l_stat是
</p>
<p>相對位移量(字節),l_whence則決定了相對位移量的起點。這與lseek中的使用
</p>
<p>方法一樣。 </p>
<p>l 區域的長度,這由l_len表示。 </p>
<p>關于加鎖和解鎖區域的說明還要注意下列各點: </p>
<p>l
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -