http://en.wikipedia.org/wiki/STREAMS
STREAMS(流)是系統V提供的構造內核設備驅動程序和網絡協議包的一種通用方法,對STREAMS進行討論的目的是為了理解系統V的終端接口、I/O多路轉接中poll(輪詢)函數的使用 以及基于STREAMS的管道和命名管道的實現。
請注意不要將這里說明的STREAMS(流)與標準I/O庫(http://www.CUOXin.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的不同參數所產生的不同消息類型??刂菩畔⒑蛿祿蓅trbuf結構指定:
struct strbuf{ int maxlen; /* size of buffer */ int len; /* number of bytes currently in buffer */ char *buf; /* pointer to buffer */};
注:n/a或N/A是英語“不適用”(Not applicable)等類似單詞的縮寫,??稍诟鞣N表格中看到。N/A比較多用在填寫表格的時候,表示“本欄目(對我)不適用”。在沒有東西可填寫,但空格也不允許此項留白的時候,可以寫N/A。在英語國家,也會用n/a或者n.a.來表達,都是同一個意思。
當用putmsg或putpmsg發送消息時,len指定緩沖區中數據的字節數。當用getmsg或getpmsg接收消息時,maxlen指定緩沖區長度(使內核不會溢出緩沖區),而len則由內核設置為存放在緩沖區中的數據量。消息長度為0是允許的,len為-1說明沒有控制信息或數據。
為什么需要傳送控制信息和數據兩者呢?提供這兩者使我們可以實現用戶進程和流之間的服務接口??赡茏顬槿肆私獾姆战涌谑窍到yV的傳輸層接口(Transport Layer Interface,TLI),它提供了網絡系統接口。
控制信息的另一個例子是發送一個無連接的網絡消息(數據報)。為了發送該消息,需要說明消息的內容(數據)和該消息的目的地址(控制消息)。如果不能將數據和控制一起發送,那么就要某種專門設計的方案。例如,可以用ioctl說明地址,然后用write發送數據。另一種技術可能要求地址占用數據的前N個字節,而數據是write寫的。將控制信息與數據分開,并且提供處理兩者的函數(putmsg和getmsg)是處理這種問題的比較清晰的方法。
有約25種不同類型的消息,但是只有少數幾種用于用戶進程和流首之間,其余的只在內核中順流、逆流傳送。(對于編寫流處理模塊的人員而言,這些消息是非常有用的,但是對編寫用戶級代碼的人員而言,它們可以忽略。)在我們所使用的函數(read、write、getmsg、getpmsg、putmsg和putpmsg)中,只涉及三種消息類型,它們是:
流中的消息都有一個排隊優先級:
普通消息是優先級波段為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,若出錯則返回-1
對流也可以使用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.CUOXin.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>intisastream(int fd){ return(ioctl(fd, I_CANPUT, 0) != -1);}
程序清單14-8可用于測試此函數。
程序清單14-8 測試isastream函數
#include "apue.h"#include <fcntl.h>int main(int argc, char *argv[]){ int i, fd; for(i=1; i<argc; i++) { if((fd = open(argv[i], O_RDONLY)) < 0) { err_ret("%s: can't open", argv[i]); continue; } if(isastream(fd) == 0) err_ret("%s: not a stream", argv[i]); else err_msg("%s: streams device", argv[i]); } exit(0);}
實例
如果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+1]; /* 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>intmain(int argc, char *argv[]){ int fd, i, nmods; struct str_list list; if(argc != 2) err_quit("usage: %s <pathname>", argv[0]); if((fd = open(argv[1], O_RDONLY)) < 0) err_sys("can't open %s", argv[1]); if(isastream(fd) == 0) err_quit("%s is not a stream", argv[1]); /* * Fetch number of modules. */ if((nmods = ioctl(fd, I_LIST, (void *) 0)) < 0) 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) < 0) err_sys("I_LIST error for list"); /* * Print the names. */ for(i=1; i<=nmods; i++) printf(" %s: %s/n", (i == nmods) ? "driver" : "module", list.sl_modlist++->l_name); exit(0);}
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.CUOXin.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);兩個函數返回值:若成功則返回非負值,若出錯則返回-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。如果控制和數據都有一些余留在流首讀隊列中,那么返回常量值是(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.CUOXin.com/nufangrensheng/p/3498248.html)的基礎上改寫的,它用getmsg代替了read。
程序清單14-10 用getmsg將標準輸入復制到標準輸出
#include "apue.h"#include <stropts.h>#define BUFFSIZE 4096int 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 = 0; /* return any message */ if ((n = getmsg(STDIN_FILENO, &ctl, &dat, &flag)) < 0) err_sys("getmsg error"); fprintf(stderr, "flag = %d, ctl.len = %d, dat.len = %d/n", flag, ctl.len, dat.len); if (dat.len == 0) exit(0); else if (dat.len > 0) if (write(STDOUT_FILENO, dat.buf, dat.len) != dat.len) err_sys("write error"); }}
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www.apuebook.com/。
新聞熱點
疑難解答