STREAMS(流)是system V提供的构造内核设备驱动程序和网络协议包的一种通用方法。
流在用户进程和设备驱动程序之前提供了一条全双工通路。流无需和实际硬件设备直接会话,流也可以用来构造伪设备驱动
程序,下图是一个简单流的基本构造。
在流首之下可以压入处理模块,这个可以用ioctl命令实现,下图表示包含一个处理模块的流。各方框之间用两根带箭头的线
连接,以突出流的全双工特性,并强调两个方向的处理是相互独立进行的。
任意数量的处理模块可以压入流,STREAMS模块是作为内核的一部分执行的,这类似于设备驱动程序。当构造内核时,STREAMS
模块联编进入内核。
可以使用之前说明的函数访问流:open,close,read,write和ioctl。另外,在SVR3内核中增加了3个支持流的新函数(getmsg,
putmsg和poll),在SVR4中又加了两个处理流内不同优先级波段消息的函数(getpmsg和putpmsg)。
打开(open)流使用的路径名参数通常在/dev目录之下,所有STREAMS设备都是字符特殊文件。
我们可以自己编写STREAMS处理模块并将它们压入流中。
STREAMS的所有输入输出都是基于消息的,流首和用户进程使用read,write,ioctl,getmsg,getpmsg,putmsg和putpmsg交换
消息。
在用户进程和流首之间,消息由下列几部分组成:消息类型,可选择的控制信息以及可选择的数据。
下表列出了对应于write,putmsg和putpmsg的不同参数锁产生的不同消息类型。
控制信息和数据由strbuf结构指定:
struct strbuf{
int maxlen; //size of buffer
int len; //number of bytes currently in buffer
char* buf; //pointer to buffer
}
当用putmsg或putpmsg发送消息时,len指定缓冲区中数据的字节数。当用getmsg或getpmsg接收消息时,maxlen指定缓冲
区长度(使内核不会溢出缓冲区),而len 则由内核设置为存放在缓冲区的数据量。消息长度为0是允许的,len为-1说明没有
控制信息或数据。
在我们所使用的函数(read,write,getmsg,getpmsg,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,出错返回-1.对流可以使用write函数,它等效于不带任何控制信息,flag为0的putmsg。
之前提到过ioctl函数,它能做其他IO函数不能处理的事情,STREAMS系统继承了这种传统。在linux和solars中,使用ioctl
可对流执行将近40中不同操作。头文件<stropts.h>包含在使用这些操作的C代码中,ioctl的第二个参数request说明执行哪一
个操作。所有request都以I_开始。第三个参数的作用与request有关,有时它是一个整型值,有时它是指向一个整型或一个
数据结构的指针。
比如可以使用ioctl函数检查描述符是否引用STREAMS设备:
return(ioctl(fd,I_CANPUT,0) != -1); //若为STREAMS设备则返回1,否则返回0
还可以使用ioctl函数获取已压入该流所有模块的名字,包含最顶端的驱动程序,此时参数request是I_LIST,第三个参数应当
是指向str_list结构的指针。
struct str_list{
int sl_nmods; //number of entries int array
struct str_mlist *sl_modlist; //ptr to first element of array
}
其中str_mlist定义如下:
struct str_mlist{
char l_name[FIMNAMESZ+1]; //null terminated module name
}
如果ioctl的第三个参数是0,则该函数返回值是模块数,而不是模块名,我们先用这种ioctl调用确定模块数,然后再分配所
要求的str_mlist结构数。
可以用两个ioctl命令取得和设置一个流的写模式,如果将request设置为I_GWPORT,第三个参数设置为指向一个整型变量
的指针,则该流的当前写模式在该整型变量中返回。如果将request设置为I_SWPORT,第三个参数是一个整型值,则其值
成为该流新的写模式,我们可以先获取当前写模式值,然后修改它,则进行设置。
目前只定义了两个写模式值。
SNDZERO:对管道和FIFO的0长度write会造成顺流传送一个0长度消息。按系统默认,0长度写不发送消息。
SNDPIPE:在流上已出错后,若调用write和putmsg,则向调用进程发送SIGPIPE信息。
使用read,getmsg或getpmsg函数从流首读STREAMS消息。
#include<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); //若成功返回非负值,出错返回-1.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。如果控制和数据都有一些余留,则返回的常量值
是 MOREDATA | MORECTL。
如果读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)。