📄 12.htm
字号:
<p>有时需要确定一个描述符是否引用一个流。这与调用isatty函数来确定一个描述符
</p>
<p>是否引用一个终端设备相类似(11.9节)。SVR4提供isastream函数。 </p>
<p>int isastream(int filedes); </p>
<p>返回:若是流返回1,否则返回0 </p>
<p>(由于某种原因,SVR4的设计者忘记将此函数的原型放在头文件中,所以我们也不
</p>
<p>能为此函数写一条#include语句。) </p>
<p>与isatty类似,它通常是用一个只对流设备才有效的ioctl函数来进行测试的
</p>
<p>。程序12.8是该函数的一种可能的实现。它使用I_CANPUT ioctl来测试由第3个参
</p>
<p>数说明的优先波段是否是可写的?如果该ioctl执行成功,则它对所涉及的流并末
</p>
<p>作任何改变。 </p>
<p>#include <stropts.h> </p>
<p>#include <unistd.h> </p>
<p>int </p>
<p>isastream(int fd) </p>
<p>{ </p>
<p>return(ioctl(fd, I_CANPUT, 0) != -1); </p>
<p>} </p>
<p>程序12.8 检查描述符是否引用流设备 </p>
<p>程序12.9 可用于测试此函数。 </p>
<p>#include <sys/types.h> </p>
<p>#include <sys/fcntl.h> </p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>main(int argc, char *argv[]) </p>
<p>{ </p>
<p>int i, fd; </p>
<p>for (i = 1; i < argc; i++) { </p>
<p>printf("%s: ", argv[i]); </p>
<p>if ( (fd = open(argv[i], O_RDONLY)) < 0) { </p>
<p>err_ret("%s: can't open", argv[i]); </p>
<p>continue; </p>
<p>} </p>
<p>if (isastream(fd) == 0) </p>
<p>err_ret("%s: not a stream", argv[i]); </p>
<p>else </p>
<p>err_msg("%s: streams device", argv[i]); </p>
<p>} </p>
<p>exit(0); </p>
<p>} </p>
<p>程序12.9 测试isasteam函数 </p>
<p>运行此程序,得到很多由ioctl函数返回的出错信息。 </p>
<p>$ a.out /dev/tty /dev/vidadm /dev/null /etc/motd </p>
<p>/dev/tty:/dev/tty:streams device </p>
<p>/dev/vidadm:/dev/vidadm:not a stream:Invalid argument </p>
<p>/dev/null:/dev/null:not a stream:No suck device </p>
<p>/etc/motd: /etc/motd:not a stream:Not a typewriter </p>
<p>/dev/tty在SVR之下是个流设备,这与我们所期望的一致。/dev/vidadm不是一个流
</p>
<p>设备,但是它是支持其它ioctl请求的字符特殊文件。对于不知道这种ioctl请求的
</p>
<p>设备,它返回EINVAL。/dev/null是一种不支持任何ioctl操作的字符特殊文件,所
</p>
<p>以ioctl返回ENODEV。最后,/etc/motd是一个普通文件,而不是字符特殊文件,所
</p>
<p>以返回ENOTTY(这是在这种情况下的典型返回值)。 </p>
<p>ENOTTY("不是打字机")是个历史产物,当ioctl企图对并不引用字符特
</p>
<p>殊设备 </p>
<p>的描述符进行操作时,Unix系统核都返回ENOTTY。 </p>
<p>实例 </p>
<p>如果ioctl参数request是I_LIST,则系统返回该流上所有模块的名字,包括最顶端
</p>
<p>的驱动程序。其第3个参数应当是指向str_list结构是指针。 </p>
<p>struct str_list{ </p>
<p>int sl_nmods; 数组中项的数目 </p>
<p>struct str_modlist *sl_modlist; 指向数组中第一个元素的指针 </p>
<p>}; </p>
<p>应将sl_modlist设置为指向str_mlist结构数组的第一个元素,将sl_nmods设置为
</p>
<p>该数组中的项数。 </p>
<p>struct str_mlist { </p>
<p>char l_name[FMNAMESZ+1]; 以null结尾的模块名字 </p>
<p>}; </p>
<p>常数FMNAMESZ定义在头文件<sys/conf.h>中,其值常常是8。l_name的实际长度是
</p>
<p>FMNAMESZ+1,增加1个字节是为了存放null终止符。 </p>
<p>如果ioctl的第3个参数是0,则该函数返回的是模块数,而不是模块名。我们将先
</p>
<p>用这种ioctl调用确定模块数,然后再分配所要求的str_mlist结构数。 </p>
<p>程序12.10例示了I_LIST操作。由ioctl返回的名字列表并不对模块和驱动程序进行
</p>
<p>区分,因为在该列表中的最后一项是处于流底部的驱动程序,所以在打印时将其标
</p>
<p>明为驱动程序。 </p>
<p>#include <sys/conf.h> </p>
<p>#include <sys/types.h> </p>
<p>#include <fcntl.h> </p>
<p>#include <stropts.h> </p>
<p>#include "ourhdr.h" </p>
<p>int </p>
<p>main(int argc, char *argv[]) </p>
<p>{ </p>
<p>int fd, i, nmods; </p>
<p>struct str_list list; </p>
<p>if (argc != 2) </p>
<p>err_quit("usage: a.out <pathname>"); </p>
<p>if ( (fd = open(argv[1], O_RDONLY)) < 0) </p>
<p>err_sys("can't open %s", argv[1]); </p>
<p>if (isastream(fd) == 0) </p>
<p>err_quit("%s is not a stream", argv[1]); </p>
<p>/* fetch number of modules */ </p>
<p>if ( (nmods = ioctl(fd, I_LIST, (void *) 0)) < 0) </p>
<p>err_sys("I_LIST error for nmods"); </p>
<p>printf("#modules = %d\n", nmods); </p>
<p>/* allocate storage for all the module names */ </p>
<p>list.sl_modlist = calloc(nmods, sizeof(struct str_mlist)); </p>
<p>if (list.sl_modlist == NULL) </p>
<p>err_sys("calloc error"); </p>
<p>list.sl_nmods = nmods; </p>
<p>/* and fetch the module names */ </p>
<p>if (ioctl(fd, I_LIST, &list) < 0) </p>
<p>err_sys("I_LIST error for list"); </p>
<p>/* print the module names */ </p>
<p>for (i = 1; i <= nmods; i++) </p>
<p>printf(" %s: %s\n", (i == nmods) ? "driver" : "module", </p>
<p>list.sl_modlist++); </p>
<p>exit(0); </p>
<p>} </p>
<p>程序12.10 打印流中的模块名 </p>
<p>$ who </p>
<p>stevens console sep 25 06:12 </p>
<p>stevens pts001 Oct 12 07:12 </p>
<p>$ a.out /dev/pts001 </p>
<p>#modules=4 </p>
<p>module:ttcompat </p>
<p>module:ldterm </p>
<p>module:ptem </p>
<p>driver:pts </p>
<p>$ a.out /dev/console </p>
<p>#modules=5 </p>
<p>module:ttcompat </p>
<p>module:ldterm </p>
<p>module:ansi </p>
<p>module:char </p>
<p>driver:cmux </p>
<p>在这两种情形中,顶上的两个流模块都是一样的(ttcompac和ldterm),但是余下
</p>
<p>的模块和驱动程序则不同。在第十九章中我们将说明伪终端情形。
</p>
<p>write至流设备 </p>
<p>在图12.10中可以看到write至流设备产生一个M_DATA消息。一般而言,这确实如此
</p>
<p>,但是也还有一些情况需要考虑。首先,流中顶部的一个处理模块规定了可顺流传
</p>
<p>送的最小、最大数据包长度。(无法查询该模块中规定的这些值。)如果write的
</p>
<p>数据长度超过最大值,则流首将这一数据分解成最大长度的若干数据包。最后一个
</p>
<p>数据包的长度则小于最大值。 </p>
<p>接着要考虑的是:如果向流write 0个字节,又将如何呢?除非流涉及管道或FIFO
</p>
<p>,否则就顺流发送0长消息。对于管道FIFO,为与以前版本兼容,系统的默认处理
</p>
<p>方式是忽略0长write。可以用ioctl设置实现管道和FIFO的流写方式以更改这种默
</p>
<p>认处理方式。 </p>
<p>写方式 </p>
<p>可以用ioctl取得和设置一个流的写方式。如果将request设置为I_GWROPT,第三个
</p>
<p>参数为指向一个整型变量的指针,则该流的当前写方式就在该整型量中返回。如果
</p>
<p>将request设置为I_SWROPT,第3个参数是一个整型,则其值就成为该流新的写方式。
</p>
<p>如同处理文件描述符标志和文件状态标志(3.13节)一样,总是应当先取当前写方式
</p>
<p>值,然后修改它,而不只是将写方式设置为某个绝对值(很可能要关闭某些原来打开
</p>
<p>的位)。 </p>
<p>目前,只定义了两个写方式值。 </p>
<p>SNDZERO 对管道和FIFO的0长写会造成顺流传送一个0长消息。按系统默认,0长写
</p>
<p>不发送消息。 </p>
<p>SNPIPE 在流上已出错后,若调用write或putmsg,则向调用进程发送SIGPIPE信
</p>
<p>号。 </p>
<p>一个流也有读方式,我们先说明getmsg和getpmsg函数,然后再说明读方式。
</p>
<p>_______________________________________________________________________ </p>
<p>____ </p>
<p>#include <stropts.h> </p>
<p>int getmsg(int filedes,struct strbuf *ctlptr, </p>
<p>struct strbuf *dataptr,int *flagptr); </p>
<p>int getpmsg(int filedes,struct strbuf *ctlptr, </p>
<p>struct strbuf *dataptr, int *bandptr, int *flagptr); </p>
<p>两个函数返回:若成功为非负值,出错为-1 </p>
<p>_____________________________________________________________________ </p>
<p>________ </p>
<p>getmsg和getpmsg函数 </p>
<p>用read,getmsg和getpmsg从流首读流消息。 </p>
<p>注意,flagptr和bandptr是指向整型的指针。在调用之前,这两个指针所指向的整
</p>
<p>型单元中应设置成所希望的消息类型,在返回时,此整型量设置为所读到的消息的
</p>
<p>类型。 </p>
<p>如果flagptr指向的整型单元的值是0,则getmsg返回在流首读队列中的下一个消息
</p>
<p>。如果下一个消息是高优先权消息,则在返回时,flagptr所指向的整型单元设置
</p>
<p>为RS_HIPRI。如果希望只接收高优先权消息,则在调用getmsg之前必须将flagptr
</p>
<p>所指向的整型单元设置为RS_HIPRI。 </p>
<p>这两个函数有很多条件来确定返回给调用者何种消息:(a)flagptr和bandptr所指
</p>
<p>向的值,(b)ctlptr->maxlen和dataptr->maxlen的值。对使用getmsg而言,并不需
</p>
<p>要了解所有这些细节。如欲了解这些细节请参阅getmsg(2)手册页。
</p>
<p>读方式 </p>
<p>如果read流设备会发生些什么呢?有两个潜在的问题:(1)如果读到流中消息的记
</p>
<p>录边界将会怎样?(2)如果调用read,而流中下一个消息有控制信息又将如何?对
</p>
<p>第一种情况的默认处理方式被称为字节流方式。在这种方式中,read从流中取数据
</p>
<p>直至满足了要求,或者已经没有数据。在这种方式中,忽略流中消息的边界。对第
</p>
<p>二种情况的默认处理是,如果在队列的前端有控制消息,则read出错返回。可以改
</p>
<p>变这两种默认处理方式。 </p>
<p>在调用ioctl时,若将request设置为I_GRDOPT,第3个参数又是指向一个整型单元
</p>
<p>的指针,则对该流的当前读方式在该整型单元中返回。如果将request设置为I_SR
</p>
<p>DOPT,第3个参数是整型值,则该流的读方式设置为该值。读方式值有下列三个:
</p>
<p>RNORM 普通,字节流方式(与上面说明的相同)。这是默认方式。 </p>
<p>RMSGN
消息不删除方式。读从流中取数据直至读到所要求的字节数,或者到达消
</p>
<p>息边界。如果某次读只用了一个消息的一部分,则其余下部分仍留在流中,以供下
</p>
<p>一个读取。 </p>
<p>RMSGD
消息删除方式。这与不删除方式的区别是,如果某次读只用一个消息的一
</p>
<p>部分。则余下部分就被删除,不再使用。 </p>
<p>在读方式中还可指定另外三个常数,以便设置在读到流中包含协议信息的消息时r
</p>
<p>ead的处理方法: </p>
<p>RPROTNORM 协议-普通方式:read出错返回,errno设置为EBADMSG。这是默认方式
</p>
<p>。 </p>
<p>RPROTDAT 协议-数据方式:read将控制部分作为数据返回给调用者。 </p>
<p>RPROTDIS 协议-删除方式:read删除消息中的控制信息,但是返回消息中的数
</p>
<p>据。 </p>
<p>实例 </p>
<p>程序12.11是在程序3.3的基础上改写
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -