http://en.wikipedia.org/wiki/STREAMS

STREAMS(流)是系统V提供的构造内核设备驱动程序和网络协议包的一种通用方法,对STREAMS进行讨论的目的是为了理解系统V的终端接口、I/O多路转接中poll(轮询)函数的使用 以及基于STREAMS的管道和命名管道的实现。

请注意不要将这里说明的STREAMS(流)与标准I/O库(http://www.cnblogs.com/nufangrensheng/p/3505254.html)中使用的流(stream)相混淆。流机制是由Dennis Ritchie开发的,其目的是用通用、灵活的方法改写传统的字符I/O系统(c-list)并与网络协议相适应,后来稍加增强,名称改用大写字母,成为STREAMS机制,被加入到SVR3。在Linux中,STREAMS子系统是可用的,但是用户必须自行将该子系统安装到系统中,通常它默认为不包括在系统中。

流在用户进程和设备驱动程序之间提供了一条全双工通路。流无需和实际硬件设备直接会话,流也可以用来构造伪设备驱动程序。图14-5示出了包含一个处理模块的流。各方框之间用两根带箭头的线连接,以突出流的全双工特征,并强调两个方向的处理是相互独立进行的。

                                                  

图14-4 一个简单的流                                                                   图14-5 具有处理模块的流

任意数量的处理模块可以压入流。我们使用术语压入,是因为每一新模块总是插入到流首之下,而将以前的模块下压。(这类似于后进先出的栈。)图14-5标出来流的两侧,分别称为顺流(downstream)和逆流(upstream)。写到流首的数据将顺流而下传送,由设备驱动程序读到的数据则逆流而上传送。

STREAMS模块是作为内核的一部分执行的,这类似于设备驱动程序。当构造内核时,STREAMS模块联编进入内核。如果系统支持动态可装入的内核模块(Linux和Solaris是这样做的),则我们可以试图将没有联编进内核的STREAMS模块压入一个流;但不保证STREAMS模块和驱动程序的任意组合将能正常工作。

用文件I/O中说明的函数访问流,它们是:open、close、read、write和ioctl。另外,在SVR3内核中增加了3个支持流的新函数(getmsg、putmsg和poll),在SVR4中又加了两个处理流内不同优先级波段消息的函数(getpmsg和putpmsg)。

打开(open)流时使用的路径名参数通常在/dev目录之下。仅仅用ls -l查看设备名,不能判断该设备是不是STREAMS设备。所有STREAMS设备都是字符特殊文件。

虽然某些有关STREAMS的文献暗示我们可以编写处理模块,并且不加细究地就可将它们压入流中,但是编写这些模块如果编写设备驱动程序一样,需要专门的技术。通常只有特殊的应用程序或函数才压入和弹出STREAMS模块。

1、STREAMS消息

STREAMS的所有输入和输出都基于消息。流首和用户进程使用read、write、ioctl、getmsg、getpmsg、putmsg和putpmsg交换消息。在流首、各处理模块和设备驱动程序之间,消息可以顺流而下,也可以逆流而上。

在用户进程和流首之间,消息由下列几部分组成:消息类型、可选择的控制信息以及可选择的数据。表14-4列出了对应于write、putmsg和putpmsg的不同参数所产生的不同消息类型。控制信息和数据由strbuf结构指定:

struct strbuf{
int maxlen; /* size of buffer */
int len; /* number of bytes currently in buffer */
char *buf; /* pointer to buffer */
};

注:n/aN/A英语“不适用”(Not applicable)等类似单词的缩写,常可在各种表格中看到。N/A比较多用在填写表格的时候,表示“本栏目(对我)不适用”。在没有东西可填写,但空格也不允许此项留白的时候,可以写N/A。在英语国家,也会用n/a或者n.a.来表达,都是同一个意思。

当用putmsg或putpmsg发送消息时,len指定缓冲区中数据的字节数。当用getmsg或getpmsg接收消息时,maxlen指定缓冲区长度(使内核不会溢出缓冲区),而len则由内核设置为存放在缓冲区中的数据量。消息长度为0是允许的,len为-1说明没有控制信息或数据。

为什么需要传送控制信息和数据两者呢?提供这两者使我们可以实现用户进程和流之间的服务接口。可能最为人了解的服务接口是系统V的传输层接口(Transport Layer Interface,TLI),它提供了网络系统接口。

控制信息的另一个例子是发送一个无连接的网络消息(数据报)。为了发送该消息,需要说明消息的内容(数据)和该消息的目的地址(控制消息)。如果不能将数据和控制一起发送,那么就要某种专门设计的方案。例如,可以用ioctl说明地址,然后用write发送数据。另一种技术可能要求地址占用数据的前N个字节,而数据是write写的。将控制信息与数据分开,并且提供处理两者的函数(putmsg和getmsg)是处理这种问题的比较清晰的方法。

有约25种不同类型的消息,但是只有少数几种用于用户进程和流首之间,其余的只在内核中顺流、逆流传送。(对于编写流处理模块的人员而言,这些消息是非常有用的,但是对编写用户级代码的人员而言,它们可以忽略。)在我们所使用的函数(read、write、getmsg、getpmsg、putmsg和putpmsg)中,只涉及三种消息类型,它们是:

  • M_DATA (I/O的用户数据)。
  • M_PROTO (协议控制信息)。
  • M_PCPROTO (高优先级协议控制信息)。

流中的消息都有一个排队优先级:

  • 高优先级消息(最高优先级)。
  • 优先级波段消息。
  • 普通消息(最低优先级)。

普通消息是优先级波段为0的消息。优先级波段消息的波段可在1-255之间,波段愈高,优先级也愈高。高优先级消息的特殊性在于,在任何时候流首只有一个高优先级消息排队。在流首读队列已有一个高优先级消息时,另外的高优先级消息会被丢弃。

每个STREAMS模块有两个输入队列。一个接收来自它上面模块的消息,这种消息从流首向驱动程序顺序传送。另一个接收来自它下面模块的消息,这种消息从驱动程序向流首逆流传送。在输入队列中的消息按优先级从高到低排列。

2、putmsg和putpmsg函数

putmsg和putpmsg函数用于将STREAMS消息(控制信息或数据,或两者)写至流中。这两个函数的区别是后者允许对消息指定一个优先级波段。

#include <stropts.h>
int putmsg(int filedes, const struct strbuf *ctlptr,
const struct strbuf *dataptr, int flag);
int putpmsg(int filedes, const struct strbuf *ctlptr,
const struct strbuf *dataptr, int band, int flag);
两个函数返回值:若成功则返回0,若出错则返回-

对流也可以使用write函数,它等效于不带任何控制信息、flag为0的putmsg。

这两个函数可以产生三种不同优先级的消息:普通、优先级波段和高优先级。表14-4详细列出了这两个函数中几个参数的各种可能组合,以及所产生的不同类型的消息。

在表14-4中,N/A表示不适用。消息控制列中的“否”对应于空ctlptr参数,或ctlptr->len为-1。该列中的“是”对应于ctlptr非空,以及ctlptr->len大于等于0。这些说明同样适用于消息的数据部分(用dataptr代替ctlptr)。

3、STREAMS ioctl操作

http://www.cnblogs.com/nufangrensheng/p/3500358.html中曾提到过ioctl函数,它能做其他I/O函数不处理的事情。STREAMS系统继承了这种传统。

在Linux和Solaris中,使用ioctl可对流执行将近40种不同的操作。头文件<stropts.h>应包括在使用这些操作的C代码中。ioctl的第二个参数request说明执行哪一个操作。对流执行操作的所有request都以 I_ 开始。第三个参数的作用与request有关,有时它是一个整型值,有时它是一个指向一个整型或一个数据结构的指针。

实例:isastream函数

有时需要判断一个描述符是否引用一个流。这与调用isatty函数来判断一个描述符是否引用一个终端设备相类似(见终端I/O之终端标识)。Linux和Solaris为此提供了isastream函数。

#include <stropts.h>
int isastream(int filedes);
返回值:若为STREAMS设备则返回1,否则返回0

与isatty类似,它通常是用一个只对STREAMS设备才有效的ioctl函数来进行测试的。程序清单14-7是该函数的一种可能的实现。它使用I_CANPUT ioctl来测试由第三个参数说明的优先级波段(本实例中为0)是否可写。如果该ioctl执行成功,则它对所涉及的流并未作任何改变。

程序清单14-7 检查描述符是否引用STREAMS设备

#include <stropts.h>
#include <unistd.h> int
isastream(int fd)
{
return(ioctl(fd, I_CANPUT, ) != -);
}

程序清单14-8可用于测试此函数。

程序清单14-8 测试isastream函数

#include "apue.h"
#include <fcntl.h> int
main(int argc, char *argv[])
{
int i, fd; for(i=; i<argc; i++)
{
if((fd = open(argv[i], O_RDONLY)) < )
{
err_ret("%s: can't open", argv[i]);
continue;
} if(isastream(fd) == )
err_ret("%s: not a stream", argv[i]);
else
err_msg("%s: streams device", argv[i]);
}
exit();
}

实例

如果ioctl的参数request是I_LIST,则系统返回已压入该流所有模块的名字,包括最顶端的驱动程序(指明最顶端的原因是,在多路转接驱动程序的情况下,有多个驱动程序)。其第三个参数应当是指向str_list结构的指针

struct str_list{
int sl_nmods; /* number of entries in array */
struct str_mlist *sl_modlist; /* ptr to first element of array */
};

应将sl_modlist设置为指向str_mlist结构数组的第一个元素,将sl_nmods设置为该数组中的项数:

struct str_mlist{
char l_name[FMNAMESZ+]; /* null-terminated module name */
};

常量FMNAMESZ在头文件<sys/conf.h>中定义,其值常常是8。l_name的实际长度是FMNAMESZ+1,增加1个字节是为了存放null终止符。

如果ioctl的第三个参数是0,则该函数返回值是模块数,而不是模块名。我们将先用这种ioctl调用确定模块数,然后再分配所要求的str_mlist结构数。

程序清单14-9例示了I_LIST操作。由ioctl返回的名字列表并不对模块和驱动程序进行区分,但是考虑到该列表的最后一项是处于流底的驱动程序,所以在打印时将其表明为驱动程序。

程序清单14-9 列表流中的模块名

#include "apue.h"
#include <fcntl.h>
#include <stropts.h> int
main(int argc, char *argv[])
{
int fd, i, nmods;
struct str_list list; if(argc != )
err_quit("usage: %s <pathname>", argv[]); if((fd = open(argv[], O_RDONLY)) < )
err_sys("can't open %s", argv[]);
if(isastream(fd) == )
err_quit("%s is not a stream", argv[]); /*
* Fetch number of modules.
*/
if((nmods = ioctl(fd, I_LIST, (void *) )) < )
err_sys("I_LIST error for nmods");
printf("#modules = %d\n", nmods); /*
* Allocate storage for all the module names.
*/
list.sl_modlist = calloc(nmods, sizeof(struct str_mlist));
if(list.sl_modlist == NULL)
err_sys("calloc error");
list.sl_nmods = nmods; /*
* Fetch the module names.
*/
if(ioctl(fd, I_LIST, &list) < )
err_sys("I_LIST error for list"); /*
* Print the names.
*/
for(i=; i<=nmods; i++)
printf(" %s: %s\n", (i == nmods) ? "driver" : "module",
list.sl_modlist++->l_name); exit();
}

4、写(write)至STREAMS设备

在表14-4中可以看到写至STREAMS设备产生一个M_DATA消息。一般情况确实如此,但是也还有一些细节需要考虑。首先,流中最顶部的一个处理模块规定了可顺流传送的最小、最大数据报长度(无法查询该模块中规定的这些值)。如果写的数据长度超过最大值,则流首将这一数据按最大长度分解成若干数据包。最后一个数据包的长度可能不到最大值。

接着要考虑的是:如果向流写0个字节,又将如何呢?除非流引用管道或FIFO,否则就顺流发送0长度消息。对于管道和FIFO,为与以前版本兼容,系统的默认处理方式是忽略0长度write。可以用ioctl设置管道和FIFO流的写模式,从而更改这种默认处理方式。

5、写模式

可以用两个ioctl命令取得和设置一个流的写模式。如果将request设置为I_GWROPT,第三个参数设置为指向一个整型变量的指针,则该流的当前写模式在该整型量中返回。如果将request设置为I_SWROPT,第三个参数是一个整型值,则其值成为该流新的写模式。如同处理文件描述符标志和文件状态标志(见http://www.cnblogs.com/nufangrensheng/p/3500350.html)一样,总是应当先取当前写模式值,然后修改它,而不只是将写模式设置为某个绝对值(很可能会关闭某些原来打开的位)。

目前,只定义了两个写模式值。

SNDZERO    对管道和FIFO的0长度write会造成顺流传送一个0长度消息。按系统默认,0长度写不发送消息。

SNDPIPE     在流上已出错后,若调用write或putmsg,则向调用进程发送SIGPIPE信号。

流也有读模式,我们先说明getmsg和getpmsg函数,然后再说明读模式。

6、getmsg和getpmsg函数

使用read、getmsg或getpmsg函数从流首读STREAMS消息。

#incldue <stropts.h>
int getmsg(int filedes, struct strbuf *restrict ctlptr,
struct strbuf *restrict dataptr, int *restrict flagptr);
int getpmsg(int filedes, struct strbuf *restrict ctlptr,
struct strbuf *restrict dataptr, int *restrict bandptr,
int *restrict flagptr);
两个函数返回值:若成功则返回非负值,若出错则返回-

注意,flagptr和bandptr是指向整型的指针。在调用之前,这两个指针所指向的整型单元中应设置成所希望的消息类型;在返回时,此整型量设置为所读到的消息的类型。

如果flagptr指向的整型单元的值是0,则getmsg返回流首读队列中的下一个消息。如果下一个消息是高优先级消息,则在返回时,flagptr所指向的整型单元设置为RS_HIPRI。如果希望只接收高优先级消息,则在调用getmsg之前必须将flagptr所指向的整型单元设置为RS_HIPRI。

getpmsg使用一个不同的常量集。为了只接收高优先级消息,我们可以将flagptr指向的整型单元设置为MSG_HIPRI。为了只接收某个优先级波段或以上波段(包括高优先级消息)的消息,我们可将该整型单元设置为MSG_BAND,然后将bandptr指向的整型单元设置为该波段的非0优先级值。如果只希望接收第1个可用消息,则可将flagptr指向的整型单元设置为MSG_ANY;在返回时,该整型值将改写为MSG_HIPRI或MSG_BAND,这取决于接收到的消息的类型。如果取到的消息并非高优先级消息,那么bandptr指向的整型将包括消息的优先级波段值。

如果ctlptr是null,或ctlptr->maxlen是-1,那么消息的控制部分仍保留在流首读队列中,我们将不处理它。类似地,如果dataptr是null,或者dataptr->maxlen是-1,那么消息的数据部分仍保留在流首读队列中,我们也不处理它。否则,将按照缓冲区的容量取到消息中尽可能多的控制和数据部分,余下部分仍留在队首,等待下次取用。

如果getmsg和getpmsg调用取到一消息,那么返回值是0。如果消息控制部分中有一些余留在流首读队列中,那么返回常量MORECTL。类似地,如果消息数据中有一些余留在流首读队列中,那么返回常量MOREDATA。如果控制和数据都有一些余留在流首读队列中,那么返回常量值是(MORECTL|MOREDATA)。

7、读模式

如果读(read)STREAMS设备会发生什么呢?有两个潜在的问题:

(1)如果读到流中消息的记录边界将会怎样?

(2)如果调用read,而流中下一个消息有控制信息又将如何?

对第一种情况的默认处理模式称为字节流模式。read从流中取数据直至满足了所要求的字节数,或者已经不再有数据。在这种模式中,忽略流中消息的边界。对第二种情况的默认处理是,如果在队列的前端有控制消息,则read出错返回。可以改变这两种默认处理模式。

调用ioctl时,若将request设置为I_GRDOPT,第三个参数又是指向一个整型单元的指针,则对该流的当前读模式在该整型单元中返回。如果将request设置为I_SRDOPT,第三个参数是整型值,则将该流的读模式设置为该值。读模式值可由下列三个常量指定:

RNORM    普通,字节流模式,如上所述这是默认模式。

RMSGN    消息不丢弃模式。read从流中去数据直至读到所要求的字节数,或达到消息边界。如果某次read只用了消息的一部分,则其余下部分仍留在流中,供下次读。

RMSGD    消息丢弃模式。这与不丢弃模式的区别是,如果某次读只用了消息的一部分,则余下部分就被丢弃,不再使用。

在读模式中还可指定另外三个常量,以便设置在读到流中包含协议控制信息的消息时read的处理方法:

RPROTNORM    协议-普通模式。read出错返回,errno设置为EBADMSG。这是默认模式。

RPROTDAT       协议-数据模式。read将控制部分作为数据返回给调用者。

RPROTDIS        协议-丢弃模式。read丢弃消息中的控制信息,但是返回消息中的数据。

任一时刻,只能设置一种消息读模式以及一种协议读模式。默认读模式是(RNORM|RPROTNORM)。

实例

程序清单14-10是在程序清单3-3(http://www.cnblogs.com/nufangrensheng/p/3498248.html)的基础上改写的,它用getmsg代替了read。

程序清单14-10 用getmsg将标准输入复制到标准输出

#include "apue.h"
#include <stropts.h> #define BUFFSIZE 4096 int
main(void)
{
int n, flag;
char ctlbuf[BUFFSIZE], datbuf[BUFFSIZE];
struct strbuf ctl, dat; ctl.buf = ctlbuf;
ctl.maxlen = BUFFSIZE;
dat.buf = datbuf;
dat.maxlen = BUFFSIZE;
for( ; ; )
{
flag = ; /* return any message */
if ((n = getmsg(STDIN_FILENO, &ctl, &dat, &flag)) < )
err_sys("getmsg error");
fprintf(stderr, "flag = %d, ctl.len = %d, dat.len = %d\n",
flag, ctl.len, dat.len);
if (dat.len == )
exit();
else if (dat.len > )
if (write(STDOUT_FILENO, dat.buf, dat.len) != dat.len)
err_sys("write error");
}
}

本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

高级I/O之STREAMS的更多相关文章

  1. 高级进程间通信之基于STREAMS的管道

    基于STREAMS的管道(简称STREAMS管道,STREAMS pipe)是一个双向(全双工)管道.单个STREAMS管道就能向父.子进程提供双向的数据流. 将http://www.cnblogs. ...

  2. APUE读书笔记-第17章-高级进程间通信

    17.1 引言 *两种高级IPC:基于STREAMS的管道(STREAMS-based pipe)以及UNIX域套接字(UNIX domain socket)可以在进程间传送打开文件描述符.服务进程可 ...

  3. 数据库中的几个概念 - LGWR, ARCH,ASYNC,SYNC,AFFIRM

    双机热备(双机容错)就是对于重要的服务,使用两台服务器,互相备份,共同执行同一服务.当一台服务器出现故障时,可以由另一台服务器承担服务任务,从而在不需要人工干预的情况下,自动保证系统能持续提供服务 双 ...

  4. Python之路,Day8 - Python基础 面向对象高级进阶与socket基础

    类的成员 类的成员可以分为三大类:字段.方法和属性 注:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段.而其他的成员,则都是保存在类中,即:无论对象的 ...

  5. java-并发-高级并发对象1

    以往说到的线程对象都是java平台中非常初级的API,用于处理一些基本的任务,对于一些复杂高级的工作,就需要一些高级的并发对象,尤其是针对于当今的应用程序,要充分利用现在的多核多处理器系统的性能. 以 ...

  6. UNIX环境高级编程笔记之高级I/O

    本章说明了很多高级I/O功能: 非阻塞I/O——发一个I/O操作,不使其阻塞,记录锁,STREAMS机制 I/O多路转接——select和poll函数 readv和writev函数,以及存储映射I/O ...

  7. 第十四章:高级I/O

    14.1:引言 本章内容包括非阻塞I/O.记录锁.系统V流机制.I/O多路转接(select和poll函数).readv和writev函数以及存储映射I/O(mmap),这些都称为高级I/O. 14. ...

  8. 高级I/O之I/O多路转接——pool、select

    当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中使用阻塞I/O: ) if (write(STDOUT_FILENO, buf, n) != n) err_sys("wri ...

  9. Kotlin 语言高级安卓开发入门

    过去一年,使用 Kotlin 来为安卓开发的人越来越多.即使那些现在还没有使用这个语言的开发者,也会对这个语言的精髓产生共鸣,它给现在 Java 开发增加了简单并且强大的范式.Jake Wharton ...

随机推荐

  1. 检查ept

    cat /proc/cpuinfo | grep ept                                检查cpu是否支持ept cat /sys/module/kvm_intel/p ...

  2. DRAM 内存介绍(一)

    转载自博客大神迈克老狼的blog: http://www.cnblogs.com/mikewolf2002/archive/2012/11/13/2768804.html 参考资料:http://ww ...

  3. 现代程序设计 homework-05

    本次作业要求设计服务器和客户端,由于之前对网络编程是一窍不通,上上节课听宗学长讲述Tcp的时候心里想这个东西还真是高大上啊一点儿都听不懂,但是上个周末看了看C#网络编程的博客和书之后,发现这个东西入门 ...

  4. hadoop conf中xml文件修改

    core-site.xml <?xml version="1.0"?><?xml-stylesheet type="text/xsl" hre ...

  5. java中MessageDigest加密工具类

    import java.security.MessageDigest; public class EncryptionKit { public static String md5Encrypt(Str ...

  6. JPA project Change Event Handler问题解决[转]

    转至:http://my.oschina.net/cimu/blog/278724 这是Eclipse中的一个GUG: Bug 386171 - JPA Java Change Event Handl ...

  7. 10款无需编程的App DIY开发工具

    10款无需编程的App DIY开发工具 你有一个很棒的创意但不会编程怎么办?外包.合伙开发还是从零学编程?这里提供另外一种方式--使用无需编程的App  DIY开发工具.DIY开发工具不仅节省了开发时 ...

  8. python 循环

    200 ? "200px" : this.width)!important;} --> 介绍 python中有两种循环,分别是for...in循环.while循环:for.. ...

  9. 根据powerdesigner的OO模型生成C#代码

    2007-05-15 08:34:11|  分类: 转贴部分 |  标签:学习帖子 |字号 订阅 习惯了用Powerdesigner设计数据库模型,XDE设计类图.因此我一般的设计方法是用PD做分析模 ...

  10. jQuery UI 多选下拉框插件:jquery-ui-multiselect

    前一个项目,由于项目需求,需要大量使用到下拉多选框,而由于本人又不会写有关 CSS 样式,所以,便上网找到了这个 jQuery 插件:jquery-ui-multiselect .该款插件提供了基本下 ...