📄 12.htm
字号:
<p>err_sys("write_lock error"); </p>
<p>TELL_CHILD(pid); </p>
<p>if (waitpid(pid, NULL, 0) < 0) </p>
<p>err_sys("waitpid error"); </p>
<p>} else { /* child */ </p>
<p>WAIT_PARENT(); /* wait for parent to set lock */ </p>
<p>set_fl(fd, O_NONBLOCK); </p>
<p>/* first let's see what error we get if region is locked */ </p>
<p>if (read_lock(fd, 0, SEEK_SET, 0) != -1) /* no wait */ </p>
<p>err_sys("child: read_lock succeeded"); </p>
<p>printf("read_lock of already-locked region returns %d\n", errno); </p>
<p>/* now try to read the mandatory locked file */ </p>
<p>if (lseek(fd, 0, SEEK_SET) == -1) </p>
<p>err_sys("lseek error"); </p>
<p>if (read(fd, buff, 2) < 0) </p>
<p>err_ret("read failed (mandatory locking works)"); </p>
<p>else </p>
<p>printf("read OK (no mandatory locking), buff = %2.2s\n", buff); </p>
<p>} </p>
<p>exit(0); </p>
<p>} </p>
<p>程序12.7 检查是否支持强制性锁 </p>
<p>此程序首先创建一个文件,并使强制性锁机制对其起作用。然后fork一个子进程。
</p>
<p>父进程对整个文件设置一把写锁,子进程则将该文件的描述符设置为非阻塞的,然
</p>
<p>后企图对该文件设置一把读锁,我们期望这会出错返回,并希望看到系统返回值是
</p>
<p>EACCES或EAGAIN。接着,子进程将文件读、写位置调整到文件起点,并试图读该文
</p>
<p>件。如果系统提供强制性锁机制,则read应返回EACCES或EAGAIN(因为该描述符是
</p>
<p>非阻塞的)。否则read返回所读的数据。在SVR4中运行此程序(该系统支持强制性
</p>
<p>锁机制),得到: </p>
<p>$ a.out </p>
<p>read_lock of already-locked region returns 13 </p>
<p>read failed (mandatory locking works):No more processes </p>
<p>查看系统头文件或intro(2)手册页,可以看到错误13对应于EACCES。从例子中还可
</p>
<p>以看到,在read出错返回信息部分中包含有"No more processes"。这通常来自于
</p>
<p>fork,表示已用完了进程表项。 </p>
<p>$ a.out </p>
<p>read_lock of already_locked region returns 35 </p>
<p>read OK (no mandatory locking),buff=ab </p>
<p>其中,errno35对应于EAGAIN。该系统不支持强制性锁。 </p>
<p>实例 </p>
<p>让我们回到本节的第一个问题:当两个人同时编辑同一个文件将会怎样呢?一般的
</p>
<p>UNIX文本编辑器并不使用记录锁,所以对此问题的回答仍然是:该文件的最后结果
</p>
<p>取决于写该文件的最后一个进程。(4.3+BSD vi编辑器确实有一个编译时选择项使
</p>
<p>运行时建议性记录锁起作用,但是这一选择项并不是缺省可用的。)即使我们在一
</p>
<p>个编辑器,例如vi中使用了建议性锁,可是这把锁并不能阻止其他用户使用另一个
</p>
<p>编辑器,该编辑器没有使用建议性记录锁。 </p>
<p>若系统提供强制性记录锁,那么我们可以修改常用的编辑器(如果我们有该编辑器
</p>
<p>的源代码。)如若我们没有该编辑器的源代码,那么我们可以试一试下述方法。编
</p>
<p>写一个vi的前端程序。该程序立即调用fork,然后父进程等待子进程终止,子进程
</p>
<p>打开在命令行中指定的文件,使强制性锁起作用,对整个文件设置一把写锁,然后
</p>
<p>exec vi。在vi运行时,该文件是加了写锁的,所以其他用户不能修改它。当vi结
</p>
<p>束时,父进程从wait返回,此时我们自编的前端程序也就结束。在本例中假定锁能
</p>
<p>跨越exec,这正是我们前面所说的SVR4的情况(SVR4是我们说过的提供强制性锁的
</p>
<p>唯一系统)。 </p>
<p>这种类型的前端程序是可以编写的,但却往往不能起作用。问题出在大多数编辑器
</p>
<p>(至少是vi和ed)读它们的输入文件,然后关闭它。只要引用被编辑文件的描述符
</p>
<p>关闭了,那么加在该文件上的锁就被释放了。这意味着,在编辑器读了该文件的内
</p>
<p>容,然后关闭了它,那么锁也就不存在了。在前端程序中没有任何方法可以阻止这
</p>
<p>一点。 </p>
<p>在第十六章的数据库函数库中,我们使用了记录锁以阻止多个进程的并发存取。在
</p>
<p>本章,我们提供了时间测量以观察记录锁对进程的影响。 </p>
<p>12.4流(Streams) </p>
<p>流是系统V提供的构造内核设备驱动程序和网络协议包等的一种通用方法,我们对
</p>
<p>流进行讨论的目的是理解下列各点: </p>
<p>(a) 系统V的终端界面。 </p>
<p>(b) I/O多路复用中轮询函数的使用(12.5.2节)。 </p>
<p>(c) 基于流的管道和命名流管道的实现(15.2和12.5.2节)。 </p>
<p>流机制是由Dennis Ritchie 发展起来的[Ritchie 1984],其目的是用通用、灵活的
</p>
<p>方法改写传统的字符I/O系统并适应网络协议的需要,后来流机制被加入SVR3。SV
</p>
<p>R4则提供了对流(基于流的终端I/O系统)的全面支持。[AT&T 1990d]对SVR4实现
</p>
<p>进行了说明。 </p>
<p>请注意不要将本章说明的流与标准I/O库(5.2节)中使用的流相混淆。
</p>
<p>一个流在用户进程和设备驱动程序之间提供了一条全双工通路。流无需和实际硬件
</p>
<p>设备直接对话-流也可以用作为伪设备驱动程序。图12.8示出了一个简单流的基本
</p>
<p>结构。 </p>
<p>图12.8 一个简单流 </p>
<p>在流首之下可以压入处理模块。这可以用ioctl实现。图12.9示出了一个包含一个
</p>
<p>处理模块的流。各方框之间用两根带箭头的线连接,以强调流的全双工特征。
</p>
<p>任意个数的处理模块可以压入流。我们使用术语"压入",这是因为每一新模块总是
</p>
<p>插到流首之下,而将以前压入的模块下压。(这类似于后进、先出的栈。)在图1
</p>
<p>2.9中,我们标出了流的两侧,分别称为顺流(downstream)和逆流(upstream)
</p>
<p>。写到流首的数据将顺流而下传送。由设备驱动程序读到的数据则逆流向上传送。
</p>
<p>图12.9 具有处理模块的流 </p>
<p>流模块是作为核心部分执行的,这类似于设备驱动程序,当构造核心时,流模块连
</p>
<p>编进入核心。大多数系统不允许将末连编进入核心的流模块压入流。
</p>
<p>在图11.2中示出了基于流的终端系统的一般结构。图中标出的"读、写"函数是流首
</p>
<p>。标注为"终端行规程"的框是一个流处理模块。该处理模块的实际名称是ldterm。
</p>
<p>(各种流模块的手册页在[AT&T 1990d]的第7节和[AT&T 1991]的第7节中。
</p>
<p>用第三章中说明的函数存取流,它们是:open、close、read、write和ioctl。另
</p>
<p>外,在SVR3核中增加了3个支持流的新函数(getmsg、putmsg、和poll),在SVR4
</p>
<p>中又加了两个处理流不同优先级波段消息的函数(getpmsg和putpmsg)。本节将说
</p>
<p>明这些新函数,我们为流打开的路径名通常在/dev目录之下。用ls -l查看设备名
</p>
<p>,就能判断该设备是否为流设备。所有流设备都是字符特殊文件。
</p>
<p>虽然某些有关流的文献暗示我们可以编写处理模块,并将它们压入流中,但是编写
</p>
<p>这些模块如同编写设备驱动程序一样,需要专门的技术。 </p>
<p>在流机制之前,终端是用现存的clist机制处理的。(Bach [1986]的10.3.1节和
L </p>
<p>effler et al[1989]的9.6节)分别说明SVR2和4.3BSD中的clist
机制。将基于字符 </p>
<p>的设备添加到核心中通常涉及编写设备驱动程序,将所有有关部分都安排在驱动程
</p>
<p>序中。对新设备的存取典型地通过原始设备进行,这意味着每个用户的read,writ
</p>
<p>e都直通进入设备驱动程序。流机制使这种交互作用方式更加灵活,条理清晰,使
</p>
<p>得数据可以用流消息方式在流首和驱动程序之间传送,并使任意数的中间处理模块
</p>
<p>可对数据进行操作。 </p>
<p>流消息 </p>
<p>流的所有输入和输出都基于消息。流首和用户使用read、write、getmsg、getpms
</p>
<p>g、putmsg和putpmsg交换消息。在流首、各处理模块和设备驱动程序之间,消息可
</p>
<p>以顺流而下,也可以逆流而上。 </p>
<p>在用户进程和流首之间,消息由下列几部分组成:消息类型、可选择的控制信息,
</p>
<p>以及可选择的数据。在图12.10中示出了对应于write、putmsg和putpmsg的不同参
</p>
<p>数,所产生的不同消息类型。控制信息和数据存放在strbuf结构中:
</p>
<p>struct strbuf { </p>
<p>int maxlen; 缓存大小 </p>
<p>int Len; 当前在缓存中的字节数 </p>
<p>char *buf 缓存指针 </p>
<p>}; </p>
<p>图12.10 为write、putmsg和putpmsg产生的流消息的类型 </p>
<p>当用putmsg或putpmsg发送消息时,len指定缓存中数据的字节数。当用getmsg或g
</p>
<p>etpmsg接收消息时,maxlen
指定缓存长度(使核心不会溢出缓存),而len则由核 </p>
<p>心设置,说明存放在缓存中的数据量。0长消息是允许的,len为-1说明没有控制信
</p>
<p>息或数据。 </p>
<p>为什么我们需要传送控制信息和数据两者呢?提供这两者使我们可以实现用户进程
</p>
<p>和流之间的界面。Olander,McGrath和Israel[1986]说明了系统V服务界面的原先实
</p>
<p>现。AT&T[1990d]第五章详细说明了服务界面,还使用了一个简单的实例。可能最为
</p>
<p>人了解的服务界面是系统V的传输层界面(TLI),它提供了网络系统界面,Stevens
</p>
<p>[1990]第七章对此进行了说明。 </p>
<p>控制信息的另一个例子是发送一个无连接的网络消息(数据报)。为了发送该消息
</p>
<p>,我们需要说明消息的内容(数据)和该消息的目的地址(控制信息)。如果我们
</p>
<p>不能将数据和控制一起发送,那么就要某种专门设计的方案。例如,我们可以用i
</p>
<p>octl说明地址,然后用write发送数据。另一种技术可能要求:地址占用数据的前
</p>
<p>N个字节,用write写数据。将控制信息与数据分开,并且提供处理两者的函数(p
</p>
<p>utmsg和getmsg)是处理这种问题的较清晰的方法。 </p>
<p>有约25种不同类型的消息,但是只有少数几种用于用户进程和流首之间。其余的则
</p>
<p>只在核心中顺流、逆流传送。(对于编写流处理模块的人员而言,这些消息是非常
</p>
<p>有用的,但是对编写用户级代码的人员而言,则可忽略它们。)在我们所使用的函
</p>
<p>数(read、write、getmsg、getpmsg、putmsg和putpmsg)中,只涉及三种消息类
</p>
<p>型,它们是: </p>
<p>l M_DATA(I/O的用户数据); </p>
<p>l M_PROTO(协议控制信息); </p>
<p>l M_PCPROTO(高优先级协议控制信息)。 </p>
<p>流中的消息都有一个排队优先级; </p>
<p>l 高优先级消息(最高优先级); </p>
<p>l 优先波段消息; </p>
<p>l 普通消息(最低优先级)。 </p>
<p>普通消息是优先波段为0的消息。优先波段消息的波段可在1-255之间,波段愈高,
</p>
<p>优先级也愈高。 </p>
<p>每个流模块有两个输入队列。一个接收来自它上面模块的消息(这种消息从流首向
</p>
<p>驱动程序顺流传送)。另一个接收来自它下面模块的消息(这种消息从驱动程序向
</p>
<p>流首逆流传送)。在输入队列中的消息按优先级从高到低排列。在图12.10中,我
</p>
<p>们示出了针对write、putmsg和putpmsg的不同参数,产生不同优先级的消息。
</p>
<p>有一些消息我们未加考虑。例如,若流首从它下面接收到M_SIG消息,则产生
</p>
<p>一信号。这种方法用于终端行规程模块向相关前台进程组发送终端产生的信号
</p>
<p>putmsg和putpmsg函数 </p>
<p>putmsg和putpmsg函数用于将流消息(控制信息或数据,或两者)写至流中。这两
</p>
<p>个函数的区别是后者允许对消息指定一个优先波段。 </p>
<p>_______________________________________________________________________ </p>
<p>_______ </p>
<p>#include <stropts.h> </p>
<p>int putmsg(int filedes,const struct strbug *ctlptr, </p>
<p>const struct strbuf *dataptr,int flag); </p>
<p>int uptpmsg(int filedes,const struct strbuf *ctlptr, </p>
<p>const struct strbuf *dataptr,int band,int flag); </p>
<p>两个函数返回:若成功为0,出错为-1 </p>
<p>_______________________________________________________________________ </p>
<p>________ </p>
<p>对流也可以使用write函数,它等效于不带任何控制信息,flag为0的putmsg。
</p>
<p>这两个函数可以产生三种不同优先级的消息:普通、优先波段和高优先级。图12.
</p>
<p>10详细列出了这两个函数中几个参数的各种可能组合,以及所产生的不同类型的消
</p>
<p>息。 </p>
<p>图12.10 write,putmsg和putpmsg所产生的流消息类型 </p>
<p>在此图中,消息控制列中的"否"(no)对应于空ctlptr参数,或ctlptr->len为-1
</p>
<p>。该列中的"是"对应于ctlptr是非空,以及ctlptr->len大于等于0。这些说明同样
</p>
<p>适用于消息的数据部分(用dataptr代替ctlptr)。 </p>
<p>流 ioct l操作 </p>
<p>在SVR4中,使用ioctl可对流执行29种不同的操作。关于这些操作的说明请见stre
</p>
<p>amio(7)手册页(AT&T1990d),头文件<stropts.h>应包括在使用这些操作的C代码
</p>
<p>中。ioctl的第2个参数request说明执行29个操作中的那一个。所有request都以I
</p>
<p>_开始。第3个参数则与request有关。有时第3个参数是一个整型值,有时它是指向
</p>
<p>一个整型或一个数据结构的指针。 </p>
<p>实例-isastream函数 </p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -