⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 12.htm

📁 UNIX环境下C编程的详细详细介绍
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<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>虽然读、写磁盘文件会使调用在短暂时间内阻塞,但并不能将它们视为&quot;低速&quot;。 
</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 &lt;sys/types.h&gt; </p>

<p>#include &lt;errno.h&gt; </p>

<p>#include &lt;fcntl.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </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, &quot;read %d bytes\n&quot;, ntowrite); </p>

<p>set_fl(STDOUT_FILENO, O_NONBLOCK); /* set nonblocking */ </p>

<p>for (ptr = buf; ntowrite &gt; 0; ) { </p>

<p>errno = 0; </p>

<p>nwrite = write(STDOUT_FILENO, ptr, ntowrite); </p>

<p>fprintf(stderr, &quot;nwrite = %d, errno = %d\n&quot;, nwrite, errno); </p>

<p>if (nwrite &gt; 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 &lt; /etc/termcap &gt;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 &lt; /etc/termcap 2&gt;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,&quot;记录&quot;这个定语也是误用,因为Unix系统核根本没 
</p>

<p>有使用文件记录这种概念。一个更适合的术语可能是&quot;区域锁&quot;,因为它锁定的只是 
</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 &lt;sys/types.h&gt; </p>

<p>#include &lt;unistd.h&gt; </p>

<p>#include &lt;fcnt1.h&gt; </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 + -